aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2019-12-12 19:21:00 +0100
committerSven Gothel <[email protected]>2019-12-12 19:21:00 +0100
commit4df06c6894b39af5bf4681c0acf0c1c080084c80 (patch)
tree2505eb6e3b5798db34033c4cac2d4613bf6bda44
parent8915501ed02eac2b3bce9a7fc06cb1ab562901c3 (diff)
parentc0cf323e1d56ce605e90927324d2fdafcfbb564a (diff)
merge v1.20.0
-rw-r--r--.travis.yml28
-rw-r--r--Alc/ALc.c4703
-rw-r--r--Alc/ALu.c1923
-rw-r--r--Alc/alconfig.c698
-rw-r--r--Alc/alconfig.h17
-rw-r--r--Alc/alstring.h58
-rw-r--r--Alc/ambdec.c566
-rw-r--r--Alc/ambdec.h46
-rw-r--r--Alc/backends/alsa.c1464
-rw-r--r--Alc/backends/base.c84
-rw-r--r--Alc/backends/base.h178
-rw-r--r--Alc/backends/coreaudio.c816
-rw-r--r--Alc/backends/dsound.c1079
-rw-r--r--Alc/backends/jack.c607
-rw-r--r--Alc/backends/loopback.c128
-rw-r--r--Alc/backends/null.c220
-rw-r--r--Alc/backends/opensl.c1070
-rw-r--r--Alc/backends/oss.c869
-rw-r--r--Alc/backends/portaudio.c555
-rw-r--r--Alc/backends/pulseaudio.c1920
-rw-r--r--Alc/backends/sdl2.c288
-rw-r--r--Alc/backends/sndio.c600
-rw-r--r--Alc/backends/solaris.c360
-rw-r--r--Alc/backends/wasapi.c2045
-rw-r--r--Alc/backends/wave.c453
-rw-r--r--Alc/backends/winmm.c786
-rw-r--r--Alc/bformatdec.c492
-rw-r--r--Alc/bformatdec.h57
-rw-r--r--Alc/compat.h57
-rw-r--r--Alc/converter.c468
-rw-r--r--Alc/converter.h55
-rw-r--r--Alc/effects/autowah.c321
-rw-r--r--Alc/effects/chorus.c555
-rw-r--r--Alc/effects/compressor.c243
-rw-r--r--Alc/effects/dedicated.c184
-rw-r--r--Alc/effects/distortion.c286
-rw-r--r--Alc/effects/echo.c310
-rw-r--r--Alc/effects/fshifter.c329
-rw-r--r--Alc/effects/modulator.c307
-rw-r--r--Alc/effects/null.c179
-rw-r--r--Alc/effects/pshifter.c441
-rw-r--r--Alc/effects/reverb.c2090
-rw-r--r--Alc/filters/defs.h112
-rw-r--r--Alc/filters/nfc.c426
-rw-r--r--Alc/filters/nfc.h49
-rw-r--r--Alc/filters/splitter.c109
-rw-r--r--Alc/filters/splitter.h40
-rw-r--r--Alc/fpu_modes.h34
-rw-r--r--Alc/helpers.c1189
-rw-r--r--Alc/hrtf.c1465
-rw-r--r--Alc/hrtf.h84
-rw-r--r--Alc/logging.h69
-rw-r--r--Alc/mastering.c543
-rw-r--r--Alc/mastering.h49
-rw-r--r--Alc/mixer/defs.h119
-rw-r--r--Alc/mixer/hrtf_inc.c128
-rw-r--r--Alc/mixer/mixer_c.c169
-rw-r--r--Alc/mixer/mixer_neon.c283
-rw-r--r--Alc/mixer/mixer_sse.c250
-rw-r--r--Alc/mixer/mixer_sse2.c84
-rw-r--r--Alc/mixer/mixer_sse41.c85
-rw-r--r--Alc/mixvoice.c762
-rw-r--r--Alc/panning.c1239
-rw-r--r--Alc/polymorphism.h105
-rw-r--r--Alc/ringbuffer.c295
-rw-r--r--Alc/ringbuffer.h77
-rw-r--r--Alc/uhjfilter.c120
-rw-r--r--Alc/vector.h94
-rw-r--r--CMakeLists.txt1187
-rw-r--r--ChangeLog62
-rw-r--r--OpenAL32/Include/alAuxEffectSlot.h185
-rw-r--r--OpenAL32/Include/alBuffer.h115
-rw-r--r--OpenAL32/Include/alEffect.h213
-rw-r--r--OpenAL32/Include/alError.h29
-rw-r--r--OpenAL32/Include/alFilter.h67
-rw-r--r--OpenAL32/Include/alListener.h67
-rw-r--r--OpenAL32/Include/alMain.h920
-rw-r--r--OpenAL32/Include/alSource.h120
-rw-r--r--OpenAL32/Include/alu.h534
-rw-r--r--OpenAL32/Include/sample_cvt.h15
-rw-r--r--OpenAL32/alAuxEffectSlot.c802
-rw-r--r--OpenAL32/alBuffer.c1305
-rw-r--r--OpenAL32/alEffect.c819
-rw-r--r--OpenAL32/alFilter.c671
-rw-r--r--OpenAL32/alListener.c502
-rw-r--r--OpenAL32/alSource.c3705
-rw-r--r--OpenAL32/alState.c900
-rw-r--r--OpenAL32/event.c127
-rw-r--r--OpenAL32/sample_cvt.c276
-rw-r--r--al/auxeffectslot.cpp792
-rw-r--r--al/auxeffectslot.h105
-rw-r--r--al/buffer.cpp1391
-rw-r--r--al/buffer.h99
-rw-r--r--al/effect.cpp725
-rw-r--r--al/effect.h61
-rw-r--r--al/error.cpp (renamed from OpenAL32/alError.c)94
-rw-r--r--al/event.cpp205
-rw-r--r--al/event.h55
-rw-r--r--al/extension.cpp (renamed from OpenAL32/alExtension.c)64
-rw-r--r--al/filter.cpp650
-rw-r--r--al/filter.h56
-rw-r--r--al/listener.cpp452
-rw-r--r--al/listener.h64
-rw-r--r--al/source.cpp3348
-rw-r--r--al/source.h129
-rw-r--r--al/state.cpp880
-rw-r--r--alc/alc.cpp4195
-rw-r--r--alc/alcmain.h390
-rw-r--r--alc/alconfig.cpp549
-rw-r--r--alc/alconfig.h20
-rw-r--r--alc/alcontext.h196
-rw-r--r--alc/alu.cpp1842
-rw-r--r--alc/alu.h158
-rw-r--r--alc/ambdec.cpp434
-rw-r--r--alc/ambdec.h48
-rw-r--r--alc/ambidefs.h120
-rw-r--r--alc/backends/alsa.cpp1265
-rw-r--r--alc/backends/alsa.h19
-rw-r--r--alc/backends/base.cpp62
-rw-r--r--alc/backends/base.h84
-rw-r--r--alc/backends/coreaudio.cpp659
-rw-r--r--alc/backends/coreaudio.h19
-rw-r--r--alc/backends/dsound.cpp916
-rw-r--r--alc/backends/dsound.h19
-rw-r--r--alc/backends/jack.cpp544
-rw-r--r--alc/backends/jack.h19
-rw-r--r--alc/backends/loopback.cpp79
-rw-r--r--alc/backends/loopback.h19
-rw-r--r--alc/backends/null.cpp182
-rw-r--r--alc/backends/null.h19
-rw-r--r--alc/backends/opensl.cpp964
-rw-r--r--alc/backends/opensl.h19
-rw-r--r--alc/backends/oss.cpp712
-rw-r--r--alc/backends/oss.h19
-rw-r--r--alc/backends/portaudio.cpp447
-rw-r--r--alc/backends/portaudio.h19
-rw-r--r--alc/backends/pulseaudio.cpp1507
-rw-r--r--alc/backends/pulseaudio.h19
-rw-r--r--alc/backends/qsa.cpp (renamed from Alc/backends/qsa.c)515
-rw-r--r--alc/backends/qsa.h19
-rw-r--r--alc/backends/sdl2.cpp227
-rw-r--r--alc/backends/sdl2.h19
-rw-r--r--alc/backends/sndio.cpp476
-rw-r--r--alc/backends/sndio.h19
-rw-r--r--alc/backends/solaris.cpp299
-rw-r--r--alc/backends/solaris.h19
-rw-r--r--alc/backends/wasapi.cpp1750
-rw-r--r--alc/backends/wasapi.h19
-rw-r--r--alc/backends/wave.cpp402
-rw-r--r--alc/backends/wave.h19
-rw-r--r--alc/backends/winmm.cpp634
-rw-r--r--alc/backends/winmm.h19
-rw-r--r--alc/bformatdec.cpp203
-rw-r--r--alc/bformatdec.h62
-rw-r--r--alc/bs2b.cpp (renamed from Alc/bs2b.c)80
-rw-r--r--alc/bs2b.h (renamed from OpenAL32/Include/bs2b.h)27
-rw-r--r--alc/compat.h9
-rw-r--r--alc/converter.cpp360
-rw-r--r--alc/converter.h61
-rw-r--r--alc/cpu_caps.h (renamed from Alc/cpu_caps.h)1
-rw-r--r--alc/devformat.h121
-rw-r--r--alc/effects/autowah.cpp299
-rw-r--r--alc/effects/base.h193
-rw-r--r--alc/effects/chorus.cpp534
-rw-r--r--alc/effects/compressor.cpp221
-rw-r--r--alc/effects/dedicated.cpp160
-rw-r--r--alc/effects/distortion.cpp266
-rw-r--r--alc/effects/echo.cpp267
-rw-r--r--alc/effects/equalizer.cpp (renamed from Alc/effects/equalizer.c)249
-rw-r--r--alc/effects/fshifter.cpp332
-rw-r--r--alc/effects/modulator.cpp270
-rw-r--r--alc/effects/null.cpp165
-rw-r--r--alc/effects/pshifter.cpp373
-rw-r--r--alc/effects/reverb.cpp2090
-rw-r--r--alc/effects/vmorpher.cpp432
-rw-r--r--alc/filters/biquad.cpp (renamed from Alc/filters/filter.c)101
-rw-r--r--alc/filters/biquad.h113
-rw-r--r--alc/filters/nfc.cpp391
-rw-r--r--alc/filters/nfc.h61
-rw-r--r--alc/filters/splitter.cpp117
-rw-r--r--alc/filters/splitter.h34
-rw-r--r--alc/fpu_modes.h25
-rw-r--r--alc/helpers.cpp649
-rw-r--r--alc/hrtf.cpp1424
-rw-r--r--alc/hrtf.h115
-rw-r--r--alc/inprogext.h (renamed from Alc/inprogext.h)29
-rw-r--r--alc/logging.h59
-rw-r--r--alc/mastering.cpp468
-rw-r--r--alc/mastering.h103
-rw-r--r--alc/mixer/defs.h68
-rw-r--r--alc/mixer/hrtfbase.h119
-rw-r--r--alc/mixer/mixer_c.cpp219
-rw-r--r--alc/mixer/mixer_neon.cpp324
-rw-r--r--alc/mixer/mixer_sse.cpp297
-rw-r--r--alc/mixer/mixer_sse2.cpp83
-rw-r--r--alc/mixer/mixer_sse3.cpp (renamed from Alc/mixer/mixer_sse3.c)0
-rw-r--r--alc/mixer/mixer_sse41.cpp88
-rw-r--r--alc/panning.cpp1021
-rw-r--r--alc/ringbuffer.cpp251
-rw-r--r--alc/ringbuffer.h97
-rw-r--r--alc/uhjfilter.cpp138
-rw-r--r--alc/uhjfilter.h (renamed from Alc/uhjfilter.h)33
-rw-r--r--alc/uiddefs.cpp37
-rw-r--r--alc/voice.cpp837
-rw-r--r--alc/voice.h293
-rw-r--r--alsoftrc.sample76
-rw-r--r--appveyor.yml16
-rw-r--r--cmake/CheckFileOffsetBits.c9
-rw-r--r--cmake/CheckFileOffsetBits.cmake39
-rw-r--r--cmake/CheckSharedFunctionExists.cmake92
-rw-r--r--cmake/FindMySOFA.cmake81
-rw-r--r--cmake/FindOpenSL.cmake64
-rw-r--r--cmake/FindWindowsSDK.cmake15
-rw-r--r--common/albyte.h99
-rw-r--r--common/alcomplex.c92
-rw-r--r--common/alcomplex.cpp73
-rw-r--r--common/alcomplex.h66
-rw-r--r--common/alexcpt.cpp30
-rw-r--r--common/alexcpt.h36
-rw-r--r--common/alfstream.cpp147
-rw-r--r--common/alfstream.h70
-rw-r--r--common/align.h21
-rw-r--r--common/almalloc.c110
-rw-r--r--common/almalloc.cpp67
-rw-r--r--common/almalloc.h310
-rw-r--r--common/alnumeric.h347
-rw-r--r--common/aloptional.h149
-rw-r--r--common/alspan.h297
-rw-r--r--common/alstring.cpp45
-rw-r--r--common/alstring.h29
-rw-r--r--common/atomic.c10
-rw-r--r--common/atomic.h442
-rw-r--r--common/bool.h18
-rw-r--r--common/dynload.cpp44
-rw-r--r--common/dynload.h14
-rw-r--r--common/endiantest.h14
-rw-r--r--common/intrusive_ptr.h120
-rw-r--r--common/math_defs.h67
-rw-r--r--common/opthelpers.h39
-rw-r--r--common/pragmadefs.h21
-rw-r--r--common/rwlock.c59
-rw-r--r--common/rwlock.h31
-rw-r--r--common/static_assert.h21
-rw-r--r--common/strutils.cpp62
-rw-r--r--common/strutils.h24
-rw-r--r--common/threads.c751
-rw-r--r--common/threads.cpp169
-rw-r--r--common/threads.h261
-rw-r--r--common/uintmap.c182
-rw-r--r--common/uintmap.h41
-rw-r--r--common/vecmat.h86
-rw-r--r--common/vector.h15
-rw-r--r--common/win_main_utf8.h4
-rw-r--r--config.h.in92
-rw-r--r--docs/ambisonics.txt41
-rw-r--r--docs/env-vars.txt13
-rw-r--r--examples/alffplay.cpp1340
-rw-r--r--examples/alhrtf.c26
-rw-r--r--examples/allatency.c35
-rw-r--r--examples/alloopback.c17
-rw-r--r--examples/almultireverb.c114
-rw-r--r--examples/alplay.c9
-rw-r--r--examples/alrecord.c42
-rw-r--r--examples/alreverb.c62
-rw-r--r--examples/alstream.c24
-rw-r--r--examples/altonegen.c15
-rw-r--r--examples/common/alhelpers.c74
-rw-r--r--examples/common/alhelpers.h14
-rw-r--r--include/AL/alc.h4
-rw-r--r--include/AL/efx.h1
-rw-r--r--native-tools/bsincgen.c145
-rw-r--r--resources/openal32.rc90
-rw-r--r--resources/resource.h15
-rw-r--r--resources/router.rc90
-rw-r--r--resources/soft_oal.rc90
-rw-r--r--router/al.cpp (renamed from router/al.c)26
-rw-r--r--router/alc.cpp (renamed from router/alc.c)504
-rw-r--r--router/router.cpp (renamed from router/router.c)358
-rw-r--r--router/router.h283
-rw-r--r--utils/CIAIR.def4
-rw-r--r--utils/IRC_1005.def4
-rw-r--r--utils/MIT_KEMAR.def10
-rw-r--r--utils/MIT_KEMAR_sofa.def51
-rw-r--r--utils/SCUT_KEMAR.def48
-rw-r--r--utils/alsoft-config/CMakeLists.txt4
-rw-r--r--utils/alsoft-config/mainwindow.cpp653
-rw-r--r--utils/alsoft-config/mainwindow.h1
-rw-r--r--utils/alsoft-config/mainwindow.ui293
-rw-r--r--utils/alsoft-config/verstr.cpp10
-rw-r--r--utils/alsoft-config/verstr.h8
-rw-r--r--utils/bsincgen.c404
-rw-r--r--utils/makehrtf.c3455
-rw-r--r--utils/makemhr/loaddef.cpp2033
-rw-r--r--utils/makemhr/loaddef.h13
-rw-r--r--utils/makemhr/loadsofa.cpp668
-rw-r--r--utils/makemhr/loadsofa.h10
-rw-r--r--utils/makemhr/makemhr.cpp1797
-rw-r--r--utils/makemhr/makemhr.h128
-rw-r--r--utils/openal-info.c8
-rw-r--r--utils/sofa-info.cpp371
-rw-r--r--version.h.in1
301 files changed, 53545 insertions, 56901 deletions
diff --git a/.travis.yml b/.travis.yml
index 426eef40..98b5adcc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,8 @@
-language: c
+language: cpp
matrix:
include:
- os: linux
- dist: trusty
+ dist: xenial
- os: linux
dist: trusty
env:
@@ -24,17 +24,18 @@ 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-r16b-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-r16b/build/cmake/*' \
+ 'android-ndk-r16b/build/core/toolchains/arm-linux-androideabi-*/*' \
+ 'android-ndk-r16b/platforms/android-14/arch-arm/*' \
+ 'android-ndk-r16b/source.properties' \
+ 'android-ndk-r16b/sources/android/support/include/*' \
+ 'android-ndk-r16b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*' \
+ 'android-ndk-r16b/sources/cxx-stl/llvm-libc++/include/*' \
+ 'android-ndk-r16b/sysroot/*' \
+ 'android-ndk-r16b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \
+ 'android-ndk-r16b/toolchains/llvm/prebuilt/linux-x86_64/*'
fi
script:
- >
@@ -51,7 +52,8 @@ 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-r16b/build/cmake/android.toolchain.cmake \
-DALSOFT_REQUIRE_OPENSL=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
.
diff --git a/Alc/ALc.c b/Alc/ALc.c
deleted file mode 100644
index 8bf2e1da..00000000
--- a/Alc/ALc.c
+++ /dev/null
@@ -1,4703 +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_SOLARIS
- { "solaris", ALCsolarisBackendFactory_getFactory },
-#endif
-#ifdef HAVE_SNDIO
- { "sndio", SndioBackendFactory_getFactory },
-#endif
-#ifdef HAVE_OSS
- { "oss", ALCossBackendFactory_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),
- DECL(AL_EFFECT_FREQUENCY_SHIFTER),
-#if 0
- DECL(AL_EFFECT_VOCAL_MORPHER),
-#endif
- 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_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_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";
-
-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)
-
-
-/************************************************
- * 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, list);
- }
-
- UnlockLists();
-}
-static void ProbeAllDevicesList(void)
-{ ProbeDevices(&alcAllDevicesList, &PlaybackBackend, ALL_DEVICE_PROBE); }
-static void ProbeCaptureDeviceList(void)
-{ ProbeDevices(&alcCaptureDeviceList, &CaptureBackend, CAPTURE_DEVICE_PROBE); }
-
-
-/************************************************
- * 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);
-}
-
-
-static struct Compressor *CreateDeviceLimiter(const ALCdevice *device, const ALfloat threshold)
-{
- return CompressorInit(device->RealOut.NumChannels, device->Frequency,
- AL_TRUE, AL_TRUE, AL_TRUE, AL_TRUE, AL_TRUE, 0.001f, 0.002f,
- 0.0f, 0.0f, threshold, INFINITY, 0.0f, 0.020f, 0.200f);
-}
-
-/* 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->LimiterState;
- 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->FixedLatency = 0;
-
- 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;
- }
- }
-
- if(depth > 0)
- {
- depth = clampi(depth, 2, 24);
- device->DitherDepth = powf(2.0f, (ALfloat)(depth-1));
- }
- }
- if(!(device->DitherDepth > 0.0f))
- TRACE("Dithering disabled\n");
- else
- TRACE("Dithering enabled (%g-bit, %g)\n", log2f(device->DitherDepth)+1.0f,
- device->DitherDepth);
-
- device->LimiterState = gainLimiter;
- 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. 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)
- {
- ALfloat 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;
-
- al_free(device->Limiter);
- device->Limiter = CreateDeviceLimiter(device, log10f(thrshld) * 20.0f);
- device->FixedLatency += (ALuint)(GetCompressorLookAhead(device->Limiter) *
- DEVICE_CLOCK_RES / device->Frequency);
- }
- else
- {
- al_free(device->Limiter);
- device->Limiter = NULL;
- }
- TRACE("Output limiter %s\n", device->Limiter ? "enabled" : "disabled");
-
- aluSelectPostProcess(device);
-
- TRACE("Fixed device latency: %uns\n", device->FixedLatency);
-
- /* 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;
- device->LimiterState = ALC_DONT_CARE_SOFT;
-
- ATOMIC_INIT(&device->ContextList, NULL);
-
- device->ClockBase = 0;
- device->SamplesDone = 0;
- device->FixedLatency = 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);
- 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;
-
-
- Context->AsyncEvents = ll_ringbuffer_create(63, sizeof(AsyncEvent), false);
- if(althrd_create(&Context->EventThread, EventThread, Context) != althrd_success)
- ERR("Failed to start event thread! Expect problems.\n");
-}
-
-
-/* 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");
-
- almtx_destroy(&context->EventCbLock);
- 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)
-{
- static const AsyncEvent kill_evt = ASYNC_EVENT(EventType_KillThread);
- 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)();
-
- /* Make sure the context is finished and no longer processing in the mixer
- * before sending the message queue kill event. The backend's lock does
- * this, although waiting for a non-odd mix count would work too.
- */
-
- while(ll_ringbuffer_write(context->AsyncEvents, (const char*)&kill_evt, 1) == 0)
- althrd_yield();
- alsem_post(&context->EventSem);
- althrd_join(context->EventThread, NULL);
-
- 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 = GetClockLatency(device);
- 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 = GetClockLatency(device);
- 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 = GetClockLatency(device);
- 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->LimiterState = ALC_TRUE;
- 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);
- }
-
- {
- 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();
-
- almtx_lock(&device->BackendLock);
- if((device->Flags&DEVICE_RUNNING))
- V0(device->Backend,stop)();
- device->Flags &= ~DEVICE_RUNNING;
- almtx_unlock(&device->BackendLock);
-
- 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");
-
- {
- 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;
-}
diff --git a/Alc/ALu.c b/Alc/ALu.c
deleted file mode 100644
index 03abb116..00000000
--- a/Alc/ALu.c
+++ /dev/null
@@ -1,1923 +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;
-}
-
-
-/* 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)
-{
- AsyncEvent evt = ASYNC_EVENT(EventType_SourceStateChange);
- ALbitfieldSOFT enabledevt;
- size_t strpos;
- ALuint scale;
-
- enabledevt = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_acquire);
- if(!(enabledevt&EventType_SourceStateChange)) return;
-
- evt.u.user.type = AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT;
- evt.u.user.id = id;
- evt.u.user.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.u.user.msg, "Source ID "); strpos = 10;
- scale = 1000000000;
- while(scale > 0 && scale > id)
- scale /= 10;
- while(scale > 0)
- {
- evt.u.user.msg[strpos++] = '0' + ((id/scale)%10);
- scale /= 10;
- }
- strcpy(evt.u.user.msg+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).
- *
- * 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;
- }
-
- state = props->State;
-
- if(state == slot->Params.EffectState)
- {
- /* If the effect state is the same as current, we can decrement its
- * count safely to remove it from the update object (it can't reach
- * 0 refs since the current params also hold a reference).
- */
- DecrementRef(&state->Ref);
- props->State = NULL;
- }
- else
- {
- /* Otherwise, replace it and send off the old one with a release
- * event.
- */
- AsyncEvent evt = ASYNC_EVENT(EventType_ReleaseEffectState);
- evt.u.EffectState = slot->Params.EffectState;
-
- slot->Params.EffectState = state;
- props->State = NULL;
-
- if(LIKELY(ll_ringbuffer_write(context->AsyncEvents, (const char*)&evt, 1) != 0))
- alsem_post(&context->EventSem);
- 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 = evt.u.EffectState;
- }
- }
-
- 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->NumChannelsPerOrder[i];
- voice->Flags |= VOICE_HAS_NFC;
- }
-
- /* A scalar of 1.5 for plain stereo results in +/-60 degrees being
- * moved to +/-90 degrees for direct right and left speaker
- * responses.
- */
- CalcAngleCoeffs((Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : Azi,
- Elev, Spread, coeffs);
-
- /* NOTE: W needs to be scaled by sqrt(2) due to FuMa normalization. */
- ComputePanGains(&Device->Dry, coeffs, DryGain*SQRTF_2,
- 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]*SQRTF_2, voice->Send[i].Params[0].Gains.Target
- );
- }
- }
- else
- {
- /* Local B-Format sources have their XYZ channels rotated according
- * to the orientation.
- */
- 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
- SQRTF_2, 0.0f, 0.0f, 0.0f, // Ambi W
- 0.0f, -N[0]*SQRTF_3, N[1]*SQRTF_3, -N[2]*SQRTF_3, // Ambi X
- 0.0f, U[0]*SQRTF_3, -U[1]*SQRTF_3, U[2]*SQRTF_3, // Ambi Y
- 0.0f, -V[0]*SQRTF_3, V[1]*SQRTF_3, -V[2]*SQRTF_3 // Ambi Z
- );
-
- voice->Direct.Buffer = Device->FOAOut.Buffer;
- voice->Direct.Channels = Device->FOAOut.NumChannels;
- for(c = 0;c < num_channels;c++)
- ComputePanGains(&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++)
- ComputePanningGainsBF(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->NumChannelsPerOrder[i];
- voice->Flags |= VOICE_HAS_NFC;
- }
-
- /* Calculate the directional coefficients once, which apply to all
- * input channels.
- */
- CalcAngleCoeffs((Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(Azi, 1.5f) : 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;
- }
-
- ComputePanGains(&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->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;
- }
-
- CalcAngleCoeffs(
- (Device->Render_Mode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f)
- : chans[c].angle,
- chans[c].elevation, Spread, coeffs
- );
-
- ComputePanGains(&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(LIKELY(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;
-
- ASSUME(numchans > 0);
- ASSUME(SamplesToDo > 0);
-
- /* 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] = fast_roundf(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. There is an implied 1 bit in the mantissa
- * along with the sign bit, giving 25 bits total, so [-16777216, +16777216]
- * is the max value a normalized float can be scaled 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; \
- \
- ASSUME(numchans > 0); \
- ASSUME(SamplesToDo > 0); \
- \
- 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, 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)
- {
-#define HANDLE_WRITE(T, S) case T: \
- Write##S(Buffer, OutBuffer, SamplesDone, SamplesToDo, Channels); break;
- HANDLE_WRITE(DevFmtByte, I8)
- HANDLE_WRITE(DevFmtUByte, UI8)
- HANDLE_WRITE(DevFmtShort, I16)
- HANDLE_WRITE(DevFmtUShort, UI16)
- HANDLE_WRITE(DevFmtInt, I32)
- HANDLE_WRITE(DevFmtUInt, UI32)
- HANDLE_WRITE(DevFmtFloat, F32)
-#undef HANDLE_WRITE
- }
- }
-
- SamplesDone += SamplesToDo;
- }
- END_MIXER_MODE();
-}
-
-
-void aluHandleDisconnect(ALCdevice *device, const char *msg, ...)
-{
- AsyncEvent evt = ASYNC_EVENT(EventType_Disconnected);
- ALCcontext *ctx;
- va_list args;
- int msglen;
-
- if(!ATOMIC_EXCHANGE(&device->Connected, AL_FALSE, almemory_order_acq_rel))
- return;
-
- evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT;
- evt.u.user.id = 0;
- evt.u.user.param = 0;
-
- va_start(args, msg);
- msglen = vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args);
- va_end(args);
-
- if(msglen < 0 || (size_t)msglen >= sizeof(evt.u.user.msg))
- evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0;
-
- 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);
- }
-}
diff --git a/Alc/alconfig.c b/Alc/alconfig.c
deleted file mode 100644
index 3d0ed140..00000000
--- a/Alc/alconfig.c
+++ /dev/null
@@ -1,698 +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
-#ifdef __APPLE__
-#include <CoreFoundation/CoreFoundation.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);
- }
-
-#ifdef __APPLE__
- CFBundleRef mainBundle = CFBundleGetMainBundle();
- if(mainBundle)
- {
- unsigned char fileName[PATH_MAX];
- CFURLRef configURL;
-
- if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), NULL)) &&
- CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
- {
- f = al_fopen((const char*)fileName, "r");
- if(f)
- {
- LoadConfigFromFile(f);
- fclose(f);
- }
- }
- }
-#endif
-
- 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);
-}
diff --git a/Alc/alconfig.h b/Alc/alconfig.h
deleted file mode 100644
index 1e493e2e..00000000
--- a/Alc/alconfig.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#ifndef ALCONFIG_H
-#define ALCONFIG_H
-
-void ReadALConfig(void);
-void FreeALConfig(void);
-
-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);
-
-#endif /* ALCONFIG_H */
diff --git a/Alc/alstring.h b/Alc/alstring.h
deleted file mode 100644
index 923a5ea2..00000000
--- a/Alc/alstring.h
+++ /dev/null
@@ -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 */
diff --git a/Alc/ambdec.c b/Alc/ambdec.c
deleted file mode 100644
index da114335..00000000
--- a/Alc/ambdec.c
+++ /dev/null
@@ -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;
-}
diff --git a/Alc/ambdec.h b/Alc/ambdec.h
deleted file mode 100644
index 0bb84072..00000000
--- a/Alc/ambdec.h
+++ /dev/null
@@ -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 */
diff --git a/Alc/backends/alsa.c b/Alc/backends/alsa.c
deleted file mode 100644
index a967fff0..00000000
--- a/Alc/backends/alsa.c
+++ /dev/null
@@ -1,1464 +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_prepare(self->pcmHandle);
- if(err < 0)
- ERR("prepare failed: %s\n", snd_strerror(err));
- else
- {
- err = snd_pcm_start(self->pcmHandle);
- if(err < 0)
- ERR("start failed: %s\n", snd_strerror(err));
- }
- if(err < 0)
- {
- 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;
-}
-
-
-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, al_string *outnames)
-{
- switch(type)
- {
-#define APPEND_OUTNAME(i) do { \
- if(!alstr_empty((i)->name)) \
- alstr_append_range(outnames, VECTOR_BEGIN((i)->name), \
- VECTOR_END((i)->name)+1); \
-} while(0)
- case ALL_DEVICE_PROBE:
- probe_devices(SND_PCM_STREAM_PLAYBACK, &PlaybackDevices);
- VECTOR_FOR_EACH(const DevMap, PlaybackDevices, APPEND_OUTNAME);
- break;
-
- case CAPTURE_DEVICE_PROBE:
- probe_devices(SND_PCM_STREAM_CAPTURE, &CaptureDevices);
- VECTOR_FOR_EACH(const DevMap, CaptureDevices, APPEND_OUTNAME);
- break;
-#undef APPEND_OUTNAME
- }
-}
-
-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);
-}
diff --git a/Alc/backends/base.c b/Alc/backends/base.c
deleted file mode 100644
index 9d8614b1..00000000
--- a/Alc/backends/base.c
+++ /dev/null
@@ -1,84 +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);
-extern inline ClockLatency GetClockLatency(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))
-{
-}
diff --git a/Alc/backends/base.h b/Alc/backends/base.h
deleted file mode 100644
index 03db56e9..00000000
--- a/Alc/backends/base.h
+++ /dev/null
@@ -1,178 +0,0 @@
-#ifndef AL_BACKENDS_BASE_H
-#define AL_BACKENDS_BASE_H
-
-#include "alMain.h"
-#include "threads.h"
-#include "alstring.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);
-}
-
-
-struct ALCbackendVtable;
-
-typedef struct ALCbackend {
- const struct ALCbackendVtable *vtbl;
-
- ALCdevice *mDevice;
-
- almtx_t mMutex;
-} ALCbackend;
-
-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);
-
-struct ALCbackendVtable {
- void (*const Destruct)(ALCbackend*);
-
- ALCenum (*const open)(ALCbackend*, const ALCchar*);
-
- ALCboolean (*const reset)(ALCbackend*);
- ALCboolean (*const start)(ALCbackend*);
- void (*const stop)(ALCbackend*);
-
- ALCenum (*const captureSamples)(ALCbackend*, void*, ALCuint);
- ALCuint (*const availableSamples)(ALCbackend*);
-
- ClockLatency (*const getClockLatency)(ALCbackend*);
-
- void (*const lock)(ALCbackend*);
- void (*const unlock)(ALCbackend*);
-
- void (*const Delete)(void*);
-};
-
-#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, \
-}
-
-
-typedef enum ALCbackend_Type {
- ALCbackend_Playback,
- ALCbackend_Capture,
- ALCbackend_Loopback
-} ALCbackend_Type;
-
-
-struct ALCbackendFactoryVtable;
-
-typedef struct ALCbackendFactory {
- const struct ALCbackendFactoryVtable *vtbl;
-} ALCbackendFactory;
-
-void ALCbackendFactory_deinit(ALCbackendFactory *self);
-
-struct ALCbackendFactoryVtable {
- ALCboolean (*const init)(ALCbackendFactory *self);
- void (*const deinit)(ALCbackendFactory *self);
-
- ALCboolean (*const querySupport)(ALCbackendFactory *self, ALCbackend_Type type);
-
- void (*const probe)(ALCbackendFactory *self, enum DevProbe type, al_string *outnames);
-
- ALCbackend* (*const createBackend)(ALCbackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-};
-
-#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_THUNK2(T, ALCbackendFactory, void, probe, enum DevProbe, al_string*) \
-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, \
-}
-
-
-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 *SndioBackendFactory_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)(); }
-
-
-inline ClockLatency GetClockLatency(ALCdevice *device)
-{
- ClockLatency ret = V0(device->Backend,getClockLatency)();
- ret.Latency += device->FixedLatency;
- return ret;
-}
-
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* AL_BACKENDS_BASE_H */
diff --git a/Alc/backends/coreaudio.c b/Alc/backends/coreaudio.c
deleted file mode 100644
index adb01fa6..00000000
--- a/Alc/backends/coreaudio.c
+++ /dev/null
@@ -1,816 +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 <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;
-#if TARGET_OS_IOS
- desc.componentSubType = kAudioUnitSubType_RemoteIO;
-#else
- desc.componentSubType = kAudioUnitSubType_DefaultOutput;
-#endif
- 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;
- 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;
-#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)
- {
- 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;
- }
-
-#if !TARGET_OS_IOS
- // Get the default input device
- AudioDeviceID inputDevice = kAudioDeviceUnknown;
-
- 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;
- }
-#endif
-
- // 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, al_string *outnames);
-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, al_string *outnames)
-{
- switch(type)
- {
- case ALL_DEVICE_PROBE:
- case CAPTURE_DEVICE_PROBE:
- alstr_append_range(outnames, ca_device, ca_device+sizeof(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;
-}
diff --git a/Alc/backends/dsound.c b/Alc/backends/dsound.c
deleted file mode 100644
index c368cffb..00000000
--- a/Alc/backends/dsound.c
+++ /dev/null
@@ -1,1079 +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);
-}
-
-
-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, al_string *outnames);
-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, al_string *outnames)
-{
- HRESULT hr, hrcom;
-
- /* Initialize COM to prevent name truncation */
- hrcom = CoInitialize(NULL);
- switch(type)
- {
-#define APPEND_OUTNAME(e) do { \
- if(!alstr_empty((e)->name)) \
- alstr_append_range(outnames, VECTOR_BEGIN((e)->name), \
- VECTOR_END((e)->name)+1); \
-} while(0)
- 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, APPEND_OUTNAME);
- 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, APPEND_OUTNAME);
- break;
-#undef APPEND_OUTNAME
- }
- 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;
-}
diff --git a/Alc/backends/jack.c b/Alc/backends/jack.c
deleted file mode 100644
index fdbe93f2..00000000
--- a/Alc/backends/jack.c
+++ /dev/null
@@ -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, al_string *outnames)
-{
- switch(type)
- {
- case ALL_DEVICE_PROBE:
- alstr_append_range(outnames, jackDevice, jackDevice+sizeof(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);
-}
diff --git a/Alc/backends/loopback.c b/Alc/backends/loopback.c
deleted file mode 100644
index e9940086..00000000
--- a/Alc/backends/loopback.c
+++ /dev/null
@@ -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, al_string *outnames);
-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), al_string* UNUSED(outnames))
-{
-}
-
-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;
-}
diff --git a/Alc/backends/null.c b/Alc/backends/null.c
deleted file mode 100644
index d1c110e8..00000000
--- a/Alc/backends/null.c
+++ /dev/null
@@ -1,220 +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, al_string *outnames);
-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, al_string *outnames)
-{
- switch(type)
- {
- case ALL_DEVICE_PROBE:
- case CAPTURE_DEVICE_PROBE:
- alstr_append_range(outnames, nullDevice, nullDevice+sizeof(nullDevice));
- 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;
-}
diff --git a/Alc/backends/opensl.c b/Alc/backends/opensl.c
deleted file mode 100644
index d8ae001b..00000000
--- a/Alc/backends/opensl.c
+++ /dev/null
@@ -1,1070 +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;
-
- ll_ringbuffer_free(self->mRing);
- self->mRing = 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");
- }
-
- ALCopenslPlayback_lock(self);
- if(SL_RESULT_SUCCESS != result)
- aluHandleDisconnect(device, "Failed to get playback buffer: 0x%08x", result);
-
- while(SL_RESULT_SUCCESS == result &&
- !ATOMIC_LOAD(&self->mKillNow, almemory_order_acquire) &&
- ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
- {
- size_t todo;
-
- 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);
-
- aluMixData(device, data[0].buf, data[0].len*device->UpdateSize);
- if(data[1].len > 0)
- aluMixData(device, data[1].buf, data[1].len*device->UpdateSize);
-
- todo = data[0].len+data[1].len;
- ll_ringbuffer_write_advance(self->mRing, todo);
-
- for(size_t i = 0;i < todo;i++)
- {
- if(!data[0].len)
- {
- data[0] = data[1];
- data[1].buf = NULL;
- data[1].len = 0;
- }
-
- result = VCALL(bufferQueue,Enqueue)(data[0].buf, device->UpdateSize*self->mFrameSize);
- PRINTERR(result, "bufferQueue->Enqueue");
- if(SL_RESULT_SUCCESS != result)
- {
- aluHandleDisconnect(device, "Failed to queue audio: 0x%08x", result);
- break;
- }
-
- data[0].len--;
- data[0].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;
-
- if(self->mBufferQueueObj != NULL)
- VCALL0(self->mBufferQueueObj,Destroy)();
- self->mBufferQueueObj = NULL;
-
- ll_ringbuffer_free(self->mRing);
- self->mRing = NULL;
-
- sampleRate = device->Frequency;
-#if 0
- if(!(device->Flags&DEVICE_FREQUENCY_REQUEST))
- {
- /* 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, 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)
- {
- self->mRing = ll_ringbuffer_create(device->NumUpdates,
- self->mFrameSize*device->UpdateSize, true
- );
- if(!self->mRing)
- {
- ERR("Out of memory allocating ring buffer %ux%u %u\n", device->UpdateSize,
- device->NumUpdates, self->mFrameSize);
- result = SL_RESULT_MEMORY_FAILURE;
- }
- }
-
- 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)
-{
- SLAndroidSimpleBufferQueueItf bufferQueue;
- SLresult result;
-
- ll_ringbuffer_reset(self->mRing);
-
- 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");
- }
-}
-
-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)
-{
- 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;
-
- ll_ringbuffer_free(self->mRing);
- self->mRing = 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;
- ALCuint i;
-
- result = VCALL(self->mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
- &bufferQueue);
- PRINTERR(result, "recordObj->GetInterface");
-
- /* Read the desired samples from the ring buffer then advance its read
- * pointer.
- */
- ll_ringbuffer_get_read_vector(self->mRing, data);
- 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;
-
- ll_ringbuffer_read_advance(self->mRing, 1);
- result = VCALL(bufferQueue,Enqueue)(data[0].buf, chunk_size);
- PRINTERR(result, "bufferQueue->Enqueue");
- if(SL_RESULT_SUCCESS != result) break;
-
- data[0].len--;
- if(!data[0].len)
- data[0] = data[1];
- else
- data[0].buf += chunk_size;
- }
-
- i += rem;
- }
-
- 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, al_string *outnames)
-{
- switch(type)
- {
- case ALL_DEVICE_PROBE:
- case CAPTURE_DEVICE_PROBE:
- alstr_append_range(outnames, opensl_device, opensl_device+sizeof(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);
-}
diff --git a/Alc/backends/oss.c b/Alc/backends/oss.c
deleted file mode 100644
index 71faad25..00000000
--- a/Alc/backends/oss.c
+++ /dev/null
@@ -1,869 +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, al_string *outnames);
-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, al_string *outnames)
-{
- struct oss_device *cur = NULL;
- switch(type)
- {
- case ALL_DEVICE_PROBE:
- ALCossListFree(&oss_playback);
- ALCossListPopulate(&oss_playback, DSP_CAP_OUTPUT);
- cur = &oss_playback;
- break;
-
- case CAPTURE_DEVICE_PROBE:
- ALCossListFree(&oss_capture);
- ALCossListPopulate(&oss_capture, DSP_CAP_INPUT);
- cur = &oss_capture;
- break;
- }
- while(cur != NULL)
- {
-#ifdef HAVE_STAT
- struct stat buf;
- if(stat(cur->path, &buf) == 0)
-#endif
- alstr_append_range(outnames, cur->handle, cur->handle+strlen(cur->handle)+1);
- cur = cur->next;
- }
-}
-
-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;
-}
diff --git a/Alc/backends/portaudio.c b/Alc/backends/portaudio.c
deleted file mode 100644
index 6a6cfa31..00000000
--- a/Alc/backends/portaudio.c
+++ /dev/null
@@ -1,555 +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, al_string *outnames);
-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, al_string *outnames)
-{
- switch(type)
- {
- case ALL_DEVICE_PROBE:
- case CAPTURE_DEVICE_PROBE:
- alstr_append_range(outnames, pa_device, pa_device+sizeof(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);
-}
diff --git a/Alc/backends/pulseaudio.c b/Alc/backends/pulseaudio.c
deleted file mode 100644
index b34d7abc..00000000
--- a/Alc/backends/pulseaudio.c
+++ /dev/null
@@ -1,1920 +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, al_string *outnames);
-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, al_string *outnames)
-{
- switch(type)
- {
-#define APPEND_OUTNAME(e) do { \
- if(!alstr_empty((e)->name)) \
- alstr_append_range(outnames, VECTOR_BEGIN((e)->name), \
- VECTOR_END((e)->name)+1); \
-} while(0)
- case ALL_DEVICE_PROBE:
- ALCpulsePlayback_probeDevices();
- VECTOR_FOR_EACH(const DevMap, PlaybackDevices, APPEND_OUTNAME);
- break;
-
- case CAPTURE_DEVICE_PROBE:
- ALCpulseCapture_probeDevices();
- VECTOR_FOR_EACH(const DevMap, CaptureDevices, APPEND_OUTNAME);
- break;
-#undef APPEND_OUTNAME
- }
-}
-
-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), al_string* UNUSED(outnames))
-{
-}
-
-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);
-}
diff --git a/Alc/backends/sdl2.c b/Alc/backends/sdl2.c
deleted file mode 100644
index 3495e6bf..00000000
--- a/Alc/backends/sdl2.c
+++ /dev/null
@@ -1,288 +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, al_string *outnames);
-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, al_string *outnames)
-{
- int num_devices, i;
- al_string name;
-
- if(type != ALL_DEVICE_PROBE)
- return;
-
- AL_STRING_INIT(name);
- num_devices = SDL_GetNumAudioDevices(SDL_FALSE);
-
- alstr_append_range(outnames, defaultDeviceName, defaultDeviceName+sizeof(defaultDeviceName));
- for(i = 0;i < num_devices;++i)
- {
- alstr_copy_cstr(&name, DEVNAME_PREFIX);
- alstr_append_cstr(&name, SDL_GetAudioDeviceName(i, SDL_FALSE));
- if(!alstr_empty(name))
- alstr_append_range(outnames, VECTOR_BEGIN(name), VECTOR_END(name)+1);
- }
- 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;
-}
diff --git a/Alc/backends/sndio.c b/Alc/backends/sndio.c
deleted file mode 100644
index dd174cba..00000000
--- a/Alc/backends/sndio.c
+++ /dev/null
@@ -1,600 +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 "ringbuffer.h"
-
-#include "backends/base.h"
-
-#include <sndio.h>
-
-
-static const ALCchar sndio_device[] = "SndIO Default";
-
-
-typedef struct SndioPlayback {
- DERIVE_FROM_TYPE(ALCbackend);
-
- struct sio_hdl *sndHandle;
-
- ALvoid *mix_data;
- ALsizei data_size;
-
- ATOMIC(int) killNow;
- althrd_t thread;
-} SndioPlayback;
-
-static int SndioPlayback_mixerProc(void *ptr);
-
-static void SndioPlayback_Construct(SndioPlayback *self, ALCdevice *device);
-static void SndioPlayback_Destruct(SndioPlayback *self);
-static ALCenum SndioPlayback_open(SndioPlayback *self, const ALCchar *name);
-static ALCboolean SndioPlayback_reset(SndioPlayback *self);
-static ALCboolean SndioPlayback_start(SndioPlayback *self);
-static void SndioPlayback_stop(SndioPlayback *self);
-static DECLARE_FORWARD2(SndioPlayback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
-static DECLARE_FORWARD(SndioPlayback, ALCbackend, ALCuint, availableSamples)
-static DECLARE_FORWARD(SndioPlayback, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(SndioPlayback, ALCbackend, void, lock)
-static DECLARE_FORWARD(SndioPlayback, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(SndioPlayback)
-
-DEFINE_ALCBACKEND_VTABLE(SndioPlayback);
-
-
-static void SndioPlayback_Construct(SndioPlayback *self, ALCdevice *device)
-{
- ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
- SET_VTABLE2(SndioPlayback, ALCbackend, self);
-
- self->sndHandle = NULL;
- self->mix_data = NULL;
- ATOMIC_INIT(&self->killNow, AL_TRUE);
-}
-
-static void SndioPlayback_Destruct(SndioPlayback *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 SndioPlayback_mixerProc(void *ptr)
-{
- SndioPlayback *self = (SndioPlayback*)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;
-
- SndioPlayback_lock(self);
- aluMixData(device, WritePtr, len/frameSize);
- SndioPlayback_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 SndioPlayback_open(SndioPlayback *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 SndioPlayback_reset(SndioPlayback *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 SndioPlayback_start(SndioPlayback *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, SndioPlayback_mixerProc, self) != althrd_success)
- {
- sio_stop(self->sndHandle);
- return ALC_FALSE;
- }
-
- return ALC_TRUE;
-}
-
-static void SndioPlayback_stop(SndioPlayback *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 SndioCapture {
- DERIVE_FROM_TYPE(ALCbackend);
-
- struct sio_hdl *sndHandle;
-
- ll_ringbuffer_t *ring;
-
- ATOMIC(int) killNow;
- althrd_t thread;
-} SndioCapture;
-
-static int SndioCapture_recordProc(void *ptr);
-
-static void SndioCapture_Construct(SndioCapture *self, ALCdevice *device);
-static void SndioCapture_Destruct(SndioCapture *self);
-static ALCenum SndioCapture_open(SndioCapture *self, const ALCchar *name);
-static DECLARE_FORWARD(SndioCapture, ALCbackend, ALCboolean, reset)
-static ALCboolean SndioCapture_start(SndioCapture *self);
-static void SndioCapture_stop(SndioCapture *self);
-static ALCenum SndioCapture_captureSamples(SndioCapture *self, void *buffer, ALCuint samples);
-static ALCuint SndioCapture_availableSamples(SndioCapture *self);
-static DECLARE_FORWARD(SndioCapture, ALCbackend, ClockLatency, getClockLatency)
-static DECLARE_FORWARD(SndioCapture, ALCbackend, void, lock)
-static DECLARE_FORWARD(SndioCapture, ALCbackend, void, unlock)
-DECLARE_DEFAULT_ALLOCATORS(SndioCapture)
-
-DEFINE_ALCBACKEND_VTABLE(SndioCapture);
-
-
-static void SndioCapture_Construct(SndioCapture *self, ALCdevice *device)
-{
- ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
- SET_VTABLE2(SndioCapture, ALCbackend, self);
-
- self->sndHandle = NULL;
- self->ring = NULL;
- ATOMIC_INIT(&self->killNow, AL_TRUE);
-}
-
-static void SndioCapture_Destruct(SndioCapture *self)
-{
- if(self->sndHandle)
- sio_close(self->sndHandle);
- self->sndHandle = NULL;
-
- ll_ringbuffer_free(self->ring);
- self->ring = NULL;
-
- ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
-}
-
-
-static int SndioCapture_recordProc(void* ptr)
-{
- SndioCapture *self = (SndioCapture*)ptr;
- ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
- ALsizei frameSize;
-
- SetRTPriority();
- althrd_setname(althrd_current(), RECORD_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))
- {
- ll_ringbuffer_data_t data[2];
- size_t total, todo;
-
- ll_ringbuffer_get_write_vector(self->ring, data);
- todo = data[0].len + data[1].len;
- if(todo == 0)
- {
- static char junk[4096];
- sio_read(self->sndHandle, junk, minz(sizeof(junk)/frameSize, device->UpdateSize)*frameSize);
- continue;
- }
-
- total = 0;
- data[0].len *= frameSize;
- data[1].len *= frameSize;
- todo = minz(todo, device->UpdateSize) * frameSize;
- while(total < todo)
- {
- size_t got;
-
- if(!data[0].len)
- data[0] = data[1];
-
- got = sio_read(self->sndHandle, data[0].buf, minz(todo-total, data[0].len));
- if(!got)
- {
- SndioCapture_lock(self);
- aluHandleDisconnect(device, "Failed to read capture samples");
- SndioCapture_unlock(self);
- break;
- }
-
- data[0].buf += got;
- data[0].len -= got;
- total += got;
- }
- ll_ringbuffer_write_advance(self->ring, total / frameSize);
- }
-
- return 0;
-}
-
-
-static ALCenum SndioCapture_open(SndioCapture *self, const ALCchar *name)
-{
- ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
- struct sio_par par;
-
- if(!name)
- name = sndio_device;
- else if(strcmp(name, sndio_device) != 0)
- return ALC_INVALID_VALUE;
-
- self->sndHandle = sio_open(NULL, SIO_REC, 0);
- if(self->sndHandle == NULL)
- {
- ERR("Could not open device\n");
- return ALC_INVALID_VALUE;
- }
-
- sio_initpar(&par);
-
- switch(device->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:
- ERR("%s capture samples not supported\n", DevFmtTypeString(device->FmtType));
- return ALC_INVALID_VALUE;
- }
- par.bits = par.bps * 8;
- par.le = SIO_LE_NATIVE;
- par.msb = SIO_LE_NATIVE ? 0 : 1;
- par.rchan = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
- par.rate = device->Frequency;
-
- par.appbufsz = maxu(device->UpdateSize*device->NumUpdates, (device->Frequency+9)/10);
- par.round = clampu(par.appbufsz/device->NumUpdates, (device->Frequency+99)/100,
- (device->Frequency+19)/20);
-
- device->UpdateSize = par.round;
- device->NumUpdates = maxu(par.appbufsz/par.round, 1);
-
- if(!sio_setpar(self->sndHandle, &par) || !sio_getpar(self->sndHandle, &par))
- {
- ERR("Failed to set device parameters\n");
- return ALC_INVALID_VALUE;
- }
-
- if(par.bits != par.bps*8)
- {
- ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8);
- return ALC_INVALID_VALUE;
- }
-
- if(!((device->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0) ||
- (device->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0) ||
- (device->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0) ||
- (device->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0) ||
- (device->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0) ||
- (device->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0)) ||
- ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder) != (ALsizei)par.rchan ||
- device->Frequency != par.rate)
- {
- ERR("Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead\n",
- DevFmtTypeString(device->FmtType), DevFmtChannelsString(device->FmtChans),
- device->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate);
- return ALC_INVALID_VALUE;
- }
-
- self->ring = ll_ringbuffer_create(device->UpdateSize*device->NumUpdates, par.bps*par.rchan, 0);
- if(!self->ring)
- {
- ERR("Failed to allocate %u-byte ringbuffer\n",
- device->UpdateSize*device->NumUpdates*par.bps*par.rchan);
- return ALC_OUT_OF_MEMORY;
- }
-
- SetDefaultChannelOrder(device);
-
- alstr_copy_cstr(&device->DeviceName, name);
-
- return ALC_NO_ERROR;
-}
-
-static ALCboolean SndioCapture_start(SndioCapture *self)
-{
- 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, SndioCapture_recordProc, self) != althrd_success)
- {
- sio_stop(self->sndHandle);
- return ALC_FALSE;
- }
-
- return ALC_TRUE;
-}
-
-static void SndioCapture_stop(SndioCapture *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");
-}
-
-static ALCenum SndioCapture_captureSamples(SndioCapture *self, void *buffer, ALCuint samples)
-{
- ll_ringbuffer_read(self->ring, buffer, samples);
- return ALC_NO_ERROR;
-}
-
-static ALCuint SndioCapture_availableSamples(SndioCapture *self)
-{
- return ll_ringbuffer_read_space(self->ring);
-}
-
-
-typedef struct SndioBackendFactory {
- DERIVE_FROM_TYPE(ALCbackendFactory);
-} SndioBackendFactory;
-#define SNDIOBACKENDFACTORY_INITIALIZER { { GET_VTABLE2(SndioBackendFactory, ALCbackendFactory) } }
-
-ALCbackendFactory *SndioBackendFactory_getFactory(void);
-
-static ALCboolean SndioBackendFactory_init(SndioBackendFactory *self);
-static DECLARE_FORWARD(SndioBackendFactory, ALCbackendFactory, void, deinit)
-static ALCboolean SndioBackendFactory_querySupport(SndioBackendFactory *self, ALCbackend_Type type);
-static void SndioBackendFactory_probe(SndioBackendFactory *self, enum DevProbe type, al_string *outnames);
-static ALCbackend* SndioBackendFactory_createBackend(SndioBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(SndioBackendFactory);
-
-ALCbackendFactory *SndioBackendFactory_getFactory(void)
-{
- static SndioBackendFactory factory = SNDIOBACKENDFACTORY_INITIALIZER;
- return STATIC_CAST(ALCbackendFactory, &factory);
-}
-
-static ALCboolean SndioBackendFactory_init(SndioBackendFactory* UNUSED(self))
-{
- /* No dynamic loading */
- return ALC_TRUE;
-}
-
-static ALCboolean SndioBackendFactory_querySupport(SndioBackendFactory* UNUSED(self), ALCbackend_Type type)
-{
- if(type == ALCbackend_Playback || type == ALCbackend_Capture)
- return ALC_TRUE;
- return ALC_FALSE;
-}
-
-static void SndioBackendFactory_probe(SndioBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
-{
- switch(type)
- {
- case ALL_DEVICE_PROBE:
- case CAPTURE_DEVICE_PROBE:
- alstr_append_range(outnames, sndio_device, sndio_device+sizeof(sndio_device));
- break;
- }
-}
-
-static ALCbackend* SndioBackendFactory_createBackend(SndioBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
-{
- if(type == ALCbackend_Playback)
- {
- SndioPlayback *backend;
- NEW_OBJ(backend, SndioPlayback)(device);
- if(!backend) return NULL;
- return STATIC_CAST(ALCbackend, backend);
- }
- if(type == ALCbackend_Capture)
- {
- SndioCapture *backend;
- NEW_OBJ(backend, SndioCapture)(device);
- if(!backend) return NULL;
- return STATIC_CAST(ALCbackend, backend);
- }
-
- return NULL;
-}
diff --git a/Alc/backends/solaris.c b/Alc/backends/solaris.c
deleted file mode 100644
index 71282204..00000000
--- a/Alc/backends/solaris.c
+++ /dev/null
@@ -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, al_string *outnames);
-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, al_string *outnames)
-{
- switch(type)
- {
- case ALL_DEVICE_PROBE:
- {
-#ifdef HAVE_STAT
- struct stat buf;
- if(stat(solaris_driver, &buf) == 0)
-#endif
- alstr_append_range(outnames, solaris_device, solaris_device+sizeof(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;
-}
diff --git a/Alc/backends/wasapi.c b/Alc/backends/wasapi.c
deleted file mode 100644
index b974321b..00000000
--- a/Alc/backends/wasapi.c
+++ /dev/null
@@ -1,2045 +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(!defdevid || 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;
-}
-
-
-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, al_string *outnames);
-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, al_string *outnames)
-{
- 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)
- {
-#define APPEND_OUTNAME(e) do { \
- if(!alstr_empty((e)->name)) \
- alstr_append_range(outnames, VECTOR_BEGIN((e)->name), \
- VECTOR_END((e)->name)+1); \
-} while(0)
- case ALL_DEVICE_PROBE:
- VECTOR_FOR_EACH(const DevMap, PlaybackDevices, APPEND_OUTNAME);
- break;
-
- case CAPTURE_DEVICE_PROBE:
- VECTOR_FOR_EACH(const DevMap, CaptureDevices, APPEND_OUTNAME);
- break;
-#undef APPEND_OUTNAME
- }
- 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);
-}
diff --git a/Alc/backends/wave.c b/Alc/backends/wave.c
deleted file mode 100644
index 390b2a5f..00000000
--- a/Alc/backends/wave.c
+++ /dev/null
@@ -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, al_string *outnames);
-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, al_string *outnames)
-{
- switch(type)
- {
- case ALL_DEVICE_PROBE:
- alstr_append_range(outnames, waveDevice, waveDevice+sizeof(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;
-}
diff --git a/Alc/backends/winmm.c b/Alc/backends/winmm.c
deleted file mode 100644
index 0d4a02b8..00000000
--- a/Alc/backends/winmm.c
+++ /dev/null
@@ -1,786 +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);
-}
-
-
-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, al_string *outnames);
-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, al_string *outnames)
-{
- switch(type)
- {
-#define APPEND_OUTNAME(n) do { \
- if(!alstr_empty(*(n))) \
- alstr_append_range(outnames, VECTOR_BEGIN(*(n)), VECTOR_END(*(n))+1); \
-} while(0)
- case ALL_DEVICE_PROBE:
- ProbePlaybackDevices();
- VECTOR_FOR_EACH(const al_string, PlaybackDevices, APPEND_OUTNAME);
- break;
-
- case CAPTURE_DEVICE_PROBE:
- ProbeCaptureDevices();
- VECTOR_FOR_EACH(const al_string, CaptureDevices, APPEND_OUTNAME);
- break;
-#undef APPEND_OUTNAME
- }
-}
-
-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);
-}
diff --git a/Alc/bformatdec.c b/Alc/bformatdec.c
deleted file mode 100644
index 5233d06f..00000000
--- a/Alc/bformatdec.c
+++ /dev/null
@@ -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);
- ComputePanGains(&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++)
- {
- ALdouble gain = 0.0;
- for(k = 0;k < COUNTOF(Ambi3DDecoder);k++)
- gain += (ALdouble)Ambi3DDecoder[k][i] * encgains[k][j];
- ambiup->Gains[i][j][HF_BAND] = (ALfloat)(gain * Ambi3DDecoderHFScale[i]);
- ambiup->Gains[i][j][LF_BAND] = (ALfloat)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
- );
- }
-}
diff --git a/Alc/bformatdec.h b/Alc/bformatdec.h
deleted file mode 100644
index 2d7d1d62..00000000
--- a/Alc/bformatdec.h
+++ /dev/null
@@ -1,57 +0,0 @@
-#ifndef BFORMATDEC_H
-#define BFORMATDEC_H
-
-#include "alMain.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
-
-/* 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
-
-
-/* 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];
-
-
-struct AmbDecConf;
-struct BFormatDec;
-struct AmbiUpsampler;
-
-
-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]);
-
-/* 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);
-
-/* 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);
-
-
-/* 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);
-
-void ambiup_process(struct AmbiUpsampler *ambiup, ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALsizei OutChannels, const ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo);
-
-#endif /* BFORMATDEC_H */
diff --git a/Alc/compat.h b/Alc/compat.h
deleted file mode 100644
index 495bfdf2..00000000
--- a/Alc/compat.h
+++ /dev/null
@@ -1,57 +0,0 @@
-#ifndef AL_COMPAT_H
-#define AL_COMPAT_H
-
-#include "alstring.h"
-
-#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 __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* AL_COMPAT_H */
diff --git a/Alc/converter.c b/Alc/converter.c
deleted file mode 100644
index ef2eb9af..00000000
--- a/Alc/converter.c
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/Alc/converter.h b/Alc/converter.h
deleted file mode 100644
index b58fd831..00000000
--- a/Alc/converter.h
+++ /dev/null
@@ -1,55 +0,0 @@
-#ifndef CONVERTER_H
-#define CONVERTER_H
-
-#include "alMain.h"
-#include "alu.h"
-
-#ifdef __cpluspluc
-extern "C" {
-#endif
-
-typedef struct SampleConverter {
- enum DevFmtType mSrcType;
- enum DevFmtType mDstType;
- ALsizei mNumChannels;
- ALsizei mSrcTypeSize;
- ALsizei mDstTypeSize;
-
- ALint mSrcPrepCount;
-
- ALsizei mFracOffset;
- ALsizei mIncrement;
- InterpState mState;
- ResamplerFunc mResample;
-
- alignas(16) ALfloat mSrcSamples[BUFFERSIZE];
- alignas(16) ALfloat mDstSamples[BUFFERSIZE];
-
- struct {
- alignas(16) ALfloat mPrevSamples[MAX_RESAMPLE_PADDING*2];
- } Chan[];
-} SampleConverter;
-
-SampleConverter *CreateSampleConverter(enum DevFmtType srcType, enum DevFmtType dstType, ALsizei numchans, ALsizei srcRate, ALsizei dstRate);
-void DestroySampleConverter(SampleConverter **converter);
-
-ALsizei SampleConverterInput(SampleConverter *converter, const ALvoid **src, ALsizei *srcframes, ALvoid *dst, ALsizei dstframes);
-ALsizei SampleConverterAvailableOut(SampleConverter *converter, ALsizei srcframes);
-
-
-typedef struct ChannelConverter {
- enum DevFmtType mSrcType;
- enum DevFmtChannels mSrcChans;
- enum DevFmtChannels mDstChans;
-} ChannelConverter;
-
-ChannelConverter *CreateChannelConverter(enum DevFmtType srcType, enum DevFmtChannels srcChans, enum DevFmtChannels dstChans);
-void DestroyChannelConverter(ChannelConverter **converter);
-
-void ChannelConverterInput(ChannelConverter *converter, const ALvoid *src, ALfloat *dst, ALsizei frames);
-
-#ifdef __cpluspluc
-}
-#endif
-
-#endif /* CONVERTER_H */
diff --git a/Alc/effects/autowah.c b/Alc/effects/autowah.c
deleted file mode 100644
index ba1180ef..00000000
--- a/Alc/effects/autowah.c
+++ /dev/null
@@ -1,321 +0,0 @@
-/**
- * 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 <math.h>
-#include <stdlib.h>
-
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-#include "alu.h"
-#include "filters/defs.h"
-
-#define MIN_FREQ 20.0f
-#define MAX_FREQ 2500.0f
-#define Q_FACTOR 5.0f
-
-typedef struct ALautowahState {
- DERIVE_FROM_TYPE(ALeffectState);
-
- /* Effect parameters */
- ALfloat AttackRate;
- ALfloat ReleaseRate;
- ALfloat ResonanceGain;
- ALfloat PeakGain;
- ALfloat FreqMinNorm;
- ALfloat BandwidthNorm;
- ALfloat env_delay;
-
- /* Filter components derived from the envelope. */
- struct {
- ALfloat cos_w0;
- ALfloat alpha;
- } Env[BUFFERSIZE];
-
- struct {
- /* Effect filters' history. */
- struct {
- ALfloat z1, z2;
- } Filter;
-
- /* Effect gains for each output channel */
- ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
- ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
- } Chans[MAX_EFFECT_CHANNELS];
-
- /* Effects buffers */
- alignas(16) ALfloat BufferOut[BUFFERSIZE];
-} ALautowahState;
-
-static ALvoid ALautowahState_Destruct(ALautowahState *state);
-static ALboolean ALautowahState_deviceUpdate(ALautowahState *state, ALCdevice *device);
-static ALvoid ALautowahState_update(ALautowahState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
-static ALvoid ALautowahState_process(ALautowahState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
-DECLARE_DEFAULT_ALLOCATORS(ALautowahState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALautowahState);
-
-static void ALautowahState_Construct(ALautowahState *state)
-{
- ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
- SET_VTABLE2(ALautowahState, ALeffectState, state);
-}
-
-static ALvoid ALautowahState_Destruct(ALautowahState *state)
-{
- ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-static ALboolean ALautowahState_deviceUpdate(ALautowahState *state, ALCdevice *UNUSED(device))
-{
- /* (Re-)initializing parameters and clear the buffers. */
- ALsizei i, j;
-
- state->AttackRate = 1.0f;
- state->ReleaseRate = 1.0f;
- state->ResonanceGain = 10.0f;
- state->PeakGain = 4.5f;
- state->FreqMinNorm = 4.5e-4f;
- state->BandwidthNorm = 0.05f;
- state->env_delay = 0.0f;
-
- memset(state->Env, 0, sizeof(state->Env));
-
- for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
- {
- for(j = 0;j < MAX_OUTPUT_CHANNELS;j++)
- state->Chans[i].CurrentGains[j] = 0.0f;
- state->Chans[i].Filter.z1 = 0.0f;
- state->Chans[i].Filter.z2 = 0.0f;
- }
-
- return AL_TRUE;
-}
-
-static ALvoid ALautowahState_update(ALautowahState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
-{
- const ALCdevice *device = context->Device;
- ALfloat ReleaseTime;
- ALsizei i;
-
- ReleaseTime = clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f);
-
- state->AttackRate = expf(-1.0f / (props->Autowah.AttackTime*device->Frequency));
- state->ReleaseRate = expf(-1.0f / (ReleaseTime*device->Frequency));
- /* 0-20dB Resonance Peak gain */
- state->ResonanceGain = sqrtf(log10f(props->Autowah.Resonance)*10.0f / 3.0f);
- state->PeakGain = 1.0f - log10f(props->Autowah.PeakGain/AL_AUTOWAH_MAX_PEAK_GAIN);
- state->FreqMinNorm = MIN_FREQ / device->Frequency;
- state->BandwidthNorm = (MAX_FREQ-MIN_FREQ) / device->Frequency;
-
- STATIC_CAST(ALeffectState,state)->OutBuffer = device->FOAOut.Buffer;
- STATIC_CAST(ALeffectState,state)->OutChannels = device->FOAOut.NumChannels;
- for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
- ComputePanGains(&device->FOAOut, IdentityMatrixf.m[i], slot->Params.Gain,
- state->Chans[i].TargetGains);
-}
-
-static ALvoid ALautowahState_process(ALautowahState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
-{
- const ALfloat attack_rate = state->AttackRate;
- const ALfloat release_rate = state->ReleaseRate;
- const ALfloat res_gain = state->ResonanceGain;
- const ALfloat peak_gain = state->PeakGain;
- const ALfloat freq_min = state->FreqMinNorm;
- const ALfloat bandwidth = state->BandwidthNorm;
- ALfloat env_delay;
- ALsizei c, i;
-
- env_delay = state->env_delay;
- for(i = 0;i < SamplesToDo;i++)
- {
- ALfloat w0, sample, a;
-
- /* Envelope follower described on the book: Audio Effects, Theory,
- * Implementation and Application.
- */
- sample = peak_gain * fabsf(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) * F_TAU;
- state->Env[i].cos_w0 = cosf(w0);
- state->Env[i].alpha = sinf(w0)/(2.0f * Q_FACTOR);
- }
- state->env_delay = env_delay;
-
- for(c = 0;c < MAX_EFFECT_CHANNELS; c++)
- {
- /* 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.
- */
- ALfloat z1 = state->Chans[c].Filter.z1;
- ALfloat z2 = state->Chans[c].Filter.z2;
-
- for(i = 0;i < SamplesToDo;i++)
- {
- const ALfloat alpha = state->Env[i].alpha;
- const ALfloat cos_w0 = state->Env[i].cos_w0;
- ALfloat input, output;
- ALfloat 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 = SamplesIn[c][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]);
- state->BufferOut[i] = output;
- }
- state->Chans[c].Filter.z1 = z1;
- state->Chans[c].Filter.z2 = z2;
-
- /* Now, mix the processed sound data to the output. */
- MixSamples(state->BufferOut, NumChannels, SamplesOut, state->Chans[c].CurrentGains,
- state->Chans[c].TargetGains, SamplesToDo, 0, SamplesToDo);
- }
-}
-
-typedef struct AutowahStateFactory {
- DERIVE_FROM_TYPE(EffectStateFactory);
-} AutowahStateFactory;
-
-static ALeffectState *AutowahStateFactory_create(AutowahStateFactory *UNUSED(factory))
-{
- ALautowahState *state;
-
- NEW_OBJ0(state, ALautowahState)();
- if(!state) return NULL;
-
- return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(AutowahStateFactory);
-
-EffectStateFactory *AutowahStateFactory_getFactory(void)
-{
- static AutowahStateFactory AutowahFactory = { { GET_VTABLE2(AutowahStateFactory, EffectStateFactory) } };
-
- return STATIC_CAST(EffectStateFactory, &AutowahFactory);
-}
-
-void ALautowah_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
-{
- ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_AUTOWAH_ATTACK_TIME:
- if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME))
- SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah attack time out of range");
- props->Autowah.AttackTime = val;
- break;
-
- case AL_AUTOWAH_RELEASE_TIME:
- if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME))
- SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah release time out of range");
- props->Autowah.ReleaseTime = val;
- break;
-
- case AL_AUTOWAH_RESONANCE:
- if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE))
- SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah resonance out of range");
- props->Autowah.Resonance = val;
- break;
-
- case AL_AUTOWAH_PEAK_GAIN:
- if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN))
- SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah peak gain out of range");
- props->Autowah.PeakGain = val;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
- }
-}
-
-void ALautowah_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{
- ALautowah_setParamf(effect, context, param, vals[0]);
-}
-
-void ALautowah_setParami(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint UNUSED(val))
-{
- alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param);
-}
-
-void ALautowah_setParamiv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
-{
- alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param);
-}
-
-void ALautowah_getParami(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(val))
-{
- alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param);
-}
-void ALautowah_getParamiv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
-{
- alSetError(context, AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param);
-}
-
-void ALautowah_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
-{
-
- const ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_AUTOWAH_ATTACK_TIME:
- *val = props->Autowah.AttackTime;
- break;
-
- case AL_AUTOWAH_RELEASE_TIME:
- *val = props->Autowah.ReleaseTime;
- break;
-
- case AL_AUTOWAH_RESONANCE:
- *val = props->Autowah.Resonance;
- break;
-
- case AL_AUTOWAH_PEAK_GAIN:
- *val = props->Autowah.PeakGain;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
- }
-
-}
-
-void ALautowah_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{
- ALautowah_getParamf(effect, context, param, vals);
-}
-
-DEFINE_ALEFFECT_VTABLE(ALautowah);
diff --git a/Alc/effects/chorus.c b/Alc/effects/chorus.c
deleted file mode 100644
index f2861cf5..00000000
--- a/Alc/effects/chorus.c
+++ /dev/null
@@ -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);
- ComputePanGains(&device->Dry, coeffs, Slot->Params.Gain, state->Gains[0].Target);
- CalcAngleCoeffs( F_PI_2, 0.0f, 0.0f, coeffs);
- ComputePanGains(&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);
diff --git a/Alc/effects/compressor.c b/Alc/effects/compressor.c
deleted file mode 100644
index 2b4a76b0..00000000
--- a/Alc/effects/compressor.c
+++ /dev/null
@@ -1,243 +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"
-
-
-#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 */
-
-
-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 AttackMult;
- ALfloat ReleaseMult;
- ALfloat EnvFollower;
-} 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->AttackMult = 1.0f;
- state->ReleaseMult = 1.0f;
- state->EnvFollower = 1.0f;
-}
-
-static ALvoid ALcompressorState_Destruct(ALcompressorState *state)
-{
- ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-static ALboolean ALcompressorState_deviceUpdate(ALcompressorState *state, ALCdevice *device)
-{
- /* Number of samples to do a full attack and release (non-integer sample
- * counts are okay).
- */
- const ALfloat attackCount = (ALfloat)device->Frequency * ATTACK_TIME;
- const ALfloat releaseCount = (ALfloat)device->Frequency * RELEASE_TIME;
-
- /* Calculate per-sample multipliers to attack and release at the desired
- * rates.
- */
- state->AttackMult = powf(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount);
- state->ReleaseMult = powf(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount);
-
- 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++)
- ComputePanGains(&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 gains[256];
- ALsizei td = mini(256, SamplesToDo-base);
- ALfloat env = state->EnvFollower;
-
- /* Generate the per-sample gains from the signal envelope. */
- if(state->Enabled)
- {
- for(i = 0;i < td;++i)
- {
- /* Clamp the absolute amplitude to the defined envelope limits,
- * then attack or release the envelope to reach it.
- */
- ALfloat amplitude = clampf(fabsf(SamplesIn[0][base+i]),
- AMP_ENVELOPE_MIN, AMP_ENVELOPE_MAX);
- if(amplitude > env)
- env = minf(env*state->AttackMult, amplitude);
- else if(amplitude < env)
- env = maxf(env*state->ReleaseMult, 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(i = 0;i < td;++i)
- {
- ALfloat amplitude = 1.0f;
- if(amplitude > env)
- env = minf(env*state->AttackMult, amplitude);
- else if(amplitude < env)
- env = maxf(env*state->ReleaseMult, amplitude);
-
- gains[i] = 1.0f / env;
- }
- }
- state->EnvFollower = env;
-
- /* Now compress the signal amplitude to output. */
- for(j = 0;j < MAX_EFFECT_CHANNELS;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] += SamplesIn[j][base+i] * gains[i] * gain;
- }
- }
-
- 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);
diff --git a/Alc/effects/dedicated.c b/Alc/effects/dedicated.c
deleted file mode 100644
index 0e1fd389..00000000
--- a/Alc/effects/dedicated.c
+++ /dev/null
@@ -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;
- ComputePanGains(&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);
diff --git a/Alc/effects/distortion.c b/Alc/effects/distortion.c
deleted file mode 100644
index de8da4fe..00000000
--- a/Alc/effects/distortion.c
+++ /dev/null
@@ -1,286 +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);
- ComputePanGains(&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);
diff --git a/Alc/effects/echo.c b/Alc/effects/echo.c
deleted file mode 100644
index 4570fcb1..00000000
--- a/Alc/effects/echo.c
+++ /dev/null
@@ -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);
- ComputePanGains(&device->Dry, coeffs, slot->Params.Gain, state->Gains[0].Target);
-
- /* Second tap panning */
- CalcAngleCoeffs( F_PI_2*lrpan, 0.0f, spread, coeffs);
- ComputePanGains(&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);
diff --git a/Alc/effects/fshifter.c b/Alc/effects/fshifter.c
deleted file mode 100644
index 7d72472a..00000000
--- a/Alc/effects/fshifter.c
+++ /dev/null
@@ -1,329 +0,0 @@
-/**
- * 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 <math.h>
-#include <stdlib.h>
-
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-#include "alu.h"
-#include "filters/defs.h"
-
-#include "alcomplex.h"
-
-#define HIL_SIZE 1024
-#define OVERSAMP (1<<2)
-
-#define HIL_STEP (HIL_SIZE / OVERSAMP)
-#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1))
-
-
-typedef struct ALfshifterState {
- DERIVE_FROM_TYPE(ALeffectState);
-
- /* Effect parameters */
- ALsizei count;
- ALsizei PhaseStep;
- ALsizei Phase;
- ALdouble ld_sign;
-
- /*Effects buffers*/
- ALfloat InFIFO[HIL_SIZE];
- ALcomplex OutFIFO[HIL_SIZE];
- ALcomplex OutputAccum[HIL_SIZE];
- ALcomplex Analytic[HIL_SIZE];
- ALcomplex Outdata[BUFFERSIZE];
-
- alignas(16) ALfloat BufferOut[BUFFERSIZE];
-
- /* Effect gains for each output channel */
- ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
- ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
-} ALfshifterState;
-
-static ALvoid ALfshifterState_Destruct(ALfshifterState *state);
-static ALboolean ALfshifterState_deviceUpdate(ALfshifterState *state, ALCdevice *device);
-static ALvoid ALfshifterState_update(ALfshifterState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
-static ALvoid ALfshifterState_process(ALfshifterState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
-DECLARE_DEFAULT_ALLOCATORS(ALfshifterState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALfshifterState);
-
-/* Define a Hann window, used to filter the HIL input and output. */
-alignas(16) static ALdouble HannWindow[HIL_SIZE];
-
-static void InitHannWindow(void)
-{
- ALsizei i;
-
- /* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
- for(i = 0;i < HIL_SIZE>>1;i++)
- {
- ALdouble val = sin(M_PI * (ALdouble)i / (ALdouble)(HIL_SIZE-1));
- HannWindow[i] = HannWindow[HIL_SIZE-1-i] = val * val;
- }
-}
-
-static alonce_flag HannInitOnce = AL_ONCE_FLAG_INIT;
-
-static void ALfshifterState_Construct(ALfshifterState *state)
-{
- ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
- SET_VTABLE2(ALfshifterState, ALeffectState, state);
-
- alcall_once(&HannInitOnce, InitHannWindow);
-}
-
-static ALvoid ALfshifterState_Destruct(ALfshifterState *state)
-{
- ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-static ALboolean ALfshifterState_deviceUpdate(ALfshifterState *state, ALCdevice *UNUSED(device))
-{
- /* (Re-)initializing parameters and clear the buffers. */
- state->count = FIFO_LATENCY;
- state->PhaseStep = 0;
- state->Phase = 0;
- state->ld_sign = 1.0;
-
- memset(state->InFIFO, 0, sizeof(state->InFIFO));
- memset(state->OutFIFO, 0, sizeof(state->OutFIFO));
- memset(state->OutputAccum, 0, sizeof(state->OutputAccum));
- memset(state->Analytic, 0, sizeof(state->Analytic));
-
- memset(state->CurrentGains, 0, sizeof(state->CurrentGains));
- memset(state->TargetGains, 0, sizeof(state->TargetGains));
-
- return AL_TRUE;
-}
-
-static ALvoid ALfshifterState_update(ALfshifterState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
-{
- const ALCdevice *device = context->Device;
- ALfloat coeffs[MAX_AMBI_COEFFS];
- ALfloat step;
-
- step = props->Fshifter.Frequency / (ALfloat)device->Frequency;
- state->PhaseStep = fastf2i(minf(step, 0.5f) * FRACTIONONE);
-
- switch(props->Fshifter.LeftDirection)
- {
- case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN:
- state->ld_sign = -1.0;
- break;
-
- case AL_FREQUENCY_SHIFTER_DIRECTION_UP:
- state->ld_sign = 1.0;
- break;
-
- case AL_FREQUENCY_SHIFTER_DIRECTION_OFF:
- state->Phase = 0;
- state->PhaseStep = 0;
- break;
- }
-
- CalcAngleCoeffs(0.0f, 0.0f, 0.0f, coeffs);
- ComputePanGains(&device->Dry, coeffs, slot->Params.Gain, state->TargetGains);
-}
-
-static ALvoid ALfshifterState_process(ALfshifterState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
-{
- static const ALcomplex complex_zero = { 0.0, 0.0 };
- ALfloat *restrict BufferOut = state->BufferOut;
- ALsizei j, k, base;
-
- for(base = 0;base < SamplesToDo;)
- {
- ALsizei todo = mini(HIL_SIZE-state->count, SamplesToDo-base);
-
- ASSUME(todo > 0);
-
- /* Fill FIFO buffer with samples data */
- k = state->count;
- for(j = 0;j < todo;j++,k++)
- {
- state->InFIFO[k] = SamplesIn[0][base+j];
- state->Outdata[base+j] = state->OutFIFO[k-FIFO_LATENCY];
- }
- state->count += todo;
- base += todo;
-
- /* Check whether FIFO buffer is filled */
- if(state->count < HIL_SIZE) continue;
-
- state->count = FIFO_LATENCY;
-
- /* Real signal windowing and store in Analytic buffer */
- for(k = 0;k < HIL_SIZE;k++)
- {
- state->Analytic[k].Real = state->InFIFO[k] * HannWindow[k];
- state->Analytic[k].Imag = 0.0;
- }
-
- /* Processing signal by Discrete Hilbert Transform (analytical signal). */
- complex_hilbert(state->Analytic, HIL_SIZE);
-
- /* Windowing and add to output accumulator */
- for(k = 0;k < HIL_SIZE;k++)
- {
- state->OutputAccum[k].Real += 2.0/OVERSAMP*HannWindow[k]*state->Analytic[k].Real;
- state->OutputAccum[k].Imag += 2.0/OVERSAMP*HannWindow[k]*state->Analytic[k].Imag;
- }
-
- /* Shift accumulator, input & output FIFO */
- for(k = 0;k < HIL_STEP;k++) state->OutFIFO[k] = state->OutputAccum[k];
- for(j = 0;k < HIL_SIZE;k++,j++) state->OutputAccum[j] = state->OutputAccum[k];
- for(;j < HIL_SIZE;j++) state->OutputAccum[j] = complex_zero;
- for(k = 0;k < FIFO_LATENCY;k++)
- state->InFIFO[k] = state->InFIFO[k+HIL_STEP];
- }
-
- /* Process frequency shifter using the analytic signal obtained. */
- for(k = 0;k < SamplesToDo;k++)
- {
- ALdouble phase = state->Phase * ((1.0/FRACTIONONE) * 2.0*M_PI);
- BufferOut[k] = (ALfloat)(state->Outdata[k].Real*cos(phase) +
- state->Outdata[k].Imag*sin(phase)*state->ld_sign);
-
- state->Phase += state->PhaseStep;
- state->Phase &= FRACTIONMASK;
- }
-
- /* Now, mix the processed sound data to the output. */
- MixSamples(BufferOut, NumChannels, SamplesOut, state->CurrentGains, state->TargetGains,
- maxi(SamplesToDo, 512), 0, SamplesToDo);
-}
-
-typedef struct FshifterStateFactory {
- DERIVE_FROM_TYPE(EffectStateFactory);
-} FshifterStateFactory;
-
-static ALeffectState *FshifterStateFactory_create(FshifterStateFactory *UNUSED(factory))
-{
- ALfshifterState *state;
-
- NEW_OBJ0(state, ALfshifterState)();
- if(!state) return NULL;
-
- return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(FshifterStateFactory);
-
-EffectStateFactory *FshifterStateFactory_getFactory(void)
-{
- static FshifterStateFactory FshifterFactory = { { GET_VTABLE2(FshifterStateFactory, EffectStateFactory) } };
-
- return STATIC_CAST(EffectStateFactory, &FshifterFactory);
-}
-
-void ALfshifter_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
-{
- ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_FREQUENCY_SHIFTER_FREQUENCY:
- if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY))
- SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter frequency out of range");
- props->Fshifter.Frequency = val;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", param);
- }
-}
-
-void ALfshifter_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{
- ALfshifter_setParamf(effect, context, param, vals[0]);
-}
-
-void ALfshifter_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
-{
- ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
- if(!(val >= AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION))
- SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter left direction out of range");
- props->Fshifter.LeftDirection = val;
- break;
-
- case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
- if(!(val >= AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION))
- SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter right direction out of range");
- props->Fshifter.RightDirection = val;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", param);
- }
-}
-void ALfshifter_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
-{
- ALfshifter_setParami(effect, context, param, vals[0]);
-}
-
-void ALfshifter_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
-{
- const ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
- *val = props->Fshifter.LeftDirection;
- break;
- case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
- *val = props->Fshifter.RightDirection;
- break;
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x", param);
- }
-}
-void ALfshifter_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
-{
- ALfshifter_getParami(effect, context, param, vals);
-}
-
-void ALfshifter_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
-{
-
- const ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_FREQUENCY_SHIFTER_FREQUENCY:
- *val = props->Fshifter.Frequency;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", param);
- }
-
-}
-
-void ALfshifter_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{
- ALfshifter_getParamf(effect, context, param, vals);
-}
-
-DEFINE_ALEFFECT_VTABLE(ALfshifter);
diff --git a/Alc/effects/modulator.c b/Alc/effects/modulator.c
deleted file mode 100644
index e368adb8..00000000
--- a/Alc/effects/modulator.c
+++ /dev/null
@@ -1,307 +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;
-
- 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((ALfloat)index * (F_TAU / WAVEFORM_FRACONE));
-}
-
-static inline ALfloat Saw(ALsizei index)
-{
- return (ALfloat)index*(2.0f/WAVEFORM_FRACONE) - 1.0f;
-}
-
-static inline ALfloat Square(ALsizei index)
-{
- return (ALfloat)(((index>>(WAVEFORM_FRACBITS-2))&2) - 1);
-}
-
-static inline ALfloat One(ALsizei UNUSED(index))
-{
- return 1.0f;
-}
-
-#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)
-DECL_TEMPLATE(One)
-
-#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 f0norm;
- ALsizei i;
-
- state->step = fastf2i(props->Modulator.Frequency / (ALfloat)device->Frequency *
- WAVEFORM_FRACONE);
- state->step = clampi(state->step, 0, WAVEFORM_FRACONE-1);
-
- if(state->step == 0)
- state->GetSamples = ModulateOne;
- else 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;
-
- f0norm = props->Modulator.HighPassCutoff / (ALfloat)device->Frequency;
- f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
- /* Bandwidth value is constant in octaves. */
- BiquadFilter_setParams(&state->Chans[0].Filter, BiquadType_HighPass, 1.0f,
- f0norm, calc_rcpQ_from_bandwidth(f0norm, 0.75f));
- 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++)
- ComputePanGains(&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)
-{
- const ALsizei step = state->step;
- ALsizei base;
-
- for(base = 0;base < SamplesToDo;)
- {
- alignas(16) ALfloat modsamples[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++)
- {
- alignas(16) ALfloat temps[MAX_UPDATE_SAMPLES];
-
- BiquadFilter_process(&state->Chans[c].Filter, temps, &SamplesIn[c][base], td);
- for(i = 0;i < td;i++)
- temps[i] *= modsamples[i];
-
- MixSamples(temps, 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);
diff --git a/Alc/effects/null.c b/Alc/effects/null.c
deleted file mode 100644
index e57359e3..00000000
--- a/Alc/effects/null.c
+++ /dev/null
@@ -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);
diff --git a/Alc/effects/pshifter.c b/Alc/effects/pshifter.c
deleted file mode 100644
index ed18e9a8..00000000
--- a/Alc/effects/pshifter.c
+++ /dev/null
@@ -1,441 +0,0 @@
-/**
- * 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 <math.h>
-#include <stdlib.h>
-
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-#include "alu.h"
-#include "filters/defs.h"
-
-#include "alcomplex.h"
-
-
-#define STFT_SIZE 1024
-#define STFT_HALF_SIZE (STFT_SIZE>>1)
-#define OVERSAMP (1<<2)
-
-#define STFT_STEP (STFT_SIZE / OVERSAMP)
-#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1))
-
-
-typedef struct ALphasor {
- ALdouble Amplitude;
- ALdouble Phase;
-} ALphasor;
-
-typedef struct ALFrequencyDomain {
- ALdouble Amplitude;
- ALdouble Frequency;
-} ALfrequencyDomain;
-
-
-typedef struct ALpshifterState {
- DERIVE_FROM_TYPE(ALeffectState);
-
- /* Effect parameters */
- ALsizei count;
- ALsizei PitchShiftI;
- ALfloat PitchShift;
- ALfloat FreqPerBin;
-
- /*Effects buffers*/
- ALfloat InFIFO[STFT_SIZE];
- ALfloat OutFIFO[STFT_STEP];
- ALdouble LastPhase[STFT_HALF_SIZE+1];
- ALdouble SumPhase[STFT_HALF_SIZE+1];
- ALdouble OutputAccum[STFT_SIZE];
-
- ALcomplex FFTbuffer[STFT_SIZE];
-
- ALfrequencyDomain Analysis_buffer[STFT_HALF_SIZE+1];
- ALfrequencyDomain Syntesis_buffer[STFT_HALF_SIZE+1];
-
- alignas(16) ALfloat BufferOut[BUFFERSIZE];
-
- /* Effect gains for each output channel */
- ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
- ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
-} ALpshifterState;
-
-static ALvoid ALpshifterState_Destruct(ALpshifterState *state);
-static ALboolean ALpshifterState_deviceUpdate(ALpshifterState *state, ALCdevice *device);
-static ALvoid ALpshifterState_update(ALpshifterState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props);
-static ALvoid ALpshifterState_process(ALpshifterState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
-DECLARE_DEFAULT_ALLOCATORS(ALpshifterState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ALpshifterState);
-
-
-/* Define a Hann window, used to filter the STFT input and output. */
-alignas(16) static ALdouble HannWindow[STFT_SIZE];
-
-static void InitHannWindow(void)
-{
- ALsizei i;
-
- /* Create lookup table of the Hann window for the desired size, i.e. STFT_SIZE */
- for(i = 0;i < STFT_SIZE>>1;i++)
- {
- ALdouble val = sin(M_PI * (ALdouble)i / (ALdouble)(STFT_SIZE-1));
- HannWindow[i] = HannWindow[STFT_SIZE-1-i] = val * val;
- }
-}
-static alonce_flag HannInitOnce = AL_ONCE_FLAG_INIT;
-
-
-static inline ALint double2int(ALdouble d)
-{
-#if ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \
- !defined(__SSE2_MATH__)) || (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2)
- ALint sign, shift;
- ALint64 mant;
- union {
- ALdouble d;
- ALint64 i64;
- } conv;
-
- conv.d = d;
- sign = (conv.i64>>63) | 1;
- shift = ((conv.i64>>52)&0x7ff) - (1023+52);
-
- /* Over/underflow */
- if(UNLIKELY(shift >= 63 || shift < -52))
- return 0;
-
- mant = (conv.i64&I64(0xfffffffffffff)) | I64(0x10000000000000);
- if(LIKELY(shift < 0))
- return (ALint)(mant >> -shift) * sign;
- return (ALint)(mant << shift) * sign;
-
-#else
-
- return (ALint)d;
-#endif
-}
-
-
-/* Converts ALcomplex to ALphasor */
-static inline ALphasor rect2polar(ALcomplex number)
-{
- ALphasor polar;
-
- polar.Amplitude = sqrt(number.Real*number.Real + number.Imag*number.Imag);
- polar.Phase = atan2(number.Imag, number.Real);
-
- return polar;
-}
-
-/* Converts ALphasor to ALcomplex */
-static inline ALcomplex polar2rect(ALphasor number)
-{
- ALcomplex cartesian;
-
- cartesian.Real = number.Amplitude * cos(number.Phase);
- cartesian.Imag = number.Amplitude * sin(number.Phase);
-
- return cartesian;
-}
-
-
-static void ALpshifterState_Construct(ALpshifterState *state)
-{
- ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
- SET_VTABLE2(ALpshifterState, ALeffectState, state);
-
- alcall_once(&HannInitOnce, InitHannWindow);
-}
-
-static ALvoid ALpshifterState_Destruct(ALpshifterState *state)
-{
- ALeffectState_Destruct(STATIC_CAST(ALeffectState,state));
-}
-
-static ALboolean ALpshifterState_deviceUpdate(ALpshifterState *state, ALCdevice *device)
-{
- /* (Re-)initializing parameters and clear the buffers. */
- state->count = FIFO_LATENCY;
- state->PitchShiftI = FRACTIONONE;
- state->PitchShift = 1.0f;
- state->FreqPerBin = device->Frequency / (ALfloat)STFT_SIZE;
-
- memset(state->InFIFO, 0, sizeof(state->InFIFO));
- memset(state->OutFIFO, 0, sizeof(state->OutFIFO));
- memset(state->FFTbuffer, 0, sizeof(state->FFTbuffer));
- memset(state->LastPhase, 0, sizeof(state->LastPhase));
- memset(state->SumPhase, 0, sizeof(state->SumPhase));
- memset(state->OutputAccum, 0, sizeof(state->OutputAccum));
- memset(state->Analysis_buffer, 0, sizeof(state->Analysis_buffer));
- memset(state->Syntesis_buffer, 0, sizeof(state->Syntesis_buffer));
-
- memset(state->CurrentGains, 0, sizeof(state->CurrentGains));
- memset(state->TargetGains, 0, sizeof(state->TargetGains));
-
- return AL_TRUE;
-}
-
-static ALvoid ALpshifterState_update(ALpshifterState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
-{
- const ALCdevice *device = context->Device;
- ALfloat coeffs[MAX_AMBI_COEFFS];
- float pitch;
-
- pitch = powf(2.0f,
- (ALfloat)(props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune) / 1200.0f
- );
- state->PitchShiftI = fastf2i(pitch*FRACTIONONE);
- state->PitchShift = state->PitchShiftI * (1.0f/FRACTIONONE);
-
- CalcAngleCoeffs(0.0f, 0.0f, 0.0f, coeffs);
- ComputePanGains(&device->Dry, coeffs, slot->Params.Gain, state->TargetGains);
-}
-
-static ALvoid ALpshifterState_process(ALpshifterState *state, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
-{
- /* Pitch shifter engine based on the work of Stephan Bernsee.
- * http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
- */
-
- static const ALdouble expected = M_PI*2.0 / OVERSAMP;
- const ALdouble freq_per_bin = state->FreqPerBin;
- ALfloat *restrict bufferOut = state->BufferOut;
- ALsizei count = state->count;
- ALsizei i, j, k;
-
- for(i = 0;i < SamplesToDo;)
- {
- do {
- /* Fill FIFO buffer with samples data */
- state->InFIFO[count] = SamplesIn[0][i];
- bufferOut[i] = state->OutFIFO[count - FIFO_LATENCY];
-
- count++;
- } while(++i < SamplesToDo && count < STFT_SIZE);
-
- /* Check whether FIFO buffer is filled */
- if(count < STFT_SIZE) break;
- count = FIFO_LATENCY;
-
- /* Real signal windowing and store in FFTbuffer */
- for(k = 0;k < STFT_SIZE;k++)
- {
- state->FFTbuffer[k].Real = state->InFIFO[k] * HannWindow[k];
- state->FFTbuffer[k].Imag = 0.0;
- }
-
- /* ANALYSIS */
- /* Apply FFT to FFTbuffer data */
- complex_fft(state->FFTbuffer, STFT_SIZE, -1.0);
-
- /* Analyze the obtained data. Since the real FFT is symmetric, only
- * STFT_HALF_SIZE+1 samples are needed.
- */
- for(k = 0;k < STFT_HALF_SIZE+1;k++)
- {
- ALphasor component;
- ALdouble tmp;
- ALint qpd;
-
- /* Compute amplitude and phase */
- component = rect2polar(state->FFTbuffer[k]);
-
- /* Compute phase difference and subtract expected phase difference */
- tmp = (component.Phase - state->LastPhase[k]) - k*expected;
-
- /* Map delta phase into +/- Pi interval */
- qpd = double2int(tmp / M_PI);
- tmp -= M_PI * (qpd + (qpd%2));
-
- /* Get deviation from bin frequency from the +/- Pi interval */
- tmp /= expected;
-
- /* Compute the k-th partials' true frequency, twice the amplitude
- * for maintain the gain (because half of bins are used) and store
- * amplitude and true frequency in analysis buffer.
- */
- state->Analysis_buffer[k].Amplitude = 2.0 * component.Amplitude;
- state->Analysis_buffer[k].Frequency = (k + tmp) * freq_per_bin;
-
- /* Store actual phase[k] for the calculations in the next frame*/
- state->LastPhase[k] = component.Phase;
- }
-
- /* PROCESSING */
- /* pitch shifting */
- for(k = 0;k < STFT_HALF_SIZE+1;k++)
- {
- state->Syntesis_buffer[k].Amplitude = 0.0;
- state->Syntesis_buffer[k].Frequency = 0.0;
- }
-
- for(k = 0;k < STFT_HALF_SIZE+1;k++)
- {
- j = (k*state->PitchShiftI) >> FRACTIONBITS;
- if(j >= STFT_HALF_SIZE+1) break;
-
- state->Syntesis_buffer[j].Amplitude += state->Analysis_buffer[k].Amplitude;
- state->Syntesis_buffer[j].Frequency = state->Analysis_buffer[k].Frequency *
- state->PitchShift;
- }
-
- /* SYNTHESIS */
- /* Synthesis the processing data */
- for(k = 0;k < STFT_HALF_SIZE+1;k++)
- {
- ALphasor component;
- ALdouble tmp;
-
- /* Compute bin deviation from scaled freq */
- tmp = state->Syntesis_buffer[k].Frequency/freq_per_bin - k;
-
- /* Calculate actual delta phase and accumulate it to get bin phase */
- state->SumPhase[k] += (k + tmp) * expected;
-
- component.Amplitude = state->Syntesis_buffer[k].Amplitude;
- component.Phase = state->SumPhase[k];
-
- /* Compute phasor component to cartesian complex number and storage it into FFTbuffer*/
- state->FFTbuffer[k] = polar2rect(component);
- }
- /* zero negative frequencies for recontruct a real signal */
- for(k = STFT_HALF_SIZE+1;k < STFT_SIZE;k++)
- {
- state->FFTbuffer[k].Real = 0.0;
- state->FFTbuffer[k].Imag = 0.0;
- }
-
- /* Apply iFFT to buffer data */
- complex_fft(state->FFTbuffer, STFT_SIZE, 1.0);
-
- /* Windowing and add to output */
- for(k = 0;k < STFT_SIZE;k++)
- state->OutputAccum[k] += HannWindow[k] * state->FFTbuffer[k].Real /
- (0.5 * STFT_HALF_SIZE * OVERSAMP);
-
- /* Shift accumulator, input & output FIFO */
- for(k = 0;k < STFT_STEP;k++) state->OutFIFO[k] = (ALfloat)state->OutputAccum[k];
- for(j = 0;k < STFT_SIZE;k++,j++) state->OutputAccum[j] = state->OutputAccum[k];
- for(;j < STFT_SIZE;j++) state->OutputAccum[j] = 0.0;
- for(k = 0;k < FIFO_LATENCY;k++)
- state->InFIFO[k] = state->InFIFO[k+STFT_STEP];
- }
- state->count = count;
-
- /* Now, mix the processed sound data to the output. */
- MixSamples(bufferOut, NumChannels, SamplesOut, state->CurrentGains, state->TargetGains,
- maxi(SamplesToDo, 512), 0, SamplesToDo);
-}
-
-typedef struct PshifterStateFactory {
- DERIVE_FROM_TYPE(EffectStateFactory);
-} PshifterStateFactory;
-
-static ALeffectState *PshifterStateFactory_create(PshifterStateFactory *UNUSED(factory))
-{
- ALpshifterState *state;
-
- NEW_OBJ0(state, ALpshifterState)();
- if(!state) return NULL;
-
- return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(PshifterStateFactory);
-
-EffectStateFactory *PshifterStateFactory_getFactory(void)
-{
- static PshifterStateFactory PshifterFactory = { { GET_VTABLE2(PshifterStateFactory, EffectStateFactory) } };
-
- return STATIC_CAST(EffectStateFactory, &PshifterFactory);
-}
-
-
-void ALpshifter_setParamf(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat UNUSED(val))
-{
- alSetError( context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param );
-}
-
-void ALpshifter_setParamfv(ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, const ALfloat *UNUSED(vals))
-{
- alSetError( context, AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", param );
-}
-
-void ALpshifter_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
-{
- ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_PITCH_SHIFTER_COARSE_TUNE:
- if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE))
- SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter coarse tune out of range");
- props->Pshifter.CoarseTune = val;
- break;
-
- case AL_PITCH_SHIFTER_FINE_TUNE:
- if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE))
- SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter fine tune out of range");
- props->Pshifter.FineTune = val;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param);
- }
-}
-void ALpshifter_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
-{
- ALpshifter_setParami(effect, context, param, vals[0]);
-}
-
-void ALpshifter_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
-{
- const ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_PITCH_SHIFTER_COARSE_TUNE:
- *val = (ALint)props->Pshifter.CoarseTune;
- break;
- case AL_PITCH_SHIFTER_FINE_TUNE:
- *val = (ALint)props->Pshifter.FineTune;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param);
- }
-}
-void ALpshifter_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
-{
- ALpshifter_getParami(effect, context, param, vals);
-}
-
-void ALpshifter_getParamf(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat *UNUSED(val))
-{
- alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param);
-}
-
-void ALpshifter_getParamfv(const ALeffect *UNUSED(effect), ALCcontext *context, ALenum param, ALfloat *UNUSED(vals))
-{
- alSetError(context, AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param);
-}
-
-DEFINE_ALEFFECT_VTABLE(ALpshifter);
diff --git a/Alc/effects/reverb.c b/Alc/effects/reverb.c
deleted file mode 100644
index 8ebc089e..00000000
--- a/Alc/effects/reverb.c
+++ /dev/null
@@ -1,2090 +0,0 @@
-/**
- * Ambisonic reverb engine for the OpenAL cross platform audio library
- * Copyright (C) 2008-2017 by Chris Robinson and Christopher Fitzgerald.
- * 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 <math.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "alAuxEffectSlot.h"
-#include "alListener.h"
-#include "alError.h"
-#include "filters/defs.h"
-
-/* This is a user config option for modifying the overall output of the reverb
- * effect.
- */
-ALfloat ReverbBoost = 1.0f;
-
-/* This is the maximum number of samples processed for each inner loop
- * iteration. */
-#define MAX_UPDATE_SAMPLES 256
-
-/* The number of samples used for cross-faded delay lines. This can be used
- * to balance the compensation for abrupt line changes and attenuation due to
- * minimally lengthed recursive lines. Try to keep this below the device
- * update size.
- */
-#define FADE_SAMPLES 128
-
-/* The number of spatialized lines or channels to process. Four channels allows
- * for a 3D A-Format response. NOTE: This can't be changed without taking care
- * of the conversion matrices, and a few places where the length arrays are
- * assumed to have 4 elements.
- */
-#define NUM_LINES 4
-
-
-/* The B-Format to A-Format conversion matrix. The arrangement of rows is
- * deliberately chosen to align the resulting lines to their spatial opposites
- * (0:above front left <-> 3:above back right, 1:below front right <-> 2:below
- * back left). It's not quite opposite, since the A-Format results in a
- * tetrahedron, but it's close enough. Should the model be extended to 8-lines
- * in the future, true opposites can be used.
- */
-static const aluMatrixf B2A = {{
- { 0.288675134595f, 0.288675134595f, 0.288675134595f, 0.288675134595f },
- { 0.288675134595f, -0.288675134595f, -0.288675134595f, 0.288675134595f },
- { 0.288675134595f, 0.288675134595f, -0.288675134595f, -0.288675134595f },
- { 0.288675134595f, -0.288675134595f, 0.288675134595f, -0.288675134595f }
-}};
-
-/* Converts A-Format to B-Format. */
-static const aluMatrixf A2B = {{
- { 0.866025403785f, 0.866025403785f, 0.866025403785f, 0.866025403785f },
- { 0.866025403785f, -0.866025403785f, 0.866025403785f, -0.866025403785f },
- { 0.866025403785f, -0.866025403785f, -0.866025403785f, 0.866025403785f },
- { 0.866025403785f, 0.866025403785f, -0.866025403785f, -0.866025403785f }
-}};
-
-static const ALfloat FadeStep = 1.0f / FADE_SAMPLES;
-
-/* The all-pass and delay lines have a variable length dependent on the
- * effect's density parameter, which helps alter the perceived environment
- * size. The size-to-density conversion is a cubed scale:
- *
- * density = min(1.0, pow(size, 3.0) / DENSITY_SCALE);
- *
- * The line lengths scale linearly with room size, so the inverse density
- * conversion is needed, taking the cube root of the re-scaled density to
- * calculate the line length multiplier:
- *
- * length_mult = max(5.0, cbrtf(density*DENSITY_SCALE));
- *
- * The density scale below will result in a max line multiplier of 50, for an
- * effective size range of 5m to 50m.
- */
-static const ALfloat DENSITY_SCALE = 125000.0f;
-
-/* All delay line lengths are specified in seconds.
- *
- * To approximate early reflections, we break them up into primary (those
- * arriving from the same direction as the source) and secondary (those
- * arriving from the opposite direction).
- *
- * The early taps decorrelate the 4-channel signal to approximate an average
- * room response for the primary reflections after the initial early delay.
- *
- * Given an average room dimension (d_a) and the speed of sound (c) we can
- * calculate the average reflection delay (r_a) regardless of listener and
- * source positions as:
- *
- * r_a = d_a / c
- * c = 343.3
- *
- * This can extended to finding the average difference (r_d) between the
- * maximum (r_1) and minimum (r_0) reflection delays:
- *
- * r_0 = 2 / 3 r_a
- * = r_a - r_d / 2
- * = r_d
- * r_1 = 4 / 3 r_a
- * = r_a + r_d / 2
- * = 2 r_d
- * r_d = 2 / 3 r_a
- * = r_1 - r_0
- *
- * As can be determined by integrating the 1D model with a source (s) and
- * listener (l) positioned across the dimension of length (d_a):
- *
- * r_d = int_(l=0)^d_a (int_(s=0)^d_a |2 d_a - 2 (l + s)| ds) dl / c
- *
- * The initial taps (T_(i=0)^N) are then specified by taking a power series
- * that ranges between r_0 and half of r_1 less r_0:
- *
- * R_i = 2^(i / (2 N - 1)) r_d
- * = r_0 + (2^(i / (2 N - 1)) - 1) r_d
- * = r_0 + T_i
- * T_i = R_i - r_0
- * = (2^(i / (2 N - 1)) - 1) r_d
- *
- * Assuming an average of 1m, we get the following taps:
- */
-static const ALfloat EARLY_TAP_LENGTHS[NUM_LINES] =
-{
- 0.0000000e+0f, 2.0213520e-4f, 4.2531060e-4f, 6.7171600e-4f
-};
-
-/* The early all-pass filter lengths are based on the early tap lengths:
- *
- * A_i = R_i / a
- *
- * Where a is the approximate maximum all-pass cycle limit (20).
- */
-static const ALfloat EARLY_ALLPASS_LENGTHS[NUM_LINES] =
-{
- 9.7096800e-5f, 1.0720356e-4f, 1.1836234e-4f, 1.3068260e-4f
-};
-
-/* The early delay lines are used to transform the primary reflections into
- * the secondary reflections. The A-format is arranged in such a way that
- * the channels/lines are spatially opposite:
- *
- * C_i is opposite C_(N-i-1)
- *
- * The delays of the two opposing reflections (R_i and O_i) from a source
- * anywhere along a particular dimension always sum to twice its full delay:
- *
- * 2 r_a = R_i + O_i
- *
- * With that in mind we can determine the delay between the two reflections
- * and thus specify our early line lengths (L_(i=0)^N) using:
- *
- * O_i = 2 r_a - R_(N-i-1)
- * L_i = O_i - R_(N-i-1)
- * = 2 (r_a - R_(N-i-1))
- * = 2 (r_a - T_(N-i-1) - r_0)
- * = 2 r_a (1 - (2 / 3) 2^((N - i - 1) / (2 N - 1)))
- *
- * Using an average dimension of 1m, we get:
- */
-static const ALfloat EARLY_LINE_LENGTHS[NUM_LINES] =
-{
- 5.9850400e-4f, 1.0913150e-3f, 1.5376658e-3f, 1.9419362e-3f
-};
-
-/* The late all-pass filter lengths are based on the late line lengths:
- *
- * A_i = (5 / 3) L_i / r_1
- */
-static const ALfloat LATE_ALLPASS_LENGTHS[NUM_LINES] =
-{
- 1.6182800e-4f, 2.0389060e-4f, 2.8159360e-4f, 3.2365600e-4f
-};
-
-/* The late lines are used to approximate the decaying cycle of recursive
- * late reflections.
- *
- * Splitting the lines in half, we start with the shortest reflection paths
- * (L_(i=0)^(N/2)):
- *
- * L_i = 2^(i / (N - 1)) r_d
- *
- * Then for the opposite (longest) reflection paths (L_(i=N/2)^N):
- *
- * L_i = 2 r_a - L_(i-N/2)
- * = 2 r_a - 2^((i - N / 2) / (N - 1)) r_d
- *
- * For our 1m average room, we get:
- */
-static const ALfloat LATE_LINE_LENGTHS[NUM_LINES] =
-{
- 1.9419362e-3f, 2.4466860e-3f, 3.3791220e-3f, 3.8838720e-3f
-};
-
-
-typedef struct DelayLineI {
- /* The delay lines use interleaved samples, with the lengths being powers
- * of 2 to allow the use of bit-masking instead of a modulus for wrapping.
- */
- ALsizei Mask;
- ALfloat (*Line)[NUM_LINES];
-} DelayLineI;
-
-typedef struct VecAllpass {
- DelayLineI Delay;
- ALfloat Coeff;
- ALsizei Offset[NUM_LINES][2];
-} VecAllpass;
-
-typedef struct T60Filter {
- /* Two filters are used to adjust the signal. One to control the low
- * frequencies, and one to control the high frequencies.
- */
- ALfloat MidGain[2];
- BiquadFilter HFFilter, LFFilter;
-} T60Filter;
-
-typedef struct EarlyReflections {
- /* A Gerzon vector all-pass filter is used to simulate initial diffusion.
- * The spread from this filter also helps smooth out the reverb tail.
- */
- VecAllpass VecAp;
-
- /* An echo line is used to complete the second half of the early
- * reflections.
- */
- DelayLineI Delay;
- ALsizei Offset[NUM_LINES][2];
- ALfloat Coeff[NUM_LINES][2];
-
- /* The gain for each output channel based on 3D panning. */
- ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS];
- ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS];
-} EarlyReflections;
-
-typedef struct LateReverb {
- /* A recursive delay line is used fill in the reverb tail. */
- DelayLineI Delay;
- ALsizei Offset[NUM_LINES][2];
-
- /* Attenuation to compensate for the modal density and decay rate of the
- * late lines.
- */
- ALfloat DensityGain[2];
-
- /* T60 decay filters are used to simulate absorption. */
- T60Filter T60[NUM_LINES];
-
- /* A Gerzon vector all-pass filter is used to simulate diffusion. */
- VecAllpass VecAp;
-
- /* The gain for each output channel based on 3D panning. */
- ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS];
- ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS];
-} LateReverb;
-
-typedef struct ReverbState {
- DERIVE_FROM_TYPE(ALeffectState);
-
- /* All delay lines are allocated as a single buffer to reduce memory
- * fragmentation and management code.
- */
- ALfloat *SampleBuffer;
- ALuint TotalSamples;
-
- struct {
- /* Calculated parameters which indicate if cross-fading is needed after
- * an update.
- */
- ALfloat Density, Diffusion;
- ALfloat DecayTime, HFDecayTime, LFDecayTime;
- ALfloat HFReference, LFReference;
- } Params;
-
- /* Master effect filters */
- struct {
- BiquadFilter Lp;
- BiquadFilter Hp;
- } Filter[NUM_LINES];
-
- /* Core delay line (early reflections and late reverb tap from this). */
- DelayLineI Delay;
-
- /* Tap points for early reflection delay. */
- ALsizei EarlyDelayTap[NUM_LINES][2];
- ALfloat EarlyDelayCoeff[NUM_LINES][2];
-
- /* Tap points for late reverb feed and delay. */
- ALsizei LateFeedTap;
- ALsizei LateDelayTap[NUM_LINES][2];
-
- /* Coefficients for the all-pass and line scattering matrices. */
- ALfloat MixX;
- ALfloat MixY;
-
- EarlyReflections Early;
-
- LateReverb Late;
-
- /* Indicates the cross-fade point for delay line reads [0,FADE_SAMPLES]. */
- ALsizei FadeCount;
-
- /* Maximum number of samples to process at once. */
- ALsizei MaxUpdate[2];
-
- /* The current write offset for all delay lines. */
- ALsizei Offset;
-
- /* Temporary storage used when processing. */
- alignas(16) ALfloat TempSamples[NUM_LINES][MAX_UPDATE_SAMPLES];
- alignas(16) ALfloat MixSamples[NUM_LINES][MAX_UPDATE_SAMPLES];
-} ReverbState;
-
-static ALvoid ReverbState_Destruct(ReverbState *State);
-static ALboolean ReverbState_deviceUpdate(ReverbState *State, ALCdevice *Device);
-static ALvoid ReverbState_update(ReverbState *State, const ALCcontext *Context, const ALeffectslot *Slot, const ALeffectProps *props);
-static ALvoid ReverbState_process(ReverbState *State, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels);
-DECLARE_DEFAULT_ALLOCATORS(ReverbState)
-
-DEFINE_ALEFFECTSTATE_VTABLE(ReverbState);
-
-static void ReverbState_Construct(ReverbState *state)
-{
- ALsizei i, j;
-
- ALeffectState_Construct(STATIC_CAST(ALeffectState, state));
- SET_VTABLE2(ReverbState, ALeffectState, state);
-
- state->TotalSamples = 0;
- state->SampleBuffer = NULL;
-
- state->Params.Density = AL_EAXREVERB_DEFAULT_DENSITY;
- state->Params.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION;
- state->Params.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME;
- state->Params.HFDecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME*AL_EAXREVERB_DEFAULT_DECAY_HFRATIO;
- state->Params.LFDecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME*AL_EAXREVERB_DEFAULT_DECAY_LFRATIO;
- state->Params.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE;
- state->Params.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE;
-
- for(i = 0;i < NUM_LINES;i++)
- {
- BiquadFilter_clear(&state->Filter[i].Lp);
- BiquadFilter_clear(&state->Filter[i].Hp);
- }
-
- state->Delay.Mask = 0;
- state->Delay.Line = NULL;
-
- for(i = 0;i < NUM_LINES;i++)
- {
- state->EarlyDelayTap[i][0] = 0;
- state->EarlyDelayTap[i][1] = 0;
- state->EarlyDelayCoeff[i][0] = 0.0f;
- state->EarlyDelayCoeff[i][1] = 0.0f;
- }
-
- state->LateFeedTap = 0;
-
- for(i = 0;i < NUM_LINES;i++)
- {
- state->LateDelayTap[i][0] = 0;
- state->LateDelayTap[i][1] = 0;
- }
-
- state->MixX = 0.0f;
- state->MixY = 0.0f;
-
- state->Early.VecAp.Delay.Mask = 0;
- state->Early.VecAp.Delay.Line = NULL;
- state->Early.VecAp.Coeff = 0.0f;
- state->Early.Delay.Mask = 0;
- state->Early.Delay.Line = NULL;
- for(i = 0;i < NUM_LINES;i++)
- {
- state->Early.VecAp.Offset[i][0] = 0;
- state->Early.VecAp.Offset[i][1] = 0;
- state->Early.Offset[i][0] = 0;
- state->Early.Offset[i][1] = 0;
- state->Early.Coeff[i][0] = 0.0f;
- state->Early.Coeff[i][1] = 0.0f;
- }
-
- state->Late.DensityGain[0] = 0.0f;
- state->Late.DensityGain[1] = 0.0f;
- state->Late.Delay.Mask = 0;
- state->Late.Delay.Line = NULL;
- state->Late.VecAp.Delay.Mask = 0;
- state->Late.VecAp.Delay.Line = NULL;
- state->Late.VecAp.Coeff = 0.0f;
- for(i = 0;i < NUM_LINES;i++)
- {
- state->Late.Offset[i][0] = 0;
- state->Late.Offset[i][1] = 0;
-
- state->Late.VecAp.Offset[i][0] = 0;
- state->Late.VecAp.Offset[i][1] = 0;
-
- state->Late.T60[i].MidGain[0] = 0.0f;
- state->Late.T60[i].MidGain[1] = 0.0f;
- BiquadFilter_clear(&state->Late.T60[i].HFFilter);
- BiquadFilter_clear(&state->Late.T60[i].LFFilter);
- }
-
- for(i = 0;i < NUM_LINES;i++)
- {
- for(j = 0;j < MAX_OUTPUT_CHANNELS;j++)
- {
- state->Early.CurrentGain[i][j] = 0.0f;
- state->Early.PanGain[i][j] = 0.0f;
- state->Late.CurrentGain[i][j] = 0.0f;
- state->Late.PanGain[i][j] = 0.0f;
- }
- }
-
- state->FadeCount = 0;
- state->MaxUpdate[0] = MAX_UPDATE_SAMPLES;
- state->MaxUpdate[1] = MAX_UPDATE_SAMPLES;
- state->Offset = 0;
-}
-
-static ALvoid ReverbState_Destruct(ReverbState *State)
-{
- al_free(State->SampleBuffer);
- State->SampleBuffer = NULL;
-
- ALeffectState_Destruct(STATIC_CAST(ALeffectState,State));
-}
-
-/**************************************
- * Device Update *
- **************************************/
-
-static inline ALfloat CalcDelayLengthMult(ALfloat density)
-{
- return maxf(5.0f, cbrtf(density*DENSITY_SCALE));
-}
-
-/* Given the allocated sample buffer, this function updates each delay line
- * offset.
- */
-static inline ALvoid RealizeLineOffset(ALfloat *sampleBuffer, DelayLineI *Delay)
-{
- union {
- ALfloat *f;
- ALfloat (*f4)[NUM_LINES];
- } u;
- u.f = &sampleBuffer[(ptrdiff_t)Delay->Line * NUM_LINES];
- Delay->Line = u.f4;
-}
-
-/* Calculate the length of a delay line and store its mask and offset. */
-static ALuint CalcLineLength(const ALfloat length, const ptrdiff_t offset, const ALuint frequency,
- const ALuint extra, DelayLineI *Delay)
-{
- ALuint samples;
-
- /* All line lengths are powers of 2, calculated from their lengths in
- * seconds, rounded up.
- */
- samples = float2int(ceilf(length*frequency));
- samples = NextPowerOf2(samples + extra);
-
- /* All lines share a single sample buffer. */
- Delay->Mask = samples - 1;
- Delay->Line = (ALfloat(*)[NUM_LINES])offset;
-
- /* Return the sample count for accumulation. */
- return samples;
-}
-
-/* Calculates the delay line metrics and allocates the shared sample buffer
- * for all lines given the sample rate (frequency). If an allocation failure
- * occurs, it returns AL_FALSE.
- */
-static ALboolean AllocLines(const ALuint frequency, ReverbState *State)
-{
- ALuint totalSamples, i;
- ALfloat multiplier, length;
-
- /* All delay line lengths are calculated to accomodate the full range of
- * lengths given their respective paramters.
- */
- totalSamples = 0;
-
- /* Multiplier for the maximum density value, i.e. density=1, which is
- * actually the least density...
- */
- multiplier = CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY);
-
- /* The main delay length includes the maximum early reflection delay, the
- * largest early tap width, the maximum late reverb delay, and the
- * largest late tap width. Finally, it must also be extended by the
- * update size (MAX_UPDATE_SAMPLES) for block processing.
- */
- length = AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS[NUM_LINES-1]*multiplier +
- AL_EAXREVERB_MAX_LATE_REVERB_DELAY +
- (LATE_LINE_LENGTHS[NUM_LINES-1] - LATE_LINE_LENGTHS[0])*0.25f*multiplier;
- totalSamples += CalcLineLength(length, totalSamples, frequency, MAX_UPDATE_SAMPLES,
- &State->Delay);
-
- /* The early vector all-pass line. */
- length = EARLY_ALLPASS_LENGTHS[NUM_LINES-1] * multiplier;
- totalSamples += CalcLineLength(length, totalSamples, frequency, 0,
- &State->Early.VecAp.Delay);
-
- /* The early reflection line. */
- length = EARLY_LINE_LENGTHS[NUM_LINES-1] * multiplier;
- totalSamples += CalcLineLength(length, totalSamples, frequency, 0,
- &State->Early.Delay);
-
- /* The late vector all-pass line. */
- length = LATE_ALLPASS_LENGTHS[NUM_LINES-1] * multiplier;
- totalSamples += CalcLineLength(length, totalSamples, frequency, 0,
- &State->Late.VecAp.Delay);
-
- /* The late delay lines are calculated from the largest maximum density
- * line length.
- */
- length = LATE_LINE_LENGTHS[NUM_LINES-1] * multiplier;
- totalSamples += CalcLineLength(length, totalSamples, frequency, 0,
- &State->Late.Delay);
-
- if(totalSamples != State->TotalSamples)
- {
- ALfloat *newBuffer;
-
- TRACE("New reverb buffer length: %ux4 samples\n", totalSamples);
- newBuffer = al_calloc(16, sizeof(ALfloat[NUM_LINES]) * totalSamples);
- if(!newBuffer) return AL_FALSE;
-
- al_free(State->SampleBuffer);
- State->SampleBuffer = newBuffer;
- State->TotalSamples = totalSamples;
- }
-
- /* Update all delays to reflect the new sample buffer. */
- RealizeLineOffset(State->SampleBuffer, &State->Delay);
- RealizeLineOffset(State->SampleBuffer, &State->Early.VecAp.Delay);
- RealizeLineOffset(State->SampleBuffer, &State->Early.Delay);
- RealizeLineOffset(State->SampleBuffer, &State->Late.VecAp.Delay);
- RealizeLineOffset(State->SampleBuffer, &State->Late.Delay);
-
- /* Clear the sample buffer. */
- for(i = 0;i < State->TotalSamples;i++)
- State->SampleBuffer[i] = 0.0f;
-
- return AL_TRUE;
-}
-
-static ALboolean ReverbState_deviceUpdate(ReverbState *State, ALCdevice *Device)
-{
- ALuint frequency = Device->Frequency;
- ALfloat multiplier;
- ALsizei i, j;
-
- /* Allocate the delay lines. */
- if(!AllocLines(frequency, State))
- return AL_FALSE;
-
- multiplier = CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY);
-
- /* The late feed taps are set a fixed position past the latest delay tap. */
- State->LateFeedTap = float2int((AL_EAXREVERB_MAX_REFLECTIONS_DELAY +
- EARLY_TAP_LENGTHS[NUM_LINES-1]*multiplier) *
- frequency);
-
- /* Clear filters and gain coefficients since the delay lines were all just
- * cleared (if not reallocated).
- */
- for(i = 0;i < NUM_LINES;i++)
- {
- BiquadFilter_clear(&State->Filter[i].Lp);
- BiquadFilter_clear(&State->Filter[i].Hp);
- }
-
- for(i = 0;i < NUM_LINES;i++)
- {
- State->EarlyDelayCoeff[i][0] = 0.0f;
- State->EarlyDelayCoeff[i][1] = 0.0f;
- }
-
- for(i = 0;i < NUM_LINES;i++)
- {
- State->Early.Coeff[i][0] = 0.0f;
- State->Early.Coeff[i][1] = 0.0f;
- }
-
- State->Late.DensityGain[0] = 0.0f;
- State->Late.DensityGain[1] = 0.0f;
- for(i = 0;i < NUM_LINES;i++)
- {
- State->Late.T60[i].MidGain[0] = 0.0f;
- State->Late.T60[i].MidGain[1] = 0.0f;
- BiquadFilter_clear(&State->Late.T60[i].HFFilter);
- BiquadFilter_clear(&State->Late.T60[i].LFFilter);
- }
-
- for(i = 0;i < NUM_LINES;i++)
- {
- for(j = 0;j < MAX_OUTPUT_CHANNELS;j++)
- {
- State->Early.CurrentGain[i][j] = 0.0f;
- State->Early.PanGain[i][j] = 0.0f;
- State->Late.CurrentGain[i][j] = 0.0f;
- State->Late.PanGain[i][j] = 0.0f;
- }
- }
-
- /* Reset counters and offset base. */
- State->FadeCount = 0;
- State->MaxUpdate[0] = MAX_UPDATE_SAMPLES;
- State->MaxUpdate[1] = MAX_UPDATE_SAMPLES;
- State->Offset = 0;
-
- return AL_TRUE;
-}
-
-/**************************************
- * Effect Update *
- **************************************/
-
-/* Calculate a decay coefficient given the length of each cycle and the time
- * until the decay reaches -60 dB.
- */
-static inline ALfloat CalcDecayCoeff(const ALfloat length, const ALfloat decayTime)
-{
- return powf(REVERB_DECAY_GAIN, length/decayTime);
-}
-
-/* Calculate a decay length from a coefficient and the time until the decay
- * reaches -60 dB.
- */
-static inline ALfloat CalcDecayLength(const ALfloat coeff, const ALfloat decayTime)
-{
- return log10f(coeff) * decayTime / log10f(REVERB_DECAY_GAIN);
-}
-
-/* Calculate an attenuation to be applied to the input of any echo models to
- * compensate for modal density and decay time.
- */
-static inline ALfloat CalcDensityGain(const ALfloat a)
-{
- /* The energy of a signal can be obtained by finding the area under the
- * squared signal. This takes the form of Sum(x_n^2), where x is the
- * amplitude for the sample n.
- *
- * Decaying feedback matches exponential decay of the form Sum(a^n),
- * where a is the attenuation coefficient, and n is the sample. The area
- * under this decay curve can be calculated as: 1 / (1 - a).
- *
- * Modifying the above equation to find the area under the squared curve
- * (for energy) yields: 1 / (1 - a^2). Input attenuation can then be
- * calculated by inverting the square root of this approximation,
- * yielding: 1 / sqrt(1 / (1 - a^2)), simplified to: sqrt(1 - a^2).
- */
- return sqrtf(1.0f - a*a);
-}
-
-/* Calculate the scattering matrix coefficients given a diffusion factor. */
-static inline ALvoid CalcMatrixCoeffs(const ALfloat diffusion, ALfloat *x, ALfloat *y)
-{
- ALfloat n, t;
-
- /* The matrix is of order 4, so n is sqrt(4 - 1). */
- n = sqrtf(3.0f);
- t = diffusion * atanf(n);
-
- /* Calculate the first mixing matrix coefficient. */
- *x = cosf(t);
- /* Calculate the second mixing matrix coefficient. */
- *y = sinf(t) / n;
-}
-
-/* Calculate the limited HF ratio for use with the late reverb low-pass
- * filters.
- */
-static ALfloat CalcLimitedHfRatio(const ALfloat hfRatio, const ALfloat airAbsorptionGainHF,
- const ALfloat decayTime, const ALfloat SpeedOfSound)
-{
- ALfloat limitRatio;
-
- /* Find the attenuation due to air absorption in dB (converting delay
- * time to meters using the speed of sound). Then reversing the decay
- * equation, solve for HF ratio. The delay length is cancelled out of
- * the equation, so it can be calculated once for all lines.
- */
- limitRatio = 1.0f / (CalcDecayLength(airAbsorptionGainHF, decayTime) * SpeedOfSound);
-
- /* Using the limit calculated above, apply the upper bound to the HF ratio.
- */
- return minf(limitRatio, hfRatio);
-}
-
-
-/* Calculates the 3-band T60 damping coefficients for a particular delay line
- * of specified length, using a combination of two shelf filter sections given
- * decay times for each band split at two reference frequencies.
- */
-static void CalcT60DampingCoeffs(const ALfloat length, const ALfloat lfDecayTime,
- const ALfloat mfDecayTime, const ALfloat hfDecayTime,
- const ALfloat lf0norm, const ALfloat hf0norm,
- T60Filter *filter)
-{
- ALfloat lfGain = CalcDecayCoeff(length, lfDecayTime);
- ALfloat mfGain = CalcDecayCoeff(length, mfDecayTime);
- ALfloat hfGain = CalcDecayCoeff(length, hfDecayTime);
-
- filter->MidGain[1] = mfGain;
- BiquadFilter_setParams(&filter->LFFilter, BiquadType_LowShelf, lfGain/mfGain, lf0norm,
- calc_rcpQ_from_slope(lfGain/mfGain, 1.0f));
- BiquadFilter_setParams(&filter->HFFilter, BiquadType_HighShelf, hfGain/mfGain, hf0norm,
- calc_rcpQ_from_slope(hfGain/mfGain, 1.0f));
-}
-
-/* Update the offsets for the main effect delay line. */
-static ALvoid UpdateDelayLine(const ALfloat earlyDelay, const ALfloat lateDelay, const ALfloat density, const ALfloat decayTime, const ALuint frequency, ReverbState *State)
-{
- ALfloat multiplier, length;
- ALuint i;
-
- multiplier = CalcDelayLengthMult(density);
-
- /* Early reflection taps are decorrelated by means of an average room
- * reflection approximation described above the definition of the taps.
- * This approximation is linear and so the above density multiplier can
- * be applied to adjust the width of the taps. A single-band decay
- * coefficient is applied to simulate initial attenuation and absorption.
- *
- * Late reverb taps are based on the late line lengths to allow a zero-
- * delay path and offsets that would continue the propagation naturally
- * into the late lines.
- */
- for(i = 0;i < NUM_LINES;i++)
- {
- length = earlyDelay + EARLY_TAP_LENGTHS[i]*multiplier;
- State->EarlyDelayTap[i][1] = float2int(length * frequency);
-
- length = EARLY_TAP_LENGTHS[i]*multiplier;
- State->EarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime);
-
- length = lateDelay + (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS[0])*0.25f*multiplier;
- State->LateDelayTap[i][1] = State->LateFeedTap + float2int(length * frequency);
- }
-}
-
-/* Update the early reflection line lengths and gain coefficients. */
-static ALvoid UpdateEarlyLines(const ALfloat density, const ALfloat diffusion, const ALfloat decayTime, const ALuint frequency, EarlyReflections *Early)
-{
- ALfloat multiplier, length;
- ALsizei i;
-
- multiplier = CalcDelayLengthMult(density);
-
- /* Calculate the all-pass feed-back/forward coefficient. */
- Early->VecAp.Coeff = sqrtf(0.5f) * powf(diffusion, 2.0f);
-
- for(i = 0;i < NUM_LINES;i++)
- {
- /* Calculate the length (in seconds) of each all-pass line. */
- length = EARLY_ALLPASS_LENGTHS[i] * multiplier;
-
- /* Calculate the delay offset for each all-pass line. */
- Early->VecAp.Offset[i][1] = float2int(length * frequency);
-
- /* Calculate the length (in seconds) of each delay line. */
- length = EARLY_LINE_LENGTHS[i] * multiplier;
-
- /* Calculate the delay offset for each delay line. */
- Early->Offset[i][1] = float2int(length * frequency);
-
- /* Calculate the gain (coefficient) for each line. */
- Early->Coeff[i][1] = CalcDecayCoeff(length, decayTime);
- }
-}
-
-/* Update the late reverb line lengths and T60 coefficients. */
-static ALvoid UpdateLateLines(const ALfloat density, const ALfloat diffusion, const ALfloat lfDecayTime, const ALfloat mfDecayTime, const ALfloat hfDecayTime, const ALfloat lf0norm, const ALfloat hf0norm, const ALuint frequency, LateReverb *Late)
-{
- /* Scaling factor to convert the normalized reference frequencies from
- * representing 0...freq to 0...max_reference.
- */
- const ALfloat norm_weight_factor = (ALfloat)frequency / AL_EAXREVERB_MAX_HFREFERENCE;
- ALfloat multiplier, length, bandWeights[3];
- ALsizei i;
-
- /* To compensate for changes in modal density and decay time of the late
- * reverb signal, the input is attenuated based on the maximal energy of
- * the outgoing signal. This approximation is used to keep the apparent
- * energy of the signal equal for all ranges of density and decay time.
- *
- * The average length of the delay lines is used to calculate the
- * attenuation coefficient.
- */
- multiplier = CalcDelayLengthMult(density);
- length = (LATE_LINE_LENGTHS[0] + LATE_LINE_LENGTHS[1] +
- LATE_LINE_LENGTHS[2] + LATE_LINE_LENGTHS[3]) / 4.0f * multiplier;
- length += (LATE_ALLPASS_LENGTHS[0] + LATE_ALLPASS_LENGTHS[1] +
- LATE_ALLPASS_LENGTHS[2] + LATE_ALLPASS_LENGTHS[3]) / 4.0f * multiplier;
- /* The density gain calculation uses an average decay time weighted by
- * approximate bandwidth. This attempts to compensate for losses of energy
- * that reduce decay time due to scattering into highly attenuated bands.
- */
- bandWeights[0] = lf0norm*norm_weight_factor;
- bandWeights[1] = hf0norm*norm_weight_factor - lf0norm*norm_weight_factor;
- bandWeights[2] = 1.0f - hf0norm*norm_weight_factor;
- Late->DensityGain[1] = CalcDensityGain(
- CalcDecayCoeff(length,
- bandWeights[0]*lfDecayTime + bandWeights[1]*mfDecayTime + bandWeights[2]*hfDecayTime
- )
- );
-
- /* Calculate the all-pass feed-back/forward coefficient. */
- Late->VecAp.Coeff = sqrtf(0.5f) * powf(diffusion, 2.0f);
-
- for(i = 0;i < NUM_LINES;i++)
- {
- /* Calculate the length (in seconds) of each all-pass line. */
- length = LATE_ALLPASS_LENGTHS[i] * multiplier;
-
- /* Calculate the delay offset for each all-pass line. */
- Late->VecAp.Offset[i][1] = float2int(length * frequency);
-
- /* Calculate the length (in seconds) of each delay line. */
- length = LATE_LINE_LENGTHS[i] * multiplier;
-
- /* Calculate the delay offset for each delay line. */
- Late->Offset[i][1] = float2int(length*frequency + 0.5f);
-
- /* Approximate the absorption that the vector all-pass would exhibit
- * given the current diffusion so we don't have to process a full T60
- * filter for each of its four lines.
- */
- length += lerp(LATE_ALLPASS_LENGTHS[i],
- (LATE_ALLPASS_LENGTHS[0] + LATE_ALLPASS_LENGTHS[1] +
- LATE_ALLPASS_LENGTHS[2] + LATE_ALLPASS_LENGTHS[3]) / 4.0f,
- diffusion) * multiplier;
-
- /* Calculate the T60 damping coefficients for each line. */
- CalcT60DampingCoeffs(length, lfDecayTime, mfDecayTime, hfDecayTime,
- lf0norm, hf0norm, &Late->T60[i]);
- }
-}
-
-/* Creates a transform matrix given a reverb vector. The vector pans the reverb
- * reflections toward the given direction, using its magnitude (up to 1) as a
- * focal strength. This function results in a B-Format transformation matrix
- * that spatially focuses the signal in the desired direction.
- */
-static aluMatrixf GetTransformFromVector(const ALfloat *vec)
-{
- aluMatrixf focus;
- ALfloat norm[3];
- ALfloat mag;
-
- /* Normalize the panning vector according to the N3D scale, which has an
- * extra sqrt(3) term on the directional components. Converting from OpenAL
- * to B-Format also requires negating X (ACN 1) and Z (ACN 3). Note however
- * that the reverb panning vectors use left-handed coordinates, unlike the
- * rest of OpenAL which use right-handed. This is fixed by negating Z,
- * which cancels out with the B-Format Z negation.
- */
- mag = sqrtf(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]);
- if(mag > 1.0f)
- {
- norm[0] = vec[0] / mag * -SQRTF_3;
- norm[1] = vec[1] / mag * SQRTF_3;
- norm[2] = vec[2] / mag * SQRTF_3;
- mag = 1.0f;
- }
- else
- {
- /* If the magnitude is less than or equal to 1, just apply the sqrt(3)
- * term. There's no need to renormalize the magnitude since it would
- * just be reapplied in the matrix.
- */
- norm[0] = vec[0] * -SQRTF_3;
- norm[1] = vec[1] * SQRTF_3;
- norm[2] = vec[2] * SQRTF_3;
- }
-
- aluMatrixfSet(&focus,
- 1.0f, 0.0f, 0.0f, 0.0f,
- norm[0], 1.0f-mag, 0.0f, 0.0f,
- norm[1], 0.0f, 1.0f-mag, 0.0f,
- norm[2], 0.0f, 0.0f, 1.0f-mag
- );
-
- return focus;
-}
-
-/* Update the early and late 3D panning gains. */
-static ALvoid Update3DPanning(const ALCdevice *Device, const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, const ALfloat earlyGain, const ALfloat lateGain, ReverbState *State)
-{
- aluMatrixf transform, rot;
- ALsizei i;
-
- STATIC_CAST(ALeffectState,State)->OutBuffer = Device->FOAOut.Buffer;
- STATIC_CAST(ALeffectState,State)->OutChannels = Device->FOAOut.NumChannels;
-
- /* Note: _res is transposed. */
-#define MATRIX_MULT(_res, _m1, _m2) do { \
- int row, col; \
- for(col = 0;col < 4;col++) \
- { \
- for(row = 0;row < 4;row++) \
- _res.m[col][row] = _m1.m[row][0]*_m2.m[0][col] + _m1.m[row][1]*_m2.m[1][col] + \
- _m1.m[row][2]*_m2.m[2][col] + _m1.m[row][3]*_m2.m[3][col]; \
- } \
-} while(0)
- /* Create a matrix that first converts A-Format to B-Format, then
- * transforms the B-Format signal according to the panning vector.
- */
- rot = GetTransformFromVector(ReflectionsPan);
- MATRIX_MULT(transform, rot, A2B);
- memset(&State->Early.PanGain, 0, sizeof(State->Early.PanGain));
- for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
- ComputePanGains(&Device->FOAOut, transform.m[i], earlyGain,
- State->Early.PanGain[i]);
-
- rot = GetTransformFromVector(LateReverbPan);
- MATRIX_MULT(transform, rot, A2B);
- memset(&State->Late.PanGain, 0, sizeof(State->Late.PanGain));
- for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
- ComputePanGains(&Device->FOAOut, transform.m[i], lateGain,
- State->Late.PanGain[i]);
-#undef MATRIX_MULT
-}
-
-static void ReverbState_update(ReverbState *State, const ALCcontext *Context, const ALeffectslot *Slot, const ALeffectProps *props)
-{
- const ALCdevice *Device = Context->Device;
- const ALlistener *Listener = Context->Listener;
- ALuint frequency = Device->Frequency;
- ALfloat lf0norm, hf0norm, hfRatio;
- ALfloat lfDecayTime, hfDecayTime;
- ALfloat gain, gainlf, gainhf;
- ALsizei i;
-
- /* Calculate the master filters */
- hf0norm = minf(props->Reverb.HFReference / frequency, 0.49f);
- /* Restrict the filter gains from going below -60dB to keep the filter from
- * killing most of the signal.
- */
- gainhf = maxf(props->Reverb.GainHF, 0.001f);
- BiquadFilter_setParams(&State->Filter[0].Lp, BiquadType_HighShelf, gainhf, hf0norm,
- calc_rcpQ_from_slope(gainhf, 1.0f));
- lf0norm = minf(props->Reverb.LFReference / frequency, 0.49f);
- gainlf = maxf(props->Reverb.GainLF, 0.001f);
- BiquadFilter_setParams(&State->Filter[0].Hp, BiquadType_LowShelf, gainlf, lf0norm,
- calc_rcpQ_from_slope(gainlf, 1.0f));
- for(i = 1;i < NUM_LINES;i++)
- {
- BiquadFilter_copyParams(&State->Filter[i].Lp, &State->Filter[0].Lp);
- BiquadFilter_copyParams(&State->Filter[i].Hp, &State->Filter[0].Hp);
- }
-
- /* Update the main effect delay and associated taps. */
- UpdateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay,
- props->Reverb.Density, props->Reverb.DecayTime, frequency,
- State);
-
- /* Update the early lines. */
- UpdateEarlyLines(props->Reverb.Density, props->Reverb.Diffusion,
- props->Reverb.DecayTime, frequency, &State->Early);
-
- /* Get the mixing matrix coefficients. */
- CalcMatrixCoeffs(props->Reverb.Diffusion, &State->MixX, &State->MixY);
-
- /* If the HF limit parameter is flagged, calculate an appropriate limit
- * based on the air absorption parameter.
- */
- hfRatio = props->Reverb.DecayHFRatio;
- if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f)
- hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF,
- props->Reverb.DecayTime, Listener->Params.ReverbSpeedOfSound
- );
-
- /* Calculate the LF/HF decay times. */
- lfDecayTime = clampf(props->Reverb.DecayTime * props->Reverb.DecayLFRatio,
- AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME);
- hfDecayTime = clampf(props->Reverb.DecayTime * hfRatio,
- AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME);
-
- /* Update the late lines. */
- UpdateLateLines(props->Reverb.Density, props->Reverb.Diffusion,
- lfDecayTime, props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm,
- frequency, &State->Late
- );
-
- /* Update early and late 3D panning. */
- gain = props->Reverb.Gain * Slot->Params.Gain * ReverbBoost;
- Update3DPanning(Device, props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan,
- props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain,
- State);
-
- /* Calculate the max update size from the smallest relevant delay. */
- State->MaxUpdate[1] = mini(MAX_UPDATE_SAMPLES,
- mini(State->Early.Offset[0][1], State->Late.Offset[0][1])
- );
-
- /* Determine if delay-line cross-fading is required. Density is essentially
- * a master control for the feedback delays, so changes the offsets of many
- * delay lines.
- */
- if(State->Params.Density != props->Reverb.Density ||
- /* Diffusion and decay times influences the decay rate (gain) of the
- * late reverb T60 filter.
- */
- State->Params.Diffusion != props->Reverb.Diffusion ||
- State->Params.DecayTime != props->Reverb.DecayTime ||
- State->Params.HFDecayTime != hfDecayTime ||
- State->Params.LFDecayTime != lfDecayTime ||
- /* HF/LF References control the weighting used to calculate the density
- * gain.
- */
- State->Params.HFReference != props->Reverb.HFReference ||
- State->Params.LFReference != props->Reverb.LFReference)
- State->FadeCount = 0;
- State->Params.Density = props->Reverb.Density;
- State->Params.Diffusion = props->Reverb.Diffusion;
- State->Params.DecayTime = props->Reverb.DecayTime;
- State->Params.HFDecayTime = hfDecayTime;
- State->Params.LFDecayTime = lfDecayTime;
- State->Params.HFReference = props->Reverb.HFReference;
- State->Params.LFReference = props->Reverb.LFReference;
-}
-
-
-/**************************************
- * Effect Processing *
- **************************************/
-
-/* Basic delay line input/output routines. */
-static inline ALfloat DelayLineOut(const DelayLineI *Delay, const ALsizei offset, const ALsizei c)
-{
- return Delay->Line[offset&Delay->Mask][c];
-}
-
-/* Cross-faded delay line output routine. Instead of interpolating the
- * offsets, this interpolates (cross-fades) the outputs at each offset.
- */
-static inline ALfloat FadedDelayLineOut(const DelayLineI *Delay, const ALsizei off0,
- const ALsizei off1, const ALsizei c,
- const ALfloat sc0, const ALfloat sc1)
-{
- return Delay->Line[off0&Delay->Mask][c]*sc0 +
- Delay->Line[off1&Delay->Mask][c]*sc1;
-}
-
-
-static inline void DelayLineIn(const DelayLineI *Delay, ALsizei offset, const ALsizei c,
- const ALfloat *restrict in, ALsizei count)
-{
- ALsizei i;
- for(i = 0;i < count;i++)
- Delay->Line[(offset++)&Delay->Mask][c] = *(in++);
-}
-
-/* Applies a scattering matrix to the 4-line (vector) input. This is used
- * for both the below vector all-pass model and to perform modal feed-back
- * delay network (FDN) mixing.
- *
- * The matrix is derived from a skew-symmetric matrix to form a 4D rotation
- * matrix with a single unitary rotational parameter:
- *
- * [ d, a, b, c ] 1 = a^2 + b^2 + c^2 + d^2
- * [ -a, d, c, -b ]
- * [ -b, -c, d, a ]
- * [ -c, b, -a, d ]
- *
- * The rotation is constructed from the effect's diffusion parameter,
- * yielding:
- *
- * 1 = x^2 + 3 y^2
- *
- * Where a, b, and c are the coefficient y with differing signs, and d is the
- * coefficient x. The final matrix is thus:
- *
- * [ x, y, -y, y ] n = sqrt(matrix_order - 1)
- * [ -y, x, y, y ] t = diffusion_parameter * atan(n)
- * [ y, -y, x, y ] x = cos(t)
- * [ -y, -y, -y, x ] y = sin(t) / n
- *
- * Any square orthogonal matrix with an order that is a power of two will
- * work (where ^T is transpose, ^-1 is inverse):
- *
- * M^T = M^-1
- *
- * Using that knowledge, finding an appropriate matrix can be accomplished
- * naively by searching all combinations of:
- *
- * M = D + S - S^T
- *
- * Where D is a diagonal matrix (of x), and S is a triangular matrix (of y)
- * whose combination of signs are being iterated.
- */
-static inline void VectorPartialScatter(ALfloat *restrict out, const ALfloat *restrict in,
- const ALfloat xCoeff, const ALfloat yCoeff)
-{
- out[0] = xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]);
- out[1] = xCoeff*in[1] + yCoeff*(-in[0] + in[2] + in[3]);
- out[2] = xCoeff*in[2] + yCoeff*( in[0] + -in[1] + in[3]);
- out[3] = xCoeff*in[3] + yCoeff*(-in[0] + -in[1] + -in[2] );
-}
-#define VectorScatterDelayIn(delay, o, in, xcoeff, ycoeff) \
- VectorPartialScatter((delay)->Line[(o)&(delay)->Mask], in, xcoeff, ycoeff)
-
-/* Utilizes the above, but reverses the input channels. */
-static inline void VectorScatterRevDelayIn(const DelayLineI *Delay, ALint offset,
- const ALfloat xCoeff, const ALfloat yCoeff,
- const ALfloat (*restrict in)[MAX_UPDATE_SAMPLES],
- const ALsizei count)
-{
- const DelayLineI delay = *Delay;
- ALsizei i, j;
-
- for(i = 0;i < count;++i)
- {
- ALfloat f[NUM_LINES];
- for(j = 0;j < NUM_LINES;j++)
- f[NUM_LINES-1-j] = in[j][i];
-
- VectorScatterDelayIn(&delay, offset++, f, xCoeff, yCoeff);
- }
-}
-
-/* This applies a Gerzon multiple-in/multiple-out (MIMO) vector all-pass
- * filter to the 4-line input.
- *
- * It works by vectorizing a regular all-pass filter and replacing the delay
- * element with a scattering matrix (like the one above) and a diagonal
- * matrix of delay elements.
- *
- * Two static specializations are used for transitional (cross-faded) delay
- * line processing and non-transitional processing.
- */
-static void VectorAllpass_Unfaded(ALfloat (*restrict samples)[MAX_UPDATE_SAMPLES], ALsizei offset,
- const ALfloat xCoeff, const ALfloat yCoeff, ALsizei todo,
- VecAllpass *Vap)
-{
- const DelayLineI delay = Vap->Delay;
- const ALfloat feedCoeff = Vap->Coeff;
- ALsizei vap_offset[NUM_LINES];
- ALsizei i, j;
-
- ASSUME(todo > 0);
-
- for(j = 0;j < NUM_LINES;j++)
- vap_offset[j] = offset-Vap->Offset[j][0];
- for(i = 0;i < todo;i++)
- {
- ALfloat f[NUM_LINES];
-
- for(j = 0;j < NUM_LINES;j++)
- {
- ALfloat input = samples[j][i];
- ALfloat out = DelayLineOut(&delay, vap_offset[j]++, j) - feedCoeff*input;
- f[j] = input + feedCoeff*out;
-
- samples[j][i] = out;
- }
-
- VectorScatterDelayIn(&delay, offset, f, xCoeff, yCoeff);
- ++offset;
- }
-}
-static void VectorAllpass_Faded(ALfloat (*restrict samples)[MAX_UPDATE_SAMPLES], ALsizei offset,
- const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fade,
- ALsizei todo, VecAllpass *Vap)
-{
- const DelayLineI delay = Vap->Delay;
- const ALfloat feedCoeff = Vap->Coeff;
- ALsizei vap_offset[NUM_LINES][2];
- ALsizei i, j;
-
- ASSUME(todo > 0);
-
- fade *= 1.0f/FADE_SAMPLES;
- for(j = 0;j < NUM_LINES;j++)
- {
- vap_offset[j][0] = offset-Vap->Offset[j][0];
- vap_offset[j][1] = offset-Vap->Offset[j][1];
- }
- for(i = 0;i < todo;i++)
- {
- ALfloat f[NUM_LINES];
-
- for(j = 0;j < NUM_LINES;j++)
- {
- ALfloat input = samples[j][i];
- ALfloat out =
- FadedDelayLineOut(&delay, vap_offset[j][0]++, vap_offset[j][1]++, j,
- 1.0f-fade, fade
- ) - feedCoeff*input;
- f[j] = input + feedCoeff*out;
-
- samples[j][i] = out;
- }
- fade += FadeStep;
-
- VectorScatterDelayIn(&delay, offset, f, xCoeff, yCoeff);
- ++offset;
- }
-}
-
-/* This generates early reflections.
- *
- * This is done by obtaining the primary reflections (those arriving from the
- * same direction as the source) from the main delay line. These are
- * attenuated and all-pass filtered (based on the diffusion parameter).
- *
- * The early lines are then fed in reverse (according to the approximately
- * opposite spatial location of the A-Format lines) to create the secondary
- * reflections (those arriving from the opposite direction as the source).
- *
- * The early response is then completed by combining the primary reflections
- * with the delayed and attenuated output from the early lines.
- *
- * Finally, the early response is reversed, scattered (based on diffusion),
- * and fed into the late reverb section of the main delay line.
- *
- * Two static specializations are used for transitional (cross-faded) delay
- * line processing and non-transitional processing.
- */
-static void EarlyReflection_Unfaded(ReverbState *State, ALsizei offset, const ALsizei todo,
- ALfloat (*restrict out)[MAX_UPDATE_SAMPLES])
-{
- ALfloat (*restrict temps)[MAX_UPDATE_SAMPLES] = State->TempSamples;
- const DelayLineI early_delay = State->Early.Delay;
- const DelayLineI main_delay = State->Delay;
- const ALfloat mixX = State->MixX;
- const ALfloat mixY = State->MixY;
- ALsizei late_feed_tap;
- ALsizei i, j;
-
- ASSUME(todo > 0);
-
- /* First, load decorrelated samples from the main delay line as the primary
- * reflections.
- */
- for(j = 0;j < NUM_LINES;j++)
- {
- ALsizei early_delay_tap = offset - State->EarlyDelayTap[j][0];
- ALfloat coeff = State->EarlyDelayCoeff[j][0];
- for(i = 0;i < todo;i++)
- temps[j][i] = DelayLineOut(&main_delay, early_delay_tap++, j) * coeff;
- }
-
- /* Apply a vector all-pass, to help color the initial reflections based on
- * the diffusion strength.
- */
- VectorAllpass_Unfaded(temps, offset, mixX, mixY, todo, &State->Early.VecAp);
-
- /* Apply a delay and bounce to generate secondary reflections, combine with
- * the primary reflections and write out the result for mixing.
- */
- for(j = 0;j < NUM_LINES;j++)
- {
- ALint early_feedb_tap = offset - State->Early.Offset[j][0];
- ALfloat early_feedb_coeff = State->Early.Coeff[j][0];
-
- for(i = 0;i < todo;i++)
- out[j][i] = DelayLineOut(&early_delay, early_feedb_tap++, j)*early_feedb_coeff +
- temps[j][i];
- }
- for(j = 0;j < NUM_LINES;j++)
- DelayLineIn(&early_delay, offset, NUM_LINES-1-j, temps[j], todo);
-
- /* Also write the result back to the main delay line for the late reverb
- * stage to pick up at the appropriate time, appplying a scatter and
- * bounce to improve the initial diffusion in the late reverb.
- */
- late_feed_tap = offset - State->LateFeedTap;
- VectorScatterRevDelayIn(&main_delay, late_feed_tap, mixX, mixY, out, todo);
-}
-static void EarlyReflection_Faded(ReverbState *State, ALsizei offset, const ALsizei todo,
- const ALfloat fade, ALfloat (*restrict out)[MAX_UPDATE_SAMPLES])
-{
- ALfloat (*restrict temps)[MAX_UPDATE_SAMPLES] = State->TempSamples;
- const DelayLineI early_delay = State->Early.Delay;
- const DelayLineI main_delay = State->Delay;
- const ALfloat mixX = State->MixX;
- const ALfloat mixY = State->MixY;
- ALsizei late_feed_tap;
- ALsizei i, j;
-
- ASSUME(todo > 0);
-
- for(j = 0;j < NUM_LINES;j++)
- {
- ALsizei early_delay_tap0 = offset - State->EarlyDelayTap[j][0];
- ALsizei early_delay_tap1 = offset - State->EarlyDelayTap[j][1];
- ALfloat oldCoeff = State->EarlyDelayCoeff[j][0];
- ALfloat oldCoeffStep = -oldCoeff / FADE_SAMPLES;
- ALfloat newCoeffStep = State->EarlyDelayCoeff[j][1] / FADE_SAMPLES;
- ALfloat fadeCount = fade;
-
- for(i = 0;i < todo;i++)
- {
- const ALfloat fade0 = oldCoeff + oldCoeffStep*fadeCount;
- const ALfloat fade1 = newCoeffStep*fadeCount;
- temps[j][i] = FadedDelayLineOut(&main_delay,
- early_delay_tap0++, early_delay_tap1++, j, fade0, fade1
- );
- fadeCount += 1.0f;
- }
- }
-
- VectorAllpass_Faded(temps, offset, mixX, mixY, fade, todo, &State->Early.VecAp);
-
- for(j = 0;j < NUM_LINES;j++)
- {
- ALint feedb_tap0 = offset - State->Early.Offset[j][0];
- ALint feedb_tap1 = offset - State->Early.Offset[j][1];
- ALfloat feedb_oldCoeff = State->Early.Coeff[j][0];
- ALfloat feedb_oldCoeffStep = -feedb_oldCoeff / FADE_SAMPLES;
- ALfloat feedb_newCoeffStep = State->Early.Coeff[j][1] / FADE_SAMPLES;
- ALfloat fadeCount = fade;
-
- for(i = 0;i < todo;i++)
- {
- const ALfloat fade0 = feedb_oldCoeff + feedb_oldCoeffStep*fadeCount;
- const ALfloat fade1 = feedb_newCoeffStep*fadeCount;
- out[j][i] = FadedDelayLineOut(&early_delay,
- feedb_tap0++, feedb_tap1++, j, fade0, fade1
- ) + temps[j][i];
- fadeCount += 1.0f;
- }
- }
- for(j = 0;j < NUM_LINES;j++)
- DelayLineIn(&early_delay, offset, NUM_LINES-1-j, temps[j], todo);
-
- late_feed_tap = offset - State->LateFeedTap;
- VectorScatterRevDelayIn(&main_delay, late_feed_tap, mixX, mixY, out, todo);
-}
-
-/* Applies the two T60 damping filter sections. */
-static inline void LateT60Filter(ALfloat *restrict samples, const ALsizei todo, T60Filter *filter)
-{
- ALfloat temp[MAX_UPDATE_SAMPLES];
- BiquadFilter_process(&filter->HFFilter, temp, samples, todo);
- BiquadFilter_process(&filter->LFFilter, samples, temp, todo);
-}
-
-/* This generates the reverb tail using a modified feed-back delay network
- * (FDN).
- *
- * Results from the early reflections are mixed with the output from the late
- * delay lines.
- *
- * The late response is then completed by T60 and all-pass filtering the mix.
- *
- * Finally, the lines are reversed (so they feed their opposite directions)
- * and scattered with the FDN matrix before re-feeding the delay lines.
- *
- * Two variations are made, one for for transitional (cross-faded) delay line
- * processing and one for non-transitional processing.
- */
-static void LateReverb_Unfaded(ReverbState *State, ALsizei offset, const ALsizei todo,
- ALfloat (*restrict out)[MAX_UPDATE_SAMPLES])
-{
- ALfloat (*restrict temps)[MAX_UPDATE_SAMPLES] = State->TempSamples;
- const DelayLineI late_delay = State->Late.Delay;
- const DelayLineI main_delay = State->Delay;
- const ALfloat mixX = State->MixX;
- const ALfloat mixY = State->MixY;
- ALsizei i, j;
-
- ASSUME(todo > 0);
-
- /* First, load decorrelated samples from the main and feedback delay lines.
- * Filter the signal to apply its frequency-dependent decay.
- */
- for(j = 0;j < NUM_LINES;j++)
- {
- ALsizei late_delay_tap = offset - State->LateDelayTap[j][0];
- ALsizei late_feedb_tap = offset - State->Late.Offset[j][0];
- ALfloat midGain = State->Late.T60[j].MidGain[0];
- const ALfloat densityGain = State->Late.DensityGain[0] * midGain;
- for(i = 0;i < todo;i++)
- temps[j][i] = DelayLineOut(&main_delay, late_delay_tap++, j)*densityGain +
- DelayLineOut(&late_delay, late_feedb_tap++, j)*midGain;
- LateT60Filter(temps[j], todo, &State->Late.T60[j]);
- }
-
- /* Apply a vector all-pass to improve micro-surface diffusion, and write
- * out the results for mixing.
- */
- VectorAllpass_Unfaded(temps, offset, mixX, mixY, todo, &State->Late.VecAp);
-
- for(j = 0;j < NUM_LINES;j++)
- memcpy(out[j], temps[j], todo*sizeof(ALfloat));
-
- /* Finally, scatter and bounce the results to refeed the feedback buffer. */
- VectorScatterRevDelayIn(&late_delay, offset, mixX, mixY, out, todo);
-}
-static void LateReverb_Faded(ReverbState *State, ALsizei offset, const ALsizei todo,
- const ALfloat fade, ALfloat (*restrict out)[MAX_UPDATE_SAMPLES])
-{
- ALfloat (*restrict temps)[MAX_UPDATE_SAMPLES] = State->TempSamples;
- const DelayLineI late_delay = State->Late.Delay;
- const DelayLineI main_delay = State->Delay;
- const ALfloat mixX = State->MixX;
- const ALfloat mixY = State->MixY;
- ALsizei i, j;
-
- ASSUME(todo > 0);
-
- for(j = 0;j < NUM_LINES;j++)
- {
- const ALfloat oldMidGain = State->Late.T60[j].MidGain[0];
- const ALfloat midGain = State->Late.T60[j].MidGain[1];
- const ALfloat oldMidStep = -oldMidGain / FADE_SAMPLES;
- const ALfloat midStep = midGain / FADE_SAMPLES;
- const ALfloat oldDensityGain = State->Late.DensityGain[0] * oldMidGain;
- const ALfloat densityGain = State->Late.DensityGain[1] * midGain;
- const ALfloat oldDensityStep = -oldDensityGain / FADE_SAMPLES;
- const ALfloat densityStep = densityGain / FADE_SAMPLES;
- ALsizei late_delay_tap0 = offset - State->LateDelayTap[j][0];
- ALsizei late_delay_tap1 = offset - State->LateDelayTap[j][1];
- ALsizei late_feedb_tap0 = offset - State->Late.Offset[j][0];
- ALsizei late_feedb_tap1 = offset - State->Late.Offset[j][1];
- ALfloat fadeCount = fade;
-
- for(i = 0;i < todo;i++)
- {
- const ALfloat fade0 = oldDensityGain + oldDensityStep*fadeCount;
- const ALfloat fade1 = densityStep*fadeCount;
- const ALfloat gfade0 = oldMidGain + oldMidStep*fadeCount;
- const ALfloat gfade1 = midStep*fadeCount;
- temps[j][i] =
- FadedDelayLineOut(&main_delay, late_delay_tap0++, late_delay_tap1++, j,
- fade0, fade1) +
- FadedDelayLineOut(&late_delay, late_feedb_tap0++, late_feedb_tap1++, j,
- gfade0, gfade1);
- fadeCount += 1.0f;
- }
- LateT60Filter(temps[j], todo, &State->Late.T60[j]);
- }
-
- VectorAllpass_Faded(temps, offset, mixX, mixY, fade, todo, &State->Late.VecAp);
-
- for(j = 0;j < NUM_LINES;j++)
- memcpy(out[j], temps[j], todo*sizeof(ALfloat));
-
- VectorScatterRevDelayIn(&late_delay, offset, mixX, mixY, temps, todo);
-}
-
-static ALvoid ReverbState_process(ReverbState *State, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels)
-{
- ALfloat (*restrict afmt)[MAX_UPDATE_SAMPLES] = State->TempSamples;
- ALfloat (*restrict samples)[MAX_UPDATE_SAMPLES] = State->MixSamples;
- ALsizei fadeCount = State->FadeCount;
- ALsizei offset = State->Offset;
- ALsizei base, c;
-
- /* Process reverb for these samples. */
- for(base = 0;base < SamplesToDo;)
- {
- ALsizei todo = SamplesToDo - base;
- /* If cross-fading, don't do more samples than there are to fade. */
- if(FADE_SAMPLES-fadeCount > 0)
- {
- todo = mini(todo, FADE_SAMPLES-fadeCount);
- todo = mini(todo, State->MaxUpdate[0]);
- }
- todo = mini(todo, State->MaxUpdate[1]);
- /* If this is not the final update, ensure the update size is a
- * multiple of 4 for the SIMD mixers.
- */
- if(todo < SamplesToDo-base)
- todo &= ~3;
-
- /* Convert B-Format to A-Format for processing. */
- memset(afmt, 0, sizeof(*afmt)*NUM_LINES);
- for(c = 0;c < NUM_LINES;c++)
- MixRowSamples(afmt[c], B2A.m[c],
- SamplesIn, MAX_EFFECT_CHANNELS, base, todo
- );
-
- /* Process the samples for reverb. */
- for(c = 0;c < NUM_LINES;c++)
- {
- /* Band-pass the incoming samples. */
- BiquadFilter_process(&State->Filter[c].Lp, samples[0], afmt[c], todo);
- BiquadFilter_process(&State->Filter[c].Hp, samples[1], samples[0], todo);
-
- /* Feed the initial delay line. */
- DelayLineIn(&State->Delay, offset, c, samples[1], todo);
- }
-
- if(UNLIKELY(fadeCount < FADE_SAMPLES))
- {
- ALfloat fade = (ALfloat)fadeCount;
-
- /* Generate early reflections. */
- EarlyReflection_Faded(State, offset, todo, fade, samples);
- /* Mix the A-Format results to output, implicitly converting back
- * to B-Format.
- */
- for(c = 0;c < NUM_LINES;c++)
- MixSamples(samples[c], NumChannels, SamplesOut,
- State->Early.CurrentGain[c], State->Early.PanGain[c],
- SamplesToDo-base, base, todo
- );
-
- /* Generate and mix late reverb. */
- LateReverb_Faded(State, offset, todo, fade, samples);
- for(c = 0;c < NUM_LINES;c++)
- MixSamples(samples[c], NumChannels, SamplesOut,
- State->Late.CurrentGain[c], State->Late.PanGain[c],
- SamplesToDo-base, base, todo
- );
-
- /* Step fading forward. */
- fadeCount += todo;
- if(LIKELY(fadeCount >= FADE_SAMPLES))
- {
- /* Update the cross-fading delay line taps. */
- fadeCount = FADE_SAMPLES;
- for(c = 0;c < NUM_LINES;c++)
- {
- State->EarlyDelayTap[c][0] = State->EarlyDelayTap[c][1];
- State->EarlyDelayCoeff[c][0] = State->EarlyDelayCoeff[c][1];
- State->Early.VecAp.Offset[c][0] = State->Early.VecAp.Offset[c][1];
- State->Early.Offset[c][0] = State->Early.Offset[c][1];
- State->Early.Coeff[c][0] = State->Early.Coeff[c][1];
- State->LateDelayTap[c][0] = State->LateDelayTap[c][1];
- State->Late.VecAp.Offset[c][0] = State->Late.VecAp.Offset[c][1];
- State->Late.Offset[c][0] = State->Late.Offset[c][1];
- State->Late.T60[c].MidGain[0] = State->Late.T60[c].MidGain[1];
- }
- State->Late.DensityGain[0] = State->Late.DensityGain[1];
- State->MaxUpdate[0] = State->MaxUpdate[1];
- }
- }
- else
- {
- /* Generate and mix early reflections. */
- EarlyReflection_Unfaded(State, offset, todo, samples);
- for(c = 0;c < NUM_LINES;c++)
- MixSamples(samples[c], NumChannels, SamplesOut,
- State->Early.CurrentGain[c], State->Early.PanGain[c],
- SamplesToDo-base, base, todo
- );
-
- /* Generate and mix late reverb. */
- LateReverb_Unfaded(State, offset, todo, samples);
- for(c = 0;c < NUM_LINES;c++)
- MixSamples(samples[c], NumChannels, SamplesOut,
- State->Late.CurrentGain[c], State->Late.PanGain[c],
- SamplesToDo-base, base, todo
- );
- }
-
- /* Step all delays forward. */
- offset += todo;
-
- base += todo;
- }
- State->Offset = offset;
- State->FadeCount = fadeCount;
-}
-
-
-typedef struct ReverbStateFactory {
- DERIVE_FROM_TYPE(EffectStateFactory);
-} ReverbStateFactory;
-
-static ALeffectState *ReverbStateFactory_create(ReverbStateFactory* UNUSED(factory))
-{
- ReverbState *state;
-
- NEW_OBJ0(state, ReverbState)();
- if(!state) return NULL;
-
- return STATIC_CAST(ALeffectState, state);
-}
-
-DEFINE_EFFECTSTATEFACTORY_VTABLE(ReverbStateFactory);
-
-EffectStateFactory *ReverbStateFactory_getFactory(void)
-{
- static ReverbStateFactory ReverbFactory = { { GET_VTABLE2(ReverbStateFactory, EffectStateFactory) } };
-
- return STATIC_CAST(EffectStateFactory, &ReverbFactory);
-}
-
-
-void ALeaxreverb_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
-{
- ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_EAXREVERB_DECAY_HFLIMIT:
- if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hflimit out of range");
- props->Reverb.DecayHFLimit = val;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
- param);
- }
-}
-void ALeaxreverb_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
-{ ALeaxreverb_setParami(effect, context, param, vals[0]); }
-void ALeaxreverb_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
-{
- ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_EAXREVERB_DENSITY:
- if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb density out of range");
- props->Reverb.Density = val;
- break;
-
- case AL_EAXREVERB_DIFFUSION:
- if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb diffusion out of range");
- props->Reverb.Diffusion = val;
- break;
-
- case AL_EAXREVERB_GAIN:
- if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gain out of range");
- props->Reverb.Gain = val;
- break;
-
- case AL_EAXREVERB_GAINHF:
- if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainhf out of range");
- props->Reverb.GainHF = val;
- break;
-
- case AL_EAXREVERB_GAINLF:
- if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainlf out of range");
- props->Reverb.GainLF = val;
- break;
-
- case AL_EAXREVERB_DECAY_TIME:
- if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay time out of range");
- props->Reverb.DecayTime = val;
- break;
-
- case AL_EAXREVERB_DECAY_HFRATIO:
- if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hfratio out of range");
- props->Reverb.DecayHFRatio = val;
- break;
-
- case AL_EAXREVERB_DECAY_LFRATIO:
- if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay lfratio out of range");
- props->Reverb.DecayLFRatio = val;
- break;
-
- case AL_EAXREVERB_REFLECTIONS_GAIN:
- if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections gain out of range");
- props->Reverb.ReflectionsGain = val;
- break;
-
- case AL_EAXREVERB_REFLECTIONS_DELAY:
- if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections delay out of range");
- props->Reverb.ReflectionsDelay = val;
- break;
-
- case AL_EAXREVERB_LATE_REVERB_GAIN:
- if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb gain out of range");
- props->Reverb.LateReverbGain = val;
- break;
-
- case AL_EAXREVERB_LATE_REVERB_DELAY:
- if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb delay out of range");
- props->Reverb.LateReverbDelay = val;
- break;
-
- case AL_EAXREVERB_AIR_ABSORPTION_GAINHF:
- if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb air absorption gainhf out of range");
- props->Reverb.AirAbsorptionGainHF = val;
- break;
-
- case AL_EAXREVERB_ECHO_TIME:
- if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo time out of range");
- props->Reverb.EchoTime = val;
- break;
-
- case AL_EAXREVERB_ECHO_DEPTH:
- if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo depth out of range");
- props->Reverb.EchoDepth = val;
- break;
-
- case AL_EAXREVERB_MODULATION_TIME:
- if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation time out of range");
- props->Reverb.ModulationTime = val;
- break;
-
- case AL_EAXREVERB_MODULATION_DEPTH:
- if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation depth out of range");
- props->Reverb.ModulationDepth = val;
- break;
-
- case AL_EAXREVERB_HFREFERENCE:
- if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb hfreference out of range");
- props->Reverb.HFReference = val;
- break;
-
- case AL_EAXREVERB_LFREFERENCE:
- if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb lfreference out of range");
- props->Reverb.LFReference = val;
- break;
-
- case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR:
- if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb room rolloff factor out of range");
- props->Reverb.RoomRolloffFactor = val;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x",
- param);
- }
-}
-void ALeaxreverb_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{
- ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_EAXREVERB_REFLECTIONS_PAN:
- if(!(isfinite(vals[0]) && isfinite(vals[1]) && isfinite(vals[2])))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections pan out of range");
- props->Reverb.ReflectionsPan[0] = vals[0];
- props->Reverb.ReflectionsPan[1] = vals[1];
- props->Reverb.ReflectionsPan[2] = vals[2];
- break;
- case AL_EAXREVERB_LATE_REVERB_PAN:
- if(!(isfinite(vals[0]) && isfinite(vals[1]) && isfinite(vals[2])))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb pan out of range");
- props->Reverb.LateReverbPan[0] = vals[0];
- props->Reverb.LateReverbPan[1] = vals[1];
- props->Reverb.LateReverbPan[2] = vals[2];
- break;
-
- default:
- ALeaxreverb_setParamf(effect, context, param, vals[0]);
- break;
- }
-}
-
-void ALeaxreverb_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
-{
- const ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_EAXREVERB_DECAY_HFLIMIT:
- *val = props->Reverb.DecayHFLimit;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
- param);
- }
-}
-void ALeaxreverb_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
-{ ALeaxreverb_getParami(effect, context, param, vals); }
-void ALeaxreverb_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
-{
- const ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_EAXREVERB_DENSITY:
- *val = props->Reverb.Density;
- break;
-
- case AL_EAXREVERB_DIFFUSION:
- *val = props->Reverb.Diffusion;
- break;
-
- case AL_EAXREVERB_GAIN:
- *val = props->Reverb.Gain;
- break;
-
- case AL_EAXREVERB_GAINHF:
- *val = props->Reverb.GainHF;
- break;
-
- case AL_EAXREVERB_GAINLF:
- *val = props->Reverb.GainLF;
- break;
-
- case AL_EAXREVERB_DECAY_TIME:
- *val = props->Reverb.DecayTime;
- break;
-
- case AL_EAXREVERB_DECAY_HFRATIO:
- *val = props->Reverb.DecayHFRatio;
- break;
-
- case AL_EAXREVERB_DECAY_LFRATIO:
- *val = props->Reverb.DecayLFRatio;
- break;
-
- case AL_EAXREVERB_REFLECTIONS_GAIN:
- *val = props->Reverb.ReflectionsGain;
- break;
-
- case AL_EAXREVERB_REFLECTIONS_DELAY:
- *val = props->Reverb.ReflectionsDelay;
- break;
-
- case AL_EAXREVERB_LATE_REVERB_GAIN:
- *val = props->Reverb.LateReverbGain;
- break;
-
- case AL_EAXREVERB_LATE_REVERB_DELAY:
- *val = props->Reverb.LateReverbDelay;
- break;
-
- case AL_EAXREVERB_AIR_ABSORPTION_GAINHF:
- *val = props->Reverb.AirAbsorptionGainHF;
- break;
-
- case AL_EAXREVERB_ECHO_TIME:
- *val = props->Reverb.EchoTime;
- break;
-
- case AL_EAXREVERB_ECHO_DEPTH:
- *val = props->Reverb.EchoDepth;
- break;
-
- case AL_EAXREVERB_MODULATION_TIME:
- *val = props->Reverb.ModulationTime;
- break;
-
- case AL_EAXREVERB_MODULATION_DEPTH:
- *val = props->Reverb.ModulationDepth;
- break;
-
- case AL_EAXREVERB_HFREFERENCE:
- *val = props->Reverb.HFReference;
- break;
-
- case AL_EAXREVERB_LFREFERENCE:
- *val = props->Reverb.LFReference;
- break;
-
- case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR:
- *val = props->Reverb.RoomRolloffFactor;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x",
- param);
- }
-}
-void ALeaxreverb_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{
- const ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_EAXREVERB_REFLECTIONS_PAN:
- vals[0] = props->Reverb.ReflectionsPan[0];
- vals[1] = props->Reverb.ReflectionsPan[1];
- vals[2] = props->Reverb.ReflectionsPan[2];
- break;
- case AL_EAXREVERB_LATE_REVERB_PAN:
- vals[0] = props->Reverb.LateReverbPan[0];
- vals[1] = props->Reverb.LateReverbPan[1];
- vals[2] = props->Reverb.LateReverbPan[2];
- break;
-
- default:
- ALeaxreverb_getParamf(effect, context, param, vals);
- break;
- }
-}
-
-DEFINE_ALEFFECT_VTABLE(ALeaxreverb);
-
-void ALreverb_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALint val)
-{
- ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_REVERB_DECAY_HFLIMIT:
- if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hflimit out of range");
- props->Reverb.DecayHFLimit = val;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param);
- }
-}
-void ALreverb_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals)
-{ ALreverb_setParami(effect, context, param, vals[0]); }
-void ALreverb_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val)
-{
- ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_REVERB_DENSITY:
- if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb density out of range");
- props->Reverb.Density = val;
- break;
-
- case AL_REVERB_DIFFUSION:
- if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb diffusion out of range");
- props->Reverb.Diffusion = val;
- break;
-
- case AL_REVERB_GAIN:
- if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gain out of range");
- props->Reverb.Gain = val;
- break;
-
- case AL_REVERB_GAINHF:
- if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gainhf out of range");
- props->Reverb.GainHF = val;
- break;
-
- case AL_REVERB_DECAY_TIME:
- if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay time out of range");
- props->Reverb.DecayTime = val;
- break;
-
- case AL_REVERB_DECAY_HFRATIO:
- if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hfratio out of range");
- props->Reverb.DecayHFRatio = val;
- break;
-
- case AL_REVERB_REFLECTIONS_GAIN:
- if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections gain out of range");
- props->Reverb.ReflectionsGain = val;
- break;
-
- case AL_REVERB_REFLECTIONS_DELAY:
- if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections delay out of range");
- props->Reverb.ReflectionsDelay = val;
- break;
-
- case AL_REVERB_LATE_REVERB_GAIN:
- if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb gain out of range");
- props->Reverb.LateReverbGain = val;
- break;
-
- case AL_REVERB_LATE_REVERB_DELAY:
- if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb delay out of range");
- props->Reverb.LateReverbDelay = val;
- break;
-
- case AL_REVERB_AIR_ABSORPTION_GAINHF:
- if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb air absorption gainhf out of range");
- props->Reverb.AirAbsorptionGainHF = val;
- break;
-
- case AL_REVERB_ROOM_ROLLOFF_FACTOR:
- if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb room rolloff factor out of range");
- props->Reverb.RoomRolloffFactor = val;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param);
- }
-}
-void ALreverb_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals)
-{ ALreverb_setParamf(effect, context, param, vals[0]); }
-
-void ALreverb_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val)
-{
- const ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_REVERB_DECAY_HFLIMIT:
- *val = props->Reverb.DecayHFLimit;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param);
- }
-}
-void ALreverb_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals)
-{ ALreverb_getParami(effect, context, param, vals); }
-void ALreverb_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val)
-{
- const ALeffectProps *props = &effect->Props;
- switch(param)
- {
- case AL_REVERB_DENSITY:
- *val = props->Reverb.Density;
- break;
-
- case AL_REVERB_DIFFUSION:
- *val = props->Reverb.Diffusion;
- break;
-
- case AL_REVERB_GAIN:
- *val = props->Reverb.Gain;
- break;
-
- case AL_REVERB_GAINHF:
- *val = props->Reverb.GainHF;
- break;
-
- case AL_REVERB_DECAY_TIME:
- *val = props->Reverb.DecayTime;
- break;
-
- case AL_REVERB_DECAY_HFRATIO:
- *val = props->Reverb.DecayHFRatio;
- break;
-
- case AL_REVERB_REFLECTIONS_GAIN:
- *val = props->Reverb.ReflectionsGain;
- break;
-
- case AL_REVERB_REFLECTIONS_DELAY:
- *val = props->Reverb.ReflectionsDelay;
- break;
-
- case AL_REVERB_LATE_REVERB_GAIN:
- *val = props->Reverb.LateReverbGain;
- break;
-
- case AL_REVERB_LATE_REVERB_DELAY:
- *val = props->Reverb.LateReverbDelay;
- break;
-
- case AL_REVERB_AIR_ABSORPTION_GAINHF:
- *val = props->Reverb.AirAbsorptionGainHF;
- break;
-
- case AL_REVERB_ROOM_ROLLOFF_FACTOR:
- *val = props->Reverb.RoomRolloffFactor;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param);
- }
-}
-void ALreverb_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals)
-{ ALreverb_getParamf(effect, context, param, vals); }
-
-DEFINE_ALEFFECT_VTABLE(ALreverb);
diff --git a/Alc/filters/defs.h b/Alc/filters/defs.h
deleted file mode 100644
index 133a85eb..00000000
--- a/Alc/filters/defs.h
+++ /dev/null
@@ -1,112 +0,0 @@
-#ifndef ALC_FILTER_H
-#define ALC_FILTER_H
-
-#include "AL/al.h"
-#include "math_defs.h"
-
-/* Filters 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
- */
-/* Implementation note: For the shelf filters, the specified gain is for the
- * reference frequency, which is the centerpoint of the transition band. This
- * better matches EFX filter design. To set the gain for the shelf itself, use
- * the square root of the desired linear gain (or halve the dB gain).
- */
-
-typedef enum BiquadType {
- /** EFX-style low-pass filter, specifying a gain and reference frequency. */
- BiquadType_HighShelf,
- /** EFX-style high-pass filter, specifying a gain and reference frequency. */
- BiquadType_LowShelf,
- /** Peaking filter, specifying a gain and reference frequency. */
- BiquadType_Peaking,
-
- /** Low-pass cut-off filter, specifying a cut-off frequency. */
- BiquadType_LowPass,
- /** High-pass cut-off filter, specifying a cut-off frequency. */
- BiquadType_HighPass,
- /** Band-pass filter, specifying a center frequency. */
- BiquadType_BandPass,
-} BiquadType;
-
-typedef struct BiquadFilter {
- ALfloat z1, z2; /* Last two delayed components for direct form II. */
- ALfloat b0, b1, b2; /* Transfer function coefficients "b" (numerator) */
- ALfloat a1, a2; /* Transfer function coefficients "a" (denominator; a0 is
- * pre-applied). */
-} BiquadFilter;
-/* Currently only a C-based filter process method is implemented. */
-#define BiquadFilter_process BiquadFilter_processC
-
-/**
- * Calculates the rcpQ (i.e. 1/Q) coefficient for shelving filters, using the
- * reference gain and shelf slope parameter.
- * \param gain 0 < gain
- * \param slope 0 < slope <= 1
- */
-inline ALfloat calc_rcpQ_from_slope(ALfloat gain, ALfloat slope)
-{
- return sqrtf((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f);
-}
-/**
- * Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the normalized
- * reference frequency and bandwidth.
- * \param f0norm 0 < f0norm < 0.5.
- * \param bandwidth 0 < bandwidth
- */
-inline ALfloat calc_rcpQ_from_bandwidth(ALfloat f0norm, ALfloat bandwidth)
-{
- ALfloat w0 = F_TAU * f0norm;
- return 2.0f*sinhf(logf(2.0f)/2.0f*bandwidth*w0/sinf(w0));
-}
-
-inline void BiquadFilter_clear(BiquadFilter *filter)
-{
- filter->z1 = 0.0f;
- filter->z2 = 0.0f;
-}
-
-/**
- * Sets up the filter state for the specified filter type and its parameters.
- *
- * \param filter The filter object to prepare.
- * \param type The type of filter for the object to apply.
- * \param gain The gain for the reference frequency response. Only used by the
- * Shelf and Peaking filter types.
- * \param f0norm The normalized reference frequency (ref_freq / sample_rate).
- * This is the center point for the Shelf, Peaking, and BandPass
- * filter types, or the cutoff frequency for the LowPass and
- * HighPass filter types.
- * \param rcpQ The reciprocal of the Q coefficient for the filter's transition
- * band. Can be generated from calc_rcpQ_from_slope or
- * calc_rcpQ_from_bandwidth depending on the available data.
- */
-void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain, ALfloat f0norm, ALfloat rcpQ);
-
-inline void BiquadFilter_copyParams(BiquadFilter *restrict dst, const BiquadFilter *restrict src)
-{
- dst->b0 = src->b0;
- dst->b1 = src->b1;
- dst->b2 = src->b2;
- dst->a1 = src->a1;
- dst->a2 = src->a2;
-}
-
-void BiquadFilter_processC(BiquadFilter *filter, ALfloat *restrict dst, const ALfloat *restrict src, ALsizei numsamples);
-
-inline void BiquadFilter_passthru(BiquadFilter *filter, ALsizei numsamples)
-{
- if(LIKELY(numsamples >= 2))
- {
- filter->z1 = 0.0f;
- filter->z2 = 0.0f;
- }
- else if(numsamples == 1)
- {
- filter->z1 = filter->z2;
- filter->z2 = 0.0f;
- }
-}
-
-#endif /* ALC_FILTER_H */
diff --git a/Alc/filters/nfc.c b/Alc/filters/nfc.c
deleted file mode 100644
index 8869d1d0..00000000
--- a/Alc/filters/nfc.c
+++ /dev/null
@@ -1,426 +0,0 @@
-
-#include "config.h"
-
-#include "nfc.h"
-#include "alMain.h"
-
-#include <string.h>
-
-
-/* Near-field control filters are the basis for handling the near-field effect.
- * The near-field effect is a bass-boost present in the directional components
- * of a recorded signal, created as a result of the wavefront curvature (itself
- * a function of sound distance). Proper reproduction dictates this be
- * compensated for using a bass-cut given the playback speaker distance, to
- * avoid excessive bass in the playback.
- *
- * For real-time rendered audio, emulating the near-field effect based on the
- * sound source's distance, and subsequently compensating for it at output
- * based on the speaker distances, can create a more realistic perception of
- * sound distance beyond a simple 1/r attenuation.
- *
- * These filters do just that. Each one applies a low-shelf filter, created as
- * the combination of a bass-boost for a given sound source distance (near-
- * field emulation) along with a bass-cut for a given control/speaker distance
- * (near-field compensation).
- *
- * Note that it is necessary to apply a cut along with the boost, since the
- * boost alone is unstable in higher-order ambisonics as it causes an infinite
- * DC gain (even first-order ambisonics requires there to be no DC offset for
- * the boost to work). Consequently, ambisonics requires a control parameter to
- * be used to avoid an unstable boost-only filter. NFC-HOA defines this control
- * as a reference delay, calculated with:
- *
- * reference_delay = control_distance / speed_of_sound
- *
- * This means w0 (for input) or w1 (for output) should be set to:
- *
- * wN = 1 / (reference_delay * sample_rate)
- *
- * when dealing with NFC-HOA content. For FOA input content, which does not
- * specify a reference_delay variable, w0 should be set to 0 to apply only
- * near-field compensation for output. It's important that w1 be a finite,
- * positive, non-0 value or else the bass-boost will become unstable again.
- * Also, w0 should not be too large compared to w1, to avoid excessively loud
- * low frequencies.
- */
-
-static const float B[4][3] = {
- { 0.0f },
- { 1.0f },
- { 3.0f, 3.0f },
- { 3.6778f, 6.4595f, 2.3222f },
- /*{ 4.2076f, 11.4877f, 5.7924f, 9.1401f }*/
-};
-
-static void NfcFilterCreate1(struct NfcFilter1 *nfc, const float w0, const float w1)
-{
- float b_00, g_0;
- float r;
-
- nfc->base_gain = 1.0f;
- nfc->gain = 1.0f;
-
- /* Calculate bass-boost coefficients. */
- r = 0.5f * w0;
- b_00 = B[1][0] * r;
- g_0 = 1.0f + b_00;
-
- nfc->gain *= g_0;
- nfc->b1 = 2.0f * b_00 / g_0;
-
- /* Calculate bass-cut coefficients. */
- r = 0.5f * w1;
- b_00 = B[1][0] * r;
- g_0 = 1.0f + b_00;
-
- nfc->base_gain /= g_0;
- nfc->gain /= g_0;
- nfc->a1 = 2.0f * b_00 / g_0;
-}
-
-static void NfcFilterAdjust1(struct NfcFilter1 *nfc, const float w0)
-{
- float b_00, g_0;
- float r;
-
- r = 0.5f * w0;
- b_00 = B[1][0] * r;
- g_0 = 1.0f + b_00;
-
- nfc->gain = nfc->base_gain * g_0;
- nfc->b1 = 2.0f * b_00 / g_0;
-}
-
-
-static void NfcFilterCreate2(struct NfcFilter2 *nfc, const float w0, const float w1)
-{
- float b_10, b_11, g_1;
- float r;
-
- nfc->base_gain = 1.0f;
- nfc->gain = 1.0f;
-
- /* Calculate bass-boost coefficients. */
- r = 0.5f * w0;
- b_10 = B[2][0] * r;
- b_11 = B[2][1] * r * r;
- g_1 = 1.0f + b_10 + b_11;
-
- nfc->gain *= g_1;
- nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
- nfc->b2 = 4.0f * b_11 / g_1;
-
- /* Calculate bass-cut coefficients. */
- r = 0.5f * w1;
- b_10 = B[2][0] * r;
- b_11 = B[2][1] * r * r;
- g_1 = 1.0f + b_10 + b_11;
-
- nfc->base_gain /= g_1;
- nfc->gain /= g_1;
- nfc->a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
- nfc->a2 = 4.0f * b_11 / g_1;
-}
-
-static void NfcFilterAdjust2(struct NfcFilter2 *nfc, const float w0)
-{
- float b_10, b_11, g_1;
- float r;
-
- r = 0.5f * w0;
- b_10 = B[2][0] * r;
- b_11 = B[2][1] * r * r;
- g_1 = 1.0f + b_10 + b_11;
-
- nfc->gain = nfc->base_gain * g_1;
- nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
- nfc->b2 = 4.0f * b_11 / g_1;
-}
-
-
-static void NfcFilterCreate3(struct NfcFilter3 *nfc, const float w0, const float w1)
-{
- float b_10, b_11, g_1;
- float b_00, g_0;
- float r;
-
- nfc->base_gain = 1.0f;
- nfc->gain = 1.0f;
-
- /* Calculate bass-boost coefficients. */
- r = 0.5f * w0;
- b_10 = B[3][0] * r;
- b_11 = B[3][1] * r * r;
- g_1 = 1.0f + b_10 + b_11;
-
- nfc->gain *= g_1;
- nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
- nfc->b2 = 4.0f * b_11 / g_1;
-
- b_00 = B[3][2] * r;
- g_0 = 1.0f + b_00;
-
- nfc->gain *= g_0;
- nfc->b3 = 2.0f * b_00 / g_0;
-
- /* Calculate bass-cut coefficients. */
- r = 0.5f * w1;
- b_10 = B[3][0] * r;
- b_11 = B[3][1] * r * r;
- g_1 = 1.0f + b_10 + b_11;
-
- nfc->base_gain /= g_1;
- nfc->gain /= g_1;
- nfc->a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
- nfc->a2 = 4.0f * b_11 / g_1;
-
- b_00 = B[3][2] * r;
- g_0 = 1.0f + b_00;
-
- nfc->base_gain /= g_0;
- nfc->gain /= g_0;
- nfc->a3 = 2.0f * b_00 / g_0;
-}
-
-static void NfcFilterAdjust3(struct NfcFilter3 *nfc, const float w0)
-{
- float b_10, b_11, g_1;
- float b_00, g_0;
- float r;
-
- r = 0.5f * w0;
- b_10 = B[3][0] * r;
- b_11 = B[3][1] * r * r;
- g_1 = 1.0f + b_10 + b_11;
-
- nfc->gain = nfc->base_gain * g_1;
- nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
- nfc->b2 = 4.0f * b_11 / g_1;
-
- b_00 = B[3][2] * r;
- g_0 = 1.0f + b_00;
-
- nfc->gain *= g_0;
- nfc->b3 = 2.0f * b_00 / g_0;
-}
-
-
-void NfcFilterCreate(NfcFilter *nfc, const float w0, const float w1)
-{
- memset(nfc, 0, sizeof(*nfc));
- NfcFilterCreate1(&nfc->first, w0, w1);
- NfcFilterCreate2(&nfc->second, w0, w1);
- NfcFilterCreate3(&nfc->third, w0, w1);
-}
-
-void NfcFilterAdjust(NfcFilter *nfc, const float w0)
-{
- NfcFilterAdjust1(&nfc->first, w0);
- NfcFilterAdjust2(&nfc->second, w0);
- NfcFilterAdjust3(&nfc->third, w0);
-}
-
-
-void NfcFilterProcess1(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count)
-{
- const float gain = nfc->first.gain;
- const float b1 = nfc->first.b1;
- const float a1 = nfc->first.a1;
- float z1 = nfc->first.z[0];
- int i;
-
- ASSUME(count > 0);
-
- for(i = 0;i < count;i++)
- {
- float y = src[i]*gain - a1*z1;
- float out = y + b1*z1;
- z1 += y;
-
- dst[i] = out;
- }
- nfc->first.z[0] = z1;
-}
-
-void NfcFilterProcess2(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count)
-{
- const float gain = nfc->second.gain;
- const float b1 = nfc->second.b1;
- const float b2 = nfc->second.b2;
- const float a1 = nfc->second.a1;
- const float a2 = nfc->second.a2;
- float z1 = nfc->second.z[0];
- float z2 = nfc->second.z[1];
- int i;
-
- ASSUME(count > 0);
-
- for(i = 0;i < count;i++)
- {
- float y = src[i]*gain - a1*z1 - a2*z2;
- float out = y + b1*z1 + b2*z2;
- z2 += z1;
- z1 += y;
-
- dst[i] = out;
- }
- nfc->second.z[0] = z1;
- nfc->second.z[1] = z2;
-}
-
-void NfcFilterProcess3(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count)
-{
- const float gain = nfc->third.gain;
- const float b1 = nfc->third.b1;
- const float b2 = nfc->third.b2;
- const float b3 = nfc->third.b3;
- const float a1 = nfc->third.a1;
- const float a2 = nfc->third.a2;
- const float a3 = nfc->third.a3;
- float z1 = nfc->third.z[0];
- float z2 = nfc->third.z[1];
- float z3 = nfc->third.z[2];
- int i;
-
- ASSUME(count > 0);
-
- for(i = 0;i < count;i++)
- {
- float y = src[i]*gain - a1*z1 - a2*z2;
- float out = y + b1*z1 + b2*z2;
- z2 += z1;
- z1 += y;
-
- y = out - a3*z3;
- out = y + b3*z3;
- z3 += y;
-
- dst[i] = out;
- }
- nfc->third.z[0] = z1;
- nfc->third.z[1] = z2;
- nfc->third.z[2] = z3;
-}
-
-#if 0 /* Original methods the above are derived from. */
-static void NfcFilterCreate(NfcFilter *nfc, const ALsizei order, const float src_dist, const float ctl_dist, const float rate)
-{
- static const float B[4][5] = {
- { },
- { 1.0f },
- { 3.0f, 3.0f },
- { 3.6778f, 6.4595f, 2.3222f },
- { 4.2076f, 11.4877f, 5.7924f, 9.1401f }
- };
- float w0 = SPEEDOFSOUNDMETRESPERSEC / (src_dist * rate);
- float w1 = SPEEDOFSOUNDMETRESPERSEC / (ctl_dist * rate);
- ALsizei i;
- float r;
-
- nfc->g = 1.0f;
- nfc->coeffs[0] = 1.0f;
-
- /* NOTE: Slight adjustment from the literature to raise the center
- * frequency a bit (0.5 -> 1.0).
- */
- r = 1.0f * w0;
- for(i = 0; i < (order-1);i += 2)
- {
- float b_10 = B[order][i ] * r;
- float b_11 = B[order][i+1] * r * r;
- float g_1 = 1.0f + b_10 + b_11;
-
- nfc->b[i] = b_10;
- nfc->b[i + 1] = b_11;
- nfc->coeffs[0] *= g_1;
- nfc->coeffs[i+1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1;
- nfc->coeffs[i+2] = (4.0f * b_11) / g_1;
- }
- if(i < order)
- {
- float b_00 = B[order][i] * r;
- float g_0 = 1.0f + b_00;
-
- nfc->b[i] = b_00;
- nfc->coeffs[0] *= g_0;
- nfc->coeffs[i+1] = (2.0f * b_00) / g_0;
- }
-
- r = 1.0f * w1;
- for(i = 0;i < (order-1);i += 2)
- {
- float b_10 = B[order][i ] * r;
- float b_11 = B[order][i+1] * r * r;
- float g_1 = 1.0f + b_10 + b_11;
-
- nfc->g /= g_1;
- nfc->coeffs[0] /= g_1;
- nfc->coeffs[order+i+1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1;
- nfc->coeffs[order+i+2] = (4.0f * b_11) / g_1;
- }
- if(i < order)
- {
- float b_00 = B[order][i] * r;
- float g_0 = 1.0f + b_00;
-
- nfc->g /= g_0;
- nfc->coeffs[0] /= g_0;
- nfc->coeffs[order+i+1] = (2.0f * b_00) / g_0;
- }
-
- for(i = 0; i < MAX_AMBI_ORDER; i++)
- nfc->history[i] = 0.0f;
-}
-
-static void NfcFilterAdjust(NfcFilter *nfc, const float distance)
-{
- int i;
-
- nfc->coeffs[0] = nfc->g;
-
- for(i = 0;i < (nfc->order-1);i += 2)
- {
- float b_10 = nfc->b[i] / distance;
- float b_11 = nfc->b[i+1] / (distance * distance);
- float g_1 = 1.0f + b_10 + b_11;
-
- nfc->coeffs[0] *= g_1;
- nfc->coeffs[i+1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1;
- nfc->coeffs[i+2] = (4.0f * b_11) / g_1;
- }
- if(i < nfc->order)
- {
- float b_00 = nfc->b[i] / distance;
- float g_0 = 1.0f + b_00;
-
- nfc->coeffs[0] *= g_0;
- nfc->coeffs[i+1] = (2.0f * b_00) / g_0;
- }
-}
-
-static float NfcFilterProcess(const float in, NfcFilter *nfc)
-{
- int i;
- float out = in * nfc->coeffs[0];
-
- for(i = 0;i < (nfc->order-1);i += 2)
- {
- float y = out - (nfc->coeffs[nfc->order+i+1] * nfc->history[i]) -
- (nfc->coeffs[nfc->order+i+2] * nfc->history[i+1]) + 1.0e-30f;
- out = y + (nfc->coeffs[i+1]*nfc->history[i]) + (nfc->coeffs[i+2]*nfc->history[i+1]);
-
- nfc->history[i+1] += nfc->history[i];
- nfc->history[i] += y;
- }
- if(i < nfc->order)
- {
- float y = out - (nfc->coeffs[nfc->order+i+1] * nfc->history[i]) + 1.0e-30f;
-
- out = y + (nfc->coeffs[i+1] * nfc->history[i]);
- nfc->history[i] += y;
- }
-
- return out;
-}
-#endif
diff --git a/Alc/filters/nfc.h b/Alc/filters/nfc.h
deleted file mode 100644
index 12a5a18f..00000000
--- a/Alc/filters/nfc.h
+++ /dev/null
@@ -1,49 +0,0 @@
-#ifndef FILTER_NFC_H
-#define FILTER_NFC_H
-
-struct NfcFilter1 {
- float base_gain, gain;
- float b1, a1;
- float z[1];
-};
-struct NfcFilter2 {
- float base_gain, gain;
- float b1, b2, a1, a2;
- float z[2];
-};
-struct NfcFilter3 {
- float base_gain, gain;
- float b1, b2, b3, a1, a2, a3;
- float z[3];
-};
-
-typedef struct NfcFilter {
- struct NfcFilter1 first;
- struct NfcFilter2 second;
- struct NfcFilter3 third;
-} NfcFilter;
-
-
-/* NOTE:
- * w0 = speed_of_sound / (source_distance * sample_rate);
- * w1 = speed_of_sound / (control_distance * sample_rate);
- *
- * Generally speaking, the control distance should be approximately the average
- * speaker distance, or based on the reference delay if outputing NFC-HOA. It
- * must not be negative, 0, or infinite. The source distance should not be too
- * small relative to the control distance.
- */
-
-void NfcFilterCreate(NfcFilter *nfc, const float w0, const float w1);
-void NfcFilterAdjust(NfcFilter *nfc, const float w0);
-
-/* Near-field control filter for first-order ambisonic channels (1-3). */
-void NfcFilterProcess1(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count);
-
-/* Near-field control filter for second-order ambisonic channels (4-8). */
-void NfcFilterProcess2(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count);
-
-/* Near-field control filter for third-order ambisonic channels (9-15). */
-void NfcFilterProcess3(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count);
-
-#endif /* FILTER_NFC_H */
diff --git a/Alc/filters/splitter.c b/Alc/filters/splitter.c
deleted file mode 100644
index e99f4b95..00000000
--- a/Alc/filters/splitter.c
+++ /dev/null
@@ -1,109 +0,0 @@
-
-#include "config.h"
-
-#include "splitter.h"
-
-#include "math_defs.h"
-
-
-void bandsplit_init(BandSplitter *splitter, ALfloat f0norm)
-{
- ALfloat w = f0norm * F_TAU;
- ALfloat cw = cosf(w);
- if(cw > FLT_EPSILON)
- splitter->coeff = (sinf(w) - 1.0f) / cw;
- else
- splitter->coeff = cw * -0.5f;
-
- splitter->lp_z1 = 0.0f;
- splitter->lp_z2 = 0.0f;
- splitter->hp_z1 = 0.0f;
-}
-
-void bandsplit_clear(BandSplitter *splitter)
-{
- splitter->lp_z1 = 0.0f;
- splitter->lp_z2 = 0.0f;
- splitter->hp_z1 = 0.0f;
-}
-
-void bandsplit_process(BandSplitter *splitter, ALfloat *restrict hpout, ALfloat *restrict lpout,
- const ALfloat *input, ALsizei count)
-{
- ALfloat lp_coeff, hp_coeff, lp_y, hp_y, d;
- ALfloat lp_z1, lp_z2, hp_z1;
- ALsizei i;
-
- ASSUME(count > 0);
-
- hp_coeff = splitter->coeff;
- lp_coeff = splitter->coeff*0.5f + 0.5f;
- lp_z1 = splitter->lp_z1;
- lp_z2 = splitter->lp_z2;
- hp_z1 = splitter->hp_z1;
- for(i = 0;i < count;i++)
- {
- ALfloat in = input[i];
-
- /* Low-pass sample processing. */
- d = (in - lp_z1) * lp_coeff;
- lp_y = lp_z1 + d;
- lp_z1 = lp_y + d;
-
- d = (lp_y - lp_z2) * lp_coeff;
- lp_y = lp_z2 + d;
- lp_z2 = lp_y + d;
-
- lpout[i] = lp_y;
-
- /* All-pass sample processing. */
- hp_y = in*hp_coeff + hp_z1;
- hp_z1 = in - hp_y*hp_coeff;
-
- /* High-pass generated from removing low-passed output. */
- hpout[i] = hp_y - lp_y;
- }
- splitter->lp_z1 = lp_z1;
- splitter->lp_z2 = lp_z2;
- splitter->hp_z1 = hp_z1;
-}
-
-
-void splitterap_init(SplitterAllpass *splitter, ALfloat f0norm)
-{
- ALfloat w = f0norm * F_TAU;
- ALfloat cw = cosf(w);
- if(cw > FLT_EPSILON)
- splitter->coeff = (sinf(w) - 1.0f) / cw;
- else
- splitter->coeff = cw * -0.5f;
-
- splitter->z1 = 0.0f;
-}
-
-void splitterap_clear(SplitterAllpass *splitter)
-{
- splitter->z1 = 0.0f;
-}
-
-void splitterap_process(SplitterAllpass *splitter, ALfloat *restrict samples, ALsizei count)
-{
- ALfloat coeff, in, out;
- ALfloat z1;
- ALsizei i;
-
- ASSUME(count > 0);
-
- coeff = splitter->coeff;
- z1 = splitter->z1;
- for(i = 0;i < count;i++)
- {
- in = samples[i];
-
- out = in*coeff + z1;
- z1 = in - out*coeff;
-
- samples[i] = out;
- }
- splitter->z1 = z1;
-}
diff --git a/Alc/filters/splitter.h b/Alc/filters/splitter.h
deleted file mode 100644
index a788bc3e..00000000
--- a/Alc/filters/splitter.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef FILTER_SPLITTER_H
-#define FILTER_SPLITTER_H
-
-#include "alMain.h"
-
-
-/* Band splitter. Splits a signal into two phase-matching frequency bands. */
-typedef struct BandSplitter {
- ALfloat coeff;
- ALfloat lp_z1;
- ALfloat lp_z2;
- ALfloat hp_z1;
-} BandSplitter;
-
-void bandsplit_init(BandSplitter *splitter, ALfloat f0norm);
-void bandsplit_clear(BandSplitter *splitter);
-void bandsplit_process(BandSplitter *splitter, ALfloat *restrict hpout, ALfloat *restrict lpout,
- const ALfloat *input, ALsizei count);
-
-/* The all-pass portion of the band splitter. Applies the same phase shift
- * without splitting the signal.
- */
-typedef struct SplitterAllpass {
- ALfloat coeff;
- ALfloat z1;
-} SplitterAllpass;
-
-void splitterap_init(SplitterAllpass *splitter, ALfloat f0norm);
-void splitterap_clear(SplitterAllpass *splitter);
-void splitterap_process(SplitterAllpass *splitter, ALfloat *restrict samples, ALsizei count);
-
-
-typedef struct FrontStablizer {
- SplitterAllpass APFilter[MAX_OUTPUT_CHANNELS];
- BandSplitter LFilter, RFilter;
- alignas(16) ALfloat LSplit[2][BUFFERSIZE];
- alignas(16) ALfloat RSplit[2][BUFFERSIZE];
-} FrontStablizer;
-
-#endif /* FILTER_SPLITTER_H */
diff --git a/Alc/fpu_modes.h b/Alc/fpu_modes.h
deleted file mode 100644
index eb305967..00000000
--- a/Alc/fpu_modes.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef FPU_MODES_H
-#define FPU_MODES_H
-
-#ifdef HAVE_FENV_H
-#include <fenv.h>
-#endif
-
-
-typedef struct FPUCtl {
-#if defined(__GNUC__) && defined(HAVE_SSE)
- unsigned int sse_state;
-#elif defined(HAVE___CONTROL87_2)
- unsigned int state;
- unsigned int sse_state;
-#elif defined(HAVE__CONTROLFP)
- unsigned int state;
-#endif
-} FPUCtl;
-void SetMixerFPUMode(FPUCtl *ctl);
-void RestoreFPUMode(const FPUCtl *ctl);
-
-#ifdef __GNUC__
-/* Use an alternate macro set with GCC to avoid accidental continue or break
- * statements within the mixer mode.
- */
-#define START_MIXER_MODE() __extension__({ FPUCtl _oldMode; SetMixerFPUMode(&_oldMode)
-#define END_MIXER_MODE() RestoreFPUMode(&_oldMode); })
-#else
-#define START_MIXER_MODE() do { FPUCtl _oldMode; SetMixerFPUMode(&_oldMode)
-#define END_MIXER_MODE() RestoreFPUMode(&_oldMode); } while(0)
-#endif
-#define LEAVE_MIXER_MODE() RestoreFPUMode(&_oldMode)
-
-#endif /* FPU_MODES_H */
diff --git a/Alc/helpers.c b/Alc/helpers.c
deleted file mode 100644
index d2cb6253..00000000
--- a/Alc/helpers.c
+++ /dev/null
@@ -1,1189 +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
- */
-
-#ifdef _WIN32
-#ifdef __MINGW32__
-#define _WIN32_IE 0x501
-#else
-#define _WIN32_IE 0x400
-#endif
-#endif
-
-#include "config.h"
-
-#include <stdlib.h>
-#include <time.h>
-#include <errno.h>
-#include <stdarg.h>
-#include <ctype.h>
-#ifdef HAVE_MALLOC_H
-#include <malloc.h>
-#endif
-#ifdef HAVE_DIRENT_H
-#include <dirent.h>
-#endif
-#ifdef HAVE_PROC_PIDPATH
-#include <libproc.h>
-#endif
-
-#ifdef __FreeBSD__
-#include <sys/types.h>
-#include <sys/sysctl.h>
-#endif
-
-#ifndef AL_NO_UID_DEFS
-#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H)
-#define INITGUID
-#include <windows.h>
-#ifdef HAVE_GUIDDEF_H
-#include <guiddef.h>
-#else
-#include <initguid.h>
-#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_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x00,0xa0,0xc9,0x25,0xcd,0x16);
-
-DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e);
-DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6);
-DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1,0x78, 0xc2,0xf5,0x68,0xa7,0x03,0xb2);
-DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7,0xbf, 0xad,0xdc,0xa7,0xc2,0x60,0xe2);
-DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4,0xde, 0x18,0x5c,0x39,0x5c,0xd3,0x17);
-
-#ifdef HAVE_WASAPI
-#include <wtypes.h>
-#include <devpropdef.h>
-#include <propkeydef.h>
-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 );
-#endif
-#endif
-#endif /* AL_NO_UID_DEFS */
-
-#ifdef HAVE_DLFCN_H
-#include <dlfcn.h>
-#endif
-#ifdef HAVE_INTRIN_H
-#include <intrin.h>
-#endif
-#ifdef HAVE_CPUID_H
-#include <cpuid.h>
-#endif
-#ifdef HAVE_SYS_SYSCONF_H
-#include <sys/sysconf.h>
-#endif
-#ifdef HAVE_FLOAT_H
-#include <float.h>
-#endif
-#ifdef HAVE_IEEEFP_H
-#include <ieeefp.h>
-#endif
-
-#ifndef _WIN32
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-#include <fcntl.h>
-#include <unistd.h>
-#elif defined(_WIN32_IE)
-#include <shlobj.h>
-#endif
-
-#include "alMain.h"
-#include "alu.h"
-#include "cpu_caps.h"
-#include "fpu_modes.h"
-#include "atomic.h"
-#include "uintmap.h"
-#include "vector.h"
-#include "alstring.h"
-#include "compat.h"
-#include "threads.h"
-
-
-extern inline ALuint NextPowerOf2(ALuint value);
-extern inline size_t RoundUp(size_t value, size_t r);
-extern inline ALint fastf2i(ALfloat f);
-extern inline int float2int(float f);
-extern inline float fast_roundf(float f);
-#ifndef __GNUC__
-#if defined(HAVE_BITSCANFORWARD64_INTRINSIC)
-extern inline int msvc64_ctz64(ALuint64 v);
-#elif defined(HAVE_BITSCANFORWARD_INTRINSIC)
-extern inline int msvc_ctz64(ALuint64 v);
-#else
-extern inline int fallback_popcnt64(ALuint64 v);
-extern inline int fallback_ctz64(ALuint64 value);
-#endif
-#endif
-
-
-#if defined(HAVE_GCC_GET_CPUID) && (defined(__i386__) || defined(__x86_64__) || \
- defined(_M_IX86) || defined(_M_X64))
-typedef unsigned int reg_type;
-static inline void get_cpuid(int f, reg_type *regs)
-{ __get_cpuid(f, &regs[0], &regs[1], &regs[2], &regs[3]); }
-#define CAN_GET_CPUID
-#elif defined(HAVE_CPUID_INTRINSIC) && (defined(__i386__) || defined(__x86_64__) || \
- defined(_M_IX86) || defined(_M_X64))
-typedef int reg_type;
-static inline void get_cpuid(int f, reg_type *regs)
-{ (__cpuid)(regs, f); }
-#define CAN_GET_CPUID
-#endif
-
-int CPUCapFlags = 0;
-
-void FillCPUCaps(int capfilter)
-{
- int caps = 0;
-
-/* FIXME: We really should get this for all available CPUs in case different
- * CPUs have different caps (is that possible on one machine?). */
-#ifdef CAN_GET_CPUID
- union {
- reg_type regs[4];
- char str[sizeof(reg_type[4])];
- } cpuinf[3] = {{ { 0, 0, 0, 0 } }};
-
- get_cpuid(0, cpuinf[0].regs);
- if(cpuinf[0].regs[0] == 0)
- ERR("Failed to get CPUID\n");
- else
- {
- unsigned int maxfunc = cpuinf[0].regs[0];
- unsigned int maxextfunc;
-
- get_cpuid(0x80000000, cpuinf[0].regs);
- maxextfunc = cpuinf[0].regs[0];
-
- TRACE("Detected max CPUID function: 0x%x (ext. 0x%x)\n", maxfunc, maxextfunc);
-
- TRACE("Vendor ID: \"%.4s%.4s%.4s\"\n", cpuinf[0].str+4, cpuinf[0].str+12, cpuinf[0].str+8);
- if(maxextfunc >= 0x80000004)
- {
- get_cpuid(0x80000002, cpuinf[0].regs);
- get_cpuid(0x80000003, cpuinf[1].regs);
- get_cpuid(0x80000004, cpuinf[2].regs);
- TRACE("Name: \"%.16s%.16s%.16s\"\n", cpuinf[0].str, cpuinf[1].str, cpuinf[2].str);
- }
-
- if(maxfunc >= 1)
- {
- get_cpuid(1, cpuinf[0].regs);
- if((cpuinf[0].regs[3]&(1<<25)))
- caps |= CPU_CAP_SSE;
- if((caps&CPU_CAP_SSE) && (cpuinf[0].regs[3]&(1<<26)))
- caps |= CPU_CAP_SSE2;
- if((caps&CPU_CAP_SSE2) && (cpuinf[0].regs[2]&(1<<0)))
- caps |= CPU_CAP_SSE3;
- if((caps&CPU_CAP_SSE3) && (cpuinf[0].regs[2]&(1<<19)))
- caps |= CPU_CAP_SSE4_1;
- }
- }
-#else
- /* Assume support for whatever's supported if we can't check for it */
-#if defined(HAVE_SSE4_1)
-#warning "Assuming SSE 4.1 run-time support!"
- caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1;
-#elif defined(HAVE_SSE3)
-#warning "Assuming SSE 3 run-time support!"
- caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3;
-#elif defined(HAVE_SSE2)
-#warning "Assuming SSE 2 run-time support!"
- caps |= CPU_CAP_SSE | CPU_CAP_SSE2;
-#elif defined(HAVE_SSE)
-#warning "Assuming SSE run-time support!"
- caps |= CPU_CAP_SSE;
-#endif
-#endif
-#ifdef HAVE_NEON
- FILE *file = fopen("/proc/cpuinfo", "rt");
- if(!file)
- ERR("Failed to open /proc/cpuinfo, cannot check for NEON support\n");
- else
- {
- al_string features = AL_STRING_INIT_STATIC();
- char buf[256];
-
- while(fgets(buf, sizeof(buf), file) != NULL)
- {
- if(strncmp(buf, "Features\t:", 10) != 0)
- continue;
-
- alstr_copy_cstr(&features, buf+10);
- while(VECTOR_BACK(features) != '\n')
- {
- if(fgets(buf, sizeof(buf), file) == NULL)
- break;
- alstr_append_cstr(&features, buf);
- }
- break;
- }
- fclose(file);
- file = NULL;
-
- if(!alstr_empty(features))
- {
- const char *str = alstr_get_cstr(features);
- while(isspace(str[0])) ++str;
-
- TRACE("Got features string:%s\n", str);
- while((str=strstr(str, "neon")) != NULL)
- {
- if(isspace(*(str-1)) && (str[4] == 0 || isspace(str[4])))
- {
- caps |= CPU_CAP_NEON;
- break;
- }
- ++str;
- }
- }
-
- alstr_reset(&features);
- }
-#endif
-
- 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;
-}
-
-
-void SetMixerFPUMode(FPUCtl *ctl)
-{
-#if defined(__GNUC__) && defined(HAVE_SSE)
- if((CPUCapFlags&CPU_CAP_SSE))
- {
- __asm__ __volatile__("stmxcsr %0" : "=m" (*&ctl->sse_state));
- unsigned int sseState = ctl->sse_state;
- sseState |= 0x8000; /* set flush-to-zero */
- if((CPUCapFlags&CPU_CAP_SSE2))
- sseState |= 0x0040; /* set denormals-are-zero */
- __asm__ __volatile__("ldmxcsr %0" : : "m" (*&sseState));
- }
-
-#elif defined(HAVE___CONTROL87_2)
-
- __control87_2(0, 0, &ctl->state, &ctl->sse_state);
- _control87(_DN_FLUSH, _MCW_DN);
-
-#elif defined(HAVE__CONTROLFP)
-
- ctl->state = _controlfp(0, 0);
- _controlfp(_DN_FLUSH, _MCW_DN);
-#endif
-}
-
-void RestoreFPUMode(const FPUCtl *ctl)
-{
-#if defined(__GNUC__) && defined(HAVE_SSE)
- if((CPUCapFlags&CPU_CAP_SSE))
- __asm__ __volatile__("ldmxcsr %0" : : "m" (*&ctl->sse_state));
-
-#elif defined(HAVE___CONTROL87_2)
-
- int mode;
- __control87_2(ctl->state, _MCW_DN, &mode, NULL);
- __control87_2(ctl->sse_state, _MCW_DN, NULL, &mode);
-
-#elif defined(HAVE__CONTROLFP)
-
- _controlfp(ctl->state, _MCW_DN);
-#endif
-}
-
-
-static int StringSortCompare(const void *str1, const void *str2)
-{
- return alstr_cmp(*(const_al_string*)str1, *(const_al_string*)str2);
-}
-
-#ifdef _WIN32
-
-static WCHAR *strrchrW(WCHAR *str, WCHAR ch)
-{
- WCHAR *ret = NULL;
- while(*str)
- {
- if(*str == ch)
- ret = str;
- ++str;
- }
- return ret;
-}
-
-void GetProcBinary(al_string *path, al_string *fname)
-{
- WCHAR *pathname, *sep;
- DWORD pathlen;
- DWORD len;
-
- pathlen = 256;
- pathname = malloc(pathlen * sizeof(pathname[0]));
- while(pathlen > 0 && (len=GetModuleFileNameW(NULL, pathname, pathlen)) == pathlen)
- {
- free(pathname);
- pathlen <<= 1;
- pathname = malloc(pathlen * sizeof(pathname[0]));
- }
- if(len == 0)
- {
- free(pathname);
- ERR("Failed to get process name: error %lu\n", GetLastError());
- return;
- }
-
- pathname[len] = 0;
- if((sep=strrchrW(pathname, '\\')) != NULL)
- {
- WCHAR *sep2 = strrchrW(sep+1, '/');
- if(sep2) sep = sep2;
- }
- else
- sep = strrchrW(pathname, '/');
-
- if(sep)
- {
- if(path) alstr_copy_wrange(path, pathname, sep);
- if(fname) alstr_copy_wcstr(fname, sep+1);
- }
- else
- {
- if(path) alstr_clear(path);
- if(fname) alstr_copy_wcstr(fname, pathname);
- }
- free(pathname);
-
- if(path && fname)
- TRACE("Got: %s, %s\n", alstr_get_cstr(*path), alstr_get_cstr(*fname));
- else if(path) TRACE("Got path: %s\n", alstr_get_cstr(*path));
- else if(fname) TRACE("Got filename: %s\n", alstr_get_cstr(*fname));
-}
-
-
-static WCHAR *FromUTF8(const char *str)
-{
- WCHAR *out = NULL;
- int len;
-
- if((len=MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0)) > 0)
- {
- out = calloc(sizeof(WCHAR), len);
- MultiByteToWideChar(CP_UTF8, 0, str, -1, out, len);
- }
- return out;
-}
-
-
-void *LoadLib(const char *name)
-{
- HANDLE hdl = NULL;
- WCHAR *wname;
-
- wname = FromUTF8(name);
- if(!wname)
- ERR("Failed to convert UTF-8 filename: \"%s\"\n", name);
- else
- {
- hdl = LoadLibraryW(wname);
- free(wname);
- }
- return hdl;
-}
-void CloseLib(void *handle)
-{ FreeLibrary((HANDLE)handle); }
-void *GetSymbol(void *handle, const char *name)
-{
- void *ret;
-
- ret = (void*)GetProcAddress((HANDLE)handle, name);
- if(ret == NULL)
- ERR("Failed to load %s\n", name);
- return ret;
-}
-
-WCHAR *strdupW(const WCHAR *str)
-{
- const WCHAR *n;
- WCHAR *ret;
- size_t len;
-
- n = str;
- while(*n) n++;
- len = n - str;
-
- ret = calloc(sizeof(WCHAR), len+1);
- if(ret != NULL)
- memcpy(ret, str, sizeof(WCHAR)*len);
- return ret;
-}
-
-FILE *al_fopen(const char *fname, const char *mode)
-{
- WCHAR *wname=NULL, *wmode=NULL;
- FILE *file = NULL;
-
- wname = FromUTF8(fname);
- wmode = FromUTF8(mode);
- if(!wname)
- ERR("Failed to convert UTF-8 filename: \"%s\"\n", fname);
- else if(!wmode)
- ERR("Failed to convert UTF-8 mode: \"%s\"\n", mode);
- else
- file = _wfopen(wname, wmode);
-
- free(wname);
- free(wmode);
-
- return file;
-}
-
-
-void al_print(const char *type, const char *func, const char *fmt, ...)
-{
- char str[1024];
- WCHAR *wstr;
- va_list ap;
-
- va_start(ap, fmt);
- vsnprintf(str, sizeof(str), fmt, ap);
- va_end(ap);
-
- str[sizeof(str)-1] = 0;
- wstr = FromUTF8(str);
- if(!wstr)
- fprintf(LogFile, "AL lib: %s %s: <UTF-8 error> %s", type, func, str);
- else
- {
- fprintf(LogFile, "AL lib: %s %s: %ls", type, func, wstr);
- free(wstr);
- wstr = NULL;
- }
- fflush(LogFile);
-}
-
-
-static inline int is_slash(int c)
-{ return (c == '\\' || c == '/'); }
-
-static void DirectorySearch(const char *path, const char *ext, vector_al_string *results)
-{
- al_string pathstr = AL_STRING_INIT_STATIC();
- WIN32_FIND_DATAW fdata;
- WCHAR *wpath;
- HANDLE hdl;
-
- alstr_copy_cstr(&pathstr, path);
- alstr_append_cstr(&pathstr, "\\*");
- alstr_append_cstr(&pathstr, ext);
-
- TRACE("Searching %s\n", alstr_get_cstr(pathstr));
-
- wpath = FromUTF8(alstr_get_cstr(pathstr));
-
- hdl = FindFirstFileW(wpath, &fdata);
- if(hdl != INVALID_HANDLE_VALUE)
- {
- size_t base = VECTOR_SIZE(*results);
- do {
- al_string str = AL_STRING_INIT_STATIC();
- alstr_copy_cstr(&str, path);
- alstr_append_char(&str, '\\');
- alstr_append_wcstr(&str, fdata.cFileName);
- TRACE("Got result %s\n", alstr_get_cstr(str));
- VECTOR_PUSH_BACK(*results, str);
- } while(FindNextFileW(hdl, &fdata));
- FindClose(hdl);
-
- if(VECTOR_SIZE(*results) > base)
- qsort(VECTOR_BEGIN(*results)+base, VECTOR_SIZE(*results)-base,
- sizeof(VECTOR_FRONT(*results)), StringSortCompare);
- }
-
- free(wpath);
- alstr_reset(&pathstr);
-}
-
-vector_al_string SearchDataFiles(const char *ext, const char *subdir)
-{
- static const int ids[2] = { CSIDL_APPDATA, CSIDL_COMMON_APPDATA };
- static RefCount search_lock;
- vector_al_string results = VECTOR_INIT_STATIC();
- size_t i;
-
- while(ATOMIC_EXCHANGE_SEQ(&search_lock, 1) == 1)
- althrd_yield();
-
- /* If the path is absolute, use it directly. */
- if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2]))
- {
- al_string path = AL_STRING_INIT_STATIC();
- alstr_copy_cstr(&path, subdir);
-#define FIX_SLASH(i) do { if(*(i) == '/') *(i) = '\\'; } while(0)
- VECTOR_FOR_EACH(char, path, FIX_SLASH);
-#undef FIX_SLASH
-
- DirectorySearch(alstr_get_cstr(path), ext, &results);
-
- alstr_reset(&path);
- }
- else if(subdir[0] == '\\' && subdir[1] == '\\' && subdir[2] == '?' && subdir[3] == '\\')
- DirectorySearch(subdir, ext, &results);
- else
- {
- al_string path = AL_STRING_INIT_STATIC();
- WCHAR *cwdbuf;
-
- /* Search the app-local directory. */
- if((cwdbuf=_wgetenv(L"ALSOFT_LOCAL_PATH")) && *cwdbuf != '\0')
- {
- alstr_copy_wcstr(&path, cwdbuf);
- if(is_slash(VECTOR_BACK(path)))
- {
- VECTOR_POP_BACK(path);
- *VECTOR_END(path) = 0;
- }
- }
- else if(!(cwdbuf=_wgetcwd(NULL, 0)))
- alstr_copy_cstr(&path, ".");
- else
- {
- alstr_copy_wcstr(&path, cwdbuf);
- if(is_slash(VECTOR_BACK(path)))
- {
- VECTOR_POP_BACK(path);
- *VECTOR_END(path) = 0;
- }
- free(cwdbuf);
- }
-#define FIX_SLASH(i) do { if(*(i) == '/') *(i) = '\\'; } while(0)
- VECTOR_FOR_EACH(char, path, FIX_SLASH);
-#undef FIX_SLASH
- DirectorySearch(alstr_get_cstr(path), ext, &results);
-
- /* Search the local and global data dirs. */
- for(i = 0;i < COUNTOF(ids);i++)
- {
- WCHAR buffer[MAX_PATH];
- if(SHGetSpecialFolderPathW(NULL, buffer, ids[i], FALSE) != FALSE)
- {
- alstr_copy_wcstr(&path, buffer);
- if(!is_slash(VECTOR_BACK(path)))
- alstr_append_char(&path, '\\');
- alstr_append_cstr(&path, subdir);
-#define FIX_SLASH(i) do { if(*(i) == '/') *(i) = '\\'; } while(0)
- VECTOR_FOR_EACH(char, path, FIX_SLASH);
-#undef FIX_SLASH
-
- DirectorySearch(alstr_get_cstr(path), ext, &results);
- }
- }
-
- alstr_reset(&path);
- }
-
- ATOMIC_STORE_SEQ(&search_lock, 0);
-
- return results;
-}
-
-
-struct FileMapping MapFileToMem(const char *fname)
-{
- struct FileMapping ret = { NULL, NULL, NULL, 0 };
- MEMORY_BASIC_INFORMATION meminfo;
- HANDLE file, fmap;
- WCHAR *wname;
- void *ptr;
-
- wname = FromUTF8(fname);
-
- file = CreateFileW(wname, GENERIC_READ, FILE_SHARE_READ, NULL,
- OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
- if(file == INVALID_HANDLE_VALUE)
- {
- ERR("Failed to open %s: %lu\n", fname, GetLastError());
- free(wname);
- return ret;
- }
- free(wname);
- wname = NULL;
-
- fmap = CreateFileMappingW(file, NULL, PAGE_READONLY, 0, 0, NULL);
- if(!fmap)
- {
- ERR("Failed to create map for %s: %lu\n", fname, GetLastError());
- CloseHandle(file);
- return ret;
- }
-
- ptr = MapViewOfFile(fmap, FILE_MAP_READ, 0, 0, 0);
- if(!ptr)
- {
- ERR("Failed to map %s: %lu\n", fname, GetLastError());
- CloseHandle(fmap);
- CloseHandle(file);
- return ret;
- }
-
- if(VirtualQuery(ptr, &meminfo, sizeof(meminfo)) != sizeof(meminfo))
- {
- ERR("Failed to get map size for %s: %lu\n", fname, GetLastError());
- UnmapViewOfFile(ptr);
- CloseHandle(fmap);
- CloseHandle(file);
- return ret;
- }
-
- ret.file = file;
- ret.fmap = fmap;
- ret.ptr = ptr;
- ret.len = meminfo.RegionSize;
- return ret;
-}
-
-void UnmapFileMem(const struct FileMapping *mapping)
-{
- UnmapViewOfFile(mapping->ptr);
- CloseHandle(mapping->fmap);
- CloseHandle(mapping->file);
-}
-
-#else
-
-void GetProcBinary(al_string *path, al_string *fname)
-{
- char *pathname = NULL;
- size_t pathlen;
-
-#ifdef __FreeBSD__
- int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
- if(sysctl(mib, 4, NULL, &pathlen, NULL, 0) == -1)
- WARN("Failed to sysctl kern.proc.pathname: %s\n", strerror(errno));
- else
- {
- pathname = malloc(pathlen + 1);
- sysctl(mib, 4, (void*)pathname, &pathlen, NULL, 0);
- pathname[pathlen] = 0;
- }
-#endif
-#ifdef HAVE_PROC_PIDPATH
- if(!pathname)
- {
- const pid_t pid = getpid();
- char procpath[PROC_PIDPATHINFO_MAXSIZE];
- int ret;
-
- ret = proc_pidpath(pid, procpath, sizeof(procpath));
- if(ret < 1)
- {
- WARN("proc_pidpath(%d, ...) failed: %s\n", pid, strerror(errno));
- free(pathname);
- pathname = NULL;
- }
- else
- {
- pathlen = strlen(procpath);
- pathname = strdup(procpath);
- }
- }
-#endif
- if(!pathname)
- {
- const char *selfname;
- ssize_t len;
-
- pathlen = 256;
- pathname = malloc(pathlen);
-
- selfname = "/proc/self/exe";
- len = readlink(selfname, pathname, pathlen);
- if(len == -1 && errno == ENOENT)
- {
- selfname = "/proc/self/file";
- len = readlink(selfname, pathname, pathlen);
- }
- if(len == -1 && errno == ENOENT)
- {
- selfname = "/proc/curproc/exe";
- len = readlink(selfname, pathname, pathlen);
- }
- if(len == -1 && errno == ENOENT)
- {
- selfname = "/proc/curproc/file";
- len = readlink(selfname, pathname, pathlen);
- }
-
- while(len > 0 && (size_t)len == pathlen)
- {
- free(pathname);
- pathlen <<= 1;
- pathname = malloc(pathlen);
- len = readlink(selfname, pathname, pathlen);
- }
- if(len <= 0)
- {
- free(pathname);
- WARN("Failed to readlink %s: %s\n", selfname, strerror(errno));
- return;
- }
-
- pathname[len] = 0;
- }
-
- char *sep = strrchr(pathname, '/');
- if(sep)
- {
- if(path) alstr_copy_range(path, pathname, sep);
- if(fname) alstr_copy_cstr(fname, sep+1);
- }
- else
- {
- if(path) alstr_clear(path);
- if(fname) alstr_copy_cstr(fname, pathname);
- }
- free(pathname);
-
- if(path && fname)
- TRACE("Got: %s, %s\n", alstr_get_cstr(*path), alstr_get_cstr(*fname));
- else if(path) TRACE("Got path: %s\n", alstr_get_cstr(*path));
- else if(fname) TRACE("Got filename: %s\n", alstr_get_cstr(*fname));
-}
-
-
-#ifdef HAVE_DLFCN_H
-
-void *LoadLib(const char *name)
-{
- const char *err;
- void *handle;
-
- dlerror();
- handle = dlopen(name, RTLD_NOW);
- if((err=dlerror()) != NULL)
- handle = NULL;
- return handle;
-}
-void CloseLib(void *handle)
-{ dlclose(handle); }
-void *GetSymbol(void *handle, const char *name)
-{
- const char *err;
- void *sym;
-
- dlerror();
- sym = dlsym(handle, name);
- if((err=dlerror()) != NULL)
- {
- WARN("Failed to load %s: %s\n", name, err);
- sym = NULL;
- }
- return sym;
-}
-
-#endif /* HAVE_DLFCN_H */
-
-void al_print(const char *type, const char *func, const char *fmt, ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- fprintf(LogFile, "AL lib: %s %s: ", type, func);
- vfprintf(LogFile, fmt, ap);
- va_end(ap);
-
- fflush(LogFile);
-}
-
-
-static void DirectorySearch(const char *path, const char *ext, vector_al_string *results)
-{
- size_t extlen = strlen(ext);
- DIR *dir;
-
- TRACE("Searching %s for *%s\n", path, ext);
- dir = opendir(path);
- if(dir != NULL)
- {
- size_t base = VECTOR_SIZE(*results);
- struct dirent *dirent;
- while((dirent=readdir(dir)) != NULL)
- {
- al_string str;
- size_t len;
- if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0)
- continue;
-
- len = strlen(dirent->d_name);
- if(!(len > extlen))
- continue;
- if(strcasecmp(dirent->d_name+len-extlen, ext) != 0)
- continue;
-
- AL_STRING_INIT(str);
- alstr_copy_cstr(&str, path);
- if(VECTOR_BACK(str) != '/')
- alstr_append_char(&str, '/');
- alstr_append_cstr(&str, dirent->d_name);
- TRACE("Got result %s\n", alstr_get_cstr(str));
- VECTOR_PUSH_BACK(*results, str);
- }
- closedir(dir);
-
- if(VECTOR_SIZE(*results) > base)
- qsort(VECTOR_BEGIN(*results)+base, VECTOR_SIZE(*results)-base,
- sizeof(VECTOR_FRONT(*results)), StringSortCompare);
- }
-}
-
-vector_al_string SearchDataFiles(const char *ext, const char *subdir)
-{
- static RefCount search_lock;
- vector_al_string results = VECTOR_INIT_STATIC();
-
- while(ATOMIC_EXCHANGE_SEQ(&search_lock, 1) == 1)
- althrd_yield();
-
- if(subdir[0] == '/')
- DirectorySearch(subdir, ext, &results);
- else
- {
- al_string path = AL_STRING_INIT_STATIC();
- const char *str, *next;
-
- /* Search the app-local directory. */
- if((str=getenv("ALSOFT_LOCAL_PATH")) && *str != '\0')
- DirectorySearch(str, ext, &results);
- else
- {
- size_t cwdlen = 256;
- char *cwdbuf = malloc(cwdlen);
- while(!getcwd(cwdbuf, cwdlen))
- {
- free(cwdbuf);
- cwdbuf = NULL;
- if(errno != ERANGE)
- break;
- cwdlen <<= 1;
- cwdbuf = malloc(cwdlen);
- }
- if(!cwdbuf)
- DirectorySearch(".", ext, &results);
- else
- {
- DirectorySearch(cwdbuf, ext, &results);
- free(cwdbuf);
- cwdbuf = NULL;
- }
- }
-
- // Search local data dir
- if((str=getenv("XDG_DATA_HOME")) != NULL && str[0] != '\0')
- {
- alstr_copy_cstr(&path, str);
- if(VECTOR_BACK(path) != '/')
- alstr_append_char(&path, '/');
- alstr_append_cstr(&path, subdir);
- DirectorySearch(alstr_get_cstr(path), ext, &results);
- }
- else if((str=getenv("HOME")) != NULL && str[0] != '\0')
- {
- alstr_copy_cstr(&path, str);
- if(VECTOR_BACK(path) == '/')
- {
- VECTOR_POP_BACK(path);
- *VECTOR_END(path) = 0;
- }
- alstr_append_cstr(&path, "/.local/share/");
- alstr_append_cstr(&path, subdir);
- DirectorySearch(alstr_get_cstr(path), ext, &results);
- }
-
- // Search global data dirs
- if((str=getenv("XDG_DATA_DIRS")) == NULL || str[0] == '\0')
- str = "/usr/local/share/:/usr/share/";
-
- next = str;
- while((str=next) != NULL && str[0] != '\0')
- {
- next = strchr(str, ':');
- if(!next)
- alstr_copy_cstr(&path, str);
- else
- {
- alstr_copy_range(&path, str, next);
- ++next;
- }
- if(!alstr_empty(path))
- {
- if(VECTOR_BACK(path) != '/')
- alstr_append_char(&path, '/');
- alstr_append_cstr(&path, subdir);
-
- DirectorySearch(alstr_get_cstr(path), ext, &results);
- }
- }
-
- alstr_reset(&path);
- }
-
- ATOMIC_STORE_SEQ(&search_lock, 0);
-
- return results;
-}
-
-
-struct FileMapping MapFileToMem(const char *fname)
-{
- struct FileMapping ret = { -1, NULL, 0 };
- struct stat sbuf;
- void *ptr;
- int fd;
-
- fd = open(fname, O_RDONLY, 0);
- if(fd == -1)
- {
- ERR("Failed to open %s: (%d) %s\n", fname, errno, strerror(errno));
- return ret;
- }
- if(fstat(fd, &sbuf) == -1)
- {
- ERR("Failed to stat %s: (%d) %s\n", fname, errno, strerror(errno));
- close(fd);
- return ret;
- }
-
- ptr = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
- if(ptr == MAP_FAILED)
- {
- ERR("Failed to map %s: (%d) %s\n", fname, errno, strerror(errno));
- close(fd);
- return ret;
- }
-
- ret.fd = fd;
- ret.ptr = ptr;
- ret.len = sbuf.st_size;
- return ret;
-}
-
-void UnmapFileMem(const struct FileMapping *mapping)
-{
- munmap(mapping->ptr, mapping->len);
- close(mapping->fd);
-}
-
-#endif
-
-
-void SetRTPriority(void)
-{
- ALboolean failed = AL_FALSE;
-
-#ifdef _WIN32
- if(RTPrioLevel > 0)
- failed = !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
-#elif defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
- if(RTPrioLevel > 0)
- {
- struct sched_param param;
- /* Use the minimum real-time priority possible for now (on Linux this
- * should be 1 for SCHED_RR) */
- param.sched_priority = sched_get_priority_min(SCHED_RR);
- failed = !!pthread_setschedparam(pthread_self(), SCHED_RR, &param);
- }
-#else
- /* Real-time priority not available */
- failed = (RTPrioLevel>0);
-#endif
- if(failed)
- ERR("Failed to set priority level for thread\n");
-}
-
-
-extern inline void alstr_reset(al_string *str);
-extern inline size_t alstr_length(const_al_string str);
-extern inline ALboolean alstr_empty(const_al_string str);
-extern inline const al_string_char_type *alstr_get_cstr(const_al_string str);
-
-void alstr_clear(al_string *str)
-{
- if(!alstr_empty(*str))
- {
- /* Reserve one more character than the total size of the string. This
- * is to ensure we have space to add a null terminator in the string
- * data so it can be used as a C-style string.
- */
- VECTOR_RESIZE(*str, 0, 1);
- VECTOR_ELEM(*str, 0) = 0;
- }
-}
-
-static inline int alstr_compare(const al_string_char_type *str1, size_t str1len,
- const al_string_char_type *str2, size_t str2len)
-{
- size_t complen = (str1len < str2len) ? str1len : str2len;
- int ret = memcmp(str1, str2, complen);
- if(ret == 0)
- {
- if(str1len > str2len) return 1;
- if(str1len < str2len) return -1;
- }
- return ret;
-}
-int alstr_cmp(const_al_string str1, const_al_string str2)
-{
- return alstr_compare(&VECTOR_FRONT(str1), alstr_length(str1),
- &VECTOR_FRONT(str2), alstr_length(str2));
-}
-int alstr_cmp_cstr(const_al_string str1, const al_string_char_type *str2)
-{
- return alstr_compare(&VECTOR_FRONT(str1), alstr_length(str1),
- str2, strlen(str2));
-}
-
-void alstr_copy(al_string *str, const_al_string from)
-{
- size_t len = alstr_length(from);
- size_t i;
-
- VECTOR_RESIZE(*str, len, len+1);
- for(i = 0;i < len;i++)
- VECTOR_ELEM(*str, i) = VECTOR_ELEM(from, i);
- VECTOR_ELEM(*str, i) = 0;
-}
-
-void alstr_copy_cstr(al_string *str, const al_string_char_type *from)
-{
- size_t len = strlen(from);
- size_t i;
-
- VECTOR_RESIZE(*str, len, len+1);
- for(i = 0;i < len;i++)
- VECTOR_ELEM(*str, i) = from[i];
- VECTOR_ELEM(*str, i) = 0;
-}
-
-void alstr_copy_range(al_string *str, const al_string_char_type *from, const al_string_char_type *to)
-{
- size_t len = to - from;
- size_t i;
-
- VECTOR_RESIZE(*str, len, len+1);
- for(i = 0;i < len;i++)
- VECTOR_ELEM(*str, i) = from[i];
- VECTOR_ELEM(*str, i) = 0;
-}
-
-void alstr_append_char(al_string *str, const al_string_char_type c)
-{
- size_t len = alstr_length(*str);
- VECTOR_RESIZE(*str, len+1, len+2);
- VECTOR_BACK(*str) = c;
- VECTOR_ELEM(*str, len+1) = 0;
-}
-
-void alstr_append_cstr(al_string *str, const al_string_char_type *from)
-{
- size_t len = strlen(from);
- if(len != 0)
- {
- size_t base = alstr_length(*str);
- size_t i;
-
- VECTOR_RESIZE(*str, base+len, base+len+1);
- for(i = 0;i < len;i++)
- VECTOR_ELEM(*str, base+i) = from[i];
- VECTOR_ELEM(*str, base+i) = 0;
- }
-}
-
-void alstr_append_range(al_string *str, const al_string_char_type *from, const al_string_char_type *to)
-{
- size_t len = to - from;
- if(len != 0)
- {
- size_t base = alstr_length(*str);
- size_t i;
-
- VECTOR_RESIZE(*str, base+len, base+len+1);
- for(i = 0;i < len;i++)
- VECTOR_ELEM(*str, base+i) = from[i];
- VECTOR_ELEM(*str, base+i) = 0;
- }
-}
-
-#ifdef _WIN32
-void alstr_copy_wcstr(al_string *str, const wchar_t *from)
-{
- int len;
- if((len=WideCharToMultiByte(CP_UTF8, 0, from, -1, NULL, 0, NULL, NULL)) > 0)
- {
- VECTOR_RESIZE(*str, len-1, len);
- WideCharToMultiByte(CP_UTF8, 0, from, -1, &VECTOR_FRONT(*str), len, NULL, NULL);
- VECTOR_ELEM(*str, len-1) = 0;
- }
-}
-
-void alstr_append_wcstr(al_string *str, const wchar_t *from)
-{
- int len;
- if((len=WideCharToMultiByte(CP_UTF8, 0, from, -1, NULL, 0, NULL, NULL)) > 0)
- {
- size_t base = alstr_length(*str);
- VECTOR_RESIZE(*str, base+len-1, base+len);
- WideCharToMultiByte(CP_UTF8, 0, from, -1, &VECTOR_ELEM(*str, base), len, NULL, NULL);
- VECTOR_ELEM(*str, base+len-1) = 0;
- }
-}
-
-void alstr_copy_wrange(al_string *str, const wchar_t *from, const wchar_t *to)
-{
- int len;
- if((len=WideCharToMultiByte(CP_UTF8, 0, from, (int)(to-from), NULL, 0, NULL, NULL)) > 0)
- {
- VECTOR_RESIZE(*str, len, len+1);
- WideCharToMultiByte(CP_UTF8, 0, from, (int)(to-from), &VECTOR_FRONT(*str), len+1, NULL, NULL);
- VECTOR_ELEM(*str, len) = 0;
- }
-}
-
-void alstr_append_wrange(al_string *str, const wchar_t *from, const wchar_t *to)
-{
- int len;
- if((len=WideCharToMultiByte(CP_UTF8, 0, from, (int)(to-from), NULL, 0, NULL, NULL)) > 0)
- {
- size_t base = alstr_length(*str);
- VECTOR_RESIZE(*str, base+len, base+len+1);
- WideCharToMultiByte(CP_UTF8, 0, from, (int)(to-from), &VECTOR_ELEM(*str, base), len+1, NULL, NULL);
- VECTOR_ELEM(*str, base+len) = 0;
- }
-}
-#endif
diff --git a/Alc/hrtf.c b/Alc/hrtf.c
deleted file mode 100644
index ddbd3a28..00000000
--- a/Alc/hrtf.c
+++ /dev/null
@@ -1,1465 +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 <ctype.h>
-
-#include "AL/al.h"
-#include "AL/alc.h"
-#include "alMain.h"
-#include "alSource.h"
-#include "alu.h"
-#include "hrtf.h"
-#include "alconfig.h"
-#include "filters/splitter.h"
-
-#include "compat.h"
-#include "almalloc.h"
-
-
-/* Current data set limits defined by the makehrtf utility. */
-#define MIN_IR_SIZE (8)
-#define MAX_IR_SIZE (512)
-#define MOD_IR_SIZE (8)
-
-#define MIN_FD_COUNT (1)
-#define MAX_FD_COUNT (16)
-
-#define MIN_FD_DISTANCE (50)
-#define MAX_FD_DISTANCE (2500)
-
-#define MIN_EV_COUNT (5)
-#define MAX_EV_COUNT (128)
-
-#define MIN_AZ_COUNT (1)
-#define MAX_AZ_COUNT (128)
-
-#define MAX_HRIR_DELAY (HRTF_HISTORY_LENGTH-1)
-
-struct HrtfEntry {
- struct HrtfEntry *next;
- struct Hrtf *handle;
- char filename[];
-};
-
-static const ALchar magicMarker00[8] = "MinPHR00";
-static const ALchar magicMarker01[8] = "MinPHR01";
-static const ALchar magicMarker02[8] = "MinPHR02";
-
-/* First value for pass-through coefficients (remaining are 0), used for omni-
- * directional sounds. */
-static const ALfloat PassthruCoeff = 0.707106781187f/*sqrt(0.5)*/;
-
-static ATOMIC_FLAG LoadedHrtfLock = ATOMIC_FLAG_INIT;
-static struct HrtfEntry *LoadedHrtfs = NULL;
-
-
-/* Calculate the elevation index given the polar elevation in radians. This
- * will return an index between 0 and (evcount - 1).
- */
-static ALsizei CalcEvIndex(ALsizei evcount, ALfloat ev, ALfloat *mu)
-{
- ALsizei idx;
- ev = (F_PI_2+ev) * (evcount-1) / F_PI;
- idx = float2int(ev);
-
- *mu = ev - idx;
- return mini(idx, evcount-1);
-}
-
-/* Calculate the azimuth index given the polar azimuth in radians. This will
- * return an index between 0 and (azcount - 1).
- */
-static ALsizei CalcAzIndex(ALsizei azcount, ALfloat az, ALfloat *mu)
-{
- ALsizei idx;
- az = (F_TAU+az) * azcount / F_TAU;
-
- idx = float2int(az);
- *mu = az - idx;
- return idx % azcount;
-}
-
-/* Calculates static HRIR coefficients and delays for the given polar elevation
- * and azimuth in radians. The coefficients are normalized.
- */
-void GetHrtfCoeffs(const struct Hrtf *Hrtf, ALfloat elevation, ALfloat azimuth, ALfloat spread,
- ALfloat (*restrict coeffs)[2], ALsizei *delays)
-{
- ALsizei evidx, azidx, idx[4];
- ALsizei evoffset;
- ALfloat emu, amu[2];
- ALfloat blend[4];
- ALfloat dirfact;
- ALsizei i, c;
-
- dirfact = 1.0f - (spread / F_TAU);
-
- /* Claculate the lower elevation index. */
- evidx = CalcEvIndex(Hrtf->evCount, elevation, &emu);
- evoffset = Hrtf->evOffset[evidx];
-
- /* Calculate lower azimuth index. */
- azidx= CalcAzIndex(Hrtf->azCount[evidx], azimuth, &amu[0]);
-
- /* Calculate the lower HRIR indices. */
- idx[0] = evoffset + azidx;
- idx[1] = evoffset + ((azidx+1) % Hrtf->azCount[evidx]);
- if(evidx < Hrtf->evCount-1)
- {
- /* Increment elevation to the next (upper) index. */
- evidx++;
- evoffset = Hrtf->evOffset[evidx];
-
- /* Calculate upper azimuth index. */
- azidx = CalcAzIndex(Hrtf->azCount[evidx], azimuth, &amu[1]);
-
- /* Calculate the upper HRIR indices. */
- idx[2] = evoffset + azidx;
- idx[3] = evoffset + ((azidx+1) % Hrtf->azCount[evidx]);
- }
- else
- {
- /* If the lower elevation is the top index, the upper elevation is the
- * same as the lower.
- */
- amu[1] = amu[0];
- idx[2] = idx[0];
- idx[3] = idx[1];
- }
-
- /* Calculate bilinear blending weights, attenuated according to the
- * directional panning factor.
- */
- blend[0] = (1.0f-emu) * (1.0f-amu[0]) * dirfact;
- blend[1] = (1.0f-emu) * ( amu[0]) * dirfact;
- blend[2] = ( emu) * (1.0f-amu[1]) * dirfact;
- blend[3] = ( emu) * ( amu[1]) * dirfact;
-
- /* Calculate the blended HRIR delays. */
- delays[0] = fastf2i(
- Hrtf->delays[idx[0]][0]*blend[0] + Hrtf->delays[idx[1]][0]*blend[1] +
- Hrtf->delays[idx[2]][0]*blend[2] + Hrtf->delays[idx[3]][0]*blend[3]
- );
- delays[1] = fastf2i(
- Hrtf->delays[idx[0]][1]*blend[0] + Hrtf->delays[idx[1]][1]*blend[1] +
- Hrtf->delays[idx[2]][1]*blend[2] + Hrtf->delays[idx[3]][1]*blend[3]
- );
-
- /* Calculate the sample offsets for the HRIR indices. */
- idx[0] *= Hrtf->irSize;
- idx[1] *= Hrtf->irSize;
- idx[2] *= Hrtf->irSize;
- idx[3] *= Hrtf->irSize;
-
- ASSUME(Hrtf->irSize >= MIN_IR_SIZE && (Hrtf->irSize%MOD_IR_SIZE) == 0);
- coeffs = ASSUME_ALIGNED(coeffs, 16);
- /* Calculate the blended HRIR coefficients. */
- coeffs[0][0] = PassthruCoeff * (1.0f-dirfact);
- coeffs[0][1] = PassthruCoeff * (1.0f-dirfact);
- for(i = 1;i < Hrtf->irSize;i++)
- {
- coeffs[i][0] = 0.0f;
- coeffs[i][1] = 0.0f;
- }
- for(c = 0;c < 4;c++)
- {
- const ALfloat (*restrict srccoeffs)[2] = ASSUME_ALIGNED(Hrtf->coeffs+idx[c], 16);
- for(i = 0;i < Hrtf->irSize;i++)
- {
- coeffs[i][0] += srccoeffs[i][0] * blend[c];
- coeffs[i][1] += srccoeffs[i][1] * blend[c];
- }
- }
-}
-
-
-void BuildBFormatHrtf(const struct Hrtf *Hrtf, DirectHrtfState *state, ALsizei NumChannels, const struct AngularPoint *AmbiPoints, const ALfloat (*restrict AmbiMatrix)[MAX_AMBI_COEFFS], ALsizei AmbiCount, const ALfloat *restrict AmbiOrderHFGain)
-{
-/* Set this to 2 for dual-band HRTF processing. May require a higher quality
- * band-splitter, or better calculation of the new IR length to deal with the
- * tail generated by the filter.
- */
-#define NUM_BANDS 2
- BandSplitter splitter;
- ALdouble (*tmpres)[HRIR_LENGTH][2];
- ALsizei *restrict idx;
- ALsizei min_delay = HRTF_HISTORY_LENGTH;
- ALsizei max_delay = 0;
- ALfloat temps[3][HRIR_LENGTH];
- ALsizei max_length;
- ALsizei i, c, b;
-
- idx = al_calloc(DEF_ALIGN, AmbiCount*sizeof(*idx));
-
- for(c = 0;c < AmbiCount;c++)
- {
- ALuint evidx, azidx;
- ALuint evoffset;
- ALuint azcount;
-
- /* Calculate elevation index. */
- evidx = (ALsizei)((F_PI_2+AmbiPoints[c].Elev) * (Hrtf->evCount-1) / F_PI + 0.5f);
- evidx = clampi(evidx, 0, Hrtf->evCount-1);
-
- azcount = Hrtf->azCount[evidx];
- evoffset = Hrtf->evOffset[evidx];
-
- /* Calculate azimuth index for this elevation. */
- azidx = (ALsizei)((F_TAU+AmbiPoints[c].Azim) * azcount / F_TAU + 0.5f) % azcount;
-
- /* Calculate indices for left and right channels. */
- idx[c] = evoffset + azidx;
-
- min_delay = mini(min_delay, mini(Hrtf->delays[idx[c]][0], Hrtf->delays[idx[c]][1]));
- max_delay = maxi(max_delay, maxi(Hrtf->delays[idx[c]][0], Hrtf->delays[idx[c]][1]));
- }
-
- tmpres = al_calloc(16, NumChannels * sizeof(*tmpres));
-
- memset(temps, 0, sizeof(temps));
- bandsplit_init(&splitter, 400.0f / (ALfloat)Hrtf->sampleRate);
- for(c = 0;c < AmbiCount;c++)
- {
- const ALfloat (*fir)[2] = &Hrtf->coeffs[idx[c] * Hrtf->irSize];
- ALsizei ldelay = Hrtf->delays[idx[c]][0] - min_delay;
- ALsizei rdelay = Hrtf->delays[idx[c]][1] - min_delay;
-
- if(NUM_BANDS == 1)
- {
- for(i = 0;i < NumChannels;++i)
- {
- ALdouble mult = (ALdouble)AmbiOrderHFGain[(ALsizei)sqrt(i)] * AmbiMatrix[c][i];
- ALsizei lidx = ldelay, ridx = rdelay;
- ALsizei j = 0;
- while(lidx < HRIR_LENGTH && ridx < HRIR_LENGTH && j < Hrtf->irSize)
- {
- tmpres[i][lidx++][0] += fir[j][0] * mult;
- tmpres[i][ridx++][1] += fir[j][1] * mult;
- j++;
- }
- }
- }
- else
- {
- /* Band-split left HRIR into low and high frequency responses. */
- bandsplit_clear(&splitter);
- for(i = 0;i < Hrtf->irSize;i++)
- temps[2][i] = fir[i][0];
- bandsplit_process(&splitter, temps[0], temps[1], temps[2], HRIR_LENGTH);
-
- /* Apply left ear response with delay. */
- for(i = 0;i < NumChannels;++i)
- {
- ALfloat hfgain = AmbiOrderHFGain[(ALsizei)sqrt(i)];
- for(b = 0;b < NUM_BANDS;b++)
- {
- ALdouble mult = AmbiMatrix[c][i] * (ALdouble)((b==0) ? hfgain : 1.0);
- ALsizei lidx = ldelay;
- ALsizei j = 0;
- while(lidx < HRIR_LENGTH)
- tmpres[i][lidx++][0] += temps[b][j++] * mult;
- }
- }
-
- /* Band-split right HRIR into low and high frequency responses. */
- bandsplit_clear(&splitter);
- for(i = 0;i < Hrtf->irSize;i++)
- temps[2][i] = fir[i][1];
- bandsplit_process(&splitter, temps[0], temps[1], temps[2], HRIR_LENGTH);
-
- /* Apply right ear response with delay. */
- for(i = 0;i < NumChannels;++i)
- {
- ALfloat hfgain = AmbiOrderHFGain[(ALsizei)sqrt(i)];
- for(b = 0;b < NUM_BANDS;b++)
- {
- ALdouble mult = AmbiMatrix[c][i] * (ALdouble)((b==0) ? hfgain : 1.0);
- ALsizei ridx = rdelay;
- ALsizei j = 0;
- while(ridx < HRIR_LENGTH)
- tmpres[i][ridx++][1] += temps[b][j++] * mult;
- }
- }
- }
- }
-
- for(i = 0;i < NumChannels;++i)
- {
- int idx;
- for(idx = 0;idx < HRIR_LENGTH;idx++)
- {
- state->Chan[i].Coeffs[idx][0] = (ALfloat)tmpres[i][idx][0];
- state->Chan[i].Coeffs[idx][1] = (ALfloat)tmpres[i][idx][1];
- }
- }
- al_free(tmpres);
- tmpres = NULL;
- al_free(idx);
- idx = NULL;
-
- if(NUM_BANDS == 1)
- max_length = mini(max_delay-min_delay + Hrtf->irSize, HRIR_LENGTH);
- else
- {
- /* Increase the IR size by 2/3rds to account for the tail generated by
- * the band-split filter.
- */
- const ALsizei irsize = mini(Hrtf->irSize*5/3, HRIR_LENGTH);
- max_length = mini(max_delay-min_delay + irsize, HRIR_LENGTH);
- }
- /* Round up to the next IR size multiple. */
- max_length += MOD_IR_SIZE-1;
- max_length -= max_length%MOD_IR_SIZE;
-
- TRACE("Skipped delay: %d, max delay: %d, new FIR length: %d\n",
- min_delay, max_delay-min_delay, max_length);
- state->IrSize = max_length;
-#undef NUM_BANDS
-}
-
-
-static struct Hrtf *CreateHrtfStore(ALuint rate, ALsizei irSize,
- ALfloat distance, ALsizei evCount, ALsizei irCount, const ALubyte *azCount,
- const ALushort *evOffset, const ALfloat (*coeffs)[2], const ALubyte (*delays)[2],
- const char *filename)
-{
- struct Hrtf *Hrtf;
- size_t total;
-
- total = sizeof(struct Hrtf);
- total += sizeof(Hrtf->azCount[0])*evCount;
- total = RoundUp(total, sizeof(ALushort)); /* Align for ushort fields */
- total += sizeof(Hrtf->evOffset[0])*evCount;
- total = RoundUp(total, 16); /* Align for coefficients using SIMD */
- total += sizeof(Hrtf->coeffs[0])*irSize*irCount;
- total += sizeof(Hrtf->delays[0])*irCount;
-
- Hrtf = al_calloc(16, total);
- if(Hrtf == NULL)
- ERR("Out of memory allocating storage for %s.\n", filename);
- else
- {
- uintptr_t offset = sizeof(struct Hrtf);
- char *base = (char*)Hrtf;
- ALushort *_evOffset;
- ALubyte *_azCount;
- ALubyte (*_delays)[2];
- ALfloat (*_coeffs)[2];
- ALsizei i;
-
- InitRef(&Hrtf->ref, 0);
- Hrtf->sampleRate = rate;
- Hrtf->irSize = irSize;
- Hrtf->distance = distance;
- Hrtf->evCount = evCount;
-
- /* Set up pointers to storage following the main HRTF struct. */
- _azCount = (ALubyte*)(base + offset);
- offset += sizeof(_azCount[0])*evCount;
-
- offset = RoundUp(offset, sizeof(ALushort)); /* Align for ushort fields */
- _evOffset = (ALushort*)(base + offset);
- offset += sizeof(_evOffset[0])*evCount;
-
- offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */
- _coeffs = (ALfloat(*)[2])(base + offset);
- offset += sizeof(_coeffs[0])*irSize*irCount;
-
- _delays = (ALubyte(*)[2])(base + offset);
- offset += sizeof(_delays[0])*irCount;
-
- assert(offset == total);
-
- /* Copy input data to storage. */
- for(i = 0;i < evCount;i++) _azCount[i] = azCount[i];
- for(i = 0;i < evCount;i++) _evOffset[i] = evOffset[i];
- for(i = 0;i < irSize*irCount;i++)
- {
- _coeffs[i][0] = coeffs[i][0];
- _coeffs[i][1] = coeffs[i][1];
- }
- for(i = 0;i < irCount;i++)
- {
- _delays[i][0] = delays[i][0];
- _delays[i][1] = delays[i][1];
- }
-
- /* Finally, assign the storage pointers. */
- Hrtf->azCount = _azCount;
- Hrtf->evOffset = _evOffset;
- Hrtf->coeffs = _coeffs;
- Hrtf->delays = _delays;
- }
-
- return Hrtf;
-}
-
-static ALubyte GetLE_ALubyte(const ALubyte **data, size_t *len)
-{
- ALubyte ret = (*data)[0];
- *data += 1; *len -= 1;
- return ret;
-}
-
-static ALshort GetLE_ALshort(const ALubyte **data, size_t *len)
-{
- ALshort ret = (*data)[0] | ((*data)[1]<<8);
- *data += 2; *len -= 2;
- return ret;
-}
-
-static ALushort GetLE_ALushort(const ALubyte **data, size_t *len)
-{
- ALushort ret = (*data)[0] | ((*data)[1]<<8);
- *data += 2; *len -= 2;
- return ret;
-}
-
-static ALint GetLE_ALint24(const ALubyte **data, size_t *len)
-{
- ALint ret = (*data)[0] | ((*data)[1]<<8) | ((*data)[2]<<16);
- *data += 3; *len -= 3;
- return (ret^0x800000) - 0x800000;
-}
-
-static ALuint GetLE_ALuint(const ALubyte **data, size_t *len)
-{
- ALuint ret = (*data)[0] | ((*data)[1]<<8) | ((*data)[2]<<16) | ((*data)[3]<<24);
- *data += 4; *len -= 4;
- return ret;
-}
-
-static const ALubyte *Get_ALubytePtr(const ALubyte **data, size_t *len, size_t size)
-{
- const ALubyte *ret = *data;
- *data += size; *len -= size;
- return ret;
-}
-
-static struct Hrtf *LoadHrtf00(const ALubyte *data, size_t datalen, const char *filename)
-{
- struct Hrtf *Hrtf = NULL;
- ALboolean failed = AL_FALSE;
- ALuint rate = 0;
- ALushort irCount = 0;
- ALushort irSize = 0;
- ALubyte evCount = 0;
- ALubyte *azCount = NULL;
- ALushort *evOffset = NULL;
- ALfloat (*coeffs)[2] = NULL;
- ALubyte (*delays)[2] = NULL;
- ALsizei i, j;
-
- if(datalen < 9)
- {
- ERR("Unexpected end of %s data (req %d, rem "SZFMT")\n", filename, 9, datalen);
- return NULL;
- }
-
- rate = GetLE_ALuint(&data, &datalen);
-
- irCount = GetLE_ALushort(&data, &datalen);
-
- irSize = GetLE_ALushort(&data, &datalen);
-
- evCount = GetLE_ALubyte(&data, &datalen);
-
- if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE))
- {
- ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n",
- irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE);
- failed = AL_TRUE;
- }
- if(evCount < MIN_EV_COUNT || evCount > MAX_EV_COUNT)
- {
- ERR("Unsupported elevation count: evCount=%d (%d to %d)\n",
- evCount, MIN_EV_COUNT, MAX_EV_COUNT);
- failed = AL_TRUE;
- }
- if(failed)
- return NULL;
-
- if(datalen < evCount*2u)
- {
- ERR("Unexpected end of %s data (req %d, rem "SZFMT")\n", filename, evCount*2, datalen);
- return NULL;
- }
-
- azCount = malloc(sizeof(azCount[0])*evCount);
- evOffset = malloc(sizeof(evOffset[0])*evCount);
- if(azCount == NULL || evOffset == NULL)
- {
- ERR("Out of memory.\n");
- failed = AL_TRUE;
- }
-
- if(!failed)
- {
- evOffset[0] = GetLE_ALushort(&data, &datalen);
- for(i = 1;i < evCount;i++)
- {
- evOffset[i] = GetLE_ALushort(&data, &datalen);
- if(evOffset[i] <= evOffset[i-1])
- {
- ERR("Invalid evOffset: evOffset[%d]=%d (last=%d)\n",
- i, evOffset[i], evOffset[i-1]);
- failed = AL_TRUE;
- }
-
- azCount[i-1] = evOffset[i] - evOffset[i-1];
- if(azCount[i-1] < MIN_AZ_COUNT || azCount[i-1] > MAX_AZ_COUNT)
- {
- ERR("Unsupported azimuth count: azCount[%d]=%d (%d to %d)\n",
- i-1, azCount[i-1], MIN_AZ_COUNT, MAX_AZ_COUNT);
- failed = AL_TRUE;
- }
- }
- if(irCount <= evOffset[i-1])
- {
- ERR("Invalid evOffset: evOffset[%d]=%d (irCount=%d)\n",
- i-1, evOffset[i-1], irCount);
- failed = AL_TRUE;
- }
-
- azCount[i-1] = irCount - evOffset[i-1];
- if(azCount[i-1] < MIN_AZ_COUNT || azCount[i-1] > MAX_AZ_COUNT)
- {
- ERR("Unsupported azimuth count: azCount[%d]=%d (%d to %d)\n",
- i-1, azCount[i-1], MIN_AZ_COUNT, MAX_AZ_COUNT);
- failed = AL_TRUE;
- }
- }
-
- if(!failed)
- {
- coeffs = malloc(sizeof(coeffs[0])*irSize*irCount);
- delays = malloc(sizeof(delays[0])*irCount);
- if(coeffs == NULL || delays == NULL)
- {
- ERR("Out of memory.\n");
- failed = AL_TRUE;
- }
- }
-
- if(!failed)
- {
- size_t reqsize = 2*irSize*irCount + irCount;
- if(datalen < reqsize)
- {
- ERR("Unexpected end of %s data (req "SZFMT", rem "SZFMT")\n",
- filename, reqsize, datalen);
- failed = AL_TRUE;
- }
- }
-
- if(!failed)
- {
- for(i = 0;i < irCount;i++)
- {
- for(j = 0;j < irSize;j++)
- coeffs[i*irSize + j][0] = GetLE_ALshort(&data, &datalen) / 32768.0f;
- }
-
- for(i = 0;i < irCount;i++)
- {
- delays[i][0] = GetLE_ALubyte(&data, &datalen);
- if(delays[i][0] > MAX_HRIR_DELAY)
- {
- ERR("Invalid delays[%d]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY);
- failed = AL_TRUE;
- }
- }
- }
-
- if(!failed)
- {
- /* Mirror the left ear responses to the right ear. */
- for(i = 0;i < evCount;i++)
- {
- ALushort evoffset = evOffset[i];
- ALubyte azcount = azCount[i];
- for(j = 0;j < azcount;j++)
- {
- ALsizei lidx = evoffset + j;
- ALsizei ridx = evoffset + ((azcount-j) % azcount);
- ALsizei k;
-
- for(k = 0;k < irSize;k++)
- coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0];
- delays[ridx][1] = delays[lidx][0];
- }
- }
-
- Hrtf = CreateHrtfStore(rate, irSize, 0.0f, evCount, irCount, azCount,
- evOffset, coeffs, delays, filename);
- }
-
- free(azCount);
- free(evOffset);
- free(coeffs);
- free(delays);
- return Hrtf;
-}
-
-static struct Hrtf *LoadHrtf01(const ALubyte *data, size_t datalen, const char *filename)
-{
- struct Hrtf *Hrtf = NULL;
- ALboolean failed = AL_FALSE;
- ALuint rate = 0;
- ALushort irCount = 0;
- ALushort irSize = 0;
- ALubyte evCount = 0;
- const ALubyte *azCount = NULL;
- ALushort *evOffset = NULL;
- ALfloat (*coeffs)[2] = NULL;
- ALubyte (*delays)[2] = NULL;
- ALsizei i, j;
-
- if(datalen < 6)
- {
- ERR("Unexpected end of %s data (req %d, rem "SZFMT"\n", filename, 6, datalen);
- return NULL;
- }
-
- rate = GetLE_ALuint(&data, &datalen);
-
- irSize = GetLE_ALubyte(&data, &datalen);
-
- evCount = GetLE_ALubyte(&data, &datalen);
-
- if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE))
- {
- ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n",
- irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE);
- failed = AL_TRUE;
- }
- if(evCount < MIN_EV_COUNT || evCount > MAX_EV_COUNT)
- {
- ERR("Unsupported elevation count: evCount=%d (%d to %d)\n",
- evCount, MIN_EV_COUNT, MAX_EV_COUNT);
- failed = AL_TRUE;
- }
- if(failed)
- return NULL;
-
- if(datalen < evCount)
- {
- ERR("Unexpected end of %s data (req %d, rem "SZFMT"\n", filename, evCount, datalen);
- return NULL;
- }
-
- azCount = Get_ALubytePtr(&data, &datalen, evCount);
-
- evOffset = malloc(sizeof(evOffset[0])*evCount);
- if(azCount == NULL || evOffset == NULL)
- {
- ERR("Out of memory.\n");
- failed = AL_TRUE;
- }
-
- if(!failed)
- {
- for(i = 0;i < evCount;i++)
- {
- if(azCount[i] < MIN_AZ_COUNT || azCount[i] > MAX_AZ_COUNT)
- {
- ERR("Unsupported azimuth count: azCount[%d]=%d (%d to %d)\n",
- i, azCount[i], MIN_AZ_COUNT, MAX_AZ_COUNT);
- failed = AL_TRUE;
- }
- }
- }
-
- if(!failed)
- {
- evOffset[0] = 0;
- irCount = azCount[0];
- for(i = 1;i < evCount;i++)
- {
- evOffset[i] = evOffset[i-1] + azCount[i-1];
- irCount += azCount[i];
- }
-
- coeffs = malloc(sizeof(coeffs[0])*irSize*irCount);
- delays = malloc(sizeof(delays[0])*irCount);
- if(coeffs == NULL || delays == NULL)
- {
- ERR("Out of memory.\n");
- failed = AL_TRUE;
- }
- }
-
- if(!failed)
- {
- size_t reqsize = 2*irSize*irCount + irCount;
- if(datalen < reqsize)
- {
- ERR("Unexpected end of %s data (req "SZFMT", rem "SZFMT"\n",
- filename, reqsize, datalen);
- failed = AL_TRUE;
- }
- }
-
- if(!failed)
- {
- for(i = 0;i < irCount;i++)
- {
- for(j = 0;j < irSize;j++)
- coeffs[i*irSize + j][0] = GetLE_ALshort(&data, &datalen) / 32768.0f;
- }
-
- for(i = 0;i < irCount;i++)
- {
- delays[i][0] = GetLE_ALubyte(&data, &datalen);
- if(delays[i][0] > MAX_HRIR_DELAY)
- {
- ERR("Invalid delays[%d]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY);
- failed = AL_TRUE;
- }
- }
- }
-
- if(!failed)
- {
- /* Mirror the left ear responses to the right ear. */
- for(i = 0;i < evCount;i++)
- {
- ALushort evoffset = evOffset[i];
- ALubyte azcount = azCount[i];
- for(j = 0;j < azcount;j++)
- {
- ALsizei lidx = evoffset + j;
- ALsizei ridx = evoffset + ((azcount-j) % azcount);
- ALsizei k;
-
- for(k = 0;k < irSize;k++)
- coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0];
- delays[ridx][1] = delays[lidx][0];
- }
- }
-
- Hrtf = CreateHrtfStore(rate, irSize, 0.0f, evCount, irCount, azCount,
- evOffset, coeffs, delays, filename);
- }
-
- free(evOffset);
- free(coeffs);
- free(delays);
- return Hrtf;
-}
-
-#define SAMPLETYPE_S16 0
-#define SAMPLETYPE_S24 1
-
-#define CHANTYPE_LEFTONLY 0
-#define CHANTYPE_LEFTRIGHT 1
-
-static struct Hrtf *LoadHrtf02(const ALubyte *data, size_t datalen, const char *filename)
-{
- struct Hrtf *Hrtf = NULL;
- ALboolean failed = AL_FALSE;
- ALuint rate = 0;
- ALubyte sampleType;
- ALubyte channelType;
- ALushort irCount = 0;
- ALushort irSize = 0;
- ALubyte fdCount = 0;
- ALushort distance = 0;
- ALubyte evCount = 0;
- const ALubyte *azCount = NULL;
- ALushort *evOffset = NULL;
- ALfloat (*coeffs)[2] = NULL;
- ALubyte (*delays)[2] = NULL;
- ALsizei i, j;
-
- if(datalen < 8)
- {
- ERR("Unexpected end of %s data (req %d, rem "SZFMT"\n", filename, 8, datalen);
- return NULL;
- }
-
- rate = GetLE_ALuint(&data, &datalen);
- sampleType = GetLE_ALubyte(&data, &datalen);
- channelType = GetLE_ALubyte(&data, &datalen);
-
- irSize = GetLE_ALubyte(&data, &datalen);
-
- fdCount = GetLE_ALubyte(&data, &datalen);
-
- if(sampleType > SAMPLETYPE_S24)
- {
- ERR("Unsupported sample type: %d\n", sampleType);
- failed = AL_TRUE;
- }
- if(channelType > CHANTYPE_LEFTRIGHT)
- {
- ERR("Unsupported channel type: %d\n", channelType);
- failed = AL_TRUE;
- }
-
- if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE))
- {
- ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n",
- irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE);
- failed = AL_TRUE;
- }
- if(fdCount != 1)
- {
- ERR("Multiple field-depths not supported: fdCount=%d (%d to %d)\n",
- evCount, MIN_FD_COUNT, MAX_FD_COUNT);
- failed = AL_TRUE;
- }
- if(failed)
- return NULL;
-
- for(i = 0;i < fdCount;i++)
- {
- if(datalen < 3)
- {
- ERR("Unexpected end of %s data (req %d, rem "SZFMT"\n", filename, 3, datalen);
- return NULL;
- }
-
- distance = GetLE_ALushort(&data, &datalen);
- if(distance < MIN_FD_DISTANCE || distance > MAX_FD_DISTANCE)
- {
- ERR("Unsupported field distance: distance=%d (%dmm to %dmm)\n",
- distance, MIN_FD_DISTANCE, MAX_FD_DISTANCE);
- failed = AL_TRUE;
- }
-
- evCount = GetLE_ALubyte(&data, &datalen);
- if(evCount < MIN_EV_COUNT || evCount > MAX_EV_COUNT)
- {
- ERR("Unsupported elevation count: evCount=%d (%d to %d)\n",
- evCount, MIN_EV_COUNT, MAX_EV_COUNT);
- failed = AL_TRUE;
- }
- if(failed)
- return NULL;
-
- if(datalen < evCount)
- {
- ERR("Unexpected end of %s data (req %d, rem "SZFMT"\n", filename, evCount, datalen);
- return NULL;
- }
-
- azCount = Get_ALubytePtr(&data, &datalen, evCount);
- for(j = 0;j < evCount;j++)
- {
- if(azCount[j] < MIN_AZ_COUNT || azCount[j] > MAX_AZ_COUNT)
- {
- ERR("Unsupported azimuth count: azCount[%d]=%d (%d to %d)\n",
- j, azCount[j], MIN_AZ_COUNT, MAX_AZ_COUNT);
- failed = AL_TRUE;
- }
- }
- }
- if(failed)
- return NULL;
-
- evOffset = malloc(sizeof(evOffset[0])*evCount);
- if(azCount == NULL || evOffset == NULL)
- {
- ERR("Out of memory.\n");
- failed = AL_TRUE;
- }
-
- if(!failed)
- {
- evOffset[0] = 0;
- irCount = azCount[0];
- for(i = 1;i < evCount;i++)
- {
- evOffset[i] = evOffset[i-1] + azCount[i-1];
- irCount += azCount[i];
- }
-
- coeffs = malloc(sizeof(coeffs[0])*irSize*irCount);
- delays = malloc(sizeof(delays[0])*irCount);
- if(coeffs == NULL || delays == NULL)
- {
- ERR("Out of memory.\n");
- failed = AL_TRUE;
- }
- }
-
- if(!failed)
- {
- size_t reqsize = 2*irSize*irCount + irCount;
- if(datalen < reqsize)
- {
- ERR("Unexpected end of %s data (req "SZFMT", rem "SZFMT"\n",
- filename, reqsize, datalen);
- failed = AL_TRUE;
- }
- }
-
- if(!failed)
- {
- if(channelType == CHANTYPE_LEFTONLY)
- {
- if(sampleType == SAMPLETYPE_S16)
- for(i = 0;i < irCount;i++)
- {
- for(j = 0;j < irSize;j++)
- coeffs[i*irSize + j][0] = GetLE_ALshort(&data, &datalen) / 32768.0f;
- }
- else if(sampleType == SAMPLETYPE_S24)
- for(i = 0;i < irCount;i++)
- {
- for(j = 0;j < irSize;j++)
- coeffs[i*irSize + j][0] = GetLE_ALint24(&data, &datalen) / 8388608.0f;
- }
-
- for(i = 0;i < irCount;i++)
- {
- delays[i][0] = GetLE_ALubyte(&data, &datalen);
- if(delays[i][0] > MAX_HRIR_DELAY)
- {
- ERR("Invalid delays[%d][0]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY);
- failed = AL_TRUE;
- }
- }
- }
- else if(channelType == CHANTYPE_LEFTRIGHT)
- {
- if(sampleType == SAMPLETYPE_S16)
- for(i = 0;i < irCount;i++)
- {
- for(j = 0;j < irSize;j++)
- {
- coeffs[i*irSize + j][0] = GetLE_ALshort(&data, &datalen) / 32768.0f;
- coeffs[i*irSize + j][1] = GetLE_ALshort(&data, &datalen) / 32768.0f;
- }
- }
- else if(sampleType == SAMPLETYPE_S24)
- for(i = 0;i < irCount;i++)
- {
- for(j = 0;j < irSize;j++)
- {
- coeffs[i*irSize + j][0] = GetLE_ALint24(&data, &datalen) / 8388608.0f;
- coeffs[i*irSize + j][1] = GetLE_ALint24(&data, &datalen) / 8388608.0f;
- }
- }
-
- for(i = 0;i < irCount;i++)
- {
- delays[i][0] = GetLE_ALubyte(&data, &datalen);
- if(delays[i][0] > MAX_HRIR_DELAY)
- {
- ERR("Invalid delays[%d][0]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY);
- failed = AL_TRUE;
- }
- delays[i][1] = GetLE_ALubyte(&data, &datalen);
- if(delays[i][1] > MAX_HRIR_DELAY)
- {
- ERR("Invalid delays[%d][1]: %d (%d)\n", i, delays[i][1], MAX_HRIR_DELAY);
- failed = AL_TRUE;
- }
- }
- }
- }
-
- if(!failed)
- {
- if(channelType == CHANTYPE_LEFTONLY)
- {
- /* Mirror the left ear responses to the right ear. */
- for(i = 0;i < evCount;i++)
- {
- ALushort evoffset = evOffset[i];
- ALubyte azcount = azCount[i];
- for(j = 0;j < azcount;j++)
- {
- ALsizei lidx = evoffset + j;
- ALsizei ridx = evoffset + ((azcount-j) % azcount);
- ALsizei k;
-
- for(k = 0;k < irSize;k++)
- coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0];
- delays[ridx][1] = delays[lidx][0];
- }
- }
- }
-
- Hrtf = CreateHrtfStore(rate, irSize,
- (ALfloat)distance / 1000.0f, evCount, irCount, azCount, evOffset,
- coeffs, delays, filename
- );
- }
-
- free(evOffset);
- free(coeffs);
- free(delays);
- return Hrtf;
-}
-
-
-static void AddFileEntry(vector_EnumeratedHrtf *list, const_al_string filename)
-{
- EnumeratedHrtf entry = { AL_STRING_INIT_STATIC(), NULL };
- struct HrtfEntry *loaded_entry;
- const EnumeratedHrtf *iter;
- const char *name;
- const char *ext;
- int i;
-
- /* Check if this file has already been loaded globally. */
- loaded_entry = LoadedHrtfs;
- while(loaded_entry)
- {
- if(alstr_cmp_cstr(filename, loaded_entry->filename) == 0)
- {
- /* Check if this entry has already been added to the list. */
-#define MATCH_ENTRY(i) (loaded_entry == (i)->hrtf)
- VECTOR_FIND_IF(iter, const EnumeratedHrtf, *list, MATCH_ENTRY);
-#undef MATCH_ENTRY
- if(iter != VECTOR_END(*list))
- {
- TRACE("Skipping duplicate file entry %s\n", alstr_get_cstr(filename));
- return;
- }
-
- break;
- }
- loaded_entry = loaded_entry->next;
- }
-
- if(!loaded_entry)
- {
- TRACE("Got new file \"%s\"\n", alstr_get_cstr(filename));
-
- loaded_entry = al_calloc(DEF_ALIGN,
- FAM_SIZE(struct HrtfEntry, filename, alstr_length(filename)+1)
- );
- loaded_entry->next = LoadedHrtfs;
- loaded_entry->handle = NULL;
- strcpy(loaded_entry->filename, alstr_get_cstr(filename));
- LoadedHrtfs = loaded_entry;
- }
-
- /* TODO: Get a human-readable name from the HRTF data (possibly coming in a
- * format update). */
- name = strrchr(alstr_get_cstr(filename), '/');
- if(!name) name = strrchr(alstr_get_cstr(filename), '\\');
- if(!name) name = alstr_get_cstr(filename);
- else ++name;
-
- ext = strrchr(name, '.');
-
- i = 0;
- do {
- if(!ext)
- alstr_copy_cstr(&entry.name, name);
- else
- alstr_copy_range(&entry.name, name, ext);
- if(i != 0)
- {
- char str[64];
- snprintf(str, sizeof(str), " #%d", i+1);
- alstr_append_cstr(&entry.name, str);
- }
- ++i;
-
-#define MATCH_NAME(i) (alstr_cmp(entry.name, (i)->name) == 0)
- VECTOR_FIND_IF(iter, const EnumeratedHrtf, *list, MATCH_NAME);
-#undef MATCH_NAME
- } while(iter != VECTOR_END(*list));
- entry.hrtf = loaded_entry;
-
- TRACE("Adding entry \"%s\" from file \"%s\"\n", alstr_get_cstr(entry.name),
- alstr_get_cstr(filename));
- VECTOR_PUSH_BACK(*list, entry);
-}
-
-/* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer
- * for input instead of opening the given filename.
- */
-static void AddBuiltInEntry(vector_EnumeratedHrtf *list, const_al_string filename, ALuint residx)
-{
- EnumeratedHrtf entry = { AL_STRING_INIT_STATIC(), NULL };
- struct HrtfEntry *loaded_entry;
- struct Hrtf *hrtf = NULL;
- const EnumeratedHrtf *iter;
- const char *name;
- const char *ext;
- int i;
-
- loaded_entry = LoadedHrtfs;
- while(loaded_entry)
- {
- if(alstr_cmp_cstr(filename, loaded_entry->filename) == 0)
- {
-#define MATCH_ENTRY(i) (loaded_entry == (i)->hrtf)
- VECTOR_FIND_IF(iter, const EnumeratedHrtf, *list, MATCH_ENTRY);
-#undef MATCH_ENTRY
- if(iter != VECTOR_END(*list))
- {
- TRACE("Skipping duplicate file entry %s\n", alstr_get_cstr(filename));
- return;
- }
-
- break;
- }
- loaded_entry = loaded_entry->next;
- }
-
- if(!loaded_entry)
- {
- size_t namelen = alstr_length(filename)+32;
-
- TRACE("Got new file \"%s\"\n", alstr_get_cstr(filename));
-
- loaded_entry = al_calloc(DEF_ALIGN,
- FAM_SIZE(struct HrtfEntry, filename, namelen)
- );
- loaded_entry->next = LoadedHrtfs;
- loaded_entry->handle = hrtf;
- snprintf(loaded_entry->filename, namelen, "!%u_%s",
- residx, alstr_get_cstr(filename));
- LoadedHrtfs = loaded_entry;
- }
-
- /* TODO: Get a human-readable name from the HRTF data (possibly coming in a
- * format update). */
- name = strrchr(alstr_get_cstr(filename), '/');
- if(!name) name = strrchr(alstr_get_cstr(filename), '\\');
- if(!name) name = alstr_get_cstr(filename);
- else ++name;
-
- ext = strrchr(name, '.');
-
- i = 0;
- do {
- if(!ext)
- alstr_copy_cstr(&entry.name, name);
- else
- alstr_copy_range(&entry.name, name, ext);
- if(i != 0)
- {
- char str[64];
- snprintf(str, sizeof(str), " #%d", i+1);
- alstr_append_cstr(&entry.name, str);
- }
- ++i;
-
-#define MATCH_NAME(i) (alstr_cmp(entry.name, (i)->name) == 0)
- VECTOR_FIND_IF(iter, const EnumeratedHrtf, *list, MATCH_NAME);
-#undef MATCH_NAME
- } while(iter != VECTOR_END(*list));
- entry.hrtf = loaded_entry;
-
- TRACE("Adding built-in entry \"%s\"\n", alstr_get_cstr(entry.name));
- VECTOR_PUSH_BACK(*list, entry);
-}
-
-
-#define IDR_DEFAULT_44100_MHR 1
-#define IDR_DEFAULT_48000_MHR 2
-
-#ifndef ALSOFT_EMBED_HRTF_DATA
-
-static const ALubyte *GetResource(int UNUSED(name), size_t *size)
-{
- *size = 0;
- return NULL;
-}
-
-#else
-
-#include "default-44100.mhr.h"
-#include "default-48000.mhr.h"
-
-static const ALubyte *GetResource(int name, size_t *size)
-{
- if(name == IDR_DEFAULT_44100_MHR)
- {
- *size = sizeof(hrtf_default_44100);
- return hrtf_default_44100;
- }
- if(name == IDR_DEFAULT_48000_MHR)
- {
- *size = sizeof(hrtf_default_48000);
- return hrtf_default_48000;
- }
- *size = 0;
- return NULL;
-}
-#endif
-
-vector_EnumeratedHrtf EnumerateHrtf(const_al_string devname)
-{
- vector_EnumeratedHrtf list = VECTOR_INIT_STATIC();
- const char *defaulthrtf = "";
- const char *pathlist = "";
- bool usedefaults = true;
-
- if(ConfigValueStr(alstr_get_cstr(devname), NULL, "hrtf-paths", &pathlist))
- {
- al_string pname = AL_STRING_INIT_STATIC();
- while(pathlist && *pathlist)
- {
- const char *next, *end;
-
- while(isspace(*pathlist) || *pathlist == ',')
- pathlist++;
- if(*pathlist == '\0')
- continue;
-
- next = strchr(pathlist, ',');
- if(next)
- end = next++;
- else
- {
- end = pathlist + strlen(pathlist);
- usedefaults = false;
- }
-
- while(end != pathlist && isspace(*(end-1)))
- --end;
- if(end != pathlist)
- {
- vector_al_string flist;
- size_t i;
-
- alstr_copy_range(&pname, pathlist, end);
-
- flist = SearchDataFiles(".mhr", alstr_get_cstr(pname));
- for(i = 0;i < VECTOR_SIZE(flist);i++)
- AddFileEntry(&list, VECTOR_ELEM(flist, i));
- VECTOR_FOR_EACH(al_string, flist, alstr_reset);
- VECTOR_DEINIT(flist);
- }
-
- pathlist = next;
- }
-
- alstr_reset(&pname);
- }
- else if(ConfigValueExists(alstr_get_cstr(devname), NULL, "hrtf_tables"))
- ERR("The hrtf_tables option is deprecated, please use hrtf-paths instead.\n");
-
- if(usedefaults)
- {
- al_string ename = AL_STRING_INIT_STATIC();
- vector_al_string flist;
- const ALubyte *rdata;
- size_t rsize, i;
-
- flist = SearchDataFiles(".mhr", "openal/hrtf");
- for(i = 0;i < VECTOR_SIZE(flist);i++)
- AddFileEntry(&list, VECTOR_ELEM(flist, i));
- VECTOR_FOR_EACH(al_string, flist, alstr_reset);
- VECTOR_DEINIT(flist);
-
- rdata = GetResource(IDR_DEFAULT_44100_MHR, &rsize);
- if(rdata != NULL && rsize > 0)
- {
- alstr_copy_cstr(&ename, "Built-In 44100hz");
- AddBuiltInEntry(&list, ename, IDR_DEFAULT_44100_MHR);
- }
-
- rdata = GetResource(IDR_DEFAULT_48000_MHR, &rsize);
- if(rdata != NULL && rsize > 0)
- {
- alstr_copy_cstr(&ename, "Built-In 48000hz");
- AddBuiltInEntry(&list, ename, IDR_DEFAULT_48000_MHR);
- }
- alstr_reset(&ename);
- }
-
- if(VECTOR_SIZE(list) > 1 && ConfigValueStr(alstr_get_cstr(devname), NULL, "default-hrtf", &defaulthrtf))
- {
- const EnumeratedHrtf *iter;
- /* Find the preferred HRTF and move it to the front of the list. */
-#define FIND_ENTRY(i) (alstr_cmp_cstr((i)->name, defaulthrtf) == 0)
- VECTOR_FIND_IF(iter, const EnumeratedHrtf, list, FIND_ENTRY);
-#undef FIND_ENTRY
- if(iter == VECTOR_END(list))
- WARN("Failed to find default HRTF \"%s\"\n", defaulthrtf);
- else if(iter != VECTOR_BEGIN(list))
- {
- EnumeratedHrtf entry = *iter;
- memmove(&VECTOR_ELEM(list,1), &VECTOR_ELEM(list,0),
- (iter-VECTOR_BEGIN(list))*sizeof(EnumeratedHrtf));
- VECTOR_ELEM(list,0) = entry;
- }
- }
-
- return list;
-}
-
-void FreeHrtfList(vector_EnumeratedHrtf *list)
-{
-#define CLEAR_ENTRY(i) alstr_reset(&(i)->name)
- VECTOR_FOR_EACH(EnumeratedHrtf, *list, CLEAR_ENTRY);
- VECTOR_DEINIT(*list);
-#undef CLEAR_ENTRY
-}
-
-struct Hrtf *GetLoadedHrtf(struct HrtfEntry *entry)
-{
- struct Hrtf *hrtf = NULL;
- struct FileMapping fmap;
- const ALubyte *rdata;
- const char *name;
- ALuint residx;
- size_t rsize;
- char ch;
-
- while(ATOMIC_FLAG_TEST_AND_SET(&LoadedHrtfLock, almemory_order_seq_cst))
- althrd_yield();
-
- if(entry->handle)
- {
- hrtf = entry->handle;
- Hrtf_IncRef(hrtf);
- goto done;
- }
-
- fmap.ptr = NULL;
- fmap.len = 0;
- if(sscanf(entry->filename, "!%u%c", &residx, &ch) == 2 && ch == '_')
- {
- name = strchr(entry->filename, ch)+1;
-
- TRACE("Loading %s...\n", name);
- rdata = GetResource(residx, &rsize);
- if(rdata == NULL || rsize == 0)
- {
- ERR("Could not get resource %u, %s\n", residx, name);
- goto done;
- }
- }
- else
- {
- name = entry->filename;
-
- TRACE("Loading %s...\n", entry->filename);
- fmap = MapFileToMem(entry->filename);
- if(fmap.ptr == NULL)
- {
- ERR("Could not open %s\n", entry->filename);
- goto done;
- }
-
- rdata = fmap.ptr;
- rsize = fmap.len;
- }
-
- if(rsize < sizeof(magicMarker02))
- ERR("%s data is too short ("SZFMT" bytes)\n", name, rsize);
- else if(memcmp(rdata, magicMarker02, sizeof(magicMarker02)) == 0)
- {
- TRACE("Detected data set format v2\n");
- hrtf = LoadHrtf02(rdata+sizeof(magicMarker02),
- rsize-sizeof(magicMarker02), name
- );
- }
- else if(memcmp(rdata, magicMarker01, sizeof(magicMarker01)) == 0)
- {
- TRACE("Detected data set format v1\n");
- hrtf = LoadHrtf01(rdata+sizeof(magicMarker01),
- rsize-sizeof(magicMarker01), name
- );
- }
- else if(memcmp(rdata, magicMarker00, sizeof(magicMarker00)) == 0)
- {
- TRACE("Detected data set format v0\n");
- hrtf = LoadHrtf00(rdata+sizeof(magicMarker00),
- rsize-sizeof(magicMarker00), name
- );
- }
- else
- ERR("Invalid header in %s: \"%.8s\"\n", name, (const char*)rdata);
- if(fmap.ptr)
- UnmapFileMem(&fmap);
-
- if(!hrtf)
- {
- ERR("Failed to load %s\n", name);
- goto done;
- }
- entry->handle = hrtf;
- Hrtf_IncRef(hrtf);
-
- TRACE("Loaded HRTF support for format: %s %uhz\n",
- DevFmtChannelsString(DevFmtStereo), hrtf->sampleRate);
-
-done:
- ATOMIC_FLAG_CLEAR(&LoadedHrtfLock, almemory_order_seq_cst);
- return hrtf;
-}
-
-
-void Hrtf_IncRef(struct Hrtf *hrtf)
-{
- uint ref = IncrementRef(&hrtf->ref);
- TRACEREF("%p increasing refcount to %u\n", hrtf, ref);
-}
-
-void Hrtf_DecRef(struct Hrtf *hrtf)
-{
- struct HrtfEntry *Hrtf;
- uint ref = DecrementRef(&hrtf->ref);
- TRACEREF("%p decreasing refcount to %u\n", hrtf, ref);
- if(ref == 0)
- {
- while(ATOMIC_FLAG_TEST_AND_SET(&LoadedHrtfLock, almemory_order_seq_cst))
- althrd_yield();
-
- Hrtf = LoadedHrtfs;
- while(Hrtf != NULL)
- {
- /* Need to double-check that it's still unused, as another device
- * could've reacquired this HRTF after its reference went to 0 and
- * before the lock was taken.
- */
- if(hrtf == Hrtf->handle && ReadRef(&hrtf->ref) == 0)
- {
- al_free(Hrtf->handle);
- Hrtf->handle = NULL;
- TRACE("Unloaded unused HRTF %s\n", Hrtf->filename);
- }
- Hrtf = Hrtf->next;
- }
-
- ATOMIC_FLAG_CLEAR(&LoadedHrtfLock, almemory_order_seq_cst);
- }
-}
-
-
-void FreeHrtfs(void)
-{
- struct HrtfEntry *Hrtf = LoadedHrtfs;
- LoadedHrtfs = NULL;
-
- while(Hrtf != NULL)
- {
- struct HrtfEntry *next = Hrtf->next;
- al_free(Hrtf->handle);
- al_free(Hrtf);
- Hrtf = next;
- }
-}
diff --git a/Alc/hrtf.h b/Alc/hrtf.h
deleted file mode 100644
index ab68929b..00000000
--- a/Alc/hrtf.h
+++ /dev/null
@@ -1,84 +0,0 @@
-#ifndef ALC_HRTF_H
-#define ALC_HRTF_H
-
-#include "AL/al.h"
-#include "AL/alc.h"
-
-#include "alMain.h"
-#include "alstring.h"
-#include "atomic.h"
-
-
-#define HRTF_HISTORY_BITS (6)
-#define HRTF_HISTORY_LENGTH (1<<HRTF_HISTORY_BITS)
-#define HRTF_HISTORY_MASK (HRTF_HISTORY_LENGTH-1)
-
-#define HRIR_BITS (7)
-#define HRIR_LENGTH (1<<HRIR_BITS)
-#define HRIR_MASK (HRIR_LENGTH-1)
-
-
-struct HrtfEntry;
-
-struct Hrtf {
- RefCount ref;
-
- ALuint sampleRate;
- ALsizei irSize;
-
- ALfloat distance;
- ALubyte evCount;
-
- const ALubyte *azCount;
- const ALushort *evOffset;
- const ALfloat (*coeffs)[2];
- const ALubyte (*delays)[2];
-};
-
-
-typedef struct HrtfState {
- alignas(16) ALfloat History[HRTF_HISTORY_LENGTH];
- alignas(16) ALfloat Values[HRIR_LENGTH][2];
-} HrtfState;
-
-typedef struct HrtfParams {
- alignas(16) ALfloat Coeffs[HRIR_LENGTH][2];
- ALsizei Delay[2];
- ALfloat Gain;
-} HrtfParams;
-
-typedef struct DirectHrtfState {
- /* HRTF filter state for dry buffer content */
- ALsizei Offset;
- ALsizei IrSize;
- struct {
- alignas(16) ALfloat Values[HRIR_LENGTH][2];
- alignas(16) ALfloat Coeffs[HRIR_LENGTH][2];
- } Chan[];
-} DirectHrtfState;
-
-struct AngularPoint {
- ALfloat Elev;
- ALfloat Azim;
-};
-
-
-void FreeHrtfs(void);
-
-vector_EnumeratedHrtf EnumerateHrtf(const_al_string devname);
-void FreeHrtfList(vector_EnumeratedHrtf *list);
-struct Hrtf *GetLoadedHrtf(struct HrtfEntry *entry);
-void Hrtf_IncRef(struct Hrtf *hrtf);
-void Hrtf_DecRef(struct Hrtf *hrtf);
-
-void GetHrtfCoeffs(const struct Hrtf *Hrtf, ALfloat elevation, ALfloat azimuth, ALfloat spread, ALfloat (*coeffs)[2], ALsizei *delays);
-
-/**
- * Produces HRTF filter coefficients for decoding B-Format, given a set of
- * virtual speaker positions, a matching decoding matrix, and per-order high-
- * frequency gains for the decoder. The calculated impulse responses are
- * ordered and scaled according to the matrix input.
- */
-void BuildBFormatHrtf(const struct Hrtf *Hrtf, DirectHrtfState *state, ALsizei NumChannels, const struct AngularPoint *AmbiPoints, const ALfloat (*restrict AmbiMatrix)[MAX_AMBI_COEFFS], ALsizei AmbiCount, const ALfloat *restrict AmbiOrderHFGain);
-
-#endif /* ALC_HRTF_H */
diff --git a/Alc/logging.h b/Alc/logging.h
deleted file mode 100644
index 785771c8..00000000
--- a/Alc/logging.h
+++ /dev/null
@@ -1,69 +0,0 @@
-#ifndef LOGGING_H
-#define LOGGING_H
-
-#include <stdio.h>
-
-
-#ifdef __GNUC__
-#define DECL_FORMAT(x, y, z) __attribute__((format(x, (y), (z))))
-#else
-#define DECL_FORMAT(x, y, z)
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-extern FILE *LogFile;
-
-#if defined(__GNUC__) && !defined(_WIN32)
-#define AL_PRINT(T, MSG, ...) fprintf(LogFile, "AL lib: %s %s: "MSG, T, __FUNCTION__ , ## __VA_ARGS__)
-#else
-void al_print(const char *type, const char *func, const char *fmt, ...) DECL_FORMAT(printf, 3,4);
-#define AL_PRINT(T, ...) al_print((T), __FUNCTION__, __VA_ARGS__)
-#endif
-
-#ifdef __ANDROID__
-#include <android/log.h>
-#define LOG_ANDROID(T, MSG, ...) __android_log_print(T, "openal", "AL lib: %s: "MSG, __FUNCTION__ , ## __VA_ARGS__)
-#else
-#define LOG_ANDROID(T, MSG, ...) ((void)0)
-#endif
-
-enum LogLevel {
- NoLog,
- LogError,
- LogWarning,
- LogTrace,
- LogRef
-};
-extern enum LogLevel LogLevel;
-
-#define TRACEREF(...) do { \
- if(LogLevel >= LogRef) \
- AL_PRINT("(--)", __VA_ARGS__); \
-} while(0)
-
-#define TRACE(...) do { \
- if(LogLevel >= LogTrace) \
- AL_PRINT("(II)", __VA_ARGS__); \
- LOG_ANDROID(ANDROID_LOG_DEBUG, __VA_ARGS__); \
-} while(0)
-
-#define WARN(...) do { \
- if(LogLevel >= LogWarning) \
- AL_PRINT("(WW)", __VA_ARGS__); \
- LOG_ANDROID(ANDROID_LOG_WARN, __VA_ARGS__); \
-} while(0)
-
-#define ERR(...) do { \
- if(LogLevel >= LogError) \
- AL_PRINT("(EE)", __VA_ARGS__); \
- LOG_ANDROID(ANDROID_LOG_ERROR, __VA_ARGS__); \
-} while(0)
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* LOGGING_H */
diff --git a/Alc/mastering.c b/Alc/mastering.c
deleted file mode 100644
index 6745c1c7..00000000
--- a/Alc/mastering.c
+++ /dev/null
@@ -1,543 +0,0 @@
-#include "config.h"
-
-#include <math.h>
-
-#include "mastering.h"
-#include "alu.h"
-#include "almalloc.h"
-#include "static_assert.h"
-#include "math_defs.h"
-
-
-/* Early MSVC lacks round/roundf */
-#if defined(_MSC_VER) && _MSC_VER < 1800
-static double round(double val)
-{
- if(val < 0.0)
- return ceil(val-0.5);
- return floor(val+0.5);
-}
-#define roundf(f) ((float)round((float)(f)))
-#endif
-
-
-/* These structures assume BUFFERSIZE is a power of 2. */
-static_assert((BUFFERSIZE & (BUFFERSIZE-1)) == 0, "BUFFERSIZE is not a power of 2");
-
-typedef struct SlidingHold {
- ALfloat Values[BUFFERSIZE];
- ALsizei Expiries[BUFFERSIZE];
- ALsizei LowerIndex;
- ALsizei UpperIndex;
- ALsizei Length;
-} SlidingHold;
-
-/* General topology and basic automation was based on the following paper:
- *
- * D. Giannoulis, M. Massberg and J. D. Reiss,
- * "Parameter Automation in a Dynamic Range Compressor,"
- * Journal of the Audio Engineering Society, v61 (10), Oct. 2013
- *
- * Available (along with supplemental reading) at:
- *
- * http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/
- */
-typedef struct Compressor {
- ALsizei NumChans;
- ALuint SampleRate;
-
- struct {
- ALuint Knee : 1;
- ALuint Attack : 1;
- ALuint Release : 1;
- ALuint PostGain : 1;
- ALuint Declip : 1;
- } Auto;
-
- ALsizei LookAhead;
-
- ALfloat PreGain;
- ALfloat PostGain;
-
- ALfloat Threshold;
- ALfloat Slope;
- ALfloat Knee;
-
- ALfloat Attack;
- ALfloat Release;
-
- alignas(16) ALfloat SideChain[2*BUFFERSIZE];
- alignas(16) ALfloat CrestFactor[BUFFERSIZE];
-
- SlidingHold *Hold;
- ALfloat (*Delay)[BUFFERSIZE];
- ALsizei DelayIndex;
-
- ALfloat CrestCoeff;
- ALfloat GainEstimate;
- ALfloat AdaptCoeff;
-
- ALfloat LastPeakSq;
- ALfloat LastRmsSq;
- ALfloat LastRelease;
- ALfloat LastAttack;
- ALfloat LastGainDev;
-} Compressor;
-
-
-/* This sliding hold follows the input level with an instant attack and a
- * fixed duration hold before an instant release to the next highest level.
- * It is a sliding window maximum (descending maxima) implementation based on
- * Richard Harter's ascending minima algorithm available at:
- *
- * http://www.richardhartersworld.com/cri/2001/slidingmin.html
- */
-static ALfloat UpdateSlidingHold(SlidingHold *Hold, const ALsizei i, const ALfloat in)
-{
- const ALsizei mask = BUFFERSIZE - 1;
- const ALsizei length = Hold->Length;
- ALfloat *restrict values = Hold->Values;
- ALsizei *restrict expiries = Hold->Expiries;
- ALsizei lowerIndex = Hold->LowerIndex;
- ALsizei upperIndex = Hold->UpperIndex;
-
- if(i >= expiries[upperIndex])
- upperIndex = (upperIndex + 1) & mask;
-
- if(in >= values[upperIndex])
- {
- values[upperIndex] = in;
- expiries[upperIndex] = i + length;
- lowerIndex = upperIndex;
- }
- else
- {
- do {
- do {
- if(!(in >= values[lowerIndex]))
- goto found_place;
- } while(lowerIndex--);
- lowerIndex = mask;
- } while(1);
- found_place:
-
- lowerIndex = (lowerIndex + 1) & mask;
- values[lowerIndex] = in;
- expiries[lowerIndex] = i + length;
- }
-
- Hold->LowerIndex = lowerIndex;
- Hold->UpperIndex = upperIndex;
-
- return values[upperIndex];
-}
-
-static void ShiftSlidingHold(SlidingHold *Hold, const ALsizei n)
-{
- const ALsizei lowerIndex = Hold->LowerIndex;
- ALsizei *restrict expiries = Hold->Expiries;
- ALsizei i = Hold->UpperIndex;
-
- if(lowerIndex < i)
- {
- for(;i < BUFFERSIZE;i++)
- expiries[i] -= n;
- i = 0;
- }
- for(;i < lowerIndex;i++)
- expiries[i] -= n;
-
- expiries[i] -= n;
-}
-
-/* Multichannel compression is linked via the absolute maximum of all
- * channels.
- */
-static void LinkChannels(Compressor *Comp, const ALsizei SamplesToDo, ALfloat (*restrict OutBuffer)[BUFFERSIZE])
-{
- const ALsizei index = Comp->LookAhead;
- const ALsizei numChans = Comp->NumChans;
- ALfloat *restrict sideChain = Comp->SideChain;
- ALsizei c, i;
-
- ASSUME(SamplesToDo > 0);
- ASSUME(numChans > 0);
-
- for(i = 0;i < SamplesToDo;i++)
- sideChain[index + i] = 0.0f;
-
- for(c = 0;c < numChans;c++)
- {
- ALsizei offset = index;
- for(i = 0;i < SamplesToDo;i++)
- {
- sideChain[offset] = maxf(sideChain[offset], fabsf(OutBuffer[c][i]));
- ++offset;
- }
- }
-}
-
-/* This calculates the squared crest factor of the control signal for the
- * basic automation of the attack/release times. As suggested by the paper,
- * it uses an instantaneous squared peak detector and a squared RMS detector
- * both with 200ms release times.
- */
-static void CrestDetector(Compressor *Comp, const ALsizei SamplesToDo)
-{
- const ALfloat a_crest = Comp->CrestCoeff;
- const ALsizei index = Comp->LookAhead;
- const ALfloat *restrict sideChain = Comp->SideChain;
- ALfloat *restrict crestFactor = Comp->CrestFactor;
- ALfloat y2_peak = Comp->LastPeakSq;
- ALfloat y2_rms = Comp->LastRmsSq;
- ALsizei i;
-
- ASSUME(SamplesToDo > 0);
-
- for(i = 0;i < SamplesToDo;i++)
- {
- ALfloat x_abs = sideChain[index + i];
- ALfloat x2 = maxf(0.000001f, x_abs * x_abs);
-
- y2_peak = maxf(x2, lerp(x2, y2_peak, a_crest));
- y2_rms = lerp(x2, y2_rms, a_crest);
- crestFactor[i] = y2_peak / y2_rms;
- }
-
- Comp->LastPeakSq = y2_peak;
- Comp->LastRmsSq = y2_rms;
-}
-
-/* The side-chain starts with a simple peak detector (based on the absolute
- * value of the incoming signal) and performs most of its operations in the
- * log domain.
- */
-static void PeakDetector(Compressor *Comp, const ALsizei SamplesToDo)
-{
- const ALsizei index = Comp->LookAhead;
- ALfloat *restrict sideChain = Comp->SideChain;
- ALsizei i;
-
- ASSUME(SamplesToDo > 0);
-
- for(i = 0;i < SamplesToDo;i++)
- {
- const ALuint offset = index + i;
- const ALfloat x_abs = sideChain[offset];
-
- sideChain[offset] = logf(maxf(0.000001f, x_abs));
- }
-}
-
-/* An optional hold can be used to extend the peak detector so it can more
- * solidly detect fast transients. This is best used when operating as a
- * limiter.
- */
-static void PeakHoldDetector(Compressor *Comp, const ALsizei SamplesToDo)
-{
- const ALsizei index = Comp->LookAhead;
- ALfloat *restrict sideChain = Comp->SideChain;
- SlidingHold *hold = Comp->Hold;
- ALsizei i;
-
- ASSUME(SamplesToDo > 0);
-
- for(i = 0;i < SamplesToDo;i++)
- {
- const ALsizei offset = index + i;
- const ALfloat x_abs = sideChain[offset];
- const ALfloat x_G = logf(maxf(0.000001f, x_abs));
-
- sideChain[offset] = UpdateSlidingHold(hold, i, x_G);
- }
-
- ShiftSlidingHold(hold, SamplesToDo);
-}
-
-/* This is the heart of the feed-forward compressor. It operates in the log
- * domain (to better match human hearing) and can apply some basic automation
- * to knee width, attack/release times, make-up/post gain, and clipping
- * reduction.
- */
-static void GainCompressor(Compressor *Comp, const ALsizei SamplesToDo)
-{
- const bool autoKnee = Comp->Auto.Knee;
- const bool autoAttack = Comp->Auto.Attack;
- const bool autoRelease = Comp->Auto.Release;
- const bool autoPostGain = Comp->Auto.PostGain;
- const bool autoDeclip = Comp->Auto.Declip;
- const ALsizei lookAhead = Comp->LookAhead;
- const ALfloat threshold = Comp->Threshold;
- const ALfloat slope = Comp->Slope;
- const ALfloat attack = Comp->Attack;
- const ALfloat release = Comp->Release;
- const ALfloat c_est = Comp->GainEstimate;
- const ALfloat a_adp = Comp->AdaptCoeff;
- const ALfloat *restrict crestFactor = Comp->CrestFactor;
- ALfloat *restrict sideChain = Comp->SideChain;
- ALfloat postGain = Comp->PostGain;
- ALfloat knee = Comp->Knee;
- ALfloat t_att = attack;
- ALfloat t_rel = release - attack;
- ALfloat a_att = expf(-1.0f / t_att);
- ALfloat a_rel = expf(-1.0f / t_rel);
- ALfloat y_1 = Comp->LastRelease;
- ALfloat y_L = Comp->LastAttack;
- ALfloat c_dev = Comp->LastGainDev;
- ALsizei i;
-
- ASSUME(SamplesToDo > 0);
-
- for(i = 0;i < SamplesToDo;i++)
- {
- const ALfloat y2_crest = crestFactor[i];
- const ALfloat x_G = sideChain[lookAhead + i];
- const ALfloat x_over = x_G - threshold;
- ALfloat knee_h;
- ALfloat y_G;
- ALfloat x_L;
-
- if(autoKnee)
- knee = maxf(0.0f, 2.5f * (c_dev + c_est));
- knee_h = 0.5f * knee;
-
- /* This is the gain computer. It applies a static compression curve
- * to the control signal.
- */
- if(x_over <= -knee_h)
- y_G = 0.0f;
- else if(fabsf(x_over) < knee_h)
- y_G = (x_over + knee_h) * (x_over + knee_h) / (2.0f * knee);
- else
- y_G = x_over;
-
- x_L = -slope * y_G;
-
- if(autoAttack)
- {
- t_att = 2.0f * attack / y2_crest;
- a_att = expf(-1.0f / t_att);
- }
-
- if(autoRelease)
- {
- t_rel = 2.0f * release / y2_crest - t_att;
- a_rel = expf(-1.0f / t_rel);
- }
-
- /* Gain smoothing (ballistics) is done via a smooth decoupled peak
- * detector. The attack time is subtracted from the release time
- * above to compensate for the chained operating mode.
- */
- y_1 = maxf(x_L, lerp(x_L, y_1, a_rel));
- y_L = lerp(y_1, y_L, a_att);
-
- /* Knee width and make-up gain automation make use of a smoothed
- * measurement of deviation between the control signal and estimate.
- * The estimate is also used to bias the measurement to hot-start its
- * average.
- */
- c_dev = lerp(-y_L - c_est, c_dev, a_adp);
-
- if(autoPostGain)
- {
- /* Clipping reduction is only viable when make-up gain is being
- * automated. It modifies the deviation to further attenuate the
- * control signal when clipping is detected. The adaptation
- * time is sufficiently long enough to suppress further clipping
- * at the same output level.
- */
- if(autoDeclip)
- c_dev = maxf(c_dev, sideChain[i] - y_L - threshold - c_est);
-
- postGain = -(c_dev + c_est);
- }
-
- sideChain[i] = expf(postGain - y_L);
- }
-
- Comp->LastRelease = y_1;
- Comp->LastAttack = y_L;
- Comp->LastGainDev = c_dev;
-}
-
-/* Combined with the hold time, a look-ahead delay can improve handling of
- * fast transients by allowing the envelope time to converge prior to
- * reaching the offending impulse. This is best used when operating as a
- * limiter.
- */
-static void SignalDelay(Compressor *Comp, const ALsizei SamplesToDo, ALfloat (*restrict OutBuffer)[BUFFERSIZE])
-{
- const ALsizei mask = BUFFERSIZE - 1;
- const ALsizei numChans = Comp->NumChans;
- const ALsizei indexIn = Comp->DelayIndex;
- const ALsizei indexOut = Comp->DelayIndex - Comp->LookAhead;
- ALfloat (*restrict delay)[BUFFERSIZE] = Comp->Delay;
- ALsizei c, i;
-
- ASSUME(SamplesToDo > 0);
- ASSUME(numChans > 0);
-
- for(c = 0;c < numChans;c++)
- {
- for(i = 0;i < SamplesToDo;i++)
- {
- ALfloat sig = OutBuffer[c][i];
-
- OutBuffer[c][i] = delay[c][(indexOut + i) & mask];
- delay[c][(indexIn + i) & mask] = sig;
- }
- }
-
- Comp->DelayIndex = (indexIn + SamplesToDo) & mask;
-}
-
-/* The compressor is initialized with the following settings:
- *
- * NumChans - Number of channels to process.
- * SampleRate - Sample rate to process.
- * AutoKnee - Whether to automate the knee width parameter.
- * AutoAttack - Whether to automate the attack time parameter.
- * AutoRelease - Whether to automate the release time parameter.
- * AutoPostGain - Whether to automate the make-up (post) gain parameter.
- * AutoDeclip - Whether to automate clipping reduction. Ignored when
- * not automating make-up gain.
- * LookAheadTime - Look-ahead time (in seconds).
- * HoldTime - Peak hold-time (in seconds).
- * PreGainDb - Gain applied before detection (in dB).
- * PostGainDb - Make-up gain applied after compression (in dB).
- * ThresholdDb - Triggering threshold (in dB).
- * Ratio - Compression ratio (x:1). Set to INFINITY for true
- * limiting. Ignored when automating knee width.
- * KneeDb - Knee width (in dB). Ignored when automating knee
- * width.
- * AttackTimeMin - Attack time (in seconds). Acts as a maximum when
- * automating attack time.
- * ReleaseTimeMin - Release time (in seconds). Acts as a maximum when
- * automating release time.
- */
-Compressor* CompressorInit(const ALsizei NumChans, const ALuint SampleRate,
- const ALboolean AutoKnee, const ALboolean AutoAttack,
- const ALboolean AutoRelease, const ALboolean AutoPostGain,
- const ALboolean AutoDeclip, const ALfloat LookAheadTime,
- const ALfloat HoldTime, const ALfloat PreGainDb,
- const ALfloat PostGainDb, const ALfloat ThresholdDb,
- const ALfloat Ratio, const ALfloat KneeDb,
- const ALfloat AttackTime, const ALfloat ReleaseTime)
-{
- Compressor *Comp;
- ALsizei lookAhead;
- ALsizei hold;
- size_t size;
-
- lookAhead = (ALsizei)clampf(roundf(LookAheadTime*SampleRate), 0.0f, BUFFERSIZE-1);
- hold = (ALsizei)clampf(roundf(HoldTime*SampleRate), 0.0f, BUFFERSIZE-1);
- /* The sliding hold implementation doesn't handle a length of 1. A 1-sample
- * hold is useless anyway, it would only ever give back what was just given
- * to it.
- */
- if(hold == 1)
- hold = 0;
-
- size = sizeof(*Comp);
- if(lookAhead > 0)
- {
- size += sizeof(*Comp->Delay) * NumChans;
- if(hold > 0)
- size += sizeof(*Comp->Hold);
- }
-
- Comp = al_calloc(16, size);
- Comp->NumChans = NumChans;
- Comp->SampleRate = SampleRate;
- Comp->Auto.Knee = AutoKnee;
- Comp->Auto.Attack = AutoAttack;
- Comp->Auto.Release = AutoRelease;
- Comp->Auto.PostGain = AutoPostGain;
- Comp->Auto.Declip = AutoPostGain && AutoDeclip;
- Comp->LookAhead = lookAhead;
- Comp->PreGain = powf(10.0f, PreGainDb / 20.0f);
- Comp->PostGain = PostGainDb * logf(10.0f) / 20.0f;
- Comp->Threshold = ThresholdDb * logf(10.0f) / 20.0f;
- Comp->Slope = 1.0f / maxf(1.0f, Ratio) - 1.0f;
- Comp->Knee = maxf(0.0f, KneeDb * logf(10.0f) / 20.0f);
- Comp->Attack = maxf(1.0f, AttackTime * SampleRate);
- Comp->Release = maxf(1.0f, ReleaseTime * SampleRate);
-
- /* Knee width automation actually treats the compressor as a limiter. By
- * varying the knee width, it can effectively be seen as applying
- * compression over a wide range of ratios.
- */
- if(AutoKnee)
- Comp->Slope = -1.0f;
-
- if(lookAhead > 0)
- {
- if(hold > 0)
- {
- Comp->Hold = (SlidingHold*)(Comp + 1);
- Comp->Hold->Values[0] = -HUGE_VALF;
- Comp->Hold->Expiries[0] = hold;
- Comp->Hold->Length = hold;
- Comp->Delay = (ALfloat(*)[BUFFERSIZE])(Comp->Hold + 1);
- }
- else
- {
- Comp->Delay = (ALfloat(*)[BUFFERSIZE])(Comp + 1);
- }
- }
-
- Comp->CrestCoeff = expf(-1.0f / (0.200f * SampleRate)); // 200ms
- Comp->GainEstimate = Comp->Threshold * -0.5f * Comp->Slope;
- Comp->AdaptCoeff = expf(-1.0f / (2.0f * SampleRate)); // 2s
-
- return Comp;
-}
-
-void ApplyCompression(Compressor *Comp, const ALsizei SamplesToDo, ALfloat (*restrict OutBuffer)[BUFFERSIZE])
-{
- const ALsizei numChans = Comp->NumChans;
- const ALfloat preGain = Comp->PreGain;
- ALfloat *restrict sideChain;
- ALsizei c, i;
-
- ASSUME(SamplesToDo > 0);
- ASSUME(numChans > 0);
-
- if(preGain != 1.0f)
- {
- for(c = 0;c < numChans;c++)
- {
- for(i = 0;i < SamplesToDo;i++)
- OutBuffer[c][i] *= preGain;
- }
- }
-
- LinkChannels(Comp, SamplesToDo, OutBuffer);
-
- if(Comp->Auto.Attack || Comp->Auto.Release)
- CrestDetector(Comp, SamplesToDo);
-
- if(Comp->Hold)
- PeakHoldDetector(Comp, SamplesToDo);
- else
- PeakDetector(Comp, SamplesToDo);
-
- GainCompressor(Comp, SamplesToDo);
-
- if(Comp->Delay)
- SignalDelay(Comp, SamplesToDo, OutBuffer);
-
- sideChain = Comp->SideChain;
- for(c = 0;c < numChans;c++)
- {
- for(i = 0;i < SamplesToDo;i++)
- OutBuffer[c][i] *= sideChain[i];
- }
-
- memmove(sideChain, sideChain+SamplesToDo, Comp->LookAhead*sizeof(ALfloat));
-}
-
-
-ALsizei GetCompressorLookAhead(const Compressor *Comp)
-{ return Comp->LookAhead; }
diff --git a/Alc/mastering.h b/Alc/mastering.h
deleted file mode 100644
index b68b0de1..00000000
--- a/Alc/mastering.h
+++ /dev/null
@@ -1,49 +0,0 @@
-#ifndef MASTERING_H
-#define MASTERING_H
-
-#include "AL/al.h"
-
-/* For BUFFERSIZE. */
-#include "alMain.h"
-
-struct Compressor;
-
-/* The compressor is initialized with the following settings:
- *
- * NumChans - Number of channels to process.
- * SampleRate - Sample rate to process.
- * AutoKnee - Whether to automate the knee width parameter.
- * AutoAttack - Whether to automate the attack time parameter.
- * AutoRelease - Whether to automate the release time parameter.
- * AutoPostGain - Whether to automate the make-up (post) gain parameter.
- * AutoDeclip - Whether to automate clipping reduction. Ignored when
- * not automating make-up gain.
- * LookAheadTime - Look-ahead time (in seconds).
- * HoldTime - Peak hold-time (in seconds).
- * PreGainDb - Gain applied before detection (in dB).
- * PostGainDb - Make-up gain applied after compression (in dB).
- * ThresholdDb - Triggering threshold (in dB).
- * Ratio - Compression ratio (x:1). Set to INFINIFTY for true
- * limiting. Ignored when automating knee width.
- * KneeDb - Knee width (in dB). Ignored when automating knee
- * width.
- * AttackTimeMin - Attack time (in seconds). Acts as a maximum when
- * automating attack time.
- * ReleaseTimeMin - Release time (in seconds). Acts as a maximum when
- * automating release time.
- */
-struct Compressor* CompressorInit(const ALsizei NumChans, const ALuint SampleRate,
- const ALboolean AutoKnee, const ALboolean AutoAttack,
- const ALboolean AutoRelease, const ALboolean AutoPostGain,
- const ALboolean AutoDeclip, const ALfloat LookAheadTime,
- const ALfloat HoldTime, const ALfloat PreGainDb,
- const ALfloat PostGainDb, const ALfloat ThresholdDb,
- const ALfloat Ratio, const ALfloat KneeDb,
- const ALfloat AttackTime, const ALfloat ReleaseTime);
-
-void ApplyCompression(struct Compressor *Comp, const ALsizei SamplesToDo,
- ALfloat (*restrict OutBuffer)[BUFFERSIZE]);
-
-ALsizei GetCompressorLookAhead(const struct Compressor *Comp);
-
-#endif /* MASTERING_H */
diff --git a/Alc/mixer/defs.h b/Alc/mixer/defs.h
deleted file mode 100644
index 8f6e3755..00000000
--- a/Alc/mixer/defs.h
+++ /dev/null
@@ -1,119 +0,0 @@
-#ifndef MIXER_DEFS_H
-#define MIXER_DEFS_H
-
-#include "AL/alc.h"
-#include "AL/al.h"
-#include "alMain.h"
-#include "alu.h"
-
-struct MixGains;
-
-struct MixHrtfParams;
-struct HrtfState;
-
-/* C resamplers */
-const ALfloat *Resample_copy_C(const InterpState *state, const ALfloat *restrict src, ALsizei frac, ALint increment, ALfloat *restrict dst, ALsizei dstlen);
-const ALfloat *Resample_point_C(const InterpState *state, const ALfloat *restrict src, ALsizei frac, ALint increment, ALfloat *restrict dst, ALsizei dstlen);
-const ALfloat *Resample_lerp_C(const InterpState *state, const ALfloat *restrict src, ALsizei frac, ALint increment, ALfloat *restrict dst, ALsizei dstlen);
-const ALfloat *Resample_cubic_C(const InterpState *state, const ALfloat *restrict src, ALsizei frac, ALint increment, ALfloat *restrict dst, ALsizei dstlen);
-const ALfloat *Resample_bsinc_C(const InterpState *state, const ALfloat *restrict src, ALsizei frac, ALint increment, ALfloat *restrict dst, ALsizei dstlen);
-
-
-/* C mixers */
-void MixHrtf_C(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, ALsizei OutPos,
- const ALsizei IrSize, struct MixHrtfParams *hrtfparams,
- struct HrtfState *hrtfstate, ALsizei BufferSize);
-void MixHrtfBlend_C(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, ALsizei OutPos,
- const ALsizei IrSize, const HrtfParams *oldparams,
- MixHrtfParams *newparams, HrtfState *hrtfstate,
- ALsizei BufferSize);
-void MixDirectHrtf_C(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, const ALsizei IrSize,
- const ALfloat (*restrict Coeffs)[2], ALfloat (*restrict Values)[2],
- ALsizei BufferSize);
-void Mix_C(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
- ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
- ALsizei BufferSize);
-void MixRow_C(ALfloat *OutBuffer, const ALfloat *Gains,
- const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans,
- ALsizei InPos, ALsizei BufferSize);
-
-/* SSE mixers */
-void MixHrtf_SSE(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, ALsizei OutPos,
- const ALsizei IrSize, struct MixHrtfParams *hrtfparams,
- struct HrtfState *hrtfstate, ALsizei BufferSize);
-void MixHrtfBlend_SSE(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, ALsizei OutPos,
- const ALsizei IrSize, const HrtfParams *oldparams,
- MixHrtfParams *newparams, HrtfState *hrtfstate,
- ALsizei BufferSize);
-void MixDirectHrtf_SSE(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, const ALsizei IrSize,
- const ALfloat (*restrict Coeffs)[2], ALfloat (*restrict Values)[2],
- ALsizei BufferSize);
-void Mix_SSE(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
- ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
- ALsizei BufferSize);
-void MixRow_SSE(ALfloat *OutBuffer, const ALfloat *Gains,
- const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans,
- ALsizei InPos, ALsizei BufferSize);
-
-/* SSE resamplers */
-inline void InitiatePositionArrays(ALsizei frac, ALint increment, ALsizei *restrict frac_arr, ALsizei *restrict pos_arr, ALsizei size)
-{
- ALsizei i;
-
- pos_arr[0] = 0;
- frac_arr[0] = frac;
- for(i = 1;i < size;i++)
- {
- ALint frac_tmp = frac_arr[i-1] + increment;
- pos_arr[i] = pos_arr[i-1] + (frac_tmp>>FRACTIONBITS);
- frac_arr[i] = frac_tmp&FRACTIONMASK;
- }
-}
-
-const ALfloat *Resample_lerp_SSE2(const InterpState *state, const ALfloat *restrict src,
- ALsizei frac, ALint increment, ALfloat *restrict dst,
- ALsizei numsamples);
-const ALfloat *Resample_lerp_SSE41(const InterpState *state, const ALfloat *restrict src,
- ALsizei frac, ALint increment, ALfloat *restrict dst,
- ALsizei numsamples);
-
-const ALfloat *Resample_bsinc_SSE(const InterpState *state, const ALfloat *restrict src,
- ALsizei frac, ALint increment, ALfloat *restrict dst,
- ALsizei dstlen);
-
-/* Neon mixers */
-void MixHrtf_Neon(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, ALsizei OutPos,
- const ALsizei IrSize, struct MixHrtfParams *hrtfparams,
- struct HrtfState *hrtfstate, ALsizei BufferSize);
-void MixHrtfBlend_Neon(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, ALsizei OutPos,
- const ALsizei IrSize, const HrtfParams *oldparams,
- MixHrtfParams *newparams, HrtfState *hrtfstate,
- ALsizei BufferSize);
-void MixDirectHrtf_Neon(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, const ALsizei IrSize,
- const ALfloat (*restrict Coeffs)[2], ALfloat (*restrict Values)[2],
- ALsizei BufferSize);
-void Mix_Neon(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
- ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
- ALsizei BufferSize);
-void MixRow_Neon(ALfloat *OutBuffer, const ALfloat *Gains,
- const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans,
- ALsizei InPos, ALsizei BufferSize);
-
-/* Neon resamplers */
-const ALfloat *Resample_lerp_Neon(const InterpState *state, const ALfloat *restrict src,
- ALsizei frac, ALint increment, ALfloat *restrict dst,
- ALsizei numsamples);
-const ALfloat *Resample_bsinc_Neon(const InterpState *state, const ALfloat *restrict src,
- ALsizei frac, ALint increment, ALfloat *restrict dst,
- ALsizei dstlen);
-
-#endif /* MIXER_DEFS_H */
diff --git a/Alc/mixer/hrtf_inc.c b/Alc/mixer/hrtf_inc.c
deleted file mode 100644
index 3ef22f24..00000000
--- a/Alc/mixer/hrtf_inc.c
+++ /dev/null
@@ -1,128 +0,0 @@
-#include "config.h"
-
-#include "alMain.h"
-#include "alSource.h"
-
-#include "hrtf.h"
-#include "align.h"
-#include "alu.h"
-#include "defs.h"
-
-
-static inline void ApplyCoeffs(ALsizei Offset, ALfloat (*restrict Values)[2],
- const ALsizei irSize,
- const ALfloat (*restrict Coeffs)[2],
- ALfloat left, ALfloat right);
-
-
-void MixHrtf(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, ALsizei OutPos,
- const ALsizei IrSize, MixHrtfParams *hrtfparams, HrtfState *hrtfstate,
- ALsizei BufferSize)
-{
- const ALfloat (*Coeffs)[2] = ASSUME_ALIGNED(hrtfparams->Coeffs, 16);
- const ALsizei Delay[2] = { hrtfparams->Delay[0], hrtfparams->Delay[1] };
- const ALfloat gainstep = hrtfparams->GainStep;
- const ALfloat gain = hrtfparams->Gain;
- ALfloat g, stepcount = 0.0f;
- ALfloat left, right;
- ALsizei i;
-
- ASSUME(IrSize >= 4);
- ASSUME(BufferSize > 0);
-
- LeftOut += OutPos;
- RightOut += OutPos;
- for(i = 0;i < BufferSize;i++)
- {
- hrtfstate->History[Offset&HRTF_HISTORY_MASK] = *(data++);
-
- g = gain + gainstep*stepcount;
- left = hrtfstate->History[(Offset-Delay[0])&HRTF_HISTORY_MASK]*g;
- right = hrtfstate->History[(Offset-Delay[1])&HRTF_HISTORY_MASK]*g;
-
- hrtfstate->Values[(Offset+IrSize-1)&HRIR_MASK][0] = 0.0f;
- hrtfstate->Values[(Offset+IrSize-1)&HRIR_MASK][1] = 0.0f;
-
- ApplyCoeffs(Offset, hrtfstate->Values, IrSize, Coeffs, left, right);
- *(LeftOut++) += hrtfstate->Values[Offset&HRIR_MASK][0];
- *(RightOut++) += hrtfstate->Values[Offset&HRIR_MASK][1];
-
- stepcount += 1.0f;
- Offset++;
- }
- hrtfparams->Gain = gain + gainstep*stepcount;
-}
-
-void MixHrtfBlend(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, ALsizei OutPos,
- const ALsizei IrSize, const HrtfParams *oldparams,
- MixHrtfParams *newparams, HrtfState *hrtfstate,
- ALsizei BufferSize)
-{
- const ALfloat (*OldCoeffs)[2] = ASSUME_ALIGNED(oldparams->Coeffs, 16);
- const ALsizei OldDelay[2] = { oldparams->Delay[0], oldparams->Delay[1] };
- const ALfloat oldGain = oldparams->Gain;
- const ALfloat oldGainStep = -oldGain / (ALfloat)BufferSize;
- const ALfloat (*NewCoeffs)[2] = ASSUME_ALIGNED(newparams->Coeffs, 16);
- const ALsizei NewDelay[2] = { newparams->Delay[0], newparams->Delay[1] };
- const ALfloat newGain = newparams->Gain;
- const ALfloat newGainStep = newparams->GainStep;
- ALfloat g, stepcount = 0.0f;
- ALfloat left, right;
- ALsizei i;
-
- ASSUME(IrSize >= 4);
- ASSUME(BufferSize > 0);
-
- LeftOut += OutPos;
- RightOut += OutPos;
- for(i = 0;i < BufferSize;i++)
- {
- hrtfstate->Values[(Offset+IrSize-1)&HRIR_MASK][0] = 0.0f;
- hrtfstate->Values[(Offset+IrSize-1)&HRIR_MASK][1] = 0.0f;
-
- hrtfstate->History[Offset&HRTF_HISTORY_MASK] = *(data++);
-
- g = oldGain + oldGainStep*stepcount;
- left = hrtfstate->History[(Offset-OldDelay[0])&HRTF_HISTORY_MASK]*g;
- right = hrtfstate->History[(Offset-OldDelay[1])&HRTF_HISTORY_MASK]*g;
- ApplyCoeffs(Offset, hrtfstate->Values, IrSize, OldCoeffs, left, right);
-
- g = newGain + newGainStep*stepcount;
- left = hrtfstate->History[(Offset-NewDelay[0])&HRTF_HISTORY_MASK]*g;
- right = hrtfstate->History[(Offset-NewDelay[1])&HRTF_HISTORY_MASK]*g;
- ApplyCoeffs(Offset, hrtfstate->Values, IrSize, NewCoeffs, left, right);
-
- *(LeftOut++) += hrtfstate->Values[Offset&HRIR_MASK][0];
- *(RightOut++) += hrtfstate->Values[Offset&HRIR_MASK][1];
-
- stepcount += 1.0f;
- Offset++;
- }
- newparams->Gain = newGain + newGainStep*stepcount;
-}
-
-void MixDirectHrtf(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, const ALsizei IrSize,
- const ALfloat (*restrict Coeffs)[2], ALfloat (*restrict Values)[2],
- ALsizei BufferSize)
-{
- ALfloat insample;
- ALsizei i;
-
- ASSUME(IrSize >= 4);
- ASSUME(BufferSize > 0);
-
- for(i = 0;i < BufferSize;i++)
- {
- Values[(Offset+IrSize)&HRIR_MASK][0] = 0.0f;
- Values[(Offset+IrSize)&HRIR_MASK][1] = 0.0f;
- Offset++;
-
- insample = *(data++);
- ApplyCoeffs(Offset, Values, IrSize, Coeffs, insample, insample);
- *(LeftOut++) += Values[Offset&HRIR_MASK][0];
- *(RightOut++) += Values[Offset&HRIR_MASK][1];
- }
-}
diff --git a/Alc/mixer/mixer_c.c b/Alc/mixer/mixer_c.c
deleted file mode 100644
index 14d7c669..00000000
--- a/Alc/mixer/mixer_c.c
+++ /dev/null
@@ -1,169 +0,0 @@
-#include "config.h"
-
-#include <assert.h>
-
-#include "alMain.h"
-#include "alu.h"
-#include "alSource.h"
-#include "alAuxEffectSlot.h"
-#include "defs.h"
-
-
-static inline ALfloat do_point(const InterpState* UNUSED(state), const ALfloat *restrict vals, ALsizei UNUSED(frac))
-{ return vals[0]; }
-static inline ALfloat do_lerp(const InterpState* UNUSED(state), const ALfloat *restrict vals, ALsizei frac)
-{ return lerp(vals[0], vals[1], frac * (1.0f/FRACTIONONE)); }
-static inline ALfloat do_cubic(const InterpState* UNUSED(state), const ALfloat *restrict vals, ALsizei frac)
-{ return cubic(vals[0], vals[1], vals[2], vals[3], frac * (1.0f/FRACTIONONE)); }
-static inline ALfloat do_bsinc(const InterpState *state, const ALfloat *restrict vals, ALsizei frac)
-{
- const ALfloat *fil, *scd, *phd, *spd;
- ALsizei j_f, pi;
- ALfloat pf, r;
-
- ASSUME(state->bsinc.m > 0);
-
- // Calculate the phase index and factor.
-#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
- pi = frac >> FRAC_PHASE_BITDIFF;
- pf = (frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * (1.0f/(1<<FRAC_PHASE_BITDIFF));
-#undef FRAC_PHASE_BITDIFF
-
- fil = ASSUME_ALIGNED(state->bsinc.filter + state->bsinc.m*pi*4, 16);
- scd = ASSUME_ALIGNED(fil + state->bsinc.m, 16);
- phd = ASSUME_ALIGNED(scd + state->bsinc.m, 16);
- spd = ASSUME_ALIGNED(phd + state->bsinc.m, 16);
-
- // Apply the scale and phase interpolated filter.
- r = 0.0f;
- for(j_f = 0;j_f < state->bsinc.m;j_f++)
- r += (fil[j_f] + state->bsinc.sf*scd[j_f] + pf*(phd[j_f] + state->bsinc.sf*spd[j_f])) * vals[j_f];
- return r;
-}
-
-const ALfloat *Resample_copy_C(const InterpState* UNUSED(state),
- const ALfloat *restrict src, ALsizei UNUSED(frac), ALint UNUSED(increment),
- ALfloat *restrict dst, ALsizei numsamples)
-{
-#if defined(HAVE_SSE) || defined(HAVE_NEON)
- /* Avoid copying the source data if it's aligned like the destination. */
- if((((intptr_t)src)&15) == (((intptr_t)dst)&15))
- return src;
-#endif
- memcpy(dst, src, numsamples*sizeof(ALfloat));
- return dst;
-}
-
-#define DECL_TEMPLATE(Tag, Sampler, O) \
-const ALfloat *Resample_##Tag##_C(const InterpState *state, \
- const ALfloat *restrict src, ALsizei frac, ALint increment, \
- ALfloat *restrict dst, ALsizei numsamples) \
-{ \
- const InterpState istate = *state; \
- ALsizei i; \
- \
- ASSUME(numsamples > 0); \
- \
- src -= O; \
- for(i = 0;i < numsamples;i++) \
- { \
- dst[i] = Sampler(&istate, src, frac); \
- \
- frac += increment; \
- src += frac>>FRACTIONBITS; \
- frac &= FRACTIONMASK; \
- } \
- return dst; \
-}
-
-DECL_TEMPLATE(point, do_point, 0)
-DECL_TEMPLATE(lerp, do_lerp, 0)
-DECL_TEMPLATE(cubic, do_cubic, 1)
-DECL_TEMPLATE(bsinc, do_bsinc, istate.bsinc.l)
-
-#undef DECL_TEMPLATE
-
-
-static inline void ApplyCoeffs(ALsizei Offset, ALfloat (*restrict Values)[2],
- const ALsizei IrSize,
- const ALfloat (*restrict Coeffs)[2],
- ALfloat left, ALfloat right)
-{
- ALsizei c;
- for(c = 0;c < IrSize;c++)
- {
- const ALsizei off = (Offset+c)&HRIR_MASK;
- Values[off][0] += Coeffs[c][0] * left;
- Values[off][1] += Coeffs[c][1] * right;
- }
-}
-
-#define MixHrtf MixHrtf_C
-#define MixHrtfBlend MixHrtfBlend_C
-#define MixDirectHrtf MixDirectHrtf_C
-#include "hrtf_inc.c"
-
-
-void Mix_C(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
- ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
- ALsizei BufferSize)
-{
- const ALfloat delta = (Counter > 0) ? 1.0f/(ALfloat)Counter : 0.0f;
- ALsizei c;
-
- ASSUME(OutChans > 0);
- ASSUME(BufferSize > 0);
-
- for(c = 0;c < OutChans;c++)
- {
- ALsizei pos = 0;
- ALfloat gain = CurrentGains[c];
- const ALfloat diff = TargetGains[c] - gain;
-
- if(fabsf(diff) > FLT_EPSILON)
- {
- ALsizei minsize = mini(BufferSize, Counter);
- const ALfloat step = diff * delta;
- ALfloat step_count = 0.0f;
- for(;pos < minsize;pos++)
- {
- OutBuffer[c][OutPos+pos] += data[pos] * (gain + step*step_count);
- step_count += 1.0f;
- }
- if(pos == Counter)
- gain = TargetGains[c];
- else
- gain += step*step_count;
- CurrentGains[c] = gain;
- }
-
- if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
- continue;
- for(;pos < BufferSize;pos++)
- OutBuffer[c][OutPos+pos] += data[pos]*gain;
- }
-}
-
-/* Basically the inverse of the above. Rather than one input going to multiple
- * outputs (each with its own gain), it's multiple inputs (each with its own
- * gain) going to one output. This applies one row (vs one column) of a matrix
- * transform. And as the matrices are more or less static once set up, no
- * stepping is necessary.
- */
-void MixRow_C(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans, ALsizei InPos, ALsizei BufferSize)
-{
- ALsizei c, i;
-
- ASSUME(InChans > 0);
- ASSUME(BufferSize > 0);
-
- for(c = 0;c < InChans;c++)
- {
- const ALfloat gain = Gains[c];
- if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
- continue;
-
- for(i = 0;i < BufferSize;i++)
- OutBuffer[i] += data[c][InPos+i] * gain;
- }
-}
diff --git a/Alc/mixer/mixer_neon.c b/Alc/mixer/mixer_neon.c
deleted file mode 100644
index 9bf5521a..00000000
--- a/Alc/mixer/mixer_neon.c
+++ /dev/null
@@ -1,283 +0,0 @@
-#include "config.h"
-
-#include <arm_neon.h>
-
-#include "AL/al.h"
-#include "AL/alc.h"
-#include "alMain.h"
-#include "alu.h"
-#include "hrtf.h"
-#include "defs.h"
-
-
-const ALfloat *Resample_lerp_Neon(const InterpState* UNUSED(state),
- const ALfloat *restrict src, ALsizei frac, ALint increment,
- ALfloat *restrict dst, ALsizei numsamples)
-{
- const int32x4_t increment4 = vdupq_n_s32(increment*4);
- const float32x4_t fracOne4 = vdupq_n_f32(1.0f/FRACTIONONE);
- const int32x4_t fracMask4 = vdupq_n_s32(FRACTIONMASK);
- alignas(16) ALsizei pos_[4], frac_[4];
- int32x4_t pos4, frac4;
- ALsizei todo, pos, i;
-
- ASSUME(numsamples > 0);
-
- InitiatePositionArrays(frac, increment, frac_, pos_, 4);
- frac4 = vld1q_s32(frac_);
- pos4 = vld1q_s32(pos_);
-
- todo = numsamples & ~3;
- for(i = 0;i < todo;i += 4)
- {
- const int pos0 = vgetq_lane_s32(pos4, 0);
- const int pos1 = vgetq_lane_s32(pos4, 1);
- const int pos2 = vgetq_lane_s32(pos4, 2);
- const int pos3 = vgetq_lane_s32(pos4, 3);
- const float32x4_t val1 = (float32x4_t){src[pos0], src[pos1], src[pos2], src[pos3]};
- const float32x4_t val2 = (float32x4_t){src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1]};
-
- /* val1 + (val2-val1)*mu */
- const float32x4_t r0 = vsubq_f32(val2, val1);
- const float32x4_t mu = vmulq_f32(vcvtq_f32_s32(frac4), fracOne4);
- const float32x4_t out = vmlaq_f32(val1, mu, r0);
-
- vst1q_f32(&dst[i], out);
-
- frac4 = vaddq_s32(frac4, increment4);
- pos4 = vaddq_s32(pos4, vshrq_n_s32(frac4, FRACTIONBITS));
- frac4 = vandq_s32(frac4, fracMask4);
- }
-
- /* NOTE: These four elements represent the position *after* the last four
- * samples, so the lowest element is the next position to resample.
- */
- pos = vgetq_lane_s32(pos4, 0);
- frac = vgetq_lane_s32(frac4, 0);
-
- for(;i < numsamples;++i)
- {
- dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE));
-
- frac += increment;
- pos += frac>>FRACTIONBITS;
- frac &= FRACTIONMASK;
- }
- return dst;
-}
-
-const ALfloat *Resample_bsinc_Neon(const InterpState *state,
- const ALfloat *restrict src, ALsizei frac, ALint increment,
- ALfloat *restrict dst, ALsizei dstlen)
-{
- const ALfloat *const filter = state->bsinc.filter;
- const float32x4_t sf4 = vdupq_n_f32(state->bsinc.sf);
- const ALsizei m = state->bsinc.m;
- const float32x4_t *fil, *scd, *phd, *spd;
- ALsizei pi, i, j, offset;
- float32x4_t r4;
- ALfloat pf;
-
- ASSUME(m > 0);
- ASSUME(dstlen > 0);
-
- src -= state->bsinc.l;
- for(i = 0;i < dstlen;i++)
- {
- // Calculate the phase index and factor.
-#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
- pi = frac >> FRAC_PHASE_BITDIFF;
- pf = (frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * (1.0f/(1<<FRAC_PHASE_BITDIFF));
-#undef FRAC_PHASE_BITDIFF
-
- offset = m*pi*4;
- fil = ASSUME_ALIGNED(filter + offset, 16); offset += m;
- scd = ASSUME_ALIGNED(filter + offset, 16); offset += m;
- phd = ASSUME_ALIGNED(filter + offset, 16); offset += m;
- spd = ASSUME_ALIGNED(filter + offset, 16);
-
- // Apply the scale and phase interpolated filter.
- r4 = vdupq_n_f32(0.0f);
- {
- const ALsizei count = m >> 2;
- const float32x4_t pf4 = vdupq_n_f32(pf);
-
- ASSUME(count > 0);
-
- for(j = 0;j < count;j++)
- {
- /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */
- const float32x4_t f4 = vmlaq_f32(
- vmlaq_f32(fil[j], sf4, scd[j]),
- pf4, vmlaq_f32(phd[j], sf4, spd[j])
- );
- /* r += f*src */
- r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j*4]));
- }
- }
- r4 = vaddq_f32(r4, vcombine_f32(vrev64_f32(vget_high_f32(r4)),
- vrev64_f32(vget_low_f32(r4))));
- dst[i] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
-
- frac += increment;
- src += frac>>FRACTIONBITS;
- frac &= FRACTIONMASK;
- }
- return dst;
-}
-
-
-static inline void ApplyCoeffs(ALsizei Offset, ALfloat (*restrict Values)[2],
- const ALsizei IrSize,
- const ALfloat (*restrict Coeffs)[2],
- ALfloat left, ALfloat right)
-{
- ALsizei c;
- float32x4_t leftright4;
- {
- float32x2_t leftright2 = vdup_n_f32(0.0);
- leftright2 = vset_lane_f32(left, leftright2, 0);
- leftright2 = vset_lane_f32(right, leftright2, 1);
- leftright4 = vcombine_f32(leftright2, leftright2);
- }
- Values = ASSUME_ALIGNED(Values, 16);
- Coeffs = ASSUME_ALIGNED(Coeffs, 16);
- for(c = 0;c < IrSize;c += 2)
- {
- const ALsizei o0 = (Offset+c)&HRIR_MASK;
- const ALsizei o1 = (o0+1)&HRIR_MASK;
- float32x4_t vals = vcombine_f32(vld1_f32((float32_t*)&Values[o0][0]),
- vld1_f32((float32_t*)&Values[o1][0]));
- float32x4_t coefs = vld1q_f32((float32_t*)&Coeffs[c][0]);
-
- vals = vmlaq_f32(vals, coefs, leftright4);
-
- vst1_f32((float32_t*)&Values[o0][0], vget_low_f32(vals));
- vst1_f32((float32_t*)&Values[o1][0], vget_high_f32(vals));
- }
-}
-
-#define MixHrtf MixHrtf_Neon
-#define MixHrtfBlend MixHrtfBlend_Neon
-#define MixDirectHrtf MixDirectHrtf_Neon
-#include "hrtf_inc.c"
-
-
-void Mix_Neon(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
- ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
- ALsizei BufferSize)
-{
- const ALfloat delta = (Counter > 0) ? 1.0f/(ALfloat)Counter : 0.0f;
- ALsizei c;
-
- ASSUME(OutChans > 0);
- ASSUME(BufferSize > 0);
- data = ASSUME_ALIGNED(data, 16);
- OutBuffer = ASSUME_ALIGNED(OutBuffer, 16);
-
- for(c = 0;c < OutChans;c++)
- {
- ALsizei pos = 0;
- ALfloat gain = CurrentGains[c];
- const ALfloat diff = TargetGains[c] - gain;
-
- if(fabsf(diff) > FLT_EPSILON)
- {
- ALsizei minsize = mini(BufferSize, Counter);
- const ALfloat step = diff * delta;
- ALfloat step_count = 0.0f;
- /* Mix with applying gain steps in aligned multiples of 4. */
- if(LIKELY(minsize > 3))
- {
- const float32x4_t four4 = vdupq_n_f32(4.0f);
- const float32x4_t step4 = vdupq_n_f32(step);
- const float32x4_t gain4 = vdupq_n_f32(gain);
- float32x4_t step_count4 = vsetq_lane_f32(0.0f,
- vsetq_lane_f32(1.0f,
- vsetq_lane_f32(2.0f,
- vsetq_lane_f32(3.0f, vdupq_n_f32(0.0f), 3),
- 2), 1), 0
- );
- ALsizei todo = minsize >> 2;
-
- do {
- const float32x4_t val4 = vld1q_f32(&data[pos]);
- float32x4_t dry4 = vld1q_f32(&OutBuffer[c][OutPos+pos]);
- dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4));
- step_count4 = vaddq_f32(step_count4, four4);
- vst1q_f32(&OutBuffer[c][OutPos+pos], dry4);
- pos += 4;
- } while(--todo);
- /* NOTE: step_count4 now represents the next four counts after
- * the last four mixed samples, so the lowest element
- * represents the next step count to apply.
- */
- step_count = vgetq_lane_f32(step_count4, 0);
- }
- /* Mix with applying left over gain steps that aren't aligned multiples of 4. */
- for(;pos < minsize;pos++)
- {
- OutBuffer[c][OutPos+pos] += data[pos]*(gain + step*step_count);
- step_count += 1.0f;
- }
- if(pos == Counter)
- gain = TargetGains[c];
- else
- gain += step*step_count;
- CurrentGains[c] = gain;
-
- /* Mix until pos is aligned with 4 or the mix is done. */
- minsize = mini(BufferSize, (pos+3)&~3);
- for(;pos < minsize;pos++)
- OutBuffer[c][OutPos+pos] += data[pos]*gain;
- }
-
- if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
- continue;
- if(LIKELY(BufferSize-pos > 3))
- {
- ALsizei todo = (BufferSize-pos) >> 2;
- const float32x4_t gain4 = vdupq_n_f32(gain);
- do {
- const float32x4_t val4 = vld1q_f32(&data[pos]);
- float32x4_t dry4 = vld1q_f32(&OutBuffer[c][OutPos+pos]);
- dry4 = vmlaq_f32(dry4, val4, gain4);
- vst1q_f32(&OutBuffer[c][OutPos+pos], dry4);
- pos += 4;
- } while(--todo);
- }
- for(;pos < BufferSize;pos++)
- OutBuffer[c][OutPos+pos] += data[pos]*gain;
- }
-}
-
-void MixRow_Neon(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans, ALsizei InPos, ALsizei BufferSize)
-{
- ALsizei c;
-
- ASSUME(InChans > 0);
- ASSUME(BufferSize > 0);
-
- for(c = 0;c < InChans;c++)
- {
- ALsizei pos = 0;
- const ALfloat gain = Gains[c];
- if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
- continue;
-
- if(LIKELY(BufferSize > 3))
- {
- ALsizei todo = BufferSize >> 2;
- float32x4_t gain4 = vdupq_n_f32(gain);
- do {
- const float32x4_t val4 = vld1q_f32(&data[c][InPos+pos]);
- float32x4_t dry4 = vld1q_f32(&OutBuffer[pos]);
- dry4 = vmlaq_f32(dry4, val4, gain4);
- vst1q_f32(&OutBuffer[pos], dry4);
- pos += 4;
- } while(--todo);
- }
- for(;pos < BufferSize;pos++)
- OutBuffer[pos] += data[c][InPos+pos]*gain;
- }
-}
diff --git a/Alc/mixer/mixer_sse.c b/Alc/mixer/mixer_sse.c
deleted file mode 100644
index 725a5ebc..00000000
--- a/Alc/mixer/mixer_sse.c
+++ /dev/null
@@ -1,250 +0,0 @@
-#include "config.h"
-
-#include <xmmintrin.h>
-
-#include "AL/al.h"
-#include "AL/alc.h"
-#include "alMain.h"
-#include "alu.h"
-
-#include "alSource.h"
-#include "alAuxEffectSlot.h"
-#include "defs.h"
-
-
-const ALfloat *Resample_bsinc_SSE(const InterpState *state, const ALfloat *restrict src,
- ALsizei frac, ALint increment, ALfloat *restrict dst,
- ALsizei dstlen)
-{
- const ALfloat *const filter = state->bsinc.filter;
- const __m128 sf4 = _mm_set1_ps(state->bsinc.sf);
- const ALsizei m = state->bsinc.m;
- const __m128 *fil, *scd, *phd, *spd;
- ALsizei pi, i, j, offset;
- ALfloat pf;
- __m128 r4;
-
- ASSUME(m > 0);
- ASSUME(dstlen > 0);
-
- src -= state->bsinc.l;
- for(i = 0;i < dstlen;i++)
- {
- // Calculate the phase index and factor.
-#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
- pi = frac >> FRAC_PHASE_BITDIFF;
- pf = (frac & ((1<<FRAC_PHASE_BITDIFF)-1)) * (1.0f/(1<<FRAC_PHASE_BITDIFF));
-#undef FRAC_PHASE_BITDIFF
-
- offset = m*pi*4;
- fil = (const __m128*)ASSUME_ALIGNED(filter + offset, 16); offset += m;
- scd = (const __m128*)ASSUME_ALIGNED(filter + offset, 16); offset += m;
- phd = (const __m128*)ASSUME_ALIGNED(filter + offset, 16); offset += m;
- spd = (const __m128*)ASSUME_ALIGNED(filter + offset, 16);
-
- // Apply the scale and phase interpolated filter.
- r4 = _mm_setzero_ps();
- {
- const ALsizei count = m >> 2;
- const __m128 pf4 = _mm_set1_ps(pf);
-
- ASSUME(count > 0);
-
-#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z))
- for(j = 0;j < count;j++)
- {
- /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */
- const __m128 f4 = MLA4(
- MLA4(fil[j], sf4, scd[j]),
- pf4, MLA4(phd[j], sf4, spd[j])
- );
- /* r += f*src */
- r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j*4]));
- }
-#undef MLA4
- }
- r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
- r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
- dst[i] = _mm_cvtss_f32(r4);
-
- frac += increment;
- src += frac>>FRACTIONBITS;
- frac &= FRACTIONMASK;
- }
- return dst;
-}
-
-
-static inline void ApplyCoeffs(ALsizei Offset, ALfloat (*restrict Values)[2],
- const ALsizei IrSize,
- const ALfloat (*restrict Coeffs)[2],
- ALfloat left, ALfloat right)
-{
- const __m128 lrlr = _mm_setr_ps(left, right, left, right);
- __m128 vals = _mm_setzero_ps();
- __m128 coeffs;
- ALsizei i;
-
- Values = ASSUME_ALIGNED(Values, 16);
- Coeffs = ASSUME_ALIGNED(Coeffs, 16);
- if((Offset&1))
- {
- const ALsizei o0 = Offset&HRIR_MASK;
- const ALsizei o1 = (Offset+IrSize-1)&HRIR_MASK;
- __m128 imp0, imp1;
-
- coeffs = _mm_load_ps(&Coeffs[0][0]);
- vals = _mm_loadl_pi(vals, (__m64*)&Values[o0][0]);
- imp0 = _mm_mul_ps(lrlr, coeffs);
- vals = _mm_add_ps(imp0, vals);
- _mm_storel_pi((__m64*)&Values[o0][0], vals);
- for(i = 1;i < IrSize-1;i += 2)
- {
- const ALsizei o2 = (Offset+i)&HRIR_MASK;
-
- coeffs = _mm_load_ps(&Coeffs[i+1][0]);
- vals = _mm_load_ps(&Values[o2][0]);
- imp1 = _mm_mul_ps(lrlr, coeffs);
- imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2));
- vals = _mm_add_ps(imp0, vals);
- _mm_store_ps(&Values[o2][0], vals);
- imp0 = imp1;
- }
- vals = _mm_loadl_pi(vals, (__m64*)&Values[o1][0]);
- imp0 = _mm_movehl_ps(imp0, imp0);
- vals = _mm_add_ps(imp0, vals);
- _mm_storel_pi((__m64*)&Values[o1][0], vals);
- }
- else
- {
- for(i = 0;i < IrSize;i += 2)
- {
- const ALsizei o = (Offset + i)&HRIR_MASK;
-
- coeffs = _mm_load_ps(&Coeffs[i][0]);
- vals = _mm_load_ps(&Values[o][0]);
- vals = _mm_add_ps(vals, _mm_mul_ps(lrlr, coeffs));
- _mm_store_ps(&Values[o][0], vals);
- }
- }
-}
-
-#define MixHrtf MixHrtf_SSE
-#define MixHrtfBlend MixHrtfBlend_SSE
-#define MixDirectHrtf MixDirectHrtf_SSE
-#include "hrtf_inc.c"
-
-
-void Mix_SSE(const ALfloat *data, ALsizei OutChans, ALfloat (*restrict OutBuffer)[BUFFERSIZE],
- ALfloat *CurrentGains, const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
- ALsizei BufferSize)
-{
- const ALfloat delta = (Counter > 0) ? 1.0f/(ALfloat)Counter : 0.0f;
- ALsizei c;
-
- ASSUME(OutChans > 0);
- ASSUME(BufferSize > 0);
-
- for(c = 0;c < OutChans;c++)
- {
- ALsizei pos = 0;
- ALfloat gain = CurrentGains[c];
- const ALfloat diff = TargetGains[c] - gain;
-
- if(fabsf(diff) > FLT_EPSILON)
- {
- ALsizei minsize = mini(BufferSize, Counter);
- const ALfloat step = diff * delta;
- ALfloat step_count = 0.0f;
- /* Mix with applying gain steps in aligned multiples of 4. */
- if(LIKELY(minsize > 3))
- {
- const __m128 four4 = _mm_set1_ps(4.0f);
- const __m128 step4 = _mm_set1_ps(step);
- const __m128 gain4 = _mm_set1_ps(gain);
- __m128 step_count4 = _mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f);
- ALsizei todo = minsize >> 2;
- do {
- const __m128 val4 = _mm_load_ps(&data[pos]);
- __m128 dry4 = _mm_load_ps(&OutBuffer[c][OutPos+pos]);
-#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z))
- /* dry += val * (gain + step*step_count) */
- dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4));
-#undef MLA4
- _mm_store_ps(&OutBuffer[c][OutPos+pos], dry4);
- step_count4 = _mm_add_ps(step_count4, four4);
- pos += 4;
- } while(--todo);
- /* NOTE: step_count4 now represents the next four counts after
- * the last four mixed samples, so the lowest element
- * represents the next step count to apply.
- */
- step_count = _mm_cvtss_f32(step_count4);
- }
- /* Mix with applying left over gain steps that aren't aligned multiples of 4. */
- for(;pos < minsize;pos++)
- {
- OutBuffer[c][OutPos+pos] += data[pos]*(gain + step*step_count);
- step_count += 1.0f;
- }
- if(pos == Counter)
- gain = TargetGains[c];
- else
- gain += step*step_count;
- CurrentGains[c] = gain;
-
- /* Mix until pos is aligned with 4 or the mix is done. */
- minsize = mini(BufferSize, (pos+3)&~3);
- for(;pos < minsize;pos++)
- OutBuffer[c][OutPos+pos] += data[pos]*gain;
- }
-
- if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
- continue;
- if(LIKELY(BufferSize-pos > 3))
- {
- ALsizei todo = (BufferSize-pos) >> 2;
- const __m128 gain4 = _mm_set1_ps(gain);
- do {
- const __m128 val4 = _mm_load_ps(&data[pos]);
- __m128 dry4 = _mm_load_ps(&OutBuffer[c][OutPos+pos]);
- dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4));
- _mm_store_ps(&OutBuffer[c][OutPos+pos], dry4);
- pos += 4;
- } while(--todo);
- }
- for(;pos < BufferSize;pos++)
- OutBuffer[c][OutPos+pos] += data[pos]*gain;
- }
-}
-
-void MixRow_SSE(ALfloat *OutBuffer, const ALfloat *Gains, const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans, ALsizei InPos, ALsizei BufferSize)
-{
- ALsizei c;
-
- ASSUME(InChans > 0);
- ASSUME(BufferSize > 0);
-
- for(c = 0;c < InChans;c++)
- {
- ALsizei pos = 0;
- const ALfloat gain = Gains[c];
- if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD))
- continue;
-
- if(LIKELY(BufferSize > 3))
- {
- ALsizei todo = BufferSize >> 2;
- const __m128 gain4 = _mm_set1_ps(gain);
- do {
- const __m128 val4 = _mm_load_ps(&data[c][InPos+pos]);
- __m128 dry4 = _mm_load_ps(&OutBuffer[pos]);
- dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4));
- _mm_store_ps(&OutBuffer[pos], dry4);
- pos += 4;
- } while(--todo);
- }
- for(;pos < BufferSize;pos++)
- OutBuffer[pos] += data[c][InPos+pos]*gain;
- }
-}
diff --git a/Alc/mixer/mixer_sse2.c b/Alc/mixer/mixer_sse2.c
deleted file mode 100644
index 9cbaeb0a..00000000
--- a/Alc/mixer/mixer_sse2.c
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2014 by Timothy Arceri <[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 <xmmintrin.h>
-#include <emmintrin.h>
-
-#include "alu.h"
-#include "defs.h"
-
-
-const ALfloat *Resample_lerp_SSE2(const InterpState* UNUSED(state),
- const ALfloat *restrict src, ALsizei frac, ALint increment,
- ALfloat *restrict dst, ALsizei numsamples)
-{
- const __m128i increment4 = _mm_set1_epi32(increment*4);
- const __m128 fracOne4 = _mm_set1_ps(1.0f/FRACTIONONE);
- const __m128i fracMask4 = _mm_set1_epi32(FRACTIONMASK);
- alignas(16) ALsizei pos_[4], frac_[4];
- __m128i frac4, pos4;
- ALsizei todo, pos, i;
-
- ASSUME(numsamples > 0);
-
- InitiatePositionArrays(frac, increment, frac_, pos_, 4);
- frac4 = _mm_setr_epi32(frac_[0], frac_[1], frac_[2], frac_[3]);
- pos4 = _mm_setr_epi32(pos_[0], pos_[1], pos_[2], pos_[3]);
-
- todo = numsamples & ~3;
- for(i = 0;i < todo;i += 4)
- {
- const int pos0 = _mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(0, 0, 0, 0)));
- const int pos1 = _mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(1, 1, 1, 1)));
- const int pos2 = _mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(2, 2, 2, 2)));
- const int pos3 = _mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(3, 3, 3, 3)));
- const __m128 val1 = _mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ]);
- const __m128 val2 = _mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1]);
-
- /* val1 + (val2-val1)*mu */
- const __m128 r0 = _mm_sub_ps(val2, val1);
- const __m128 mu = _mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4);
- const __m128 out = _mm_add_ps(val1, _mm_mul_ps(mu, r0));
-
- _mm_store_ps(&dst[i], out);
-
- frac4 = _mm_add_epi32(frac4, increment4);
- pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS));
- frac4 = _mm_and_si128(frac4, fracMask4);
- }
-
- /* NOTE: These four elements represent the position *after* the last four
- * samples, so the lowest element is the next position to resample.
- */
- pos = _mm_cvtsi128_si32(pos4);
- frac = _mm_cvtsi128_si32(frac4);
-
- for(;i < numsamples;++i)
- {
- dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE));
-
- frac += increment;
- pos += frac>>FRACTIONBITS;
- frac &= FRACTIONMASK;
- }
- return dst;
-}
diff --git a/Alc/mixer/mixer_sse41.c b/Alc/mixer/mixer_sse41.c
deleted file mode 100644
index e92a3dd0..00000000
--- a/Alc/mixer/mixer_sse41.c
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 2014 by Timothy Arceri <[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 <xmmintrin.h>
-#include <emmintrin.h>
-#include <smmintrin.h>
-
-#include "alu.h"
-#include "defs.h"
-
-
-const ALfloat *Resample_lerp_SSE41(const InterpState* UNUSED(state),
- const ALfloat *restrict src, ALsizei frac, ALint increment,
- ALfloat *restrict dst, ALsizei numsamples)
-{
- const __m128i increment4 = _mm_set1_epi32(increment*4);
- const __m128 fracOne4 = _mm_set1_ps(1.0f/FRACTIONONE);
- const __m128i fracMask4 = _mm_set1_epi32(FRACTIONMASK);
- alignas(16) ALsizei pos_[4], frac_[4];
- __m128i frac4, pos4;
- ALsizei todo, pos, i;
-
- ASSUME(numsamples > 0);
-
- InitiatePositionArrays(frac, increment, frac_, pos_, 4);
- frac4 = _mm_setr_epi32(frac_[0], frac_[1], frac_[2], frac_[3]);
- pos4 = _mm_setr_epi32(pos_[0], pos_[1], pos_[2], pos_[3]);
-
- todo = numsamples & ~3;
- for(i = 0;i < todo;i += 4)
- {
- const int pos0 = _mm_extract_epi32(pos4, 0);
- const int pos1 = _mm_extract_epi32(pos4, 1);
- const int pos2 = _mm_extract_epi32(pos4, 2);
- const int pos3 = _mm_extract_epi32(pos4, 3);
- const __m128 val1 = _mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ]);
- const __m128 val2 = _mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1]);
-
- /* val1 + (val2-val1)*mu */
- const __m128 r0 = _mm_sub_ps(val2, val1);
- const __m128 mu = _mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4);
- const __m128 out = _mm_add_ps(val1, _mm_mul_ps(mu, r0));
-
- _mm_store_ps(&dst[i], out);
-
- frac4 = _mm_add_epi32(frac4, increment4);
- pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS));
- frac4 = _mm_and_si128(frac4, fracMask4);
- }
-
- /* NOTE: These four elements represent the position *after* the last four
- * samples, so the lowest element is the next position to resample.
- */
- pos = _mm_cvtsi128_si32(pos4);
- frac = _mm_cvtsi128_si32(frac4);
-
- for(;i < numsamples;++i)
- {
- dst[i] = lerp(src[pos], src[pos+1], frac * (1.0f/FRACTIONONE));
-
- frac += increment;
- pos += frac>>FRACTIONBITS;
- frac &= FRACTIONMASK;
- }
- return dst;
-}
diff --git a/Alc/mixvoice.c b/Alc/mixvoice.c
deleted file mode 100644
index d019b898..00000000
--- a/Alc/mixvoice.c
+++ /dev/null
@@ -1,762 +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 "AL/al.h"
-#include "AL/alc.h"
-#include "alSource.h"
-#include "alBuffer.h"
-#include "alListener.h"
-#include "alAuxEffectSlot.h"
-#include "sample_cvt.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "ringbuffer.h"
-
-#include "cpu_caps.h"
-#include "mixer/defs.h"
-
-
-static_assert((INT_MAX>>FRACTIONBITS)/MAX_PITCH > BUFFERSIZE,
- "MAX_PITCH and/or BUFFERSIZE are too large for FRACTIONBITS!");
-
-extern inline void InitiatePositionArrays(ALsizei frac, ALint increment, ALsizei *restrict frac_arr, ALsizei *restrict pos_arr, ALsizei size);
-
-
-/* BSinc24 requires up to 23 extra samples before the current position, and 24 after. */
-static_assert(MAX_RESAMPLE_PADDING >= 24, "MAX_RESAMPLE_PADDING must be at least 24!");
-
-
-enum Resampler ResamplerDefault = LinearResampler;
-
-MixerFunc MixSamples = Mix_C;
-RowMixerFunc MixRowSamples = MixRow_C;
-static HrtfMixerFunc MixHrtfSamples = MixHrtf_C;
-static HrtfMixerBlendFunc MixHrtfBlendSamples = MixHrtfBlend_C;
-
-static MixerFunc SelectMixer(void)
-{
-#ifdef HAVE_NEON
- if((CPUCapFlags&CPU_CAP_NEON))
- return Mix_Neon;
-#endif
-#ifdef HAVE_SSE
- if((CPUCapFlags&CPU_CAP_SSE))
- return Mix_SSE;
-#endif
- return Mix_C;
-}
-
-static RowMixerFunc SelectRowMixer(void)
-{
-#ifdef HAVE_NEON
- if((CPUCapFlags&CPU_CAP_NEON))
- return MixRow_Neon;
-#endif
-#ifdef HAVE_SSE
- if((CPUCapFlags&CPU_CAP_SSE))
- return MixRow_SSE;
-#endif
- return MixRow_C;
-}
-
-static inline HrtfMixerFunc SelectHrtfMixer(void)
-{
-#ifdef HAVE_NEON
- if((CPUCapFlags&CPU_CAP_NEON))
- return MixHrtf_Neon;
-#endif
-#ifdef HAVE_SSE
- if((CPUCapFlags&CPU_CAP_SSE))
- return MixHrtf_SSE;
-#endif
- return MixHrtf_C;
-}
-
-static inline HrtfMixerBlendFunc SelectHrtfBlendMixer(void)
-{
-#ifdef HAVE_NEON
- if((CPUCapFlags&CPU_CAP_NEON))
- return MixHrtfBlend_Neon;
-#endif
-#ifdef HAVE_SSE
- if((CPUCapFlags&CPU_CAP_SSE))
- return MixHrtfBlend_SSE;
-#endif
- return MixHrtfBlend_C;
-}
-
-ResamplerFunc SelectResampler(enum Resampler resampler)
-{
- switch(resampler)
- {
- case PointResampler:
- return Resample_point_C;
- case LinearResampler:
-#ifdef HAVE_NEON
- if((CPUCapFlags&CPU_CAP_NEON))
- return Resample_lerp_Neon;
-#endif
-#ifdef HAVE_SSE4_1
- if((CPUCapFlags&CPU_CAP_SSE4_1))
- return Resample_lerp_SSE41;
-#endif
-#ifdef HAVE_SSE2
- if((CPUCapFlags&CPU_CAP_SSE2))
- return Resample_lerp_SSE2;
-#endif
- return Resample_lerp_C;
- case FIR4Resampler:
- return Resample_cubic_C;
- case BSinc12Resampler:
- case BSinc24Resampler:
-#ifdef HAVE_NEON
- if((CPUCapFlags&CPU_CAP_NEON))
- return Resample_bsinc_Neon;
-#endif
-#ifdef HAVE_SSE
- if((CPUCapFlags&CPU_CAP_SSE))
- return Resample_bsinc_SSE;
-#endif
- return Resample_bsinc_C;
- }
-
- return Resample_point_C;
-}
-
-
-void aluInitMixer(void)
-{
- const char *str;
-
- if(ConfigValueStr(NULL, NULL, "resampler", &str))
- {
- if(strcasecmp(str, "point") == 0 || strcasecmp(str, "none") == 0)
- ResamplerDefault = PointResampler;
- else if(strcasecmp(str, "linear") == 0)
- ResamplerDefault = LinearResampler;
- else if(strcasecmp(str, "cubic") == 0)
- ResamplerDefault = FIR4Resampler;
- else if(strcasecmp(str, "bsinc12") == 0)
- ResamplerDefault = BSinc12Resampler;
- else if(strcasecmp(str, "bsinc24") == 0)
- ResamplerDefault = BSinc24Resampler;
- else if(strcasecmp(str, "bsinc") == 0)
- {
- WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str);
- ResamplerDefault = BSinc12Resampler;
- }
- else if(strcasecmp(str, "sinc4") == 0 || strcasecmp(str, "sinc8") == 0)
- {
- WARN("Resampler option \"%s\" is deprecated, using cubic\n", str);
- ResamplerDefault = FIR4Resampler;
- }
- else
- {
- char *end;
- long n = strtol(str, &end, 0);
- if(*end == '\0' && (n == PointResampler || n == LinearResampler || n == FIR4Resampler))
- ResamplerDefault = n;
- else
- WARN("Invalid resampler: %s\n", str);
- }
- }
-
- MixHrtfBlendSamples = SelectHrtfBlendMixer();
- MixHrtfSamples = SelectHrtfMixer();
- MixSamples = SelectMixer();
- MixRowSamples = SelectRowMixer();
-}
-
-
-static void SendAsyncEvent(ALCcontext *context, ALuint enumtype, ALenum type,
- ALuint objid, ALuint param, const char *msg)
-{
- AsyncEvent evt = ASYNC_EVENT(enumtype);
- evt.u.user.type = type;
- evt.u.user.id = objid;
- evt.u.user.param = param;
- strcpy(evt.u.user.msg, msg);
- if(ll_ringbuffer_write(context->AsyncEvents, (const char*)&evt, 1) == 1)
- alsem_post(&context->EventSem);
-}
-
-
-static inline ALfloat Sample_ALubyte(ALubyte val)
-{ return (val-128) * (1.0f/128.0f); }
-
-static inline ALfloat Sample_ALshort(ALshort val)
-{ return val * (1.0f/32768.0f); }
-
-static inline ALfloat Sample_ALfloat(ALfloat val)
-{ return val; }
-
-static inline ALfloat Sample_ALdouble(ALdouble val)
-{ return (ALfloat)val; }
-
-typedef ALubyte ALmulaw;
-static inline ALfloat Sample_ALmulaw(ALmulaw val)
-{ return muLawDecompressionTable[val] * (1.0f/32768.0f); }
-
-typedef ALubyte ALalaw;
-static inline ALfloat Sample_ALalaw(ALalaw val)
-{ return aLawDecompressionTable[val] * (1.0f/32768.0f); }
-
-#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(ALubyte)
-DECL_TEMPLATE(ALshort)
-DECL_TEMPLATE(ALfloat)
-DECL_TEMPLATE(ALdouble)
-DECL_TEMPLATE(ALmulaw)
-DECL_TEMPLATE(ALalaw)
-
-#undef DECL_TEMPLATE
-
-static void LoadSamples(ALfloat *restrict dst, const ALvoid *restrict src, ALint srcstep,
- enum FmtType srctype, ALsizei samples)
-{
-#define HANDLE_FMT(ET, ST) case ET: Load_##ST(dst, src, srcstep, samples); break
- switch(srctype)
- {
- HANDLE_FMT(FmtUByte, ALubyte);
- HANDLE_FMT(FmtShort, ALshort);
- HANDLE_FMT(FmtFloat, ALfloat);
- HANDLE_FMT(FmtDouble, ALdouble);
- HANDLE_FMT(FmtMulaw, ALmulaw);
- HANDLE_FMT(FmtAlaw, ALalaw);
- }
-#undef HANDLE_FMT
-}
-
-
-static const ALfloat *DoFilters(BiquadFilter *lpfilter, BiquadFilter *hpfilter,
- ALfloat *restrict dst, const ALfloat *restrict src,
- ALsizei numsamples, enum ActiveFilters type)
-{
- ALsizei i;
- switch(type)
- {
- case AF_None:
- BiquadFilter_passthru(lpfilter, numsamples);
- BiquadFilter_passthru(hpfilter, numsamples);
- break;
-
- case AF_LowPass:
- BiquadFilter_process(lpfilter, dst, src, numsamples);
- BiquadFilter_passthru(hpfilter, numsamples);
- return dst;
- case AF_HighPass:
- BiquadFilter_passthru(lpfilter, numsamples);
- BiquadFilter_process(hpfilter, dst, src, numsamples);
- return dst;
-
- case AF_BandPass:
- for(i = 0;i < numsamples;)
- {
- ALfloat temp[256];
- ALsizei todo = mini(256, numsamples-i);
-
- BiquadFilter_process(lpfilter, temp, src+i, todo);
- BiquadFilter_process(hpfilter, dst+i, temp, todo);
- i += todo;
- }
- return dst;
- }
- return src;
-}
-
-
-/* This function uses these device temp buffers. */
-#define SOURCE_DATA_BUF 0
-#define RESAMPLED_BUF 1
-#define FILTERED_BUF 2
-#define NFC_DATA_BUF 3
-ALboolean MixSource(ALvoice *voice, ALuint SourceID, ALCcontext *Context, ALsizei SamplesToDo)
-{
- ALCdevice *Device = Context->Device;
- ALbufferlistitem *BufferListItem;
- ALbufferlistitem *BufferLoopItem;
- ALsizei NumChannels, SampleSize;
- ALbitfieldSOFT enabledevt;
- ALsizei buffers_done = 0;
- ResamplerFunc Resample;
- ALsizei DataPosInt;
- ALsizei DataPosFrac;
- ALint64 DataSize64;
- ALint increment;
- ALsizei Counter;
- ALsizei OutPos;
- ALsizei IrSize;
- bool isplaying;
- bool firstpass;
- bool isstatic;
- ALsizei chan;
- ALsizei send;
-
- /* Get source info */
- isplaying = true; /* Will only be called while playing. */
- isstatic = !!(voice->Flags&VOICE_IS_STATIC);
- DataPosInt = ATOMIC_LOAD(&voice->position, almemory_order_acquire);
- DataPosFrac = ATOMIC_LOAD(&voice->position_fraction, almemory_order_relaxed);
- BufferListItem = ATOMIC_LOAD(&voice->current_buffer, almemory_order_relaxed);
- BufferLoopItem = ATOMIC_LOAD(&voice->loop_buffer, almemory_order_relaxed);
- NumChannels = voice->NumChannels;
- SampleSize = voice->SampleSize;
- increment = voice->Step;
-
- IrSize = (Device->HrtfHandle ? Device->HrtfHandle->irSize : 0);
-
- Resample = ((increment == FRACTIONONE && DataPosFrac == 0) ?
- Resample_copy_C : voice->Resampler);
-
- Counter = (voice->Flags&VOICE_IS_FADING) ? SamplesToDo : 0;
- firstpass = true;
- OutPos = 0;
-
- do {
- ALsizei SrcBufferSize, DstBufferSize;
-
- /* Figure out how many buffer samples will be needed */
- DataSize64 = SamplesToDo-OutPos;
- DataSize64 *= increment;
- DataSize64 += DataPosFrac+FRACTIONMASK;
- DataSize64 >>= FRACTIONBITS;
- DataSize64 += MAX_RESAMPLE_PADDING*2;
- SrcBufferSize = (ALsizei)mini64(DataSize64, BUFFERSIZE);
-
- /* Figure out how many samples we can actually mix from this. */
- DataSize64 = SrcBufferSize;
- DataSize64 -= MAX_RESAMPLE_PADDING*2;
- DataSize64 <<= FRACTIONBITS;
- DataSize64 -= DataPosFrac;
- DstBufferSize = (ALsizei)mini64((DataSize64+(increment-1)) / increment,
- SamplesToDo - OutPos);
-
- /* Some mixers like having a multiple of 4, so try to give that unless
- * this is the last update. */
- if(DstBufferSize < SamplesToDo-OutPos)
- DstBufferSize &= ~3;
-
- /* It's impossible to have a buffer list item with no entries. */
- assert(BufferListItem->num_buffers > 0);
-
- for(chan = 0;chan < NumChannels;chan++)
- {
- const ALfloat *ResampledData;
- ALfloat *SrcData = Device->TempBuffer[SOURCE_DATA_BUF];
- ALsizei FilledAmt;
-
- /* Load the previous samples into the source data first, and clear the rest. */
- memcpy(SrcData, voice->PrevSamples[chan], MAX_RESAMPLE_PADDING*sizeof(ALfloat));
- memset(SrcData+MAX_RESAMPLE_PADDING, 0, (BUFFERSIZE-MAX_RESAMPLE_PADDING)*
- sizeof(ALfloat));
- FilledAmt = MAX_RESAMPLE_PADDING;
-
- if(isstatic)
- {
- /* TODO: For static sources, loop points are taken from the
- * first buffer (should be adjusted by any buffer offset, to
- * possibly be added later).
- */
- const ALbuffer *Buffer0 = BufferListItem->buffers[0];
- const ALsizei LoopStart = Buffer0->LoopStart;
- const ALsizei LoopEnd = Buffer0->LoopEnd;
- const ALsizei LoopSize = LoopEnd - LoopStart;
-
- /* If current pos is beyond the loop range, do not loop */
- if(!BufferLoopItem || DataPosInt >= LoopEnd)
- {
- ALsizei SizeToDo = SrcBufferSize - FilledAmt;
- ALsizei CompLen = 0;
- ALsizei i;
-
- BufferLoopItem = NULL;
-
- for(i = 0;i < BufferListItem->num_buffers;i++)
- {
- const ALbuffer *buffer = BufferListItem->buffers[i];
- const ALubyte *Data = buffer->data;
- ALsizei DataSize;
-
- if(DataPosInt >= buffer->SampleLen)
- continue;
-
- /* Load what's left to play from the buffer */
- DataSize = mini(SizeToDo, buffer->SampleLen - DataPosInt);
- CompLen = maxi(CompLen, DataSize);
-
- LoadSamples(&SrcData[FilledAmt],
- &Data[(DataPosInt*NumChannels + chan)*SampleSize],
- NumChannels, buffer->FmtType, DataSize
- );
- }
- FilledAmt += CompLen;
- }
- else
- {
- ALsizei SizeToDo = mini(SrcBufferSize - FilledAmt, LoopEnd - DataPosInt);
- ALsizei CompLen = 0;
- ALsizei i;
-
- for(i = 0;i < BufferListItem->num_buffers;i++)
- {
- const ALbuffer *buffer = BufferListItem->buffers[i];
- const ALubyte *Data = buffer->data;
- ALsizei DataSize;
-
- if(DataPosInt >= buffer->SampleLen)
- continue;
-
- /* Load what's left of this loop iteration */
- DataSize = mini(SizeToDo, buffer->SampleLen - DataPosInt);
- CompLen = maxi(CompLen, DataSize);
-
- LoadSamples(&SrcData[FilledAmt],
- &Data[(DataPosInt*NumChannels + chan)*SampleSize],
- NumChannels, buffer->FmtType, DataSize
- );
- }
- FilledAmt += CompLen;
-
- while(SrcBufferSize > FilledAmt)
- {
- const ALsizei SizeToDo = mini(SrcBufferSize - FilledAmt, LoopSize);
-
- CompLen = 0;
- for(i = 0;i < BufferListItem->num_buffers;i++)
- {
- const ALbuffer *buffer = BufferListItem->buffers[i];
- const ALubyte *Data = buffer->data;
- ALsizei DataSize;
-
- if(LoopStart >= buffer->SampleLen)
- continue;
-
- DataSize = mini(SizeToDo, buffer->SampleLen - LoopStart);
- CompLen = maxi(CompLen, DataSize);
-
- LoadSamples(&SrcData[FilledAmt],
- &Data[(LoopStart*NumChannels + chan)*SampleSize],
- NumChannels, buffer->FmtType, DataSize
- );
- }
- FilledAmt += CompLen;
- }
- }
- }
- else
- {
- /* Crawl the buffer queue to fill in the temp buffer */
- ALbufferlistitem *tmpiter = BufferListItem;
- ALsizei pos = DataPosInt;
-
- while(tmpiter && SrcBufferSize > FilledAmt)
- {
- ALsizei SizeToDo = SrcBufferSize - FilledAmt;
- ALsizei CompLen = 0;
- ALsizei i;
-
- for(i = 0;i < tmpiter->num_buffers;i++)
- {
- const ALbuffer *ALBuffer = tmpiter->buffers[i];
- ALsizei DataSize = ALBuffer ? ALBuffer->SampleLen : 0;
-
- if(DataSize > pos)
- {
- const ALubyte *Data = ALBuffer->data;
- Data += (pos*NumChannels + chan)*SampleSize;
-
- DataSize = mini(SizeToDo, DataSize - pos);
- CompLen = maxi(CompLen, DataSize);
-
- LoadSamples(&SrcData[FilledAmt], Data, NumChannels,
- ALBuffer->FmtType, DataSize);
- }
- }
- if(UNLIKELY(!CompLen))
- pos -= tmpiter->max_samples;
- else
- {
- FilledAmt += CompLen;
- if(SrcBufferSize <= FilledAmt)
- break;
- pos = 0;
- }
- tmpiter = ATOMIC_LOAD(&tmpiter->next, almemory_order_acquire);
- if(!tmpiter) tmpiter = BufferLoopItem;
- }
- }
-
- /* Store the last source samples used for next time. */
- memcpy(voice->PrevSamples[chan],
- &SrcData[(increment*DstBufferSize + DataPosFrac)>>FRACTIONBITS],
- MAX_RESAMPLE_PADDING*sizeof(ALfloat)
- );
-
- /* Now resample, then filter and mix to the appropriate outputs. */
- ResampledData = Resample(&voice->ResampleState,
- &SrcData[MAX_RESAMPLE_PADDING], DataPosFrac, increment,
- Device->TempBuffer[RESAMPLED_BUF], DstBufferSize
- );
- {
- DirectParams *parms = &voice->Direct.Params[chan];
- const ALfloat *samples;
-
- samples = DoFilters(
- &parms->LowPass, &parms->HighPass, Device->TempBuffer[FILTERED_BUF],
- ResampledData, DstBufferSize, voice->Direct.FilterType
- );
- if(!(voice->Flags&VOICE_HAS_HRTF))
- {
- if(!Counter)
- memcpy(parms->Gains.Current, parms->Gains.Target,
- sizeof(parms->Gains.Current));
- if(!(voice->Flags&VOICE_HAS_NFC))
- MixSamples(samples, voice->Direct.Channels, voice->Direct.Buffer,
- parms->Gains.Current, parms->Gains.Target, Counter, OutPos,
- DstBufferSize
- );
- else
- {
- ALfloat *nfcsamples = Device->TempBuffer[NFC_DATA_BUF];
- ALsizei chanoffset = 0;
-
- MixSamples(samples,
- voice->Direct.ChannelsPerOrder[0], voice->Direct.Buffer,
- parms->Gains.Current, parms->Gains.Target, Counter, OutPos,
- DstBufferSize
- );
- chanoffset += voice->Direct.ChannelsPerOrder[0];
-#define APPLY_NFC_MIX(order) \
- if(voice->Direct.ChannelsPerOrder[order] > 0) \
- { \
- NfcFilterProcess##order(&parms->NFCtrlFilter, nfcsamples, samples, \
- DstBufferSize); \
- MixSamples(nfcsamples, voice->Direct.ChannelsPerOrder[order], \
- voice->Direct.Buffer+chanoffset, parms->Gains.Current+chanoffset, \
- parms->Gains.Target+chanoffset, Counter, OutPos, DstBufferSize \
- ); \
- chanoffset += voice->Direct.ChannelsPerOrder[order]; \
- }
- APPLY_NFC_MIX(1)
- APPLY_NFC_MIX(2)
- APPLY_NFC_MIX(3)
-#undef APPLY_NFC_MIX
- }
- }
- else
- {
- MixHrtfParams hrtfparams;
- ALsizei fademix = 0;
- int lidx, ridx;
-
- lidx = GetChannelIdxByName(&Device->RealOut, FrontLeft);
- ridx = GetChannelIdxByName(&Device->RealOut, FrontRight);
- assert(lidx != -1 && ridx != -1);
-
- if(!Counter)
- {
- /* No fading, just overwrite the old HRTF params. */
- parms->Hrtf.Old = parms->Hrtf.Target;
- }
- else if(!(parms->Hrtf.Old.Gain > GAIN_SILENCE_THRESHOLD))
- {
- /* The old HRTF params are silent, so overwrite the old
- * coefficients with the new, and reset the old gain to
- * 0. The future mix will then fade from silence.
- */
- parms->Hrtf.Old = parms->Hrtf.Target;
- parms->Hrtf.Old.Gain = 0.0f;
- }
- else if(firstpass)
- {
- ALfloat gain;
-
- /* Fade between the coefficients over 128 samples. */
- fademix = mini(DstBufferSize, 128);
-
- /* The new coefficients need to fade in completely
- * since they're replacing the old ones. To keep the
- * gain fading consistent, interpolate between the old
- * and new target gains given how much of the fade time
- * this mix handles.
- */
- gain = lerp(parms->Hrtf.Old.Gain, parms->Hrtf.Target.Gain,
- minf(1.0f, (ALfloat)fademix/Counter));
- hrtfparams.Coeffs = parms->Hrtf.Target.Coeffs;
- hrtfparams.Delay[0] = parms->Hrtf.Target.Delay[0];
- hrtfparams.Delay[1] = parms->Hrtf.Target.Delay[1];
- hrtfparams.Gain = 0.0f;
- hrtfparams.GainStep = gain / (ALfloat)fademix;
-
- MixHrtfBlendSamples(
- voice->Direct.Buffer[lidx], voice->Direct.Buffer[ridx],
- samples, voice->Offset, OutPos, IrSize, &parms->Hrtf.Old,
- &hrtfparams, &parms->Hrtf.State, fademix
- );
- /* Update the old parameters with the result. */
- parms->Hrtf.Old = parms->Hrtf.Target;
- if(fademix < Counter)
- parms->Hrtf.Old.Gain = hrtfparams.Gain;
- }
-
- if(fademix < DstBufferSize)
- {
- ALsizei todo = DstBufferSize - fademix;
- ALfloat gain = parms->Hrtf.Target.Gain;
-
- /* Interpolate the target gain if the gain fading lasts
- * longer than this mix.
- */
- if(Counter > DstBufferSize)
- gain = lerp(parms->Hrtf.Old.Gain, gain,
- (ALfloat)todo/(Counter-fademix));
-
- hrtfparams.Coeffs = parms->Hrtf.Target.Coeffs;
- hrtfparams.Delay[0] = parms->Hrtf.Target.Delay[0];
- hrtfparams.Delay[1] = parms->Hrtf.Target.Delay[1];
- hrtfparams.Gain = parms->Hrtf.Old.Gain;
- hrtfparams.GainStep = (gain - parms->Hrtf.Old.Gain) / (ALfloat)todo;
- MixHrtfSamples(
- voice->Direct.Buffer[lidx], voice->Direct.Buffer[ridx],
- samples+fademix, voice->Offset+fademix, OutPos+fademix, IrSize,
- &hrtfparams, &parms->Hrtf.State, todo
- );
- /* Store the interpolated gain or the final target gain
- * depending if the fade is done.
- */
- if(DstBufferSize < Counter)
- parms->Hrtf.Old.Gain = gain;
- else
- parms->Hrtf.Old.Gain = parms->Hrtf.Target.Gain;
- }
- }
- }
-
- for(send = 0;send < Device->NumAuxSends;send++)
- {
- SendParams *parms = &voice->Send[send].Params[chan];
- const ALfloat *samples;
-
- if(!voice->Send[send].Buffer)
- continue;
-
- samples = DoFilters(
- &parms->LowPass, &parms->HighPass, Device->TempBuffer[FILTERED_BUF],
- ResampledData, DstBufferSize, voice->Send[send].FilterType
- );
-
- if(!Counter)
- memcpy(parms->Gains.Current, parms->Gains.Target,
- sizeof(parms->Gains.Current));
- MixSamples(samples, voice->Send[send].Channels, voice->Send[send].Buffer,
- parms->Gains.Current, parms->Gains.Target, Counter, OutPos, DstBufferSize
- );
- }
- }
- /* Update positions */
- DataPosFrac += increment*DstBufferSize;
- DataPosInt += DataPosFrac>>FRACTIONBITS;
- DataPosFrac &= FRACTIONMASK;
-
- OutPos += DstBufferSize;
- voice->Offset += DstBufferSize;
- Counter = maxi(DstBufferSize, Counter) - DstBufferSize;
- firstpass = false;
-
- if(isstatic)
- {
- if(BufferLoopItem)
- {
- /* Handle looping static source */
- const ALbuffer *Buffer = BufferListItem->buffers[0];
- ALsizei LoopStart = Buffer->LoopStart;
- ALsizei LoopEnd = Buffer->LoopEnd;
- if(DataPosInt >= LoopEnd)
- {
- assert(LoopEnd > LoopStart);
- DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart;
- }
- }
- else
- {
- /* Handle non-looping static source */
- if(DataPosInt >= BufferListItem->max_samples)
- {
- isplaying = false;
- BufferListItem = NULL;
- DataPosInt = 0;
- DataPosFrac = 0;
- break;
- }
- }
- }
- else while(1)
- {
- /* Handle streaming source */
- if(BufferListItem->max_samples > DataPosInt)
- break;
-
- DataPosInt -= BufferListItem->max_samples;
-
- buffers_done += BufferListItem->num_buffers;
- BufferListItem = ATOMIC_LOAD(&BufferListItem->next, almemory_order_relaxed);
- if(!BufferListItem && !(BufferListItem=BufferLoopItem))
- {
- isplaying = false;
- DataPosInt = 0;
- DataPosFrac = 0;
- break;
- }
- }
- } while(isplaying && OutPos < SamplesToDo);
-
- voice->Flags |= VOICE_IS_FADING;
-
- /* Update source info */
- ATOMIC_STORE(&voice->position, DataPosInt, almemory_order_relaxed);
- ATOMIC_STORE(&voice->position_fraction, DataPosFrac, almemory_order_relaxed);
- ATOMIC_STORE(&voice->current_buffer, BufferListItem, almemory_order_release);
-
- /* Send any events now, after the position/buffer info was updated. */
- enabledevt = ATOMIC_LOAD(&Context->EnabledEvts, almemory_order_acquire);
- if(buffers_done > 0 && (enabledevt&EventType_BufferCompleted))
- SendAsyncEvent(Context, EventType_BufferCompleted,
- AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, SourceID, buffers_done, "Buffer completed"
- );
-
- return isplaying;
-}
diff --git a/Alc/panning.c b/Alc/panning.c
deleted file mode 100644
index 2c0f3bf2..00000000
--- a/Alc/panning.c
+++ /dev/null
@@ -1,1239 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 1999-2010 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 "alAuxEffectSlot.h"
-#include "alu.h"
-#include "alconfig.h"
-#include "bool.h"
-#include "ambdec.h"
-#include "bformatdec.h"
-#include "filters/splitter.h"
-#include "uhjfilter.h"
-#include "bs2b.h"
-
-
-extern inline void CalcDirectionCoeffs(const ALfloat dir[3], ALfloat spread, ALfloat coeffs[MAX_AMBI_COEFFS]);
-extern inline void CalcAngleCoeffs(ALfloat azimuth, ALfloat elevation, ALfloat spread, ALfloat coeffs[MAX_AMBI_COEFFS]);
-extern inline float ScaleAzimuthFront(float azimuth, float scale);
-extern inline void ComputePanGains(const MixParams *dry, const ALfloat*restrict coeffs, ALfloat ingain, ALfloat gains[MAX_OUTPUT_CHANNELS]);
-
-
-static const ALsizei FuMa2ACN[MAX_AMBI_COEFFS] = {
- 0, /* W */
- 3, /* X */
- 1, /* Y */
- 2, /* Z */
- 6, /* R */
- 7, /* S */
- 5, /* T */
- 8, /* U */
- 4, /* V */
- 12, /* K */
- 13, /* L */
- 11, /* M */
- 14, /* N */
- 10, /* O */
- 15, /* P */
- 9, /* Q */
-};
-static const ALsizei ACN2ACN[MAX_AMBI_COEFFS] = {
- 0, 1, 2, 3, 4, 5, 6, 7,
- 8, 9, 10, 11, 12, 13, 14, 15
-};
-
-
-void CalcAmbiCoeffs(const ALfloat y, const ALfloat z, const ALfloat x, const ALfloat spread,
- ALfloat coeffs[MAX_AMBI_COEFFS])
-{
- /* Zeroth-order */
- coeffs[0] = 1.0f; /* ACN 0 = 1 */
- /* First-order */
- coeffs[1] = SQRTF_3 * y; /* ACN 1 = sqrt(3) * Y */
- coeffs[2] = SQRTF_3 * z; /* ACN 2 = sqrt(3) * Z */
- coeffs[3] = SQRTF_3 * x; /* ACN 3 = sqrt(3) * X */
- /* Second-order */
- coeffs[4] = 3.872983346f * x * y; /* ACN 4 = sqrt(15) * X * Y */
- coeffs[5] = 3.872983346f * y * z; /* ACN 5 = sqrt(15) * Y * Z */
- coeffs[6] = 1.118033989f * (3.0f*z*z - 1.0f); /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */
- coeffs[7] = 3.872983346f * x * z; /* ACN 7 = sqrt(15) * X * Z */
- coeffs[8] = 1.936491673f * (x*x - y*y); /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */
- /* Third-order */
- coeffs[9] = 2.091650066f * y * (3.0f*x*x - y*y); /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */
- coeffs[10] = 10.246950766f * z * x * y; /* ACN 10 = sqrt(105) * Z * X * Y */
- coeffs[11] = 1.620185175f * y * (5.0f*z*z - 1.0f); /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */
- coeffs[12] = 1.322875656f * z * (5.0f*z*z - 3.0f); /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */
- coeffs[13] = 1.620185175f * x * (5.0f*z*z - 1.0f); /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */
- coeffs[14] = 5.123475383f * z * (x*x - y*y); /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */
- coeffs[15] = 2.091650066f * x * (x*x - 3.0f*y*y); /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */
-
- if(spread > 0.0f)
- {
- /* Implement the spread by using a spherical source that subtends the
- * angle spread. See:
- * http://www.ppsloan.org/publications/StupidSH36.pdf - Appendix A3
- *
- * When adjusted for N3D normalization instead of SN3D, these
- * calculations are:
- *
- * ZH0 = -sqrt(pi) * (-1+ca);
- * ZH1 = 0.5*sqrt(pi) * sa*sa;
- * ZH2 = -0.5*sqrt(pi) * ca*(-1+ca)*(ca+1);
- * ZH3 = -0.125*sqrt(pi) * (-1+ca)*(ca+1)*(5*ca*ca - 1);
- * ZH4 = -0.125*sqrt(pi) * ca*(-1+ca)*(ca+1)*(7*ca*ca - 3);
- * ZH5 = -0.0625*sqrt(pi) * (-1+ca)*(ca+1)*(21*ca*ca*ca*ca - 14*ca*ca + 1);
- *
- * The gain of the source is compensated for size, so that the
- * loundness doesn't depend on the spread. Thus:
- *
- * ZH0 = 1.0f;
- * ZH1 = 0.5f * (ca+1.0f);
- * ZH2 = 0.5f * (ca+1.0f)*ca;
- * ZH3 = 0.125f * (ca+1.0f)*(5.0f*ca*ca - 1.0f);
- * ZH4 = 0.125f * (ca+1.0f)*(7.0f*ca*ca - 3.0f)*ca;
- * ZH5 = 0.0625f * (ca+1.0f)*(21.0f*ca*ca*ca*ca - 14.0f*ca*ca + 1.0f);
- */
- ALfloat ca = cosf(spread * 0.5f);
- /* Increase the source volume by up to +3dB for a full spread. */
- ALfloat scale = sqrtf(1.0f + spread/F_TAU);
-
- ALfloat ZH0_norm = scale;
- ALfloat ZH1_norm = 0.5f * (ca+1.f) * scale;
- ALfloat ZH2_norm = 0.5f * (ca+1.f)*ca * scale;
- ALfloat ZH3_norm = 0.125f * (ca+1.f)*(5.f*ca*ca-1.f) * scale;
-
- /* Zeroth-order */
- coeffs[0] *= ZH0_norm;
- /* First-order */
- coeffs[1] *= ZH1_norm;
- coeffs[2] *= ZH1_norm;
- coeffs[3] *= ZH1_norm;
- /* Second-order */
- coeffs[4] *= ZH2_norm;
- coeffs[5] *= ZH2_norm;
- coeffs[6] *= ZH2_norm;
- coeffs[7] *= ZH2_norm;
- coeffs[8] *= ZH2_norm;
- /* Third-order */
- coeffs[9] *= ZH3_norm;
- coeffs[10] *= ZH3_norm;
- coeffs[11] *= ZH3_norm;
- coeffs[12] *= ZH3_norm;
- coeffs[13] *= ZH3_norm;
- coeffs[14] *= ZH3_norm;
- coeffs[15] *= ZH3_norm;
- }
-}
-
-
-void ComputePanningGainsMC(const ChannelConfig *chancoeffs, ALsizei numchans, ALsizei numcoeffs, const ALfloat*restrict coeffs, ALfloat ingain, ALfloat gains[MAX_OUTPUT_CHANNELS])
-{
- ALsizei i, j;
-
- for(i = 0;i < numchans;i++)
- {
- float gain = 0.0f;
- for(j = 0;j < numcoeffs;j++)
- gain += chancoeffs[i][j]*coeffs[j];
- gains[i] = clampf(gain, 0.0f, 1.0f) * ingain;
- }
- for(;i < MAX_OUTPUT_CHANNELS;i++)
- gains[i] = 0.0f;
-}
-
-void ComputePanningGainsBF(const BFChannelConfig *chanmap, ALsizei numchans, const ALfloat*restrict coeffs, ALfloat ingain, ALfloat gains[MAX_OUTPUT_CHANNELS])
-{
- ALsizei i;
-
- for(i = 0;i < numchans;i++)
- gains[i] = chanmap[i].Scale * coeffs[chanmap[i].Index] * ingain;
- for(;i < MAX_OUTPUT_CHANNELS;i++)
- gains[i] = 0.0f;
-}
-
-
-static inline const char *GetLabelFromChannel(enum Channel channel)
-{
- switch(channel)
- {
- case FrontLeft: return "front-left";
- case FrontRight: return "front-right";
- case FrontCenter: return "front-center";
- case LFE: return "lfe";
- case BackLeft: return "back-left";
- case BackRight: return "back-right";
- case BackCenter: return "back-center";
- case SideLeft: return "side-left";
- case SideRight: return "side-right";
-
- case UpperFrontLeft: return "upper-front-left";
- case UpperFrontRight: return "upper-front-right";
- case UpperBackLeft: return "upper-back-left";
- case UpperBackRight: return "upper-back-right";
- case LowerFrontLeft: return "lower-front-left";
- case LowerFrontRight: return "lower-front-right";
- case LowerBackLeft: return "lower-back-left";
- case LowerBackRight: return "lower-back-right";
-
- case Aux0: return "aux-0";
- case Aux1: return "aux-1";
- case Aux2: return "aux-2";
- case Aux3: return "aux-3";
- case Aux4: return "aux-4";
- case Aux5: return "aux-5";
- case Aux6: return "aux-6";
- case Aux7: return "aux-7";
- case Aux8: return "aux-8";
- case Aux9: return "aux-9";
- case Aux10: return "aux-10";
- case Aux11: return "aux-11";
- case Aux12: return "aux-12";
- case Aux13: return "aux-13";
- case Aux14: return "aux-14";
- case Aux15: return "aux-15";
-
- case InvalidChannel: break;
- }
- return "(unknown)";
-}
-
-
-typedef struct ChannelMap {
- enum Channel ChanName;
- ChannelConfig Config;
-} ChannelMap;
-
-static void SetChannelMap(const enum Channel devchans[MAX_OUTPUT_CHANNELS],
- ChannelConfig *ambicoeffs, const ChannelMap *chanmap,
- ALsizei count, ALsizei *outcount)
-{
- ALsizei maxchans = 0;
- ALsizei i, j;
-
- for(i = 0;i < count;i++)
- {
- ALint idx = GetChannelIndex(devchans, chanmap[i].ChanName);
- if(idx < 0)
- {
- ERR("Failed to find %s channel in device\n",
- GetLabelFromChannel(chanmap[i].ChanName));
- continue;
- }
-
- maxchans = maxi(maxchans, idx+1);
- for(j = 0;j < MAX_AMBI_COEFFS;j++)
- ambicoeffs[idx][j] = chanmap[i].Config[j];
- }
- *outcount = mini(maxchans, MAX_OUTPUT_CHANNELS);
-}
-
-static bool MakeSpeakerMap(ALCdevice *device, const AmbDecConf *conf, ALsizei speakermap[MAX_OUTPUT_CHANNELS])
-{
- ALsizei i;
-
- for(i = 0;i < conf->NumSpeakers;i++)
- {
- enum Channel ch;
- int chidx = -1;
-
- /* NOTE: AmbDec does not define any standard speaker names, however
- * for this to work we have to by able to find the output channel
- * the speaker definition corresponds to. Therefore, OpenAL Soft
- * requires these channel labels to be recognized:
- *
- * LF = Front left
- * RF = Front right
- * LS = Side left
- * RS = Side right
- * LB = Back left
- * RB = Back right
- * CE = Front center
- * CB = Back center
- *
- * Additionally, surround51 will acknowledge back speakers for side
- * channels, and surround51rear will acknowledge side speakers for
- * back channels, to avoid issues with an ambdec expecting 5.1 to
- * use the side channels when the device is configured for back,
- * and vice-versa.
- */
- if(alstr_cmp_cstr(conf->Speakers[i].Name, "LF") == 0)
- ch = FrontLeft;
- else if(alstr_cmp_cstr(conf->Speakers[i].Name, "RF") == 0)
- ch = FrontRight;
- else if(alstr_cmp_cstr(conf->Speakers[i].Name, "CE") == 0)
- ch = FrontCenter;
- else if(alstr_cmp_cstr(conf->Speakers[i].Name, "LS") == 0)
- {
- if(device->FmtChans == DevFmtX51Rear)
- ch = BackLeft;
- else
- ch = SideLeft;
- }
- else if(alstr_cmp_cstr(conf->Speakers[i].Name, "RS") == 0)
- {
- if(device->FmtChans == DevFmtX51Rear)
- ch = BackRight;
- else
- ch = SideRight;
- }
- else if(alstr_cmp_cstr(conf->Speakers[i].Name, "LB") == 0)
- {
- if(device->FmtChans == DevFmtX51)
- ch = SideLeft;
- else
- ch = BackLeft;
- }
- else if(alstr_cmp_cstr(conf->Speakers[i].Name, "RB") == 0)
- {
- if(device->FmtChans == DevFmtX51)
- ch = SideRight;
- else
- ch = BackRight;
- }
- else if(alstr_cmp_cstr(conf->Speakers[i].Name, "CB") == 0)
- ch = BackCenter;
- else
- {
- const char *name = alstr_get_cstr(conf->Speakers[i].Name);
- unsigned int n;
- char c;
-
- if(sscanf(name, "AUX%u%c", &n, &c) == 1 && n < 16)
- ch = Aux0+n;
- else
- {
- ERR("AmbDec speaker label \"%s\" not recognized\n", name);
- return false;
- }
- }
- chidx = GetChannelIdxByName(&device->RealOut, ch);
- if(chidx == -1)
- {
- ERR("Failed to lookup AmbDec speaker label %s\n",
- alstr_get_cstr(conf->Speakers[i].Name));
- return false;
- }
- speakermap[i] = chidx;
- }
-
- return true;
-}
-
-
-static const ChannelMap MonoCfg[1] = {
- { FrontCenter, { 1.0f } },
-}, StereoCfg[2] = {
- { FrontLeft, { 5.00000000e-1f, 2.88675135e-1f, 0.0f, 5.52305643e-2f } },
- { FrontRight, { 5.00000000e-1f, -2.88675135e-1f, 0.0f, 5.52305643e-2f } },
-}, QuadCfg[4] = {
- { BackLeft, { 3.53553391e-1f, 2.04124145e-1f, 0.0f, -2.04124145e-1f } },
- { FrontLeft, { 3.53553391e-1f, 2.04124145e-1f, 0.0f, 2.04124145e-1f } },
- { FrontRight, { 3.53553391e-1f, -2.04124145e-1f, 0.0f, 2.04124145e-1f } },
- { BackRight, { 3.53553391e-1f, -2.04124145e-1f, 0.0f, -2.04124145e-1f } },
-}, X51SideCfg[4] = {
- { SideLeft, { 3.33000782e-1f, 1.89084803e-1f, 0.0f, -2.00042375e-1f, -2.12307769e-2f, 0.0f, 0.0f, 0.0f, -1.14579885e-2f } },
- { FrontLeft, { 1.88542860e-1f, 1.27709292e-1f, 0.0f, 1.66295695e-1f, 7.30571517e-2f, 0.0f, 0.0f, 0.0f, 2.10901184e-2f } },
- { FrontRight, { 1.88542860e-1f, -1.27709292e-1f, 0.0f, 1.66295695e-1f, -7.30571517e-2f, 0.0f, 0.0f, 0.0f, 2.10901184e-2f } },
- { SideRight, { 3.33000782e-1f, -1.89084803e-1f, 0.0f, -2.00042375e-1f, 2.12307769e-2f, 0.0f, 0.0f, 0.0f, -1.14579885e-2f } },
-}, X51RearCfg[4] = {
- { BackLeft, { 3.33000782e-1f, 1.89084803e-1f, 0.0f, -2.00042375e-1f, -2.12307769e-2f, 0.0f, 0.0f, 0.0f, -1.14579885e-2f } },
- { FrontLeft, { 1.88542860e-1f, 1.27709292e-1f, 0.0f, 1.66295695e-1f, 7.30571517e-2f, 0.0f, 0.0f, 0.0f, 2.10901184e-2f } },
- { FrontRight, { 1.88542860e-1f, -1.27709292e-1f, 0.0f, 1.66295695e-1f, -7.30571517e-2f, 0.0f, 0.0f, 0.0f, 2.10901184e-2f } },
- { BackRight, { 3.33000782e-1f, -1.89084803e-1f, 0.0f, -2.00042375e-1f, 2.12307769e-2f, 0.0f, 0.0f, 0.0f, -1.14579885e-2f } },
-}, X61Cfg[6] = {
- { SideLeft, { 2.04460341e-1f, 2.17177926e-1f, 0.0f, -4.39996780e-2f, -2.60790269e-2f, 0.0f, 0.0f, 0.0f, -6.87239792e-2f } },
- { FrontLeft, { 1.58923161e-1f, 9.21772680e-2f, 0.0f, 1.59658796e-1f, 6.66278083e-2f, 0.0f, 0.0f, 0.0f, 3.84686854e-2f } },
- { FrontRight, { 1.58923161e-1f, -9.21772680e-2f, 0.0f, 1.59658796e-1f, -6.66278083e-2f, 0.0f, 0.0f, 0.0f, 3.84686854e-2f } },
- { SideRight, { 2.04460341e-1f, -2.17177926e-1f, 0.0f, -4.39996780e-2f, 2.60790269e-2f, 0.0f, 0.0f, 0.0f, -6.87239792e-2f } },
- { BackCenter, { 2.50001688e-1f, 0.00000000e+0f, 0.0f, -2.50000094e-1f, 0.00000000e+0f, 0.0f, 0.0f, 0.0f, 6.05133395e-2f } },
-}, X71Cfg[6] = {
- { BackLeft, { 2.04124145e-1f, 1.08880247e-1f, 0.0f, -1.88586120e-1f, -1.29099444e-1f, 0.0f, 0.0f, 0.0f, 7.45355993e-2f, 3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
- { SideLeft, { 2.04124145e-1f, 2.17760495e-1f, 0.0f, 0.00000000e+0f, 0.00000000e+0f, 0.0f, 0.0f, 0.0f, -1.49071198e-1f, -3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
- { FrontLeft, { 2.04124145e-1f, 1.08880247e-1f, 0.0f, 1.88586120e-1f, 1.29099444e-1f, 0.0f, 0.0f, 0.0f, 7.45355993e-2f, 3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
- { FrontRight, { 2.04124145e-1f, -1.08880247e-1f, 0.0f, 1.88586120e-1f, -1.29099444e-1f, 0.0f, 0.0f, 0.0f, 7.45355993e-2f, -3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
- { SideRight, { 2.04124145e-1f, -2.17760495e-1f, 0.0f, 0.00000000e+0f, 0.00000000e+0f, 0.0f, 0.0f, 0.0f, -1.49071198e-1f, 3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
- { BackRight, { 2.04124145e-1f, -1.08880247e-1f, 0.0f, -1.88586120e-1f, 1.29099444e-1f, 0.0f, 0.0f, 0.0f, 7.45355993e-2f, -3.73460789e-2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.00000000e+0f } },
-};
-
-static void InitNearFieldCtrl(ALCdevice *device, ALfloat ctrl_dist, ALsizei order,
- const ALsizei *restrict chans_per_order)
-{
- const char *devname = alstr_get_cstr(device->DeviceName);
- ALsizei i;
-
- if(GetConfigValueBool(devname, "decoder", "nfc", 1) && ctrl_dist > 0.0f)
- {
- /* NFC is only used when AvgSpeakerDist is greater than 0, and can only
- * be used when rendering to an ambisonic buffer.
- */
- device->AvgSpeakerDist = minf(ctrl_dist, 10.0f);
- TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist);
-
- for(i = 0;i < order+1;i++)
- device->NumChannelsPerOrder[i] = chans_per_order[i];
- for(;i < MAX_AMBI_ORDER+1;i++)
- device->NumChannelsPerOrder[i] = 0;
- }
-}
-
-static void InitDistanceComp(ALCdevice *device, const AmbDecConf *conf, const ALsizei speakermap[MAX_OUTPUT_CHANNELS])
-{
- const char *devname = alstr_get_cstr(device->DeviceName);
- ALfloat maxdist = 0.0f;
- size_t total = 0;
- ALsizei i;
-
- for(i = 0;i < conf->NumSpeakers;i++)
- maxdist = maxf(maxdist, conf->Speakers[i].Distance);
-
- if(GetConfigValueBool(devname, "decoder", "distance-comp", 1) && maxdist > 0.0f)
- {
- ALfloat srate = (ALfloat)device->Frequency;
- for(i = 0;i < conf->NumSpeakers;i++)
- {
- ALsizei chan = speakermap[i];
- ALfloat delay;
-
- /* Distance compensation only delays in steps of the sample rate.
- * This is a bit less accurate since the delay time falls to the
- * nearest sample time, but it's far simpler as it doesn't have to
- * deal with phase offsets. This means at 48khz, for instance, the
- * distance delay will be in steps of about 7 millimeters.
- */
- delay = floorf((maxdist-conf->Speakers[i].Distance) / SPEEDOFSOUNDMETRESPERSEC *
- srate + 0.5f);
- if(delay >= (ALfloat)MAX_DELAY_LENGTH)
- ERR("Delay for speaker \"%s\" exceeds buffer length (%f >= %u)\n",
- alstr_get_cstr(conf->Speakers[i].Name), delay, MAX_DELAY_LENGTH);
-
- device->ChannelDelay[chan].Length = (ALsizei)clampf(
- delay, 0.0f, (ALfloat)(MAX_DELAY_LENGTH-1)
- );
- device->ChannelDelay[chan].Gain = conf->Speakers[i].Distance / maxdist;
- TRACE("Channel %u \"%s\" distance compensation: %d samples, %f gain\n", chan,
- alstr_get_cstr(conf->Speakers[i].Name), device->ChannelDelay[chan].Length,
- device->ChannelDelay[chan].Gain
- );
-
- /* Round up to the next 4th sample, so each channel buffer starts
- * 16-byte aligned.
- */
- total += RoundUp(device->ChannelDelay[chan].Length, 4);
- }
- }
-
- if(total > 0)
- {
- device->ChannelDelay[0].Buffer = al_calloc(16, total * sizeof(ALfloat));
- for(i = 1;i < MAX_OUTPUT_CHANNELS;i++)
- {
- size_t len = RoundUp(device->ChannelDelay[i-1].Length, 4);
- device->ChannelDelay[i].Buffer = device->ChannelDelay[i-1].Buffer + len;
- }
- }
-}
-
-static void InitPanning(ALCdevice *device)
-{
- const ChannelMap *chanmap = NULL;
- ALsizei coeffcount = 0;
- ALsizei count = 0;
- ALsizei i, j;
-
- switch(device->FmtChans)
- {
- case DevFmtMono:
- count = COUNTOF(MonoCfg);
- chanmap = MonoCfg;
- coeffcount = 1;
- break;
-
- case DevFmtStereo:
- count = COUNTOF(StereoCfg);
- chanmap = StereoCfg;
- coeffcount = 4;
- break;
-
- case DevFmtQuad:
- count = COUNTOF(QuadCfg);
- chanmap = QuadCfg;
- coeffcount = 4;
- break;
-
- case DevFmtX51:
- count = COUNTOF(X51SideCfg);
- chanmap = X51SideCfg;
- coeffcount = 9;
- break;
-
- case DevFmtX51Rear:
- count = COUNTOF(X51RearCfg);
- chanmap = X51RearCfg;
- coeffcount = 9;
- break;
-
- case DevFmtX61:
- count = COUNTOF(X61Cfg);
- chanmap = X61Cfg;
- coeffcount = 9;
- break;
-
- case DevFmtX71:
- count = COUNTOF(X71Cfg);
- chanmap = X71Cfg;
- coeffcount = 16;
- break;
-
- case DevFmtAmbi3D:
- break;
- }
-
- if(device->FmtChans == DevFmtAmbi3D)
- {
- const char *devname = alstr_get_cstr(device->DeviceName);
- const ALsizei *acnmap = (device->AmbiLayout == AmbiLayout_FuMa) ? FuMa2ACN : ACN2ACN;
- const ALfloat *n3dscale = (device->AmbiScale == AmbiNorm_FuMa) ? FuMa2N3DScale :
- (device->AmbiScale == AmbiNorm_SN3D) ? SN3D2N3DScale :
- /*(device->AmbiScale == AmbiNorm_N3D) ?*/ N3D2N3DScale;
- ALfloat nfc_delay = 0.0f;
-
- count = (device->AmbiOrder == 3) ? 16 :
- (device->AmbiOrder == 2) ? 9 :
- (device->AmbiOrder == 1) ? 4 : 1;
- for(i = 0;i < count;i++)
- {
- ALsizei acn = acnmap[i];
- device->Dry.Ambi.Map[i].Scale = 1.0f/n3dscale[acn];
- device->Dry.Ambi.Map[i].Index = acn;
- }
- device->Dry.CoeffCount = 0;
- device->Dry.NumChannels = count;
-
- if(device->AmbiOrder < 2)
- {
- device->FOAOut.Ambi = device->Dry.Ambi;
- device->FOAOut.CoeffCount = device->Dry.CoeffCount;
- device->FOAOut.NumChannels = 0;
- }
- else
- {
- ALfloat w_scale=1.0f, xyz_scale=1.0f;
-
- /* FOA output is always ACN+N3D for higher-order ambisonic output.
- * The upsampler expects this and will convert it for output.
- */
- memset(&device->FOAOut.Ambi, 0, sizeof(device->FOAOut.Ambi));
- for(i = 0;i < 4;i++)
- {
- device->FOAOut.Ambi.Map[i].Scale = 1.0f;
- device->FOAOut.Ambi.Map[i].Index = i;
- }
- device->FOAOut.CoeffCount = 0;
- device->FOAOut.NumChannels = 4;
-
- if(device->AmbiOrder >= 3)
- {
- w_scale = W_SCALE_3H3P;
- xyz_scale = XYZ_SCALE_3H3P;
- }
- else
- {
- w_scale = W_SCALE_2H2P;
- xyz_scale = XYZ_SCALE_2H2P;
- }
- ambiup_reset(device->AmbiUp, device, w_scale, xyz_scale);
- }
-
- if(ConfigValueFloat(devname, "decoder", "nfc-ref-delay", &nfc_delay) && nfc_delay > 0.0f)
- {
- static const ALsizei chans_per_order[MAX_AMBI_ORDER+1] = {
- 1, 3, 5, 7
- };
- nfc_delay = clampf(nfc_delay, 0.001f, 1000.0f);
- InitNearFieldCtrl(device, nfc_delay * SPEEDOFSOUNDMETRESPERSEC,
- device->AmbiOrder, chans_per_order);
- }
- }
- else
- {
- ALfloat w_scale, xyz_scale;
-
- SetChannelMap(device->RealOut.ChannelName, device->Dry.Ambi.Coeffs,
- chanmap, count, &device->Dry.NumChannels);
- device->Dry.CoeffCount = coeffcount;
-
- w_scale = (device->Dry.CoeffCount > 9) ? W_SCALE_3H0P :
- (device->Dry.CoeffCount > 4) ? W_SCALE_2H0P : 1.0f;
- xyz_scale = (device->Dry.CoeffCount > 9) ? XYZ_SCALE_3H0P :
- (device->Dry.CoeffCount > 4) ? XYZ_SCALE_2H0P : 1.0f;
-
- memset(&device->FOAOut.Ambi, 0, sizeof(device->FOAOut.Ambi));
- for(i = 0;i < device->Dry.NumChannels;i++)
- {
- device->FOAOut.Ambi.Coeffs[i][0] = device->Dry.Ambi.Coeffs[i][0] * w_scale;
- for(j = 1;j < 4;j++)
- device->FOAOut.Ambi.Coeffs[i][j] = device->Dry.Ambi.Coeffs[i][j] * xyz_scale;
- }
- device->FOAOut.CoeffCount = 4;
- device->FOAOut.NumChannels = 0;
- }
- device->RealOut.NumChannels = 0;
-}
-
-static void InitCustomPanning(ALCdevice *device, const AmbDecConf *conf, const ALsizei speakermap[MAX_OUTPUT_CHANNELS])
-{
- ChannelMap chanmap[MAX_OUTPUT_CHANNELS];
- const ALfloat *coeff_scale = N3D2N3DScale;
- ALfloat w_scale = 1.0f;
- ALfloat xyz_scale = 1.0f;
- ALsizei i, j;
-
- if(conf->FreqBands != 1)
- ERR("Basic renderer uses the high-frequency matrix as single-band (xover_freq = %.0fhz)\n",
- conf->XOverFreq);
-
- if((conf->ChanMask&AMBI_PERIPHONIC_MASK))
- {
- if(conf->ChanMask > 0x1ff)
- {
- w_scale = W_SCALE_3H3P;
- xyz_scale = XYZ_SCALE_3H3P;
- }
- else if(conf->ChanMask > 0xf)
- {
- w_scale = W_SCALE_2H2P;
- xyz_scale = XYZ_SCALE_2H2P;
- }
- }
- else
- {
- if(conf->ChanMask > 0x1ff)
- {
- w_scale = W_SCALE_3H0P;
- xyz_scale = XYZ_SCALE_3H0P;
- }
- else if(conf->ChanMask > 0xf)
- {
- w_scale = W_SCALE_2H0P;
- xyz_scale = XYZ_SCALE_2H0P;
- }
- }
-
- if(conf->CoeffScale == ADS_SN3D)
- coeff_scale = SN3D2N3DScale;
- else if(conf->CoeffScale == ADS_FuMa)
- coeff_scale = FuMa2N3DScale;
-
- for(i = 0;i < conf->NumSpeakers;i++)
- {
- ALsizei chan = speakermap[i];
- ALfloat gain;
- ALsizei k = 0;
-
- for(j = 0;j < MAX_AMBI_COEFFS;j++)
- chanmap[i].Config[j] = 0.0f;
-
- chanmap[i].ChanName = device->RealOut.ChannelName[chan];
- for(j = 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)))
- chanmap[i].Config[j] = conf->HFMatrix[i][k++] / coeff_scale[j] * gain;
- }
- }
-
- SetChannelMap(device->RealOut.ChannelName, device->Dry.Ambi.Coeffs, chanmap,
- conf->NumSpeakers, &device->Dry.NumChannels);
- device->Dry.CoeffCount = (conf->ChanMask > 0x1ff) ? 16 :
- (conf->ChanMask > 0xf) ? 9 : 4;
-
- memset(&device->FOAOut.Ambi, 0, sizeof(device->FOAOut.Ambi));
- for(i = 0;i < device->Dry.NumChannels;i++)
- {
- device->FOAOut.Ambi.Coeffs[i][0] = device->Dry.Ambi.Coeffs[i][0] * w_scale;
- for(j = 1;j < 4;j++)
- device->FOAOut.Ambi.Coeffs[i][j] = device->Dry.Ambi.Coeffs[i][j] * xyz_scale;
- }
- device->FOAOut.CoeffCount = 4;
- device->FOAOut.NumChannels = 0;
-
- device->RealOut.NumChannels = 0;
-
- InitDistanceComp(device, conf, speakermap);
-}
-
-static void InitHQPanning(ALCdevice *device, const AmbDecConf *conf, const ALsizei speakermap[MAX_OUTPUT_CHANNELS])
-{
- static const ALsizei chans_per_order2d[MAX_AMBI_ORDER+1] = { 1, 2, 2, 2 };
- static const ALsizei chans_per_order3d[MAX_AMBI_ORDER+1] = { 1, 3, 5, 7 };
- ALfloat avg_dist;
- ALsizei count;
- ALsizei i;
-
- if((conf->ChanMask&AMBI_PERIPHONIC_MASK))
- {
- count = (conf->ChanMask > 0x1ff) ? 16 :
- (conf->ChanMask > 0xf) ? 9 : 4;
- for(i = 0;i < count;i++)
- {
- device->Dry.Ambi.Map[i].Scale = 1.0f;
- device->Dry.Ambi.Map[i].Index = i;
- }
- }
- else
- {
- static const int map[MAX_AMBI2D_COEFFS] = { 0, 1, 3, 4, 8, 9, 15 };
-
- count = (conf->ChanMask > 0x1ff) ? 7 :
- (conf->ChanMask > 0xf) ? 5 : 3;
- for(i = 0;i < count;i++)
- {
- device->Dry.Ambi.Map[i].Scale = 1.0f;
- device->Dry.Ambi.Map[i].Index = map[i];
- }
- }
- device->Dry.CoeffCount = 0;
- device->Dry.NumChannels = count;
-
- TRACE("Enabling %s-band %s-order%s ambisonic decoder\n",
- (conf->FreqBands == 1) ? "single" : "dual",
- (conf->ChanMask > 0xf) ? (conf->ChanMask > 0x1ff) ? "third" : "second" : "first",
- (conf->ChanMask&AMBI_PERIPHONIC_MASK) ? " periphonic" : ""
- );
- bformatdec_reset(device->AmbiDecoder, conf, count, device->Frequency, speakermap);
-
- if(!(conf->ChanMask > 0xf))
- {
- device->FOAOut.Ambi = device->Dry.Ambi;
- device->FOAOut.CoeffCount = device->Dry.CoeffCount;
- device->FOAOut.NumChannels = 0;
- }
- else
- {
- memset(&device->FOAOut.Ambi, 0, sizeof(device->FOAOut.Ambi));
- if((conf->ChanMask&AMBI_PERIPHONIC_MASK))
- {
- count = 4;
- for(i = 0;i < count;i++)
- {
- device->FOAOut.Ambi.Map[i].Scale = 1.0f;
- device->FOAOut.Ambi.Map[i].Index = i;
- }
- }
- else
- {
- static const int map[3] = { 0, 1, 3 };
- count = 3;
- for(i = 0;i < count;i++)
- {
- device->FOAOut.Ambi.Map[i].Scale = 1.0f;
- device->FOAOut.Ambi.Map[i].Index = map[i];
- }
- }
- device->FOAOut.CoeffCount = 0;
- device->FOAOut.NumChannels = count;
- }
-
- device->RealOut.NumChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-
- avg_dist = 0.0f;
- for(i = 0;i < conf->NumSpeakers;i++)
- avg_dist += conf->Speakers[i].Distance;
- avg_dist /= (ALfloat)conf->NumSpeakers;
- InitNearFieldCtrl(device, avg_dist,
- (conf->ChanMask > 0x1ff) ? 3 : (conf->ChanMask > 0xf) ? 2 : 1,
- (conf->ChanMask&AMBI_PERIPHONIC_MASK) ? chans_per_order3d : chans_per_order2d
- );
-
- InitDistanceComp(device, conf, speakermap);
-}
-
-static void InitHrtfPanning(ALCdevice *device)
-{
- /* NOTE: azimuth goes clockwise. */
- static const struct AngularPoint AmbiPoints[] = {
- { DEG2RAD( 90.0f), DEG2RAD( 0.0f) },
- { DEG2RAD( 35.2643897f), DEG2RAD( 45.0f) },
- { DEG2RAD( 35.2643897f), DEG2RAD( 135.0f) },
- { DEG2RAD( 35.2643897f), DEG2RAD(-135.0f) },
- { DEG2RAD( 35.2643897f), DEG2RAD( -45.0f) },
- { DEG2RAD( 0.0f), DEG2RAD( 0.0f) },
- { DEG2RAD( 0.0f), DEG2RAD( 45.0f) },
- { DEG2RAD( 0.0f), DEG2RAD( 90.0f) },
- { DEG2RAD( 0.0f), DEG2RAD( 135.0f) },
- { DEG2RAD( 0.0f), DEG2RAD( 180.0f) },
- { DEG2RAD( 0.0f), DEG2RAD(-135.0f) },
- { DEG2RAD( 0.0f), DEG2RAD( -90.0f) },
- { DEG2RAD( 0.0f), DEG2RAD( -45.0f) },
- { DEG2RAD(-35.2643897f), DEG2RAD( 45.0f) },
- { DEG2RAD(-35.2643897f), DEG2RAD( 135.0f) },
- { DEG2RAD(-35.2643897f), DEG2RAD(-135.0f) },
- { DEG2RAD(-35.2643897f), DEG2RAD( -45.0f) },
- { DEG2RAD(-90.0f), DEG2RAD( 0.0f) },
- };
- static const ALfloat AmbiMatrixFOA[][MAX_AMBI_COEFFS] = {
- { 5.55555556e-02f, 0.00000000e+00f, 1.23717915e-01f, 0.00000000e+00f },
- { 5.55555556e-02f, -5.00000000e-02f, 7.14285715e-02f, 5.00000000e-02f },
- { 5.55555556e-02f, -5.00000000e-02f, 7.14285715e-02f, -5.00000000e-02f },
- { 5.55555556e-02f, 5.00000000e-02f, 7.14285715e-02f, -5.00000000e-02f },
- { 5.55555556e-02f, 5.00000000e-02f, 7.14285715e-02f, 5.00000000e-02f },
- { 5.55555556e-02f, 0.00000000e+00f, 0.00000000e+00f, 8.66025404e-02f },
- { 5.55555556e-02f, -6.12372435e-02f, 0.00000000e+00f, 6.12372435e-02f },
- { 5.55555556e-02f, -8.66025404e-02f, 0.00000000e+00f, 0.00000000e+00f },
- { 5.55555556e-02f, -6.12372435e-02f, 0.00000000e+00f, -6.12372435e-02f },
- { 5.55555556e-02f, 0.00000000e+00f, 0.00000000e+00f, -8.66025404e-02f },
- { 5.55555556e-02f, 6.12372435e-02f, 0.00000000e+00f, -6.12372435e-02f },
- { 5.55555556e-02f, 8.66025404e-02f, 0.00000000e+00f, 0.00000000e+00f },
- { 5.55555556e-02f, 6.12372435e-02f, 0.00000000e+00f, 6.12372435e-02f },
- { 5.55555556e-02f, -5.00000000e-02f, -7.14285715e-02f, 5.00000000e-02f },
- { 5.55555556e-02f, -5.00000000e-02f, -7.14285715e-02f, -5.00000000e-02f },
- { 5.55555556e-02f, 5.00000000e-02f, -7.14285715e-02f, -5.00000000e-02f },
- { 5.55555556e-02f, 5.00000000e-02f, -7.14285715e-02f, 5.00000000e-02f },
- { 5.55555556e-02f, 0.00000000e+00f, -1.23717915e-01f, 0.00000000e+00f },
- }, AmbiMatrixHOA[][MAX_AMBI_COEFFS] = {
- { 5.55555556e-02f, 0.00000000e+00f, 1.23717915e-01f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f },
- { 5.55555556e-02f, -5.00000000e-02f, 7.14285715e-02f, 5.00000000e-02f, -4.55645099e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, -5.00000000e-02f, 7.14285715e-02f, -5.00000000e-02f, 4.55645099e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, 5.00000000e-02f, 7.14285715e-02f, -5.00000000e-02f, -4.55645099e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, 5.00000000e-02f, 7.14285715e-02f, 5.00000000e-02f, 4.55645099e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, 0.00000000e+00f, 0.00000000e+00f, 8.66025404e-02f, 0.00000000e+00f, 1.29099445e-01f },
- { 5.55555556e-02f, -6.12372435e-02f, 0.00000000e+00f, 6.12372435e-02f, -6.83467648e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, -8.66025404e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, -1.29099445e-01f },
- { 5.55555556e-02f, -6.12372435e-02f, 0.00000000e+00f, -6.12372435e-02f, 6.83467648e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, 0.00000000e+00f, 0.00000000e+00f, -8.66025404e-02f, 0.00000000e+00f, 1.29099445e-01f },
- { 5.55555556e-02f, 6.12372435e-02f, 0.00000000e+00f, -6.12372435e-02f, -6.83467648e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, 8.66025404e-02f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f, -1.29099445e-01f },
- { 5.55555556e-02f, 6.12372435e-02f, 0.00000000e+00f, 6.12372435e-02f, 6.83467648e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, -5.00000000e-02f, -7.14285715e-02f, 5.00000000e-02f, -4.55645099e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, -5.00000000e-02f, -7.14285715e-02f, -5.00000000e-02f, 4.55645099e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, 5.00000000e-02f, -7.14285715e-02f, -5.00000000e-02f, -4.55645099e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, 5.00000000e-02f, -7.14285715e-02f, 5.00000000e-02f, 4.55645099e-02f, 0.00000000e+00f },
- { 5.55555556e-02f, 0.00000000e+00f, -1.23717915e-01f, 0.00000000e+00f, 0.00000000e+00f, 0.00000000e+00f },
- };
- static const ALfloat AmbiOrderHFGainFOA[MAX_AMBI_ORDER+1] = {
- 3.00000000e+00f, 1.73205081e+00f
- }, AmbiOrderHFGainHOA[MAX_AMBI_ORDER+1] = {
- 2.40192231e+00f, 1.86052102e+00f, 9.60768923e-01f
- };
- static const ALsizei IndexMap[6] = { 0, 1, 2, 3, 4, 8 };
- static const ALsizei ChansPerOrder[MAX_AMBI_ORDER+1] = { 1, 3, 2, 0 };
- const ALfloat (*restrict AmbiMatrix)[MAX_AMBI_COEFFS] = AmbiMatrixFOA;
- const ALfloat *restrict AmbiOrderHFGain = AmbiOrderHFGainFOA;
- ALsizei count = 4;
- ALsizei i;
-
- static_assert(COUNTOF(AmbiPoints) == COUNTOF(AmbiMatrixFOA), "FOA Ambisonic HRTF mismatch");
- static_assert(COUNTOF(AmbiPoints) == COUNTOF(AmbiMatrixHOA), "HOA Ambisonic HRTF mismatch");
-
- if(device->AmbiUp)
- {
- AmbiMatrix = AmbiMatrixHOA;
- AmbiOrderHFGain = AmbiOrderHFGainHOA;
- count = COUNTOF(IndexMap);
- }
-
- device->Hrtf = al_calloc(16, FAM_SIZE(DirectHrtfState, Chan, count));
-
- for(i = 0;i < count;i++)
- {
- device->Dry.Ambi.Map[i].Scale = 1.0f;
- device->Dry.Ambi.Map[i].Index = IndexMap[i];
- }
- device->Dry.CoeffCount = 0;
- device->Dry.NumChannels = count;
-
- if(device->AmbiUp)
- {
- memset(&device->FOAOut.Ambi, 0, sizeof(device->FOAOut.Ambi));
- for(i = 0;i < 4;i++)
- {
- device->FOAOut.Ambi.Map[i].Scale = 1.0f;
- device->FOAOut.Ambi.Map[i].Index = i;
- }
- device->FOAOut.CoeffCount = 0;
- device->FOAOut.NumChannels = 4;
-
- ambiup_reset(device->AmbiUp, device, AmbiOrderHFGainFOA[0] / AmbiOrderHFGain[0],
- AmbiOrderHFGainFOA[1] / AmbiOrderHFGain[1]);
- }
- else
- {
- device->FOAOut.Ambi = device->Dry.Ambi;
- device->FOAOut.CoeffCount = device->Dry.CoeffCount;
- device->FOAOut.NumChannels = 0;
- }
-
- device->RealOut.NumChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-
- BuildBFormatHrtf(device->HrtfHandle,
- device->Hrtf, device->Dry.NumChannels, AmbiPoints, AmbiMatrix, COUNTOF(AmbiPoints),
- AmbiOrderHFGain
- );
-
- InitNearFieldCtrl(device, device->HrtfHandle->distance, device->AmbiUp ? 2 : 1,
- ChansPerOrder);
-}
-
-static void InitUhjPanning(ALCdevice *device)
-{
- ALsizei count = 3;
- ALsizei i;
-
- for(i = 0;i < count;i++)
- {
- ALsizei acn = FuMa2ACN[i];
- device->Dry.Ambi.Map[i].Scale = 1.0f/FuMa2N3DScale[acn];
- device->Dry.Ambi.Map[i].Index = acn;
- }
- device->Dry.CoeffCount = 0;
- device->Dry.NumChannels = count;
-
- device->FOAOut.Ambi = device->Dry.Ambi;
- device->FOAOut.CoeffCount = device->Dry.CoeffCount;
- device->FOAOut.NumChannels = 0;
-
- device->RealOut.NumChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
-}
-
-void aluInitRenderer(ALCdevice *device, ALint hrtf_id, enum HrtfRequestMode hrtf_appreq, enum HrtfRequestMode hrtf_userreq)
-{
- /* Hold the HRTF the device last used, in case it's used again. */
- struct Hrtf *old_hrtf = device->HrtfHandle;
- const char *mode;
- bool headphones;
- int bs2blevel;
- size_t i;
-
- al_free(device->Hrtf);
- device->Hrtf = NULL;
- device->HrtfHandle = NULL;
- alstr_clear(&device->HrtfName);
- device->Render_Mode = NormalRender;
-
- memset(&device->Dry.Ambi, 0, sizeof(device->Dry.Ambi));
- device->Dry.CoeffCount = 0;
- device->Dry.NumChannels = 0;
- for(i = 0;i < MAX_AMBI_ORDER+1;i++)
- device->NumChannelsPerOrder[i] = 0;
-
- device->AvgSpeakerDist = 0.0f;
- memset(device->ChannelDelay, 0, sizeof(device->ChannelDelay));
- for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
- {
- device->ChannelDelay[i].Gain = 1.0f;
- device->ChannelDelay[i].Length = 0;
- }
-
- al_free(device->Stablizer);
- device->Stablizer = NULL;
-
- if(device->FmtChans != DevFmtStereo)
- {
- ALsizei speakermap[MAX_OUTPUT_CHANNELS];
- const char *devname, *layout = NULL;
- AmbDecConf conf, *pconf = NULL;
-
- if(old_hrtf)
- Hrtf_DecRef(old_hrtf);
- old_hrtf = NULL;
- if(hrtf_appreq == Hrtf_Enable)
- device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT;
-
- ambdec_init(&conf);
-
- devname = alstr_get_cstr(device->DeviceName);
- switch(device->FmtChans)
- {
- case DevFmtQuad: layout = "quad"; break;
- case DevFmtX51: /* fall-through */
- case DevFmtX51Rear: layout = "surround51"; break;
- case DevFmtX61: layout = "surround61"; break;
- case DevFmtX71: layout = "surround71"; break;
- /* Mono, Stereo, and Ambisonics output don't use custom decoders. */
- case DevFmtMono:
- case DevFmtStereo:
- case DevFmtAmbi3D:
- break;
- }
- if(layout)
- {
- const char *fname;
- if(ConfigValueStr(devname, "decoder", layout, &fname))
- {
- if(!ambdec_load(&conf, fname))
- ERR("Failed to load layout file %s\n", fname);
- else
- {
- if(conf.ChanMask > 0xffff)
- ERR("Unsupported channel mask 0x%04x (max 0xffff)\n", conf.ChanMask);
- else
- {
- if(MakeSpeakerMap(device, &conf, speakermap))
- pconf = &conf;
- }
- }
- }
- }
-
- if(pconf && GetConfigValueBool(devname, "decoder", "hq-mode", 0))
- {
- ambiup_free(&device->AmbiUp);
- if(!device->AmbiDecoder)
- device->AmbiDecoder = bformatdec_alloc();
- }
- else
- {
- bformatdec_free(&device->AmbiDecoder);
- if(device->FmtChans != DevFmtAmbi3D || device->AmbiOrder < 2)
- ambiup_free(&device->AmbiUp);
- else
- {
- if(!device->AmbiUp)
- device->AmbiUp = ambiup_alloc();
- }
- }
-
- if(!pconf)
- InitPanning(device);
- else if(device->AmbiDecoder)
- InitHQPanning(device, pconf, speakermap);
- else
- InitCustomPanning(device, pconf, speakermap);
-
- /* Enable the stablizer only for formats that have front-left, front-
- * right, and front-center outputs.
- */
- switch(device->FmtChans)
- {
- case DevFmtX51:
- case DevFmtX51Rear:
- case DevFmtX61:
- case DevFmtX71:
- if(GetConfigValueBool(devname, NULL, "front-stablizer", 0))
- {
- /* Initialize band-splitting filters for the front-left and
- * front-right channels, with a crossover at 5khz (could be
- * higher).
- */
- ALfloat scale = (ALfloat)(5000.0 / device->Frequency);
- FrontStablizer *stablizer = al_calloc(16, sizeof(*stablizer));
-
- bandsplit_init(&stablizer->LFilter, scale);
- stablizer->RFilter = stablizer->LFilter;
-
- /* Initialize all-pass filters for all other channels. */
- splitterap_init(&stablizer->APFilter[0], scale);
- for(i = 1;i < (size_t)device->RealOut.NumChannels;i++)
- stablizer->APFilter[i] = stablizer->APFilter[0];
-
- device->Stablizer = stablizer;
- }
- break;
- case DevFmtMono:
- case DevFmtStereo:
- case DevFmtQuad:
- case DevFmtAmbi3D:
- break;
- }
- TRACE("Front stablizer %s\n", device->Stablizer ? "enabled" : "disabled");
-
- ambdec_deinit(&conf);
- return;
- }
-
- bformatdec_free(&device->AmbiDecoder);
-
- headphones = device->IsHeadphones;
- if(device->Type != Loopback)
- {
- const char *mode;
- if(ConfigValueStr(alstr_get_cstr(device->DeviceName), NULL, "stereo-mode", &mode))
- {
- if(strcasecmp(mode, "headphones") == 0)
- headphones = true;
- else if(strcasecmp(mode, "speakers") == 0)
- headphones = false;
- else if(strcasecmp(mode, "auto") != 0)
- ERR("Unexpected stereo-mode: %s\n", mode);
- }
- }
-
- if(hrtf_userreq == Hrtf_Default)
- {
- bool usehrtf = (headphones && hrtf_appreq != Hrtf_Disable) ||
- (hrtf_appreq == Hrtf_Enable);
- if(!usehrtf) goto no_hrtf;
-
- device->HrtfStatus = ALC_HRTF_ENABLED_SOFT;
- if(headphones && hrtf_appreq != Hrtf_Disable)
- device->HrtfStatus = ALC_HRTF_HEADPHONES_DETECTED_SOFT;
- }
- else
- {
- if(hrtf_userreq != Hrtf_Enable)
- {
- if(hrtf_appreq == Hrtf_Enable)
- device->HrtfStatus = ALC_HRTF_DENIED_SOFT;
- goto no_hrtf;
- }
- device->HrtfStatus = ALC_HRTF_REQUIRED_SOFT;
- }
-
- if(VECTOR_SIZE(device->HrtfList) == 0)
- {
- VECTOR_DEINIT(device->HrtfList);
- device->HrtfList = EnumerateHrtf(device->DeviceName);
- }
-
- if(hrtf_id >= 0 && (size_t)hrtf_id < VECTOR_SIZE(device->HrtfList))
- {
- const EnumeratedHrtf *entry = &VECTOR_ELEM(device->HrtfList, hrtf_id);
- struct Hrtf *hrtf = GetLoadedHrtf(entry->hrtf);
- if(hrtf && hrtf->sampleRate == device->Frequency)
- {
- device->HrtfHandle = hrtf;
- alstr_copy(&device->HrtfName, entry->name);
- }
- else if(hrtf)
- Hrtf_DecRef(hrtf);
- }
-
- for(i = 0;!device->HrtfHandle && i < VECTOR_SIZE(device->HrtfList);i++)
- {
- const EnumeratedHrtf *entry = &VECTOR_ELEM(device->HrtfList, i);
- struct Hrtf *hrtf = GetLoadedHrtf(entry->hrtf);
- if(hrtf && hrtf->sampleRate == device->Frequency)
- {
- device->HrtfHandle = hrtf;
- alstr_copy(&device->HrtfName, entry->name);
- }
- else if(hrtf)
- Hrtf_DecRef(hrtf);
- }
-
- if(device->HrtfHandle)
- {
- if(old_hrtf)
- Hrtf_DecRef(old_hrtf);
- old_hrtf = NULL;
-
- device->Render_Mode = HrtfRender;
- if(ConfigValueStr(alstr_get_cstr(device->DeviceName), NULL, "hrtf-mode", &mode))
- {
- if(strcasecmp(mode, "full") == 0)
- device->Render_Mode = HrtfRender;
- else if(strcasecmp(mode, "basic") == 0)
- device->Render_Mode = NormalRender;
- else
- ERR("Unexpected hrtf-mode: %s\n", mode);
- }
-
- if(device->Render_Mode == HrtfRender)
- {
- /* Don't bother with HOA when using full HRTF rendering. Nothing
- * needs it, and it eases the CPU/memory load.
- */
- ambiup_free(&device->AmbiUp);
- }
- else
- {
- if(!device->AmbiUp)
- device->AmbiUp = ambiup_alloc();
- }
-
- TRACE("%s HRTF rendering enabled, using \"%s\"\n",
- ((device->Render_Mode == HrtfRender) ? "Full" : "Basic"),
- alstr_get_cstr(device->HrtfName)
- );
- InitHrtfPanning(device);
- return;
- }
- device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT;
-
-no_hrtf:
- if(old_hrtf)
- Hrtf_DecRef(old_hrtf);
- old_hrtf = NULL;
- TRACE("HRTF disabled\n");
-
- device->Render_Mode = StereoPair;
-
- ambiup_free(&device->AmbiUp);
-
- bs2blevel = ((headphones && hrtf_appreq != Hrtf_Disable) ||
- (hrtf_appreq == Hrtf_Enable)) ? 5 : 0;
- if(device->Type != Loopback)
- ConfigValueInt(alstr_get_cstr(device->DeviceName), NULL, "cf_level", &bs2blevel);
- if(bs2blevel > 0 && bs2blevel <= 6)
- {
- device->Bs2b = al_calloc(16, sizeof(*device->Bs2b));
- bs2b_set_params(device->Bs2b, bs2blevel, device->Frequency);
- TRACE("BS2B enabled\n");
- InitPanning(device);
- return;
- }
-
- TRACE("BS2B disabled\n");
-
- if(ConfigValueStr(alstr_get_cstr(device->DeviceName), NULL, "stereo-encoding", &mode))
- {
- if(strcasecmp(mode, "uhj") == 0)
- device->Render_Mode = NormalRender;
- else if(strcasecmp(mode, "panpot") != 0)
- ERR("Unexpected stereo-encoding: %s\n", mode);
- }
- if(device->Render_Mode == NormalRender)
- {
- device->Uhj_Encoder = al_calloc(16, sizeof(Uhj2Encoder));
- TRACE("UHJ enabled\n");
- InitUhjPanning(device);
- return;
- }
-
- TRACE("UHJ disabled\n");
- InitPanning(device);
-}
-
-
-void aluInitEffectPanning(ALeffectslot *slot)
-{
- ALsizei i;
-
- memset(slot->ChanMap, 0, sizeof(slot->ChanMap));
- slot->NumChannels = 0;
-
- for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
- {
- slot->ChanMap[i].Scale = 1.0f;
- slot->ChanMap[i].Index = i;
- }
- slot->NumChannels = i;
-}
diff --git a/Alc/polymorphism.h b/Alc/polymorphism.h
deleted file mode 100644
index fa31fad2..00000000
--- a/Alc/polymorphism.h
+++ /dev/null
@@ -1,105 +0,0 @@
-#ifndef POLYMORPHISM_H
-#define POLYMORPHISM_H
-
-/* Macros to declare inheriting types, and to (down-)cast and up-cast. */
-#define DERIVE_FROM_TYPE(t) t t##_parent
-#define STATIC_CAST(to, obj) (&(obj)->to##_parent)
-#ifdef __GNUC__
-#define STATIC_UPCAST(to, from, obj) __extension__({ \
- static_assert(__builtin_types_compatible_p(from, __typeof(*(obj))), \
- "Invalid upcast object from type"); \
- (to*)((char*)(obj) - offsetof(to, from##_parent)); \
-})
-#else
-#define STATIC_UPCAST(to, from, obj) ((to*)((char*)(obj) - offsetof(to, from##_parent)))
-#endif
-
-/* Defines method forwards, which call the given parent's (T2's) implementation. */
-#define DECLARE_FORWARD(T1, T2, rettype, func) \
-rettype T1##_##func(T1 *obj) \
-{ return T2##_##func(STATIC_CAST(T2, obj)); }
-
-#define DECLARE_FORWARD1(T1, T2, rettype, func, argtype1) \
-rettype T1##_##func(T1 *obj, argtype1 a) \
-{ return T2##_##func(STATIC_CAST(T2, obj), a); }
-
-#define DECLARE_FORWARD2(T1, T2, rettype, func, argtype1, argtype2) \
-rettype T1##_##func(T1 *obj, argtype1 a, argtype2 b) \
-{ return T2##_##func(STATIC_CAST(T2, obj), a, b); }
-
-#define DECLARE_FORWARD3(T1, T2, rettype, func, argtype1, argtype2, argtype3) \
-rettype T1##_##func(T1 *obj, argtype1 a, argtype2 b, argtype3 c) \
-{ return T2##_##func(STATIC_CAST(T2, obj), a, b, c); }
-
-/* Defines method thunks, functions that call to the child's method. */
-#define DECLARE_THUNK(T1, T2, rettype, func) \
-static rettype T1##_##T2##_##func(T2 *obj) \
-{ return T1##_##func(STATIC_UPCAST(T1, T2, obj)); }
-
-#define DECLARE_THUNK1(T1, T2, rettype, func, argtype1) \
-static rettype T1##_##T2##_##func(T2 *obj, argtype1 a) \
-{ return T1##_##func(STATIC_UPCAST(T1, T2, obj), a); }
-
-#define DECLARE_THUNK2(T1, T2, rettype, func, argtype1, argtype2) \
-static rettype T1##_##T2##_##func(T2 *obj, argtype1 a, argtype2 b) \
-{ return T1##_##func(STATIC_UPCAST(T1, T2, obj), a, b); }
-
-#define DECLARE_THUNK3(T1, T2, rettype, func, argtype1, argtype2, argtype3) \
-static rettype T1##_##T2##_##func(T2 *obj, argtype1 a, argtype2 b, argtype3 c) \
-{ return T1##_##func(STATIC_UPCAST(T1, T2, obj), a, b, c); }
-
-#define DECLARE_THUNK4(T1, T2, rettype, func, argtype1, argtype2, argtype3, argtype4) \
-static rettype T1##_##T2##_##func(T2 *obj, argtype1 a, argtype2 b, argtype3 c, argtype4 d) \
-{ return T1##_##func(STATIC_UPCAST(T1, T2, obj), a, b, c, d); }
-
-/* Defines the default functions used to (de)allocate a polymorphic object. */
-#define DECLARE_DEFAULT_ALLOCATORS(T) \
-static void* T##_New(size_t size) { return al_malloc(16, size); } \
-static void T##_Delete(void *ptr) { al_free(ptr); }
-
-
-/* Helper to extract an argument list for virtual method calls. */
-#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
-
-/* Call a "virtual" method on an object, with arguments. */
-#define V(obj, func) ((obj)->vtbl->func((obj), EXTRACT_VCALL_ARGS
-/* Call a "virtual" method on an object, with no arguments. */
-#define V0(obj, func) ((obj)->vtbl->func((obj) EXTRACT_VCALL_ARGS
-
-
-/* Helper to extract an argument list for NEW_OBJ calls. */
-#define EXTRACT_NEW_ARGS(...) __VA_ARGS__); \
- } \
-} while(0)
-
-/* Allocate and construct an object, with arguments. */
-#define NEW_OBJ(_res, T) do { \
- _res = T##_New(sizeof(T)); \
- if(_res) \
- { \
- memset(_res, 0, sizeof(T)); \
- T##_Construct(_res, EXTRACT_NEW_ARGS
-/* Allocate and construct an object, with no arguments. */
-#define NEW_OBJ0(_res, T) do { \
- _res = T##_New(sizeof(T)); \
- if(_res) \
- { \
- memset(_res, 0, sizeof(T)); \
- T##_Construct(_res EXTRACT_NEW_ARGS
-
-/* Destructs and deallocate an object. */
-#define DELETE_OBJ(obj) do { \
- if((obj) != NULL) \
- { \
- V0((obj),Destruct)(); \
- V0((obj),Delete)(); \
- } \
-} while(0)
-
-
-/* Helper to get a type's vtable thunk for a child type. */
-#define GET_VTABLE2(T1, T2) (&(T1##_##T2##_vtable))
-/* Helper to set an object's vtable thunk for a child type. Used when constructing an object. */
-#define SET_VTABLE2(T1, T2, obj) (STATIC_CAST(T2, obj)->vtbl = GET_VTABLE2(T1, T2))
-
-#endif /* POLYMORPHISM_H */
diff --git a/Alc/ringbuffer.c b/Alc/ringbuffer.c
deleted file mode 100644
index 6c419cf8..00000000
--- a/Alc/ringbuffer.c
+++ /dev/null
@@ -1,295 +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 <string.h>
-#include <stdlib.h>
-#include <limits.h>
-
-#include "ringbuffer.h"
-#include "align.h"
-#include "atomic.h"
-#include "threads.h"
-#include "almalloc.h"
-#include "compat.h"
-
-
-/* NOTE: This lockless ringbuffer implementation is copied from JACK, extended
- * to include an element size. Consequently, parameters and return values for a
- * size or count is in 'elements', not bytes. Additionally, it only supports
- * single-consumer/single-provider operation. */
-struct ll_ringbuffer {
- ATOMIC(size_t) write_ptr;
- ATOMIC(size_t) read_ptr;
- size_t size;
- size_t size_mask;
- size_t elem_size;
-
- alignas(16) char buf[];
-};
-
-ll_ringbuffer_t *ll_ringbuffer_create(size_t sz, size_t elem_sz, int limit_writes)
-{
- ll_ringbuffer_t *rb;
- size_t power_of_two = 0;
-
- if(sz > 0)
- {
- power_of_two = sz;
- power_of_two |= power_of_two>>1;
- power_of_two |= power_of_two>>2;
- power_of_two |= power_of_two>>4;
- power_of_two |= power_of_two>>8;
- power_of_two |= power_of_two>>16;
-#if SIZE_MAX > UINT_MAX
- power_of_two |= power_of_two>>32;
-#endif
- }
- power_of_two++;
- if(power_of_two < sz) return NULL;
-
- rb = al_malloc(16, sizeof(*rb) + power_of_two*elem_sz);
- if(!rb) return NULL;
-
- ATOMIC_INIT(&rb->write_ptr, 0);
- ATOMIC_INIT(&rb->read_ptr, 0);
- rb->size = limit_writes ? sz : power_of_two;
- rb->size_mask = power_of_two - 1;
- rb->elem_size = elem_sz;
- return rb;
-}
-
-void ll_ringbuffer_free(ll_ringbuffer_t *rb)
-{
- al_free(rb);
-}
-
-void ll_ringbuffer_reset(ll_ringbuffer_t *rb)
-{
- ATOMIC_STORE(&rb->write_ptr, 0, almemory_order_release);
- ATOMIC_STORE(&rb->read_ptr, 0, almemory_order_release);
- memset(rb->buf, 0, (rb->size_mask+1)*rb->elem_size);
-}
-
-
-size_t ll_ringbuffer_read_space(const ll_ringbuffer_t *rb)
-{
- size_t w = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->write_ptr, almemory_order_acquire);
- size_t r = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->read_ptr, almemory_order_acquire);
- return (w-r) & rb->size_mask;
-}
-
-size_t ll_ringbuffer_write_space(const ll_ringbuffer_t *rb)
-{
- size_t w = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->write_ptr, almemory_order_acquire);
- size_t r = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->read_ptr, almemory_order_acquire);
- w = (r-w-1) & rb->size_mask;
- return (w > rb->size) ? rb->size : w;
-}
-
-
-size_t ll_ringbuffer_read(ll_ringbuffer_t *rb, char *dest, size_t cnt)
-{
- size_t read_ptr;
- size_t free_cnt;
- size_t cnt2;
- size_t to_read;
- size_t n1, n2;
-
- free_cnt = ll_ringbuffer_read_space(rb);
- if(free_cnt == 0) return 0;
-
- to_read = (cnt > free_cnt) ? free_cnt : cnt;
- read_ptr = ATOMIC_LOAD(&rb->read_ptr, almemory_order_relaxed) & rb->size_mask;
-
- cnt2 = read_ptr + to_read;
- if(cnt2 > rb->size_mask+1)
- {
- n1 = rb->size_mask+1 - read_ptr;
- n2 = cnt2 & rb->size_mask;
- }
- else
- {
- n1 = to_read;
- n2 = 0;
- }
-
- memcpy(dest, &rb->buf[read_ptr*rb->elem_size], n1*rb->elem_size);
- read_ptr += n1;
- if(n2)
- {
- memcpy(dest + n1*rb->elem_size, &rb->buf[(read_ptr&rb->size_mask)*rb->elem_size],
- n2*rb->elem_size);
- read_ptr += n2;
- }
- ATOMIC_STORE(&rb->read_ptr, read_ptr, almemory_order_release);
- return to_read;
-}
-
-size_t ll_ringbuffer_peek(ll_ringbuffer_t *rb, char *dest, size_t cnt)
-{
- size_t free_cnt;
- size_t cnt2;
- size_t to_read;
- size_t n1, n2;
- size_t read_ptr;
-
- free_cnt = ll_ringbuffer_read_space(rb);
- if(free_cnt == 0) return 0;
-
- to_read = (cnt > free_cnt) ? free_cnt : cnt;
- read_ptr = ATOMIC_LOAD(&rb->read_ptr, almemory_order_relaxed) & rb->size_mask;
-
- cnt2 = read_ptr + to_read;
- if(cnt2 > rb->size_mask+1)
- {
- n1 = rb->size_mask+1 - read_ptr;
- n2 = cnt2 & rb->size_mask;
- }
- else
- {
- n1 = to_read;
- n2 = 0;
- }
-
- memcpy(dest, &rb->buf[read_ptr*rb->elem_size], n1*rb->elem_size);
- if(n2)
- {
- read_ptr += n1;
- memcpy(dest + n1*rb->elem_size, &rb->buf[(read_ptr&rb->size_mask)*rb->elem_size],
- n2*rb->elem_size);
- }
- return to_read;
-}
-
-size_t ll_ringbuffer_write(ll_ringbuffer_t *rb, const char *src, size_t cnt)
-{
- size_t write_ptr;
- size_t free_cnt;
- size_t cnt2;
- size_t to_write;
- size_t n1, n2;
-
- free_cnt = ll_ringbuffer_write_space(rb);
- if(free_cnt == 0) return 0;
-
- to_write = (cnt > free_cnt) ? free_cnt : cnt;
- write_ptr = ATOMIC_LOAD(&rb->write_ptr, almemory_order_relaxed) & rb->size_mask;
-
- cnt2 = write_ptr + to_write;
- if(cnt2 > rb->size_mask+1)
- {
- n1 = rb->size_mask+1 - write_ptr;
- n2 = cnt2 & rb->size_mask;
- }
- else
- {
- n1 = to_write;
- n2 = 0;
- }
-
- memcpy(&rb->buf[write_ptr*rb->elem_size], src, n1*rb->elem_size);
- write_ptr += n1;
- if(n2)
- {
- memcpy(&rb->buf[(write_ptr&rb->size_mask)*rb->elem_size], src + n1*rb->elem_size,
- n2*rb->elem_size);
- write_ptr += n2;
- }
- ATOMIC_STORE(&rb->write_ptr, write_ptr, almemory_order_release);
- return to_write;
-}
-
-
-void ll_ringbuffer_read_advance(ll_ringbuffer_t *rb, size_t cnt)
-{
- ATOMIC_ADD(&rb->read_ptr, cnt, almemory_order_acq_rel);
-}
-
-void ll_ringbuffer_write_advance(ll_ringbuffer_t *rb, size_t cnt)
-{
- ATOMIC_ADD(&rb->write_ptr, cnt, almemory_order_acq_rel);
-}
-
-
-void ll_ringbuffer_get_read_vector(const ll_ringbuffer_t *rb, ll_ringbuffer_data_t vec[2])
-{
- size_t free_cnt;
- size_t cnt2;
- size_t w, r;
-
- w = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->write_ptr, almemory_order_acquire);
- r = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->read_ptr, almemory_order_acquire);
- w &= rb->size_mask;
- r &= rb->size_mask;
- free_cnt = (w-r) & rb->size_mask;
-
- cnt2 = r + free_cnt;
- if(cnt2 > rb->size_mask+1)
- {
- /* Two part vector: the rest of the buffer after the current write ptr,
- * plus some from the start of the buffer. */
- vec[0].buf = (char*)&rb->buf[r*rb->elem_size];
- vec[0].len = rb->size_mask+1 - r;
- vec[1].buf = (char*)rb->buf;
- vec[1].len = cnt2 & rb->size_mask;
- }
- else
- {
- /* Single part vector: just the rest of the buffer */
- vec[0].buf = (char*)&rb->buf[r*rb->elem_size];
- vec[0].len = free_cnt;
- vec[1].buf = NULL;
- vec[1].len = 0;
- }
-}
-
-void ll_ringbuffer_get_write_vector(const ll_ringbuffer_t *rb, ll_ringbuffer_data_t vec[2])
-{
- size_t free_cnt;
- size_t cnt2;
- size_t w, r;
-
- w = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->write_ptr, almemory_order_acquire);
- r = ATOMIC_LOAD(&CONST_CAST(ll_ringbuffer_t*,rb)->read_ptr, almemory_order_acquire);
- w &= rb->size_mask;
- r &= rb->size_mask;
- free_cnt = (r-w-1) & rb->size_mask;
- if(free_cnt > rb->size) free_cnt = rb->size;
-
- cnt2 = w + free_cnt;
- if(cnt2 > rb->size_mask+1)
- {
- /* Two part vector: the rest of the buffer after the current write ptr,
- * plus some from the start of the buffer. */
- vec[0].buf = (char*)&rb->buf[w*rb->elem_size];
- vec[0].len = rb->size_mask+1 - w;
- vec[1].buf = (char*)rb->buf;
- vec[1].len = cnt2 & rb->size_mask;
- }
- else
- {
- vec[0].buf = (char*)&rb->buf[w*rb->elem_size];
- vec[0].len = free_cnt;
- vec[1].buf = NULL;
- vec[1].len = 0;
- }
-}
diff --git a/Alc/ringbuffer.h b/Alc/ringbuffer.h
deleted file mode 100644
index 0d05ec84..00000000
--- a/Alc/ringbuffer.h
+++ /dev/null
@@ -1,77 +0,0 @@
-#ifndef RINGBUFFER_H
-#define RINGBUFFER_H
-
-#include <stddef.h>
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct ll_ringbuffer ll_ringbuffer_t;
-typedef struct ll_ringbuffer_data {
- char *buf;
- size_t len;
-} ll_ringbuffer_data_t;
-
-
-/**
- * Create a new ringbuffer to hold at least `sz' elements of `elem_sz' bytes.
- * The number of elements is rounded up to the next power of two (even if it is
- * already a power of two, to ensure the requested amount can be written).
- */
-ll_ringbuffer_t *ll_ringbuffer_create(size_t sz, size_t elem_sz, int limit_writes);
-/** Free all data associated with the ringbuffer `rb'. */
-void ll_ringbuffer_free(ll_ringbuffer_t *rb);
-/** Reset the read and write pointers to zero. This is not thread safe. */
-void ll_ringbuffer_reset(ll_ringbuffer_t *rb);
-
-/**
- * The non-copying data reader. `vec' is an array of two places. Set the values
- * at `vec' to hold the current readable data at `rb'. If the readable data is
- * in one segment the second segment has zero length.
- */
-void ll_ringbuffer_get_read_vector(const ll_ringbuffer_t *rb, ll_ringbuffer_data_t vec[2]);
-/**
- * The non-copying data writer. `vec' is an array of two places. Set the values
- * at `vec' to hold the current writeable data at `rb'. If the writeable data
- * is in one segment the second segment has zero length.
- */
-void ll_ringbuffer_get_write_vector(const ll_ringbuffer_t *rb, ll_ringbuffer_data_t vec[2]);
-
-/**
- * Return the number of elements available for reading. This is the number of
- * elements in front of the read pointer and behind the write pointer.
- */
-size_t ll_ringbuffer_read_space(const ll_ringbuffer_t *rb);
-/**
- * The copying data reader. Copy at most `cnt' elements from `rb' to `dest'.
- * Returns the actual number of elements copied.
- */
-size_t ll_ringbuffer_read(ll_ringbuffer_t *rb, char *dest, size_t cnt);
-/**
- * The copying data reader w/o read pointer advance. Copy at most `cnt'
- * elements from `rb' to `dest'. Returns the actual number of elements copied.
- */
-size_t ll_ringbuffer_peek(ll_ringbuffer_t *rb, char *dest, size_t cnt);
-/** Advance the read pointer `cnt' places. */
-void ll_ringbuffer_read_advance(ll_ringbuffer_t *rb, size_t cnt);
-
-/**
- * Return the number of elements available for writing. This is the number of
- * elements in front of the write pointer and behind the read pointer.
- */
-size_t ll_ringbuffer_write_space(const ll_ringbuffer_t *rb);
-/**
- * The copying data writer. Copy at most `cnt' elements to `rb' from `src'.
- * Returns the actual number of elements copied.
- */
-size_t ll_ringbuffer_write(ll_ringbuffer_t *rb, const char *src, size_t cnt);
-/** Advance the write pointer `cnt' places. */
-void ll_ringbuffer_write_advance(ll_ringbuffer_t *rb, size_t cnt);
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* RINGBUFFER_H */
diff --git a/Alc/uhjfilter.c b/Alc/uhjfilter.c
deleted file mode 100644
index 42b0bc40..00000000
--- a/Alc/uhjfilter.c
+++ /dev/null
@@ -1,120 +0,0 @@
-
-#include "config.h"
-
-#include "alu.h"
-#include "uhjfilter.h"
-
-/* This is the maximum number of samples processed for each inner loop
- * iteration. */
-#define MAX_UPDATE_SAMPLES 128
-
-
-static const ALfloat Filter1CoeffSqr[4] = {
- 0.479400865589f, 0.876218493539f, 0.976597589508f, 0.997499255936f
-};
-static const ALfloat Filter2CoeffSqr[4] = {
- 0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156685f
-};
-
-static void allpass_process(AllPassState *state, ALfloat *restrict dst, const ALfloat *restrict src, const ALfloat aa, ALsizei todo)
-{
- ALfloat z1 = state->z[0];
- ALfloat z2 = state->z[1];
- ALsizei i;
-
- for(i = 0;i < todo;i++)
- {
- ALfloat input = src[i];
- ALfloat output = input*aa + z1;
- z1 = z2; z2 = output*aa - input;
- dst[i] = output;
- }
-
- state->z[0] = z1;
- state->z[1] = z2;
-}
-
-
-/* NOTE: There seems to be a bit of an inconsistency in how this encoding is
- * supposed to work. Some references, such as
- *
- * http://members.tripod.com/martin_leese/Ambisonic/UHJ_file_format.html
- *
- * specify a pre-scaling of sqrt(2) on the W channel input, while other
- * references, such as
- *
- * https://en.wikipedia.org/wiki/Ambisonic_UHJ_format#Encoding.5B1.5D
- * and
- * https://wiki.xiph.org/Ambisonics#UHJ_format
- *
- * do not. The sqrt(2) scaling is in line with B-Format decoder coefficients
- * which include such a scaling for the W channel input, however the original
- * source for this equation is a 1985 paper by Michael Gerzon, which does not
- * apparently include the scaling. Applying the extra scaling creates a louder
- * result with a narrower stereo image compared to not scaling, and I don't
- * know which is the intended result.
- */
-
-void EncodeUhj2(Uhj2Encoder *enc, ALfloat *restrict LeftOut, ALfloat *restrict RightOut, ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo)
-{
- ALfloat D[MAX_UPDATE_SAMPLES], S[MAX_UPDATE_SAMPLES];
- ALfloat temp[2][MAX_UPDATE_SAMPLES];
- ALsizei base, i;
-
- ASSUME(SamplesToDo > 0);
-
- for(base = 0;base < SamplesToDo;)
- {
- ALsizei todo = mini(SamplesToDo - base, MAX_UPDATE_SAMPLES);
- ASSUME(todo > 0);
-
- /* D = 0.6554516*Y */
- for(i = 0;i < todo;i++)
- temp[0][i] = 0.6554516f*InSamples[2][base+i];
- allpass_process(&enc->Filter1_Y[0], temp[1], temp[0], Filter1CoeffSqr[0], todo);
- allpass_process(&enc->Filter1_Y[1], temp[0], temp[1], Filter1CoeffSqr[1], todo);
- allpass_process(&enc->Filter1_Y[2], temp[1], temp[0], Filter1CoeffSqr[2], todo);
- allpass_process(&enc->Filter1_Y[3], temp[0], temp[1], Filter1CoeffSqr[3], todo);
- /* NOTE: Filter1 requires a 1 sample delay for the final output, so
- * take the last processed sample from the previous run as the first
- * output sample.
- */
- D[0] = enc->LastY;
- for(i = 1;i < todo;i++)
- D[i] = temp[0][i-1];
- enc->LastY = temp[0][i-1];
-
- /* D += j(-0.3420201*W + 0.5098604*X) */
- for(i = 0;i < todo;i++)
- temp[0][i] = -0.3420201f*InSamples[0][base+i] +
- 0.5098604f*InSamples[1][base+i];
- allpass_process(&enc->Filter2_WX[0], temp[1], temp[0], Filter2CoeffSqr[0], todo);
- allpass_process(&enc->Filter2_WX[1], temp[0], temp[1], Filter2CoeffSqr[1], todo);
- allpass_process(&enc->Filter2_WX[2], temp[1], temp[0], Filter2CoeffSqr[2], todo);
- allpass_process(&enc->Filter2_WX[3], temp[0], temp[1], Filter2CoeffSqr[3], todo);
- for(i = 0;i < todo;i++)
- D[i] += temp[0][i];
-
- /* S = 0.9396926*W + 0.1855740*X */
- for(i = 0;i < todo;i++)
- temp[0][i] = 0.9396926f*InSamples[0][base+i] +
- 0.1855740f*InSamples[1][base+i];
- allpass_process(&enc->Filter1_WX[0], temp[1], temp[0], Filter1CoeffSqr[0], todo);
- allpass_process(&enc->Filter1_WX[1], temp[0], temp[1], Filter1CoeffSqr[1], todo);
- allpass_process(&enc->Filter1_WX[2], temp[1], temp[0], Filter1CoeffSqr[2], todo);
- allpass_process(&enc->Filter1_WX[3], temp[0], temp[1], Filter1CoeffSqr[3], todo);
- S[0] = enc->LastWX;
- for(i = 1;i < todo;i++)
- S[i] = temp[0][i-1];
- enc->LastWX = temp[0][i-1];
-
- /* Left = (S + D)/2.0 */
- for(i = 0;i < todo;i++)
- *(LeftOut++) += (S[i] + D[i]) * 0.5f;
- /* Right = (S - D)/2.0 */
- for(i = 0;i < todo;i++)
- *(RightOut++) += (S[i] - D[i]) * 0.5f;
-
- base += todo;
- }
-}
diff --git a/Alc/vector.h b/Alc/vector.h
deleted file mode 100644
index ed9acfb0..00000000
--- a/Alc/vector.h
+++ /dev/null
@@ -1,94 +0,0 @@
-#ifndef AL_VECTOR_H
-#define AL_VECTOR_H
-
-#include <stdlib.h>
-
-#include <AL/al.h>
-
-#include "almalloc.h"
-
-
-#define TYPEDEF_VECTOR(T, N) typedef struct { \
- size_t Capacity; \
- size_t Size; \
- T Data[]; \
-} _##N; \
-typedef _##N* N; \
-typedef const _##N* const_##N;
-
-#define VECTOR(T) struct { \
- size_t Capacity; \
- size_t Size; \
- T Data[]; \
-}*
-
-#define VECTOR_INIT(_x) do { (_x) = NULL; } while(0)
-#define VECTOR_INIT_STATIC() NULL
-#define VECTOR_DEINIT(_x) do { al_free((_x)); (_x) = NULL; } while(0)
-
-#define VECTOR_RESIZE(_x, _s, _c) do { \
- size_t _size = (_s); \
- size_t _cap = (_c); \
- if(_size > _cap) \
- _cap = _size; \
- \
- if(!(_x) && _cap == 0) \
- break; \
- \
- if(((_x) ? (_x)->Capacity : 0) < _cap) \
- { \
- ptrdiff_t data_offset = (_x) ? (char*)((_x)->Data) - (char*)(_x) : \
- sizeof(*(_x)); \
- size_t old_size = ((_x) ? (_x)->Size : 0); \
- void *temp; \
- \
- temp = al_calloc(16, data_offset + sizeof((_x)->Data[0])*_cap); \
- assert(temp != NULL); \
- if((_x)) \
- memcpy(((char*)temp)+data_offset, (_x)->Data, \
- sizeof((_x)->Data[0])*old_size); \
- \
- al_free((_x)); \
- (_x) = temp; \
- (_x)->Capacity = _cap; \
- } \
- (_x)->Size = _size; \
-} while(0) \
-
-#define VECTOR_CAPACITY(_x) ((_x) ? (_x)->Capacity : 0)
-#define VECTOR_SIZE(_x) ((_x) ? (_x)->Size : 0)
-
-#define VECTOR_BEGIN(_x) ((_x) ? (_x)->Data + 0 : NULL)
-#define VECTOR_END(_x) ((_x) ? (_x)->Data + (_x)->Size : NULL)
-
-#define VECTOR_PUSH_BACK(_x, _obj) do { \
- size_t _pbsize = VECTOR_SIZE(_x)+1; \
- VECTOR_RESIZE(_x, _pbsize, _pbsize); \
- (_x)->Data[(_x)->Size-1] = (_obj); \
-} while(0)
-#define VECTOR_POP_BACK(_x) ((void)((_x)->Size--))
-
-#define VECTOR_BACK(_x) ((_x)->Data[(_x)->Size-1])
-#define VECTOR_FRONT(_x) ((_x)->Data[0])
-
-#define VECTOR_ELEM(_x, _o) ((_x)->Data[(_o)])
-
-#define VECTOR_FOR_EACH(_t, _x, _f) do { \
- _t *_iter = VECTOR_BEGIN((_x)); \
- _t *_end = VECTOR_END((_x)); \
- for(;_iter != _end;++_iter) \
- _f(_iter); \
-} while(0)
-
-#define VECTOR_FIND_IF(_i, _t, _x, _f) do { \
- _t *_iter = VECTOR_BEGIN((_x)); \
- _t *_end = VECTOR_END((_x)); \
- for(;_iter != _end;++_iter) \
- { \
- if(_f(_iter)) \
- break; \
- } \
- (_i) = _iter; \
-} while(0)
-
-#endif /* AL_VECTOR_H */
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4fb2be56..5f7e1a84 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,26 +16,38 @@ IF(COMMAND CMAKE_POLICY)
IF(POLICY CMP0054)
CMAKE_POLICY(SET CMP0054 NEW)
ENDIF(POLICY CMP0054)
+ IF(POLICY CMP0075)
+ CMAKE_POLICY(SET CMP0075 NEW)
+ ENDIF(POLICY CMP0075)
ENDIF(COMMAND CMAKE_POLICY)
+IF(NOT CMAKE_BUILD_TYPE)
+ SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING
+ "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel."
+ FORCE)
+ENDIF()
+IF(NOT CMAKE_DEBUG_POSTFIX)
+ SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING
+ "Library postfix for debug builds. Normally left blank."
+ FORCE)
+ENDIF()
+
SET(CMAKE_MODULE_PATH "${OpenAL_SOURCE_DIR}/cmake")
+
INCLUDE(CheckFunctionExists)
INCLUDE(CheckLibraryExists)
-INCLUDE(CheckSharedFunctionExists)
INCLUDE(CheckIncludeFile)
INCLUDE(CheckIncludeFiles)
INCLUDE(CheckSymbolExists)
INCLUDE(CheckCCompilerFlag)
INCLUDE(CheckCXXCompilerFlag)
INCLUDE(CheckCSourceCompiles)
+INCLUDE(CheckCXXSourceCompiles)
INCLUDE(CheckTypeSize)
include(CheckStructHasMember)
-include(CheckFileOffsetBits)
include(GNUInstallDirs)
-SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS TRUE)
-
OPTION(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON)
@@ -62,7 +74,7 @@ if(DEFINED LIB_SUFFIX)
endif()
-SET(CPP_DEFS ) # C pre-process, not C++
+SET(CPP_DEFS ) # C pre-processor, not C++
SET(INC_PATHS )
SET(C_FLAGS )
SET(LINKER_FLAGS )
@@ -102,35 +114,27 @@ IF(NOT LIBTYPE)
ENDIF()
SET(LIB_MAJOR_VERSION "1")
-SET(LIB_MINOR_VERSION "19")
-SET(LIB_REVISION "1")
+SET(LIB_MINOR_VERSION "20")
+SET(LIB_REVISION "0")
SET(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}")
+SET(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0)
SET(EXPORT_DECL "")
-SET(ALIGN_DECL "")
IF(OPENAL_CUSTOM_CONFIG)
include(${OPENAL_CUSTOM_CONFIG})
ENDIF()
CHECK_TYPE_SIZE("long" SIZEOF_LONG)
-CHECK_TYPE_SIZE("long long" SIZEOF_LONG_LONG)
-CHECK_C_COMPILER_FLAG(-std=c11 HAVE_STD_C11)
-IF(HAVE_STD_C11)
- SET(CMAKE_C_FLAGS "-std=c11 ${CMAKE_C_FLAGS}")
-ELSE()
- CHECK_C_COMPILER_FLAG(-std=c99 HAVE_STD_C99)
- IF(HAVE_STD_C99)
- SET(CMAKE_C_FLAGS "-std=c99 ${CMAKE_C_FLAGS}")
- ENDIF()
-ENDIF()
+# Require C++11
+SET(CMAKE_CXX_STANDARD 11)
+SET(CMAKE_CXX_STANDARD_REQUIRED TRUE)
+
+# Prefer C11, but support C99 and C90 too.
+SET(CMAKE_C_STANDARD 11)
-CHECK_CXX_COMPILER_FLAG(-std=c++11 HAVE_STD_CXX11)
-IF(HAVE_STD_CXX11)
- SET(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
-ENDIF()
if(NOT WIN32)
# Check if _POSIX_C_SOURCE and _XOPEN_SOURCE needs to be set for POSIX functions
@@ -148,74 +152,20 @@ if(NOT WIN32)
UNSET(OLD_REQUIRED_FLAGS)
ENDIF()
-# Set defines for large file support
-CHECK_FILE_OFFSET_BITS()
-IF(_FILE_OFFSET_BITS)
- SET(CPP_DEFS ${CPP_DEFS} "_FILE_OFFSET_BITS=${_FILE_OFFSET_BITS}")
- SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -D_FILE_OFFSET_BITS=${_FILE_OFFSET_BITS}")
+# C99 has restrict, but C++ does not, so we can only utilize __restrict.
+SET(RESTRICT_DECL )
+CHECK_CXX_SOURCE_COMPILES("int *__restrict foo;
+int main() { return 0; }" HAVE___RESTRICT)
+IF(HAVE___RESTRICT)
+ SET(RESTRICT_DECL "__restrict")
ENDIF()
-SET(CPP_DEFS ${CPP_DEFS} _LARGEFILE_SOURCE _LARGE_FILES)
-SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -D_LARGEFILE_SOURCE -D_LARGE_FILES")
-
-# MSVC may need workarounds for C99 restrict and inline
-IF(MSVC)
- # TODO: Once we truly require C99, these restrict and inline checks should go
- # away.
- CHECK_C_SOURCE_COMPILES("int *restrict foo;
- int main() {return 0;}" HAVE_RESTRICT)
- IF(NOT HAVE_RESTRICT)
- SET(CPP_DEFS ${CPP_DEFS} "restrict=")
- SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Drestrict=")
- ENDIF()
-
- CHECK_C_SOURCE_COMPILES("inline void foo(void) { }
- int main() {return 0;}" HAVE_INLINE)
- IF(NOT HAVE_INLINE)
- CHECK_C_SOURCE_COMPILES("__inline void foo(void) { }
- int main() {return 0;}" HAVE___INLINE)
- IF(NOT HAVE___INLINE)
- MESSAGE(FATAL_ERROR "No inline keyword found, please report!")
- ENDIF()
- SET(CPP_DEFS ${CPP_DEFS} inline=__inline)
- SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Dinline=__inline")
- ENDIF()
-ENDIF()
-
-# Make sure we have C99-style inline semantics with GCC (4.3 or newer).
-IF(CMAKE_COMPILER_IS_GNUCC)
- SET(CMAKE_C_FLAGS "-fno-gnu89-inline ${CMAKE_C_FLAGS}")
-
- SET(OLD_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
- # Force no inlining for the next test.
- SET(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -fno-inline")
-
- CHECK_C_SOURCE_COMPILES("extern inline int foo() { return 0; }
- int main() {return foo();}" INLINE_IS_C99)
- IF(NOT INLINE_IS_C99)
- MESSAGE(FATAL_ERROR "Your compiler does not seem to have C99 inline semantics!
- Please update your compiler for better C99 compliance.")
- ENDIF()
-
- SET(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}")
-ENDIF()
-
-# Check if we have a proper timespec declaration
-CHECK_STRUCT_HAS_MEMBER("struct timespec" tv_sec time.h HAVE_STRUCT_TIMESPEC)
-IF(HAVE_STRUCT_TIMESPEC)
- # Define it here so we don't have to include config.h for it
- SET(CPP_DEFS ${CPP_DEFS} HAVE_STRUCT_TIMESPEC)
-ENDIF()
-
-# Some systems may need libatomic for C11 atomic functions to work
+# Some systems may need libatomic for atomic functions to work
SET(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
SET(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES} atomic)
-CHECK_C_SOURCE_COMPILES("#include <stdatomic.h>
-int _Atomic foo = ATOMIC_VAR_INIT(0);
-int main()
-{
- return atomic_fetch_add(&foo, 2);
-}"
+CHECK_CXX_SOURCE_COMPILES("#include <atomic>
+std::atomic<int> foo{0};
+int main() { return foo.fetch_add(2); }"
HAVE_LIBATOMIC)
IF(NOT HAVE_LIBATOMIC)
SET(CMAKE_REQUIRED_LIBRARIES "${OLD_REQUIRED_LIBRARIES}")
@@ -231,62 +181,13 @@ IF(HAVE_LIBLOG)
SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log)
ENDIF()
-# Check if we have C99 bool
-CHECK_C_SOURCE_COMPILES(
-"int main(int argc, char *argv[])
- {
- volatile _Bool ret;
- ret = (argc > 1) ? 1 : 0;
- return ret ? -1 : 0;
- }"
-HAVE_C99_BOOL)
-
-# Check if we have C11 static_assert
-CHECK_C_SOURCE_COMPILES(
-"int main()
- {
- _Static_assert(sizeof(int) == sizeof(int), \"What\");
- return 0;
- }"
-HAVE_C11_STATIC_ASSERT)
-
-# Check if we have C11 alignas
-CHECK_C_SOURCE_COMPILES(
-"_Alignas(16) int foo;
- int main()
- {
- return 0;
- }"
-HAVE_C11_ALIGNAS)
-
-# Check if we have C11 _Atomic
-CHECK_C_SOURCE_COMPILES(
-"#include <stdatomic.h>
- int _Atomic foo = ATOMIC_VAR_INIT(0);
- int main()
- {
- atomic_fetch_add(&foo, 2);
- return 0;
- }"
-HAVE_C11_ATOMIC)
-
-# Add definitions, compiler switches, etc.
-INCLUDE_DIRECTORIES("${OpenAL_SOURCE_DIR}/include" "${OpenAL_SOURCE_DIR}/common" "${OpenAL_BINARY_DIR}")
-
-IF(NOT CMAKE_BUILD_TYPE)
- SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING
- "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel."
- FORCE)
-ENDIF()
-IF(NOT CMAKE_DEBUG_POSTFIX)
- SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING
- "Library postfix for debug builds. Normally left blank."
- FORCE)
-ENDIF()
-
IF(MSVC)
- SET(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
- SET(C_FLAGS ${C_FLAGS} /wd4098)
+ SET(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS NOMINMAX)
+ CHECK_CXX_COMPILER_FLAG(/permissive- HAVE_PERMISSIVE_SWITCH)
+ IF(HAVE_PERMISSIVE_SWITCH)
+ SET(C_FLAGS ${C_FLAGS} $<$<COMPILE_LANGUAGE:CXX>:/permissive->)
+ ENDIF()
+ SET(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4268 /wd4324)
IF(NOT DXSDK_DIR)
STRING(REGEX REPLACE "\\\\" "/" DXSDK_DIR "$ENV{DXSDK_DIR}")
@@ -308,11 +209,9 @@ IF(MSVC)
ENDFOREACH(flag_var)
ENDIF()
ELSE()
- SET(C_FLAGS ${C_FLAGS} -Winline -Wall)
- CHECK_C_COMPILER_FLAG(-Wextra HAVE_W_EXTRA)
- IF(HAVE_W_EXTRA)
- SET(C_FLAGS ${C_FLAGS} -Wextra)
- ENDIF()
+ SET(C_FLAGS ${C_FLAGS} -Winline -Wunused -Wall -Wextra -Wshadow -Wconversion -Wcast-align
+ -Wpedantic
+ $<$<COMPILE_LANGUAGE:CXX>:-Wold-style-cast -Wnon-virtual-dtor -Woverloaded-virtual>)
IF(ALSOFT_WERROR)
SET(C_FLAGS ${C_FLAGS} -Werror)
@@ -331,37 +230,54 @@ ELSE()
SET(C_FLAGS ${C_FLAGS} -fno-math-errno)
ENDIF()
- CHECK_C_SOURCE_COMPILES("int foo() __attribute__((destructor));
- int main() {return 0;}" HAVE_GCC_DESTRUCTOR)
-
option(ALSOFT_STATIC_LIBGCC "Force -static-libgcc for static GCC runtimes" OFF)
if(ALSOFT_STATIC_LIBGCC)
set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} -static-libgcc)
- check_c_source_compiles(
-"#include <stdlib.h>
-int main()
-{
- return 0;
-}"
- HAVE_STATIC_LIBGCC_SWITCH
- )
- if(HAVE_STATIC_LIBGCC_SWITCH)
- SET(LINKER_FLAGS ${LINKER_FLAGS} -static-libgcc)
+ check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBGCC_SWITCH)
+ set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES})
+ unset(OLD_REQUIRED_LIBRARIES)
+
+ if(NOT HAVE_STATIC_LIBGCC_SWITCH)
+ message(FATAL_ERROR "Cannot static link libgcc")
endif()
+ set(LINKER_FLAGS ${LINKER_FLAGS} -static-libgcc)
+ endif()
+
+ option(ALSOFT_STATIC_STDCXX "Static link libstdc++" OFF)
+ if(ALSOFT_STATIC_STDCXX)
+ set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
+ set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lstdc++,--pop-state")
+ check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBSTDCXX_SWITCH)
set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES})
unset(OLD_REQUIRED_LIBRARIES)
+
+ if(NOT HAVE_STATIC_LIBSTDCXX_SWITCH)
+ message(FATAL_ERROR "Cannot static link libstdc++")
+ endif()
+ set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--push-state,-Bstatic,-lstdc++,--pop-state")
+ endif()
+
+ if(WIN32)
+ option(ALSOFT_STATIC_WINPTHREAD "Static link libwinpthread" OFF)
+ if(ALSOFT_STATIC_WINPTHREAD)
+ set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
+ set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lwinpthread,--pop-state")
+ check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBWINPTHREAD_SWITCH)
+ set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES})
+ unset(OLD_REQUIRED_LIBRARIES)
+
+ if(NOT HAVE_STATIC_LIBWINPTHREAD_SWITCH)
+ message(FATAL_ERROR "Cannot static link libwinpthread")
+ endif()
+ set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--push-state,-Bstatic,-lwinpthread,--pop-state")
+ endif()
endif()
ENDIF()
# Set visibility/export options if available
IF(WIN32)
SET(EXPORT_DECL "__declspec(dllexport)")
- IF(NOT MINGW)
- SET(ALIGN_DECL "__declspec(align(x))")
- ELSE()
- SET(ALIGN_DECL "__declspec(aligned(x))")
- ENDIF()
ELSE()
SET(OLD_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
# Yes GCC, really don't accept visibility modes you don't support
@@ -386,38 +302,14 @@ ELSE()
ENDIF()
ENDIF()
- CHECK_C_SOURCE_COMPILES("int foo __attribute__((aligned(16)));
- int main() {return 0;}" HAVE_ATTRIBUTE_ALIGNED)
- IF(HAVE_ATTRIBUTE_ALIGNED)
- SET(ALIGN_DECL "__attribute__((aligned(x)))")
- ENDIF()
-
SET(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}")
ENDIF()
-CHECK_C_SOURCE_COMPILES("
-int main()
-{
- float *ptr;
- ptr = __builtin_assume_aligned(ptr, 16);
- return 0;
-}" HAVE___BUILTIN_ASSUME_ALIGNED)
-IF(HAVE___BUILTIN_ASSUME_ALIGNED)
- SET(ASSUME_ALIGNED_DECL "__builtin_assume_aligned(x, y)")
-ELSE()
- SET(ASSUME_ALIGNED_DECL "(x)")
-ENDIF()
-
-SET(SSE_SWITCH "")
SET(SSE2_SWITCH "")
SET(SSE3_SWITCH "")
SET(SSE4_1_SWITCH "")
SET(FPU_NEON_SWITCH "")
-CHECK_C_COMPILER_FLAG(-msse HAVE_MSSE_SWITCH)
-IF(HAVE_MSSE_SWITCH)
- SET(SSE_SWITCH "-msse")
-ENDIF()
CHECK_C_COMPILER_FLAG(-msse2 HAVE_MSSE2_SWITCH)
IF(HAVE_MSSE2_SWITCH)
SET(SSE2_SWITCH "-msse2")
@@ -435,11 +327,15 @@ IF(HAVE_MFPU_NEON_SWITCH)
SET(FPU_NEON_SWITCH "-mfpu=neon")
ENDIF()
+CHECK_INCLUDE_FILE(xmmintrin.h HAVE_XMMINTRIN_H "${SSE2_SWITCH}")
+CHECK_INCLUDE_FILE(emmintrin.h HAVE_EMMINTRIN_H "${SSE2_SWITCH}")
+CHECK_INCLUDE_FILE(pmmintrin.h HAVE_PMMINTRIN_H "${SSE3_SWITCH}")
+CHECK_INCLUDE_FILE(smmintrin.h HAVE_SMMINTRIN_H "${SSE4_1_SWITCH}")
+CHECK_INCLUDE_FILE(arm_neon.h HAVE_ARM_NEON_H "${FPU_NEON_SWITCH}")
+
+SET(SSE_FLAGS )
SET(FPMATH_SET "0")
IF(CMAKE_SIZEOF_VOID_P MATCHES "4")
- IF(SSE_SWITCH OR MSVC)
- OPTION(ALSOFT_ENABLE_SSE_CODEGEN "Enable SSE code generation instead of x87 for 32-bit targets." TRUE)
- ENDIF()
IF(SSE2_SWITCH OR MSVC)
OPTION(ALSOFT_ENABLE_SSE2_CODEGEN "Enable SSE2 code generation instead of x87 for 32-bit targets." TRUE)
ENDIF()
@@ -448,55 +344,48 @@ IF(CMAKE_SIZEOF_VOID_P MATCHES "4")
IF(SSE2_SWITCH)
CHECK_C_COMPILER_FLAG("${SSE2_SWITCH} -mfpmath=sse" HAVE_MFPMATH_SSE_2)
IF(HAVE_MFPMATH_SSE_2)
- SET(C_FLAGS ${C_FLAGS} ${SSE2_SWITCH} -mfpmath=sse)
+ SET(SSE_FLAGS ${SSE_FLAGS} ${SSE2_SWITCH} -mfpmath=sse)
+ SET(C_FLAGS ${C_FLAGS} ${SSE_FLAGS})
SET(FPMATH_SET 2)
ENDIF()
ELSEIF(MSVC)
CHECK_C_COMPILER_FLAG("/arch:SSE2" HAVE_ARCH_SSE2)
IF(HAVE_ARCH_SSE2)
- SET(C_FLAGS ${C_FLAGS} "/arch:SSE2")
+ SET(SSE_FLAGS ${SSE_FLAGS} "/arch:SSE2")
+ SET(C_FLAGS ${C_FLAGS} ${SSE_FLAGS})
SET(FPMATH_SET 2)
ENDIF()
ENDIF()
ENDIF()
- IF(ALSOFT_ENABLE_SSE_CODEGEN AND NOT FPMATH_SET)
- IF(SSE_SWITCH)
- CHECK_C_COMPILER_FLAG("${SSE_SWITCH} -mfpmath=sse" HAVE_MFPMATH_SSE)
- IF(HAVE_MFPMATH_SSE)
- SET(C_FLAGS ${C_FLAGS} ${SSE_SWITCH} -mfpmath=sse)
- SET(FPMATH_SET 1)
- ENDIF()
- ELSEIF(MSVC)
- CHECK_C_COMPILER_FLAG("/arch:SSE" HAVE_ARCH_SSE)
- IF(HAVE_ARCH_SSE)
- SET(C_FLAGS ${C_FLAGS} "/arch:SSE")
- SET(FPMATH_SET 1)
- ENDIF()
- ENDIF()
- ENDIF()
+ENDIF()
+
+IF(HAVE_EMMINTRIN_H)
+ SET(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
+ FOREACH(flag_var ${SSE_FLAGS})
+ SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${flag_var}")
+ ENDFOREACH()
+
+ CHECK_C_SOURCE_COMPILES("#include <emmintrin.h>
+ int main() {_mm_pause(); return 0;}" HAVE_SSE_INTRINSICS)
+
+ SET(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS})
ENDIF()
CHECK_C_SOURCE_COMPILES("int foo(const char *str, ...) __attribute__((format(printf, 1, 2)));
int main() {return 0;}" HAVE_GCC_FORMAT)
-CHECK_INCLUDE_FILE(stdbool.h HAVE_STDBOOL_H)
-CHECK_INCLUDE_FILE(stdalign.h HAVE_STDALIGN_H)
CHECK_INCLUDE_FILE(malloc.h HAVE_MALLOC_H)
CHECK_INCLUDE_FILE(dirent.h HAVE_DIRENT_H)
-CHECK_INCLUDE_FILE(strings.h HAVE_STRINGS_H)
CHECK_INCLUDE_FILE(cpuid.h HAVE_CPUID_H)
CHECK_INCLUDE_FILE(intrin.h HAVE_INTRIN_H)
CHECK_INCLUDE_FILE(sys/sysconf.h HAVE_SYS_SYSCONF_H)
-CHECK_INCLUDE_FILE(fenv.h HAVE_FENV_H)
-CHECK_INCLUDE_FILE(float.h HAVE_FLOAT_H)
-CHECK_INCLUDE_FILE(ieeefp.h HAVE_IEEEFP_H)
CHECK_INCLUDE_FILE(guiddef.h HAVE_GUIDDEF_H)
IF(NOT HAVE_GUIDDEF_H)
CHECK_INCLUDE_FILE(initguid.h HAVE_INITGUID_H)
ENDIF()
-# Some systems need libm for some of the following math functions to work
+# Some systems need libm for some math functions to work
SET(MATH_LIB )
CHECK_LIBRARY_EXISTS(m pow "" HAVE_LIBM)
IF(HAVE_LIBM)
@@ -504,15 +393,22 @@ IF(HAVE_LIBM)
SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} m)
ENDIF()
+# Some systems need to link with -lrt for clock_gettime as used by the common
+# eaxmple functions.
+SET(RT_LIB )
+CHECK_LIBRARY_EXISTS(rt clock_gettime "" HAVE_LIBRT)
+IF(HAVE_LIBRT)
+ SET(RT_LIB rt)
+ENDIF()
+
# Check for the dlopen API (for dynamicly loading backend libs)
IF(ALSOFT_DLOPEN)
+ CHECK_INCLUDE_FILE(dlfcn.h HAVE_DLFCN_H)
CHECK_LIBRARY_EXISTS(dl dlopen "" HAVE_LIBDL)
IF(HAVE_LIBDL)
SET(EXTRA_LIBS dl ${EXTRA_LIBS})
SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} dl)
ENDIF()
-
- CHECK_INCLUDE_FILE(dlfcn.h HAVE_DLFCN_H)
ENDIF()
# Check for a cpuid intrinsic
@@ -548,105 +444,19 @@ IF(HAVE_INTRIN_H)
}" HAVE_BITSCANFORWARD_INTRINSIC)
ENDIF()
-CHECK_SYMBOL_EXISTS(sysconf unistd.h HAVE_SYSCONF)
-CHECK_SYMBOL_EXISTS(aligned_alloc stdlib.h HAVE_ALIGNED_ALLOC)
CHECK_SYMBOL_EXISTS(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN)
CHECK_SYMBOL_EXISTS(_aligned_malloc malloc.h HAVE__ALIGNED_MALLOC)
CHECK_SYMBOL_EXISTS(proc_pidpath libproc.h HAVE_PROC_PIDPATH)
-CHECK_SYMBOL_EXISTS(lrintf math.h HAVE_LRINTF)
-CHECK_SYMBOL_EXISTS(modff math.h HAVE_MODFF)
-CHECK_SYMBOL_EXISTS(log2f math.h HAVE_LOG2F)
-CHECK_SYMBOL_EXISTS(cbrtf math.h HAVE_CBRTF)
-CHECK_SYMBOL_EXISTS(copysignf math.h HAVE_COPYSIGNF)
-
-IF(HAVE_FLOAT_H)
- CHECK_SYMBOL_EXISTS(_controlfp float.h HAVE__CONTROLFP)
- CHECK_SYMBOL_EXISTS(__control87_2 float.h HAVE___CONTROL87_2)
-ENDIF()
CHECK_FUNCTION_EXISTS(stat HAVE_STAT)
-CHECK_FUNCTION_EXISTS(strtof HAVE_STRTOF)
-CHECK_FUNCTION_EXISTS(strcasecmp HAVE_STRCASECMP)
-IF(NOT HAVE_STRCASECMP)
- CHECK_FUNCTION_EXISTS(_stricmp HAVE__STRICMP)
- IF(NOT HAVE__STRICMP)
- MESSAGE(FATAL_ERROR "No case-insensitive compare function found, please report!")
- ENDIF()
-
- SET(CPP_DEFS ${CPP_DEFS} strcasecmp=_stricmp)
-ENDIF()
-
-CHECK_FUNCTION_EXISTS(strncasecmp HAVE_STRNCASECMP)
-IF(NOT HAVE_STRNCASECMP)
- CHECK_FUNCTION_EXISTS(_strnicmp HAVE__STRNICMP)
- IF(NOT HAVE__STRNICMP)
- MESSAGE(FATAL_ERROR "No case-insensitive size-limitted compare function found, please report!")
- ENDIF()
-
- SET(CPP_DEFS ${CPP_DEFS} strncasecmp=_strnicmp)
-ENDIF()
-
-CHECK_SYMBOL_EXISTS(strnlen string.h HAVE_STRNLEN)
-CHECK_SYMBOL_EXISTS(snprintf stdio.h HAVE_SNPRINTF)
-IF(NOT HAVE_SNPRINTF)
- CHECK_FUNCTION_EXISTS(_snprintf HAVE__SNPRINTF)
- IF(NOT HAVE__SNPRINTF)
- MESSAGE(FATAL_ERROR "No snprintf function found, please report!")
- ENDIF()
-
- SET(CPP_DEFS ${CPP_DEFS} snprintf=_snprintf)
-ENDIF()
-CHECK_SYMBOL_EXISTS(isfinite math.h HAVE_ISFINITE)
-IF(NOT HAVE_ISFINITE)
- CHECK_FUNCTION_EXISTS(finite HAVE_FINITE)
- IF(NOT HAVE_FINITE)
- CHECK_FUNCTION_EXISTS(_finite HAVE__FINITE)
- IF(NOT HAVE__FINITE)
- MESSAGE(FATAL_ERROR "No isfinite function found, please report!")
- ENDIF()
- SET(CPP_DEFS ${CPP_DEFS} isfinite=_finite)
- ELSE()
- SET(CPP_DEFS ${CPP_DEFS} isfinite=finite)
- ENDIF()
-ENDIF()
-
-CHECK_SYMBOL_EXISTS(isnan math.h HAVE_ISNAN)
-IF(NOT HAVE_ISNAN)
- CHECK_FUNCTION_EXISTS(_isnan HAVE__ISNAN)
- IF(NOT HAVE__ISNAN)
- MESSAGE(FATAL_ERROR "No isnan function found, please report!")
- ENDIF()
-
- SET(CPP_DEFS ${CPP_DEFS} isnan=_isnan)
-ENDIF()
-
-
-# Check if we have Windows headers
-SET(OLD_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS})
-SET(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -D_WIN32_WINNT=0x0502)
-CHECK_INCLUDE_FILE(windows.h HAVE_WINDOWS_H)
-SET(CMAKE_REQUIRED_DEFINITIONS ${OLD_REQUIRED_DEFINITIONS})
-UNSET(OLD_REQUIRED_DEFINITIONS)
-
-IF(NOT HAVE_WINDOWS_H)
- CHECK_SYMBOL_EXISTS(gettimeofday sys/time.h HAVE_GETTIMEOFDAY)
- IF(NOT HAVE_GETTIMEOFDAY)
- MESSAGE(FATAL_ERROR "No timing function found!")
- ENDIF()
-
- CHECK_SYMBOL_EXISTS(nanosleep time.h HAVE_NANOSLEEP)
- IF(NOT HAVE_NANOSLEEP)
- MESSAGE(FATAL_ERROR "No sleep function found!")
- ENDIF()
-
- # We need pthreads outside of Windows
+IF(NOT WIN32)
+ # We need pthreads outside of Windows, for semaphores. It's also used to
+ # set the priority and name of threads, when possible.
CHECK_INCLUDE_FILE(pthread.h HAVE_PTHREAD_H)
IF(NOT HAVE_PTHREAD_H)
MESSAGE(FATAL_ERROR "PThreads is required for non-Windows builds!")
ENDIF()
- # Some systems need pthread_np.h to get recursive mutexes
- CHECK_INCLUDE_FILES("pthread.h;pthread_np.h" HAVE_PTHREAD_NP_H)
CHECK_C_COMPILER_FLAG(-pthread HAVE_PTHREAD)
IF(NOT HAVE_ANDROID_H)
@@ -667,6 +477,8 @@ IF(NOT HAVE_WINDOWS_H)
CHECK_SYMBOL_EXISTS(pthread_setschedparam pthread.h HAVE_PTHREAD_SETSCHEDPARAM)
+ # Some systems need pthread_np.h to get pthread_setname_np
+ CHECK_INCLUDE_FILES("pthread.h;pthread_np.h" HAVE_PTHREAD_NP_H)
IF(HAVE_PTHREAD_NP_H)
CHECK_SYMBOL_EXISTS(pthread_setname_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SETNAME_NP)
IF(NOT HAVE_PTHREAD_SETNAME_NP)
@@ -693,7 +505,6 @@ int main()
PTHREAD_SETNAME_NP_THREE_PARAMS
)
ENDIF()
- CHECK_SYMBOL_EXISTS(pthread_mutexattr_setkind_np "pthread.h;pthread_np.h" HAVE_PTHREAD_MUTEXATTR_SETKIND_NP)
ELSE()
CHECK_SYMBOL_EXISTS(pthread_setname_np pthread.h HAVE_PTHREAD_SETNAME_NP)
IF(NOT HAVE_PTHREAD_SETNAME_NP)
@@ -718,132 +529,122 @@ int main()
PTHREAD_SETNAME_NP_THREE_PARAMS
)
ENDIF()
- CHECK_SYMBOL_EXISTS(pthread_mutexattr_setkind_np pthread.h HAVE_PTHREAD_MUTEXATTR_SETKIND_NP)
- ENDIF()
-
- CHECK_SYMBOL_EXISTS(pthread_mutex_timedlock pthread.h HAVE_PTHREAD_MUTEX_TIMEDLOCK)
-
- CHECK_LIBRARY_EXISTS(rt clock_gettime "" HAVE_LIBRT)
- IF(HAVE_LIBRT)
- SET(EXTRA_LIBS rt ${EXTRA_LIBS})
ENDIF()
ENDIF()
CHECK_SYMBOL_EXISTS(getopt unistd.h HAVE_GETOPT)
-# Check for a 64-bit type
-CHECK_INCLUDE_FILE(stdint.h HAVE_STDINT_H)
-IF(NOT HAVE_STDINT_H)
- IF(HAVE_WINDOWS_H)
- CHECK_C_SOURCE_COMPILES("#define _WIN32_WINNT 0x0502
- #include <windows.h>
- __int64 foo;
- int main() {return 0;}" HAVE___INT64)
- ENDIF()
- IF(NOT HAVE___INT64)
- IF(NOT SIZEOF_LONG MATCHES "8")
- IF(NOT SIZEOF_LONG_LONG MATCHES "8")
- MESSAGE(FATAL_ERROR "No 64-bit types found, please report!")
- ENDIF()
- ENDIF()
- ENDIF()
-ENDIF()
-
+# Common sources used by both the OpenAL implementation library and potentially
+# the OpenAL router.
SET(COMMON_OBJS
- common/alcomplex.c
+ common/albyte.h
+ common/alcomplex.cpp
common/alcomplex.h
- common/align.h
- common/almalloc.c
+ common/alexcpt.cpp
+ common/alexcpt.h
+ common/alfstream.cpp
+ common/alfstream.h
+ common/almalloc.cpp
common/almalloc.h
- common/atomic.c
+ common/alnumeric.h
+ common/aloptional.h
+ common/alspan.h
+ common/alstring.cpp
+ common/alstring.h
common/atomic.h
- common/bool.h
+ common/dynload.cpp
+ common/dynload.h
+ common/endiantest.h
+ common/intrusive_ptr.h
common/math_defs.h
- common/rwlock.c
- common/rwlock.h
- common/static_assert.h
- common/threads.c
+ common/opthelpers.h
+ common/pragmadefs.h
+ common/strutils.cpp
+ common/strutils.h
+ common/threads.cpp
common/threads.h
- common/uintmap.c
- common/uintmap.h
+ common/vecmat.h
+ common/vector.h
)
SET(OPENAL_OBJS
- OpenAL32/Include/bs2b.h
- OpenAL32/Include/alMain.h
- OpenAL32/Include/alu.h
-
- OpenAL32/Include/alAuxEffectSlot.h
- OpenAL32/alAuxEffectSlot.c
- OpenAL32/Include/alBuffer.h
- OpenAL32/alBuffer.c
- OpenAL32/Include/alEffect.h
- OpenAL32/alEffect.c
- OpenAL32/Include/alError.h
- OpenAL32/alError.c
- OpenAL32/alExtension.c
- OpenAL32/Include/alFilter.h
- OpenAL32/alFilter.c
- OpenAL32/Include/alListener.h
- OpenAL32/alListener.c
- OpenAL32/Include/alSource.h
- OpenAL32/alSource.c
- OpenAL32/alState.c
- OpenAL32/event.c
- OpenAL32/Include/sample_cvt.h
- OpenAL32/sample_cvt.c
+ al/auxeffectslot.cpp
+ al/auxeffectslot.h
+ al/buffer.cpp
+ al/buffer.h
+ al/effect.cpp
+ al/effect.h
+ al/error.cpp
+ al/event.cpp
+ al/event.h
+ al/extension.cpp
+ al/filter.cpp
+ al/filter.h
+ al/listener.cpp
+ al/listener.h
+ al/source.cpp
+ al/source.h
+ al/state.cpp
)
SET(ALC_OBJS
- Alc/ALc.c
- Alc/ALu.c
- Alc/alconfig.c
- Alc/alconfig.h
- Alc/bs2b.c
- Alc/converter.c
- Alc/converter.h
- Alc/inprogext.h
- Alc/mastering.c
- Alc/mastering.h
- Alc/ringbuffer.c
- Alc/ringbuffer.h
- Alc/effects/autowah.c
- Alc/effects/chorus.c
- Alc/effects/compressor.c
- Alc/effects/dedicated.c
- Alc/effects/distortion.c
- Alc/effects/echo.c
- Alc/effects/equalizer.c
- Alc/effects/fshifter.c
- Alc/effects/modulator.c
- Alc/effects/null.c
- Alc/effects/pshifter.c
- Alc/effects/reverb.c
- Alc/filters/defs.h
- Alc/filters/filter.c
- Alc/filters/nfc.c
- Alc/filters/nfc.h
- Alc/filters/splitter.c
- Alc/filters/splitter.h
- Alc/helpers.c
- Alc/alstring.h
- Alc/compat.h
- Alc/cpu_caps.h
- Alc/fpu_modes.h
- Alc/logging.h
- Alc/vector.h
- Alc/hrtf.c
- Alc/hrtf.h
- Alc/uhjfilter.c
- Alc/uhjfilter.h
- Alc/ambdec.c
- Alc/ambdec.h
- Alc/bformatdec.c
- Alc/bformatdec.h
- Alc/panning.c
- Alc/polymorphism.h
- Alc/mixvoice.c
- Alc/mixer/defs.h
- Alc/mixer/mixer_c.c
+ alc/alc.cpp
+ alc/alcmain.h
+ alc/alu.cpp
+ alc/alu.h
+ alc/alconfig.cpp
+ alc/alconfig.h
+ alc/alcontext.h
+ alc/ambdec.cpp
+ alc/ambdec.h
+ alc/ambidefs.h
+ alc/bformatdec.cpp
+ alc/bformatdec.h
+ alc/bs2b.cpp
+ alc/bs2b.h
+ alc/compat.h
+ alc/converter.cpp
+ alc/converter.h
+ alc/cpu_caps.h
+ alc/devformat.h
+ alc/effects/base.h
+ alc/effects/autowah.cpp
+ alc/effects/chorus.cpp
+ alc/effects/compressor.cpp
+ alc/effects/dedicated.cpp
+ alc/effects/distortion.cpp
+ alc/effects/echo.cpp
+ alc/effects/equalizer.cpp
+ alc/effects/fshifter.cpp
+ alc/effects/modulator.cpp
+ alc/effects/null.cpp
+ alc/effects/pshifter.cpp
+ alc/effects/reverb.cpp
+ alc/effects/vmorpher.cpp
+ alc/filters/biquad.h
+ alc/filters/biquad.cpp
+ alc/filters/nfc.cpp
+ alc/filters/nfc.h
+ alc/filters/splitter.cpp
+ alc/filters/splitter.h
+ alc/fpu_modes.h
+ alc/helpers.cpp
+ alc/hrtf.cpp
+ alc/hrtf.h
+ alc/inprogext.h
+ alc/logging.h
+ alc/mastering.cpp
+ alc/mastering.h
+ alc/panning.cpp
+ alc/ringbuffer.cpp
+ alc/ringbuffer.h
+ alc/uhjfilter.cpp
+ alc/uhjfilter.h
+ alc/uiddefs.cpp
+ alc/voice.cpp
+ alc/voice.h
+ alc/mixer/defs.h
+ alc/mixer/hrtfbase.h
+ alc/mixer/mixer_c.cpp
)
@@ -854,76 +655,41 @@ SET(HAVE_SSE3 0)
SET(HAVE_SSE4_1 0)
SET(HAVE_NEON 0)
-SET(HAVE_ALSA 0)
-SET(HAVE_OSS 0)
-SET(HAVE_SOLARIS 0)
-SET(HAVE_SNDIO 0)
-SET(HAVE_QSA 0)
-SET(HAVE_DSOUND 0)
-SET(HAVE_WASAPI 0)
-SET(HAVE_WINMM 0)
-SET(HAVE_PORTAUDIO 0)
-SET(HAVE_PULSEAUDIO 0)
-SET(HAVE_COREAUDIO 0)
-SET(HAVE_OPENSL 0)
-SET(HAVE_WAVE 0)
-SET(HAVE_SDL2 0)
-
-# Check for SSE support
+# Check for SSE+SSE2 support
OPTION(ALSOFT_REQUIRE_SSE "Require SSE support" OFF)
-CHECK_INCLUDE_FILE(xmmintrin.h HAVE_XMMINTRIN_H "${SSE_SWITCH}")
-IF(HAVE_XMMINTRIN_H)
+OPTION(ALSOFT_REQUIRE_SSE2 "Require SSE2 support" OFF)
+IF(HAVE_XMMINTRIN_H AND HAVE_EMMINTRIN_H)
OPTION(ALSOFT_CPUEXT_SSE "Enable SSE support" ON)
- IF(ALSOFT_CPUEXT_SSE)
- IF(ALIGN_DECL OR HAVE_C11_ALIGNAS)
- SET(HAVE_SSE 1)
- SET(ALC_OBJS ${ALC_OBJS} Alc/mixer/mixer_sse.c)
- IF(SSE_SWITCH)
- SET_SOURCE_FILES_PROPERTIES(Alc/mixer/mixer_sse.c PROPERTIES
- COMPILE_FLAGS "${SSE_SWITCH}")
- ENDIF()
- SET(CPU_EXTS "${CPU_EXTS}, SSE")
+ OPTION(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON)
+ IF(ALSOFT_CPUEXT_SSE AND ALSOFT_CPUEXT_SSE2)
+ SET(HAVE_SSE 1)
+ SET(HAVE_SSE2 1)
+ SET(ALC_OBJS ${ALC_OBJS} alc/mixer/mixer_sse.cpp alc/mixer/mixer_sse2.cpp)
+ IF(SSE2_SWITCH)
+ SET_SOURCE_FILES_PROPERTIES(alc/mixer/mixer_sse.cpp alc/mixer/mixer_sse2.cpp
+ PROPERTIES COMPILE_FLAGS "${SSE2_SWITCH}")
ENDIF()
+ SET(CPU_EXTS "${CPU_EXTS}, SSE, SSE2")
ENDIF()
ENDIF()
IF(ALSOFT_REQUIRE_SSE AND NOT HAVE_SSE)
MESSAGE(FATAL_ERROR "Failed to enabled required SSE CPU extensions")
ENDIF()
-
-OPTION(ALSOFT_REQUIRE_SSE2 "Require SSE2 support" OFF)
-CHECK_INCLUDE_FILE(emmintrin.h HAVE_EMMINTRIN_H "${SSE2_SWITCH}")
-IF(HAVE_EMMINTRIN_H)
- OPTION(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON)
- IF(HAVE_SSE AND ALSOFT_CPUEXT_SSE2)
- IF(ALIGN_DECL OR HAVE_C11_ALIGNAS)
- SET(HAVE_SSE2 1)
- SET(ALC_OBJS ${ALC_OBJS} Alc/mixer/mixer_sse2.c)
- IF(SSE2_SWITCH)
- SET_SOURCE_FILES_PROPERTIES(Alc/mixer/mixer_sse2.c PROPERTIES
- COMPILE_FLAGS "${SSE2_SWITCH}")
- ENDIF()
- SET(CPU_EXTS "${CPU_EXTS}, SSE2")
- ENDIF()
- ENDIF()
-ENDIF()
IF(ALSOFT_REQUIRE_SSE2 AND NOT HAVE_SSE2)
MESSAGE(FATAL_ERROR "Failed to enable required SSE2 CPU extensions")
ENDIF()
OPTION(ALSOFT_REQUIRE_SSE3 "Require SSE3 support" OFF)
-CHECK_INCLUDE_FILE(pmmintrin.h HAVE_PMMINTRIN_H "${SSE3_SWITCH}")
IF(HAVE_EMMINTRIN_H)
OPTION(ALSOFT_CPUEXT_SSE3 "Enable SSE3 support" ON)
IF(HAVE_SSE2 AND ALSOFT_CPUEXT_SSE3)
- IF(ALIGN_DECL OR HAVE_C11_ALIGNAS)
- SET(HAVE_SSE3 1)
- SET(ALC_OBJS ${ALC_OBJS} Alc/mixer/mixer_sse3.c)
- IF(SSE2_SWITCH)
- SET_SOURCE_FILES_PROPERTIES(Alc/mixer/mixer_sse3.c PROPERTIES
- COMPILE_FLAGS "${SSE3_SWITCH}")
- ENDIF()
- SET(CPU_EXTS "${CPU_EXTS}, SSE3")
+ SET(HAVE_SSE3 1)
+ SET(ALC_OBJS ${ALC_OBJS} alc/mixer/mixer_sse3.cpp)
+ IF(SSE2_SWITCH)
+ SET_SOURCE_FILES_PROPERTIES(alc/mixer/mixer_sse3.cpp PROPERTIES
+ COMPILE_FLAGS "${SSE3_SWITCH}")
ENDIF()
+ SET(CPU_EXTS "${CPU_EXTS}, SSE3")
ENDIF()
ENDIF()
IF(ALSOFT_REQUIRE_SSE3 AND NOT HAVE_SSE3)
@@ -931,19 +697,16 @@ IF(ALSOFT_REQUIRE_SSE3 AND NOT HAVE_SSE3)
ENDIF()
OPTION(ALSOFT_REQUIRE_SSE4_1 "Require SSE4.1 support" OFF)
-CHECK_INCLUDE_FILE(smmintrin.h HAVE_SMMINTRIN_H "${SSE4_1_SWITCH}")
IF(HAVE_SMMINTRIN_H)
OPTION(ALSOFT_CPUEXT_SSE4_1 "Enable SSE4.1 support" ON)
- IF(HAVE_SSE2 AND ALSOFT_CPUEXT_SSE4_1)
- IF(ALIGN_DECL OR HAVE_C11_ALIGNAS)
- SET(HAVE_SSE4_1 1)
- SET(ALC_OBJS ${ALC_OBJS} Alc/mixer/mixer_sse41.c)
- IF(SSE4_1_SWITCH)
- SET_SOURCE_FILES_PROPERTIES(Alc/mixer/mixer_sse41.c PROPERTIES
- COMPILE_FLAGS "${SSE4_1_SWITCH}")
- ENDIF()
- SET(CPU_EXTS "${CPU_EXTS}, SSE4.1")
+ IF(HAVE_SSE3 AND ALSOFT_CPUEXT_SSE4_1)
+ SET(HAVE_SSE4_1 1)
+ SET(ALC_OBJS ${ALC_OBJS} alc/mixer/mixer_sse41.cpp)
+ IF(SSE4_1_SWITCH)
+ SET_SOURCE_FILES_PROPERTIES(alc/mixer/mixer_sse41.cpp PROPERTIES
+ COMPILE_FLAGS "${SSE4_1_SWITCH}")
ENDIF()
+ SET(CPU_EXTS "${CPU_EXTS}, SSE4.1")
ENDIF()
ENDIF()
IF(ALSOFT_REQUIRE_SSE4_1 AND NOT HAVE_SSE4_1)
@@ -952,14 +715,13 @@ ENDIF()
# Check for ARM Neon support
OPTION(ALSOFT_REQUIRE_NEON "Require ARM Neon support" OFF)
-CHECK_INCLUDE_FILE(arm_neon.h HAVE_ARM_NEON_H ${FPU_NEON_SWITCH})
IF(HAVE_ARM_NEON_H)
OPTION(ALSOFT_CPUEXT_NEON "Enable ARM Neon support" ON)
IF(ALSOFT_CPUEXT_NEON)
SET(HAVE_NEON 1)
- SET(ALC_OBJS ${ALC_OBJS} Alc/mixer/mixer_neon.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/mixer/mixer_neon.cpp)
IF(FPU_NEON_SWITCH)
- SET_SOURCE_FILES_PROPERTIES(Alc/mixer/mixer_neon.c PROPERTIES
+ SET_SOURCE_FILES_PROPERTIES(alc/mixer/mixer_neon.cpp PROPERTIES
COMPILE_FLAGS "${FPU_NEON_SWITCH}")
ENDIF()
SET(CPU_EXTS "${CPU_EXTS}, Neon")
@@ -970,6 +732,21 @@ IF(ALSOFT_REQUIRE_NEON AND NOT HAVE_NEON)
ENDIF()
+SET(HAVE_ALSA 0)
+SET(HAVE_OSS 0)
+SET(HAVE_SOLARIS 0)
+SET(HAVE_SNDIO 0)
+SET(HAVE_QSA 0)
+SET(HAVE_DSOUND 0)
+SET(HAVE_WASAPI 0)
+SET(HAVE_WINMM 0)
+SET(HAVE_PORTAUDIO 0)
+SET(HAVE_PULSEAUDIO 0)
+SET(HAVE_COREAUDIO 0)
+SET(HAVE_OPENSL 0)
+SET(HAVE_WAVE 0)
+SET(HAVE_SDL2 0)
+
IF(WIN32 OR HAVE_DLFCN_H)
SET(IS_LINKED "")
MACRO(ADD_BACKEND_LIBS _LIBS)
@@ -983,11 +760,13 @@ ENDIF()
SET(BACKENDS "")
SET(ALC_OBJS ${ALC_OBJS}
- Alc/backends/base.c
- Alc/backends/base.h
+ alc/backends/base.cpp
+ alc/backends/base.h
# Default backends, always available
- Alc/backends/loopback.c
- Alc/backends/null.c
+ alc/backends/loopback.cpp
+ alc/backends/loopback.h
+ alc/backends/null.cpp
+ alc/backends/null.h
)
# Check ALSA backend
@@ -998,7 +777,7 @@ IF(ALSA_FOUND)
IF(ALSOFT_BACKEND_ALSA)
SET(HAVE_ALSA 1)
SET(BACKENDS "${BACKENDS} ALSA${IS_LINKED},")
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/alsa.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/alsa.cpp alc/backends/alsa.h)
ADD_BACKEND_LIBS(${ALSA_LIBRARIES})
SET(INC_PATHS ${INC_PATHS} ${ALSA_INCLUDE_DIRS})
ENDIF()
@@ -1015,7 +794,7 @@ IF(OSS_FOUND)
IF(ALSOFT_BACKEND_OSS)
SET(HAVE_OSS 1)
SET(BACKENDS "${BACKENDS} OSS,")
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/oss.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/oss.cpp alc/backends/oss.h)
IF(OSS_LIBRARIES)
SET(EXTRA_LIBS ${OSS_LIBRARIES} ${EXTRA_LIBS})
ENDIF()
@@ -1034,7 +813,7 @@ IF(AUDIOIO_FOUND)
IF(ALSOFT_BACKEND_SOLARIS)
SET(HAVE_SOLARIS 1)
SET(BACKENDS "${BACKENDS} Solaris,")
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/solaris.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/solaris.cpp alc/backends/solaris.h)
SET(INC_PATHS ${INC_PATHS} ${AUDIOIO_INCLUDE_DIRS})
ENDIF()
ENDIF()
@@ -1050,7 +829,7 @@ IF(SOUNDIO_FOUND)
IF(ALSOFT_BACKEND_SNDIO)
SET(HAVE_SNDIO 1)
SET(BACKENDS "${BACKENDS} SndIO (linked),")
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/sndio.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/sndio.cpp alc/backends/sndio.h)
SET(EXTRA_LIBS ${SOUNDIO_LIBRARIES} ${EXTRA_LIBS})
SET(INC_PATHS ${INC_PATHS} ${SOUNDIO_INCLUDE_DIRS})
ENDIF()
@@ -1067,7 +846,7 @@ IF(QSA_FOUND)
IF(ALSOFT_BACKEND_QSA)
SET(HAVE_QSA 1)
SET(BACKENDS "${BACKENDS} QSA (linked),")
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/qsa.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/qsa.cpp alc/backends/qsa.h)
SET(EXTRA_LIBS ${QSA_LIBRARIES} ${EXTRA_LIBS})
SET(INC_PATHS ${INC_PATHS} ${QSA_INCLUDE_DIRS})
ENDIF()
@@ -1080,22 +859,30 @@ ENDIF()
OPTION(ALSOFT_REQUIRE_WINMM "Require Windows Multimedia backend" OFF)
OPTION(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF)
OPTION(ALSOFT_REQUIRE_WASAPI "Require WASAPI backend" OFF)
-IF(HAVE_WINDOWS_H)
+IF(WIN32)
+ SET(WINSDK_LIB_DIRS )
+ SET(WINSDK_INCLUDE_DIRS )
+ FIND_PACKAGE(WindowsSDK)
+ IF(WINDOWSSDK_FOUND)
+ get_windowssdk_library_dirs(${WINDOWSSDK_PREFERRED_DIR} WINSDK_LIB_DIRS)
+ get_windowssdk_include_dirs(${WINDOWSSDK_PREFERRED_DIR} WINSDK_INCLUDE_DIRS)
+ ENDIF()
+
SET(OLD_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS})
SET(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -D_WIN32_WINNT=0x0502)
# Check MMSystem backend
CHECK_INCLUDE_FILES("windows.h;mmsystem.h" HAVE_MMSYSTEM_H)
- IF(HAVE_MMSYSTEM_H)
- CHECK_SHARED_FUNCTION_EXISTS(waveOutOpen "windows.h;mmsystem.h" winmm "" HAVE_LIBWINMM)
- IF(HAVE_LIBWINMM)
- OPTION(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON)
- IF(ALSOFT_BACKEND_WINMM)
- SET(HAVE_WINMM 1)
- SET(BACKENDS "${BACKENDS} WinMM,")
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/winmm.c)
- SET(EXTRA_LIBS winmm ${EXTRA_LIBS})
- ENDIF()
+ FIND_LIBRARY(WINMM_LIBRARY NAMES winmm
+ PATHS ${WINSDK_LIB_DIRS}
+ PATH_SUFFIXES lib lib/x86 lib/x64)
+ IF(HAVE_MMSYSTEM_H AND WINMM_LIBRARY)
+ OPTION(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON)
+ IF(ALSOFT_BACKEND_WINMM)
+ SET(HAVE_WINMM 1)
+ SET(BACKENDS "${BACKENDS} WinMM,")
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/winmm.cpp alc/backends/winmm.h)
+ SET(EXTRA_LIBS ${WINMM_LIBRARY} ${EXTRA_LIBS})
ENDIF()
ENDIF()
@@ -1106,7 +893,7 @@ IF(HAVE_WINDOWS_H)
IF(ALSOFT_BACKEND_DSOUND)
SET(HAVE_DSOUND 1)
SET(BACKENDS "${BACKENDS} DirectSound${IS_LINKED},")
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/dsound.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/dsound.cpp alc/backends/dsound.h)
ADD_BACKEND_LIBS(${DSOUND_LIBRARIES})
SET(INC_PATHS ${INC_PATHS} ${DSOUND_INCLUDE_DIRS})
ENDIF()
@@ -1119,7 +906,7 @@ IF(HAVE_WINDOWS_H)
IF(ALSOFT_BACKEND_WASAPI)
SET(HAVE_WASAPI 1)
SET(BACKENDS "${BACKENDS} WASAPI,")
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/wasapi.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/wasapi.cpp alc/backends/wasapi.h)
ENDIF()
ENDIF()
@@ -1144,7 +931,7 @@ IF(PORTAUDIO_FOUND)
IF(ALSOFT_BACKEND_PORTAUDIO)
SET(HAVE_PORTAUDIO 1)
SET(BACKENDS "${BACKENDS} PortAudio${IS_LINKED},")
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/portaudio.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/portaudio.cpp alc/backends/portaudio.h)
ADD_BACKEND_LIBS(${PORTAUDIO_LIBRARIES})
SET(INC_PATHS ${INC_PATHS} ${PORTAUDIO_INCLUDE_DIRS})
ENDIF()
@@ -1161,7 +948,7 @@ IF(PULSEAUDIO_FOUND)
IF(ALSOFT_BACKEND_PULSEAUDIO)
SET(HAVE_PULSEAUDIO 1)
SET(BACKENDS "${BACKENDS} PulseAudio${IS_LINKED},")
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/pulseaudio.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/pulseaudio.cpp alc/backends/pulseaudio.h)
ADD_BACKEND_LIBS(${PULSEAUDIO_LIBRARIES})
SET(INC_PATHS ${INC_PATHS} ${PULSEAUDIO_INCLUDE_DIRS})
ENDIF()
@@ -1178,7 +965,7 @@ IF(JACK_FOUND)
IF(ALSOFT_BACKEND_JACK)
SET(HAVE_JACK 1)
SET(BACKENDS "${BACKENDS} JACK${IS_LINKED},")
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/jack.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/jack.cpp alc/backends/jack.h)
ADD_BACKEND_LIBS(${JACK_LIBRARIES})
SET(INC_PATHS ${INC_PATHS} ${JACK_INCLUDE_DIRS})
ENDIF()
@@ -1190,14 +977,14 @@ ENDIF()
# Check CoreAudio backend
OPTION(ALSOFT_REQUIRE_COREAUDIO "Require CoreAudio backend" OFF)
FIND_LIBRARY(COREAUDIO_FRAMEWORK
- NAMES CoreAudio
- PATHS /System/Library/Frameworks
+ NAMES CoreAudio
+ PATHS /System/Library/Frameworks
)
IF(COREAUDIO_FRAMEWORK)
OPTION(ALSOFT_BACKEND_COREAUDIO "Enable CoreAudio backend" ON)
IF(ALSOFT_BACKEND_COREAUDIO)
SET(HAVE_COREAUDIO 1)
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/coreaudio.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/coreaudio.cpp alc/backends/coreaudio.h)
SET(BACKENDS "${BACKENDS} CoreAudio,")
SET(EXTRA_LIBS ${COREAUDIO_FRAMEWORK} ${EXTRA_LIBS})
SET(EXTRA_LIBS /System/Library/Frameworks/AudioUnit.framework ${EXTRA_LIBS})
@@ -1206,11 +993,12 @@ IF(COREAUDIO_FRAMEWORK)
# Some versions of OSX may need the AudioToolbox framework. Add it if
# it's found.
FIND_LIBRARY(AUDIOTOOLBOX_LIBRARY
- NAMES AudioToolbox
- PATHS ~/Library/Frameworks
- /Library/Frameworks
- /System/Library/Frameworks
- )
+ NAMES AudioToolbox
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /System/Library/Frameworks
+ )
IF(AUDIOTOOLBOX_LIBRARY)
SET(EXTRA_LIBS ${AUDIOTOOLBOX_LIBRARY} ${EXTRA_LIBS})
ENDIF()
@@ -1222,17 +1010,15 @@ ENDIF()
# Check for OpenSL (Android) backend
OPTION(ALSOFT_REQUIRE_OPENSL "Require OpenSL backend" OFF)
-CHECK_INCLUDE_FILES("SLES/OpenSLES.h;SLES/OpenSLES_Android.h" HAVE_SLES_OPENSLES_ANDROID_H)
-IF(HAVE_SLES_OPENSLES_ANDROID_H)
- CHECK_SHARED_FUNCTION_EXISTS(slCreateEngine "SLES/OpenSLES.h" OpenSLES "" HAVE_LIBOPENSLES)
- IF(HAVE_LIBOPENSLES)
- OPTION(ALSOFT_BACKEND_OPENSL "Enable OpenSL backend" ON)
- IF(ALSOFT_BACKEND_OPENSL)
- SET(HAVE_OPENSL 1)
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/opensl.c)
- SET(BACKENDS "${BACKENDS} OpenSL,")
- SET(EXTRA_LIBS OpenSLES ${EXTRA_LIBS})
- ENDIF()
+FIND_PACKAGE(OpenSL)
+IF(OPENSL_FOUND)
+ OPTION(ALSOFT_BACKEND_OPENSL "Enable OpenSL backend" ON)
+ IF(ALSOFT_BACKEND_OPENSL)
+ SET(HAVE_OPENSL 1)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/opensl.cpp alc/backends/opensl.h)
+ SET(BACKENDS "${BACKENDS} OpenSL,")
+ SET(EXTRA_LIBS ${OPENSL_LIBRARIES} ${EXTRA_LIBS})
+ SET(INC_PATHS ${INC_PATHS} ${OPENSL_INCLUDE_DIRS})
ENDIF()
ENDIF()
IF(ALSOFT_REQUIRE_OPENSL AND NOT HAVE_OPENSL)
@@ -1247,7 +1033,7 @@ IF(SDL2_FOUND)
OPTION(ALSOFT_BACKEND_SDL2 "Enable SDL2 backend" OFF)
IF(ALSOFT_BACKEND_SDL2)
SET(HAVE_SDL2 1)
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/sdl2.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/sdl2.cpp alc/backends/sdl2.h)
SET(BACKENDS "${BACKENDS} SDL2,")
SET(EXTRA_LIBS ${SDL2_LIBRARY} ${EXTRA_LIBS})
SET(INC_PATHS ${INC_PATHS} ${SDL2_INCLUDE_DIR})
@@ -1261,7 +1047,7 @@ ENDIF()
OPTION(ALSOFT_BACKEND_WAVE "Enable Wave Writer backend" ON)
IF(ALSOFT_BACKEND_WAVE)
SET(HAVE_WAVE 1)
- SET(ALC_OBJS ${ALC_OBJS} Alc/backends/wave.c)
+ SET(ALC_OBJS ${ALC_OBJS} alc/backends/wave.cpp alc/backends/wave.h)
SET(BACKENDS "${BACKENDS} WaveFile,")
ENDIF()
@@ -1273,11 +1059,9 @@ FIND_PACKAGE(Git)
IF(GIT_FOUND AND EXISTS "${OpenAL_SOURCE_DIR}/.git")
# Get the current working branch and its latest abbreviated commit hash
ADD_CUSTOM_TARGET(build_version
- ${CMAKE_COMMAND} -D GIT_EXECUTABLE=${GIT_EXECUTABLE}
- -D LIB_VERSION=${LIB_VERSION}
- -D SRC=${OpenAL_SOURCE_DIR}/version.h.in
- -D DST=${OpenAL_BINARY_DIR}/version.h
- -P ${OpenAL_SOURCE_DIR}/version.cmake
+ ${CMAKE_COMMAND} -D GIT_EXECUTABLE=${GIT_EXECUTABLE} -D LIB_VERSION=${LIB_VERSION}
+ -D LIB_VERSION_NUM=${LIB_VERSION_NUM} -D SRC=${OpenAL_SOURCE_DIR}/version.h.in
+ -D DST=${OpenAL_BINARY_DIR}/version.h -P ${OpenAL_SOURCE_DIR}/version.cmake
WORKING_DIRECTORY "${OpenAL_SOURCE_DIR}"
VERBATIM
)
@@ -1289,24 +1073,42 @@ ELSE()
"${OpenAL_BINARY_DIR}/version.h")
ENDIF()
-IF(NOT CMAKE_CROSSCOMPILING)
-SET(NATIVE_SRC_DIR "${OpenAL_SOURCE_DIR}/native-tools/")
-SET(NATIVE_BIN_DIR "${OpenAL_BINARY_DIR}/native-tools/")
-FILE(MAKE_DIRECTORY "${NATIVE_BIN_DIR}")
+SET(NATIVE_SRC_DIR "${OpenAL_SOURCE_DIR}/native-tools")
-SET(BIN2H_COMMAND "${NATIVE_BIN_DIR}bin2h")
-SET(BSINCGEN_COMMAND "${NATIVE_BIN_DIR}bsincgen")
-ADD_CUSTOM_COMMAND(OUTPUT "${BIN2H_COMMAND}" "${BSINCGEN_COMMAND}"
- COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" "${NATIVE_SRC_DIR}"
- COMMAND ${CMAKE_COMMAND} -E remove "${BIN2H_COMMAND}" "${BSINCGEN_COMMAND}"
- COMMAND ${CMAKE_COMMAND} --build . --config "Release"
- WORKING_DIRECTORY "${NATIVE_BIN_DIR}"
- DEPENDS "${NATIVE_SRC_DIR}CMakeLists.txt"
- IMPLICIT_DEPENDS C "${NATIVE_SRC_DIR}bin2h.c"
- C "${NATIVE_SRC_DIR}bsincgen.c"
- VERBATIM
-)
+SET(ALSOFT_NATIVE_TOOLS_PATH "" CACHE STRING "Path to prebuilt native tools (leave blank to auto-build)")
+IF(ALSOFT_NATIVE_TOOLS_PATH)
+ find_program(BIN2H_NATIVE_COMMAND NAMES bin2h
+ PATHS "${ALSOFT_NATIVE_TOOLS_PATH}"
+ NO_DEFAULT_PATH)
+ find_program(BSINCGEN_NATIVE_COMMAND NAMES bsincgen
+ PATHS "${ALSOFT_NATIVE_TOOLS_PATH}"
+ NO_DEFAULT_PATH)
+ if(NOT BIN2H_NATIVE_COMMAND OR NOT BSINCGEN_NATIVE_COMMAND)
+ message(FATAL_ERROR "Failed to find native tools in ${ALSOFT_NATIVE_TOOLS_PATH}.
+bin2h: ${BIN2H_NATIVE_COMMAND}
+bsincgen: ${BSINCGEN_NATIVE_COMMAND}")
+ endif()
+ SET(BIN2H_COMMAND ${BIN2H_NATIVE_COMMAND})
+ SET(BSINCGEN_COMMAND ${BSINCGEN_NATIVE_COMMAND})
+ELSE()
+ SET(NATIVE_BIN_DIR "${OpenAL_BINARY_DIR}/native-tools")
+ FILE(MAKE_DIRECTORY "${NATIVE_BIN_DIR}")
+
+ SET(BIN2H_COMMAND "${NATIVE_BIN_DIR}/bin2h")
+ SET(BSINCGEN_COMMAND "${NATIVE_BIN_DIR}/bsincgen")
+ ADD_CUSTOM_COMMAND(OUTPUT "${BIN2H_COMMAND}" "${BSINCGEN_COMMAND}"
+ COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" "${NATIVE_SRC_DIR}"
+ COMMAND ${CMAKE_COMMAND} -E remove "${BIN2H_COMMAND}" "${BSINCGEN_COMMAND}"
+ COMMAND ${CMAKE_COMMAND} --build . --config "Release"
+ WORKING_DIRECTORY "${NATIVE_BIN_DIR}"
+ DEPENDS "${NATIVE_SRC_DIR}/CMakeLists.txt"
+ IMPLICIT_DEPENDS
+ C "${NATIVE_SRC_DIR}/bin2h.c"
+ C "${NATIVE_SRC_DIR}/bsincgen.c"
+ VERBATIM
+ )
+ENDIF()
ADD_CUSTOM_TARGET(native-tools
DEPENDS "${BIN2H_COMMAND}" "${BSINCGEN_COMMAND}"
VERBATIM
@@ -1332,7 +1134,7 @@ endif()
ADD_CUSTOM_COMMAND(OUTPUT "${OpenAL_BINARY_DIR}/bsinc_inc.h"
COMMAND "${BSINCGEN_COMMAND}" "${OpenAL_BINARY_DIR}/bsinc_inc.h"
- DEPENDS native-tools "${NATIVE_SRC_DIR}bsincgen.c"
+ DEPENDS native-tools "${NATIVE_SRC_DIR}/bsincgen.c"
VERBATIM
)
SET(ALC_OBJS ${ALC_OBJS} "${OpenAL_BINARY_DIR}/bsinc_inc.h")
@@ -1394,15 +1196,15 @@ CONFIGURE_FILE(
@ONLY)
-# Add a static library with common functions used by multiple targets
-ADD_LIBRARY(common STATIC ${COMMON_OBJS})
+ADD_LIBRARY(common STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS})
+TARGET_INCLUDE_DIRECTORIES(common PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/include)
TARGET_COMPILE_DEFINITIONS(common PRIVATE ${CPP_DEFS})
TARGET_COMPILE_OPTIONS(common PRIVATE ${C_FLAGS})
+SET_TARGET_PROPERTIES(common PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
UNSET(HAS_ROUTER)
-SET(IMPL_TARGET OpenAL)
-SET(COMMON_LIB )
+SET(IMPL_TARGET OpenAL) # Either OpenAL or soft_oal.
SET(SUBSYS_FLAG )
# Build main library
@@ -1411,13 +1213,9 @@ IF(LIBTYPE STREQUAL "STATIC")
IF(WIN32 AND ALSOFT_NO_UID_DEFS)
SET(CPP_DEFS ${CPP_DEFS} AL_NO_UID_DEFS)
ENDIF()
- ADD_LIBRARY(OpenAL STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS})
+ ADD_LIBRARY(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS})
+ TARGET_LINK_LIBRARIES(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB})
ELSE()
- # Make sure to compile the common code with PIC, since it'll be linked into
- # shared libs that needs it.
- SET_PROPERTY(TARGET common PROPERTY POSITION_INDEPENDENT_CODE TRUE)
- SET(COMMON_LIB common)
-
IF(WIN32)
IF(MSVC)
SET(SUBSYS_FLAG ${SUBSYS_FLAG} "/SUBSYSTEM:WINDOWS")
@@ -1426,12 +1224,27 @@ ELSE()
ENDIF()
ENDIF()
+ SET(RC_CONFIG resources/openal32.rc)
IF(WIN32 AND ALSOFT_BUILD_ROUTER)
- ADD_LIBRARY(OpenAL SHARED router/router.c router/router.h router/alc.c router/al.c)
+ ADD_LIBRARY(OpenAL SHARED
+ resources/router.rc
+ router/router.cpp
+ router/router.h
+ router/alc.cpp
+ router/al.cpp
+ )
TARGET_COMPILE_DEFINITIONS(OpenAL
PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES ${CPP_DEFS})
TARGET_COMPILE_OPTIONS(OpenAL PRIVATE ${C_FLAGS})
- TARGET_LINK_LIBRARIES(OpenAL PRIVATE ${LINKER_FLAGS} ${COMMON_LIB})
+ TARGET_LINK_LIBRARIES(OpenAL PRIVATE common ${LINKER_FLAGS})
+ TARGET_INCLUDE_DIRECTORIES(OpenAL
+ PUBLIC
+ $<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
+ $<INSTALL_INTERFACE:include>
+ PRIVATE
+ ${OpenAL_SOURCE_DIR}/common
+ ${OpenAL_BINARY_DIR}
+ )
SET_TARGET_PROPERTIES(OpenAL PROPERTIES PREFIX "")
SET_TARGET_PROPERTIES(OpenAL PROPERTIES OUTPUT_NAME ${LIBNAME})
IF(TARGET build_version)
@@ -1441,24 +1254,36 @@ ELSE()
SET(LIBNAME "soft_oal")
SET(IMPL_TARGET soft_oal)
+ SET(RC_CONFIG resources/soft_oal.rc)
ENDIF()
- ADD_LIBRARY(${IMPL_TARGET} SHARED ${OPENAL_OBJS} ${ALC_OBJS})
+ ADD_LIBRARY(${IMPL_TARGET} SHARED ${OPENAL_OBJS} ${ALC_OBJS} ${RC_CONFIG})
IF(WIN32)
SET_TARGET_PROPERTIES(${IMPL_TARGET} PROPERTIES PREFIX "")
ENDIF()
+ TARGET_LINK_LIBRARIES(${IMPL_TARGET} PRIVATE common ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB})
ENDIF()
+
+TARGET_INCLUDE_DIRECTORIES(${IMPL_TARGET}
+ PUBLIC
+ $<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
+ $<INSTALL_INTERFACE:include>
+ PRIVATE
+ ${INC_PATHS}
+ ${OpenAL_BINARY_DIR}
+ ${OpenAL_SOURCE_DIR}
+ ${OpenAL_SOURCE_DIR}/alc
+ ${OpenAL_SOURCE_DIR}/common
+)
+
SET_TARGET_PROPERTIES(${IMPL_TARGET} PROPERTIES OUTPUT_NAME ${LIBNAME}
VERSION ${LIB_VERSION}
SOVERSION ${LIB_MAJOR_VERSION}
)
TARGET_COMPILE_DEFINITIONS(${IMPL_TARGET}
PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES ${CPP_DEFS})
-TARGET_INCLUDE_DIRECTORIES(${IMPL_TARGET}
- PRIVATE "${OpenAL_SOURCE_DIR}/OpenAL32/Include" "${OpenAL_SOURCE_DIR}/Alc" ${INC_PATHS})
TARGET_COMPILE_OPTIONS(${IMPL_TARGET} PRIVATE ${C_FLAGS})
-TARGET_LINK_LIBRARIES(${IMPL_TARGET}
- PRIVATE ${LINKER_FLAGS} ${COMMON_LIB} ${EXTRA_LIBS} ${MATH_LIB})
+
IF(TARGET build_version)
ADD_DEPENDENCIES(${IMPL_TARGET} build_version)
ENDIF()
@@ -1475,8 +1300,8 @@ IF(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC"
MESSAGE(STATUS "WARNING: Cannot find dlltool, disabling .def/.lib generation")
ENDIF()
ELSE()
- SET_PROPERTY(TARGET OpenAL APPEND_STRING PROPERTY LINK_FLAGS
- " -Wl,--output-def,OpenAL32.def")
+ SET_PROPERTY(TARGET OpenAL APPEND_STRING PROPERTY
+ LINK_FLAGS " -Wl,--output-def,OpenAL32.def")
ADD_CUSTOM_COMMAND(TARGET OpenAL POST_BUILD
COMMAND "${SED_EXECUTABLE}" -i -e "s/ @[^ ]*//" OpenAL32.def
COMMAND "${DLLTOOL_EXECUTABLE}" -d OpenAL32.def -l OpenAL32.lib -D OpenAL32.dll
@@ -1488,32 +1313,30 @@ ENDIF()
IF(ALSOFT_INSTALL)
INSTALL(TARGETS OpenAL EXPORT OpenAL
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
- INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/AL
- )
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/AL)
EXPORT(TARGETS OpenAL
- NAMESPACE OpenAL::
- FILE OpenALConfig.cmake)
+ NAMESPACE OpenAL::
+ FILE OpenALConfig.cmake)
INSTALL(EXPORT OpenAL
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL
- NAMESPACE OpenAL::
- FILE OpenALConfig.cmake)
- INSTALL(FILES include/AL/al.h
- include/AL/alc.h
- include/AL/alext.h
- include/AL/efx.h
- include/AL/efx-creative.h
- include/AL/efx-presets.h
- DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/AL
- )
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL
+ NAMESPACE OpenAL::
+ FILE OpenALConfig.cmake)
+ INSTALL(FILES
+ include/AL/al.h
+ include/AL/alc.h
+ include/AL/alext.h
+ include/AL/efx.h
+ include/AL/efx-creative.h
+ include/AL/efx-presets.h
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/AL)
INSTALL(FILES "${OpenAL_BINARY_DIR}/openal.pc"
- DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
IF(TARGET soft_oal)
INSTALL(TARGETS soft_oal
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
- )
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
ENDIF()
ENDIF()
@@ -1535,14 +1358,6 @@ IF(FPMATH_SET)
MESSAGE(STATUS "")
ENDIF()
-IF(WIN32)
- IF(NOT HAVE_DSOUND)
- MESSAGE(STATUS "WARNING: Building the Windows version without DirectSound output")
- MESSAGE(STATUS " This is probably NOT what you want!")
- MESSAGE(STATUS "")
- ENDIF()
-ENDIF()
-
if(ALSOFT_EMBED_HRTF_DATA)
message(STATUS "Embedding HRTF datasets")
message(STATUS "")
@@ -1551,59 +1366,76 @@ endif()
# Install alsoft.conf configuration file
IF(ALSOFT_CONFIG)
INSTALL(FILES alsoftrc.sample
- DESTINATION ${CMAKE_INSTALL_DATADIR}/openal
- )
+ DESTINATION ${CMAKE_INSTALL_DATADIR}/openal)
MESSAGE(STATUS "Installing sample configuration")
MESSAGE(STATUS "")
ENDIF()
# Install HRTF definitions
IF(ALSOFT_HRTF_DEFS)
- INSTALL(FILES hrtf/default-44100.mhr
- hrtf/default-48000.mhr
- DESTINATION ${CMAKE_INSTALL_DATADIR}/openal/hrtf
- )
+ INSTALL(FILES hrtf/default-44100.mhr hrtf/default-48000.mhr
+ DESTINATION ${CMAKE_INSTALL_DATADIR}/openal/hrtf)
MESSAGE(STATUS "Installing HRTF definitions")
MESSAGE(STATUS "")
ENDIF()
# Install AmbDec presets
IF(ALSOFT_AMBDEC_PRESETS)
- INSTALL(FILES presets/3D7.1.ambdec
- presets/hexagon.ambdec
- presets/itu5.1.ambdec
- presets/itu5.1-nocenter.ambdec
- presets/rectangle.ambdec
- presets/square.ambdec
- presets/presets.txt
- DESTINATION ${CMAKE_INSTALL_DATADIR}/openal/presets
- )
+ INSTALL(FILES
+ presets/3D7.1.ambdec
+ presets/hexagon.ambdec
+ presets/itu5.1.ambdec
+ presets/itu5.1-nocenter.ambdec
+ presets/rectangle.ambdec
+ presets/square.ambdec
+ presets/presets.txt
+ DESTINATION ${CMAKE_INSTALL_DATADIR}/openal/presets)
MESSAGE(STATUS "Installing AmbDec presets")
MESSAGE(STATUS "")
ENDIF()
IF(ALSOFT_UTILS)
+ set(UTIL_TARGETS )
+
ADD_EXECUTABLE(openal-info utils/openal-info.c)
+ TARGET_INCLUDE_DIRECTORIES(openal-info PRIVATE ${OpenAL_SOURCE_DIR}/common)
TARGET_COMPILE_OPTIONS(openal-info PRIVATE ${C_FLAGS})
TARGET_LINK_LIBRARIES(openal-info PRIVATE ${LINKER_FLAGS} OpenAL)
-
- SET(MAKEHRTF_SRCS utils/makehrtf.c)
- IF(NOT HAVE_GETOPT)
- SET(MAKEHRTF_SRCS ${MAKEHRTF_SRCS} utils/getopt.c utils/getopt.h)
- ENDIF()
- ADD_EXECUTABLE(makehrtf ${MAKEHRTF_SRCS})
- TARGET_COMPILE_DEFINITIONS(makehrtf PRIVATE ${CPP_DEFS})
- TARGET_COMPILE_OPTIONS(makehrtf PRIVATE ${C_FLAGS})
- IF(HAVE_LIBM)
- TARGET_LINK_LIBRARIES(makehrtf PRIVATE ${LINKER_FLAGS} m)
- ENDIF()
+ set(UTIL_TARGETS ${UTIL_TARGETS} openal-info)
+
+ find_package(MySOFA)
+ if(MYSOFA_FOUND)
+ set(MAKEMHR_SRCS
+ utils/makemhr/loaddef.cpp
+ utils/makemhr/loaddef.h
+ utils/makemhr/loadsofa.cpp
+ utils/makemhr/loadsofa.h
+ utils/makemhr/makemhr.cpp
+ utils/makemhr/makemhr.h)
+ if(NOT HAVE_GETOPT)
+ set(MAKEMHR_SRCS ${MAKEMHR_SRCS} utils/getopt.c utils/getopt.h)
+ endif()
+ add_executable(makemhr ${MAKEMHR_SRCS})
+ target_compile_definitions(makemhr PRIVATE ${CPP_DEFS})
+ target_include_directories(makemhr
+ PRIVATE ${OpenAL_SOURCE_DIR}/common ${OpenAL_BINARY_DIR})
+ target_compile_options(makemhr PRIVATE ${C_FLAGS})
+ target_link_libraries(makemhr PRIVATE common ${LINKER_FLAGS} MySOFA::MySOFA)
+ set(UTIL_TARGETS ${UTIL_TARGETS} makemhr)
+
+ set(SOFAINFO_SRCS utils/sofa-info.cpp)
+ add_executable(sofa-info ${SOFAINFO_SRCS})
+ target_compile_definitions(sofa-info PRIVATE ${CPP_DEFS})
+ target_include_directories(sofa-info PRIVATE ${OpenAL_SOURCE_DIR}/common)
+ target_compile_options(sofa-info PRIVATE ${C_FLAGS})
+ target_link_libraries(sofa-info PRIVATE ${LINKER_FLAGS} MySOFA::MySOFA)
+ endif()
IF(ALSOFT_INSTALL)
- INSTALL(TARGETS openal-info makehrtf
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
- )
+ INSTALL(TARGETS ${UTIL_TARGETS}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
ENDIF()
MESSAGE(STATUS "Building utility programs")
@@ -1613,20 +1445,28 @@ IF(ALSOFT_UTILS)
MESSAGE(STATUS "")
ENDIF()
-IF(ALSOFT_TESTS)
- SET(TEST_COMMON_OBJS examples/common/alhelpers.c)
- ADD_EXECUTABLE(altonegen examples/altonegen.c ${TEST_COMMON_OBJS})
+# Add a static library with common functions used by multiple example targets
+ADD_LIBRARY(ex-common STATIC EXCLUDE_FROM_ALL
+ examples/common/alhelpers.c
+ examples/common/alhelpers.h)
+TARGET_COMPILE_DEFINITIONS(ex-common PUBLIC ${CPP_DEFS})
+TARGET_INCLUDE_DIRECTORIES(ex-common PUBLIC ${OpenAL_SOURCE_DIR}/common)
+TARGET_COMPILE_OPTIONS(ex-common PUBLIC ${C_FLAGS})
+TARGET_LINK_LIBRARIES(ex-common PUBLIC OpenAL PRIVATE ${RT_LIB})
+
+IF(ALSOFT_TESTS)
+ ADD_EXECUTABLE(altonegen examples/altonegen.c)
TARGET_COMPILE_DEFINITIONS(altonegen PRIVATE ${CPP_DEFS})
+ TARGET_INCLUDE_DIRECTORIES(altonegen PRIVATE ${OpenAL_SOURCE_DIR}/common)
TARGET_COMPILE_OPTIONS(altonegen PRIVATE ${C_FLAGS})
- TARGET_LINK_LIBRARIES(altonegen PRIVATE ${LINKER_FLAGS} common OpenAL ${MATH_LIB})
+ TARGET_LINK_LIBRARIES(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} ex-common)
IF(ALSOFT_INSTALL)
INSTALL(TARGETS altonegen
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
- )
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
ENDIF()
MESSAGE(STATUS "Building test programs")
@@ -1635,96 +1475,67 @@ ENDIF()
IF(ALSOFT_EXAMPLES)
ADD_EXECUTABLE(alrecord examples/alrecord.c)
- TARGET_COMPILE_DEFINITIONS(alrecord PRIVATE ${CPP_DEFS})
- TARGET_COMPILE_OPTIONS(alrecord PRIVATE ${C_FLAGS})
- TARGET_LINK_LIBRARIES(alrecord PRIVATE ${LINKER_FLAGS} common OpenAL)
+ TARGET_LINK_LIBRARIES(alrecord PRIVATE ${LINKER_FLAGS} ex-common)
IF(ALSOFT_INSTALL)
INSTALL(TARGETS alrecord
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
- )
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
ENDIF()
MESSAGE(STATUS "Building example programs")
IF(SDL2_FOUND)
IF(SDL_SOUND_FOUND)
- # Add a static library with common functions used by multiple targets
- ADD_LIBRARY(ex-common STATIC examples/common/alhelpers.c)
- TARGET_COMPILE_DEFINITIONS(ex-common PRIVATE ${CPP_DEFS})
- TARGET_COMPILE_OPTIONS(ex-common PRIVATE ${C_FLAGS})
-
ADD_EXECUTABLE(alplay examples/alplay.c)
- TARGET_COMPILE_DEFINITIONS(alplay PRIVATE ${CPP_DEFS})
TARGET_INCLUDE_DIRECTORIES(alplay
PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR})
- TARGET_COMPILE_OPTIONS(alplay PRIVATE ${C_FLAGS})
TARGET_LINK_LIBRARIES(alplay
- PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common common
- OpenAL)
+ PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common)
ADD_EXECUTABLE(alstream examples/alstream.c)
- TARGET_COMPILE_DEFINITIONS(alstream PRIVATE ${CPP_DEFS})
TARGET_INCLUDE_DIRECTORIES(alstream
PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR})
- TARGET_COMPILE_OPTIONS(alstream PRIVATE ${C_FLAGS})
TARGET_LINK_LIBRARIES(alstream
- PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common common
- OpenAL)
+ PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common)
ADD_EXECUTABLE(alreverb examples/alreverb.c)
- TARGET_COMPILE_DEFINITIONS(alreverb PRIVATE ${CPP_DEFS})
TARGET_INCLUDE_DIRECTORIES(alreverb
PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR})
- TARGET_COMPILE_OPTIONS(alreverb PRIVATE ${C_FLAGS})
TARGET_LINK_LIBRARIES(alreverb
- PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common common
- OpenAL)
+ PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common)
ADD_EXECUTABLE(almultireverb examples/almultireverb.c)
- TARGET_COMPILE_DEFINITIONS(almultireverb PRIVATE ${CPP_DEFS})
TARGET_INCLUDE_DIRECTORIES(almultireverb
PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR})
- TARGET_COMPILE_OPTIONS(almultireverb PRIVATE ${C_FLAGS})
TARGET_LINK_LIBRARIES(almultireverb
- PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common common
- OpenAL ${MATH_LIB})
+ PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common
+ ${MATH_LIB})
ADD_EXECUTABLE(allatency examples/allatency.c)
- TARGET_COMPILE_DEFINITIONS(allatency PRIVATE ${CPP_DEFS})
TARGET_INCLUDE_DIRECTORIES(allatency
PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR})
- TARGET_COMPILE_OPTIONS(allatency PRIVATE ${C_FLAGS})
TARGET_LINK_LIBRARIES(allatency
- PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common common
- OpenAL)
+ PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common)
ADD_EXECUTABLE(alloopback examples/alloopback.c)
- TARGET_COMPILE_DEFINITIONS(alloopback PRIVATE ${CPP_DEFS})
TARGET_INCLUDE_DIRECTORIES(alloopback
PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR})
- TARGET_COMPILE_OPTIONS(alloopback PRIVATE ${C_FLAGS})
TARGET_LINK_LIBRARIES(alloopback
- PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common common
- OpenAL ${MATH_LIB})
+ PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common ${MATH_LIB})
ADD_EXECUTABLE(alhrtf examples/alhrtf.c)
- TARGET_COMPILE_DEFINITIONS(alhrtf PRIVATE ${CPP_DEFS})
TARGET_INCLUDE_DIRECTORIES(alhrtf
PRIVATE ${SDL2_INCLUDE_DIR} ${SDL_SOUND_INCLUDE_DIR})
- TARGET_COMPILE_OPTIONS(alhrtf PRIVATE ${C_FLAGS})
TARGET_LINK_LIBRARIES(alhrtf
- PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common common
- OpenAL ${MATH_LIB})
+ PRIVATE ${LINKER_FLAGS} ${SDL_SOUND_LIBRARIES} ${SDL2_LIBRARY} ex-common ${MATH_LIB})
IF(ALSOFT_INSTALL)
INSTALL(TARGETS alplay alstream alreverb almultireverb allatency alloopback alhrtf
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
- )
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
ENDIF()
MESSAGE(STATUS "Building SDL_sound example programs")
@@ -1756,20 +1567,16 @@ IF(ALSOFT_EXAMPLES)
ENDIF()
IF(FFVER_OK)
ADD_EXECUTABLE(alffplay examples/alffplay.cpp)
- TARGET_COMPILE_DEFINITIONS(alffplay PRIVATE ${CPP_DEFS})
TARGET_INCLUDE_DIRECTORIES(alffplay
PRIVATE ${SDL2_INCLUDE_DIR} ${FFMPEG_INCLUDE_DIRS})
- TARGET_COMPILE_OPTIONS(alffplay PRIVATE ${C_FLAGS})
TARGET_LINK_LIBRARIES(alffplay
- PRIVATE ${LINKER_FLAGS} ${SDL2_LIBRARY} ${FFMPEG_LIBRARIES} ex-common common
- OpenAL)
+ PRIVATE ${LINKER_FLAGS} ${SDL2_LIBRARY} ${FFMPEG_LIBRARIES} ex-common)
IF(ALSOFT_INSTALL)
INSTALL(TARGETS alffplay
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
- )
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
ENDIF()
MESSAGE(STATUS "Building SDL+FFmpeg example programs")
ENDIF()
diff --git a/ChangeLog b/ChangeLog
index d234c14f..db9ebd92 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,65 @@
+openal-soft-1.20.0:
+
+ Converted the library codebase to C++11. A lot of hacks and custom
+ structures have been replaced with standard or cleaner implementations.
+
+ Partially implemented the Vocal Morpher effect.
+
+ Fixed the bsinc SSE resamplers on non-GCC compilers.
+
+ Fixed OpenSL capture.
+
+ Fixed support for extended capture formats with OpenSL.
+
+ Fixed handling of WASAPI not reporting a default device.
+
+ Fixed performance problems relating to semaphores on macOS.
+
+ Modified the bsinc12 resampler's transition band to better avoid aliasing
+ noise.
+
+ Modified alcResetDeviceSOFT to attempt recovery of disconnected devices.
+
+ Modified the virtual speaker layout for HRTF B-Format decoding.
+
+ Modified the PulseAudio backend to use a custom processing loop.
+
+ Renamed the makehrtf utility to makemhr.
+
+ Improved the efficiency of the bsinc resamplers when up-sampling.
+
+ Improved the quality of the bsinc resamplers slightly.
+
+ Improved the efficiency of the HRTF filters.
+
+ Improved the HRTF B-Format decoder coefficient generation.
+
+ Improved reverb feedback fading to be more consistent with pan fading.
+
+ Improved handling of sources that end prematurely, avoiding loud clicks.
+
+ Improved the performance of some reverb processing loops.
+
+ Added fast_bsinc12 and 24 resamplers that improve efficiency at the cost of
+ some quality. Notably, down-sampling has less smooth pitch ramping.
+
+ Added support for SOFA input files with makemhr.
+
+ Added a build option to use pre-built native tools. For cross-compiling,
+ use with caution and ensure the native tools' binaries are kept up-to-date.
+
+ Added an adjust-latency config option for the PulseAudio backend.
+
+ Added basic support for multi-field HRTFs.
+
+ Added an option for mixing first- or second-order B-Format with HRTF
+ output. This can improve HRTF performance given a number of sources.
+
+ Added an RC file for proper DLL version information.
+
+ Disabled some old KDE workarounds by default. Specifically, PulseAudio
+ streams can now be moved (KDE may try to move them after opening).
+
openal-soft-1.19.1:
Implemented capture support for the SoundIO backend.
diff --git a/OpenAL32/Include/alAuxEffectSlot.h b/OpenAL32/Include/alAuxEffectSlot.h
deleted file mode 100644
index 03ee97d6..00000000
--- a/OpenAL32/Include/alAuxEffectSlot.h
+++ /dev/null
@@ -1,185 +0,0 @@
-#ifndef _AL_AUXEFFECTSLOT_H_
-#define _AL_AUXEFFECTSLOT_H_
-
-#include "alMain.h"
-#include "alEffect.h"
-
-#include "atomic.h"
-#include "align.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct ALeffectStateVtable;
-struct ALeffectslot;
-
-typedef struct ALeffectState {
- RefCount Ref;
- const struct ALeffectStateVtable *vtbl;
-
- ALfloat (*OutBuffer)[BUFFERSIZE];
- ALsizei OutChannels;
-} ALeffectState;
-
-void ALeffectState_Construct(ALeffectState *state);
-void ALeffectState_Destruct(ALeffectState *state);
-
-struct ALeffectStateVtable {
- void (*const Destruct)(ALeffectState *state);
-
- ALboolean (*const deviceUpdate)(ALeffectState *state, ALCdevice *device);
- void (*const update)(ALeffectState *state, const ALCcontext *context, const struct ALeffectslot *slot, const union ALeffectProps *props);
- void (*const process)(ALeffectState *state, ALsizei samplesToDo, const ALfloat (*restrict samplesIn)[BUFFERSIZE], ALfloat (*restrict samplesOut)[BUFFERSIZE], ALsizei numChannels);
-
- void (*const Delete)(void *ptr);
-};
-
-/* Small hack to use a pointer-to-array types as a normal argument type.
- * Shouldn't be used directly.
- */
-typedef ALfloat ALfloatBUFFERSIZE[BUFFERSIZE];
-
-#define DEFINE_ALEFFECTSTATE_VTABLE(T) \
-DECLARE_THUNK(T, ALeffectState, void, Destruct) \
-DECLARE_THUNK1(T, ALeffectState, ALboolean, deviceUpdate, ALCdevice*) \
-DECLARE_THUNK3(T, ALeffectState, void, update, const ALCcontext*, const ALeffectslot*, const ALeffectProps*) \
-DECLARE_THUNK4(T, ALeffectState, void, process, ALsizei, const ALfloatBUFFERSIZE*restrict, ALfloatBUFFERSIZE*restrict, ALsizei) \
-static void T##_ALeffectState_Delete(void *ptr) \
-{ return T##_Delete(STATIC_UPCAST(T, ALeffectState, (ALeffectState*)ptr)); } \
- \
-static const struct ALeffectStateVtable T##_ALeffectState_vtable = { \
- T##_ALeffectState_Destruct, \
- \
- T##_ALeffectState_deviceUpdate, \
- T##_ALeffectState_update, \
- T##_ALeffectState_process, \
- \
- T##_ALeffectState_Delete, \
-}
-
-
-struct EffectStateFactoryVtable;
-
-typedef struct EffectStateFactory {
- const struct EffectStateFactoryVtable *vtab;
-} EffectStateFactory;
-
-struct EffectStateFactoryVtable {
- ALeffectState *(*const create)(EffectStateFactory *factory);
-};
-#define EffectStateFactory_create(x) ((x)->vtab->create((x)))
-
-#define DEFINE_EFFECTSTATEFACTORY_VTABLE(T) \
-DECLARE_THUNK(T, EffectStateFactory, ALeffectState*, create) \
- \
-static const struct EffectStateFactoryVtable T##_EffectStateFactory_vtable = { \
- T##_EffectStateFactory_create, \
-}
-
-
-#define MAX_EFFECT_CHANNELS (4)
-
-
-struct ALeffectslotArray {
- ALsizei count;
- struct ALeffectslot *slot[];
-};
-
-
-struct ALeffectslotProps {
- ALfloat Gain;
- ALboolean AuxSendAuto;
-
- ALenum Type;
- ALeffectProps Props;
-
- ALeffectState *State;
-
- ATOMIC(struct ALeffectslotProps*) next;
-};
-
-
-typedef struct ALeffectslot {
- ALfloat Gain;
- ALboolean AuxSendAuto;
-
- struct {
- ALenum Type;
- ALeffectProps Props;
-
- ALeffectState *State;
- } Effect;
-
- ATOMIC_FLAG PropsClean;
-
- RefCount ref;
-
- ATOMIC(struct ALeffectslotProps*) Update;
-
- struct {
- ALfloat Gain;
- ALboolean AuxSendAuto;
-
- ALenum EffectType;
- ALeffectProps EffectProps;
- ALeffectState *EffectState;
-
- ALfloat RoomRolloff; /* Added to the source's room rolloff, not multiplied. */
- ALfloat DecayTime;
- ALfloat DecayLFRatio;
- ALfloat DecayHFRatio;
- ALboolean DecayHFLimit;
- ALfloat AirAbsorptionGainHF;
- } Params;
-
- /* Self ID */
- ALuint id;
-
- ALsizei NumChannels;
- BFChannelConfig ChanMap[MAX_EFFECT_CHANNELS];
- /* Wet buffer configuration is ACN channel order with N3D scaling:
- * * Channel 0 is the unattenuated mono signal.
- * * Channel 1 is OpenAL -X * sqrt(3)
- * * Channel 2 is OpenAL Y * sqrt(3)
- * * Channel 3 is OpenAL -Z * sqrt(3)
- * Consequently, effects that only want to work with mono input can use
- * channel 0 by itself. Effects that want multichannel can process the
- * ambisonics signal and make a B-Format source pan for first-order device
- * output (FOAOut).
- */
- alignas(16) ALfloat WetBuffer[MAX_EFFECT_CHANNELS][BUFFERSIZE];
-} ALeffectslot;
-
-ALenum InitEffectSlot(ALeffectslot *slot);
-void DeinitEffectSlot(ALeffectslot *slot);
-void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context);
-void UpdateAllEffectSlotProps(ALCcontext *context);
-ALvoid ReleaseALAuxiliaryEffectSlots(ALCcontext *Context);
-
-
-EffectStateFactory *NullStateFactory_getFactory(void);
-EffectStateFactory *ReverbStateFactory_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 *DedicatedStateFactory_getFactory(void);
-
-
-ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect);
-
-void ALeffectState_DecRef(ALeffectState *state);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/OpenAL32/Include/alBuffer.h b/OpenAL32/Include/alBuffer.h
deleted file mode 100644
index fbe3e6e5..00000000
--- a/OpenAL32/Include/alBuffer.h
+++ /dev/null
@@ -1,115 +0,0 @@
-#ifndef _AL_BUFFER_H_
-#define _AL_BUFFER_H_
-
-#include "AL/alc.h"
-#include "AL/al.h"
-#include "AL/alext.h"
-
-#include "inprogext.h"
-#include "atomic.h"
-#include "rwlock.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* User formats */
-enum UserFmtType {
- UserFmtUByte,
- UserFmtShort,
- UserFmtFloat,
- UserFmtDouble,
- UserFmtMulaw,
- UserFmtAlaw,
- UserFmtIMA4,
- UserFmtMSADPCM,
-};
-enum UserFmtChannels {
- UserFmtMono,
- UserFmtStereo,
- UserFmtRear,
- UserFmtQuad,
- UserFmtX51, /* (WFX order) */
- UserFmtX61, /* (WFX order) */
- UserFmtX71, /* (WFX order) */
- UserFmtBFormat2D, /* WXY */
- UserFmtBFormat3D, /* WXYZ */
-};
-
-ALsizei BytesFromUserFmt(enum UserFmtType type);
-ALsizei ChannelsFromUserFmt(enum UserFmtChannels chans);
-inline ALsizei FrameSizeFromUserFmt(enum UserFmtChannels chans, enum UserFmtType type)
-{
- return ChannelsFromUserFmt(chans) * BytesFromUserFmt(type);
-}
-
-
-/* Storable formats */
-enum FmtType {
- FmtUByte = UserFmtUByte,
- FmtShort = UserFmtShort,
- FmtFloat = UserFmtFloat,
- FmtDouble = UserFmtDouble,
- FmtMulaw = UserFmtMulaw,
- FmtAlaw = UserFmtAlaw,
-};
-enum FmtChannels {
- FmtMono = UserFmtMono,
- FmtStereo = UserFmtStereo,
- FmtRear = UserFmtRear,
- FmtQuad = UserFmtQuad,
- FmtX51 = UserFmtX51,
- FmtX61 = UserFmtX61,
- FmtX71 = UserFmtX71,
- FmtBFormat2D = UserFmtBFormat2D,
- FmtBFormat3D = UserFmtBFormat3D,
-};
-#define MAX_INPUT_CHANNELS (8)
-
-ALsizei BytesFromFmt(enum FmtType type);
-ALsizei ChannelsFromFmt(enum FmtChannels chans);
-inline ALsizei FrameSizeFromFmt(enum FmtChannels chans, enum FmtType type)
-{
- return ChannelsFromFmt(chans) * BytesFromFmt(type);
-}
-
-
-typedef struct ALbuffer {
- ALvoid *data;
-
- ALsizei Frequency;
- ALbitfieldSOFT Access;
- ALsizei SampleLen;
-
- enum FmtChannels FmtChannels;
- enum FmtType FmtType;
- ALsizei BytesAlloc;
-
- enum UserFmtType OriginalType;
- ALsizei OriginalSize;
- ALsizei OriginalAlign;
-
- ALsizei LoopStart;
- ALsizei LoopEnd;
-
- ATOMIC(ALsizei) UnpackAlign;
- ATOMIC(ALsizei) PackAlign;
-
- ALbitfieldSOFT MappedAccess;
- ALsizei MappedOffset;
- ALsizei MappedSize;
-
- /* Number of times buffer was attached to a source (deletion can only occur when 0) */
- RefCount ref;
-
- /* Self ID */
- ALuint id;
-} ALbuffer;
-
-ALvoid ReleaseALBuffers(ALCdevice *device);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/OpenAL32/Include/alEffect.h b/OpenAL32/Include/alEffect.h
deleted file mode 100644
index 7b849c0c..00000000
--- a/OpenAL32/Include/alEffect.h
+++ /dev/null
@@ -1,213 +0,0 @@
-#ifndef _AL_EFFECT_H_
-#define _AL_EFFECT_H_
-
-#include "alMain.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct ALeffect;
-
-enum {
- EAXREVERB_EFFECT = 0,
- REVERB_EFFECT,
- AUTOWAH_EFFECT,
- CHORUS_EFFECT,
- COMPRESSOR_EFFECT,
- DISTORTION_EFFECT,
- ECHO_EFFECT,
- EQUALIZER_EFFECT,
- FLANGER_EFFECT,
- FSHIFTER_EFFECT,
- MODULATOR_EFFECT,
- PSHIFTER_EFFECT,
- DEDICATED_EFFECT,
-
- MAX_EFFECTS
-};
-extern ALboolean DisabledEffects[MAX_EFFECTS];
-
-extern ALfloat ReverbBoost;
-
-struct EffectList {
- const char name[16];
- int type;
- ALenum val;
-};
-#define EFFECTLIST_SIZE 14
-extern const struct EffectList EffectList[EFFECTLIST_SIZE];
-
-
-struct ALeffectVtable {
- void (*const setParami)(struct ALeffect *effect, ALCcontext *context, ALenum param, ALint val);
- void (*const setParamiv)(struct ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals);
- void (*const setParamf)(struct ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val);
- void (*const setParamfv)(struct ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals);
-
- void (*const getParami)(const struct ALeffect *effect, ALCcontext *context, ALenum param, ALint *val);
- void (*const getParamiv)(const struct ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals);
- void (*const getParamf)(const struct ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val);
- void (*const getParamfv)(const struct ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals);
-};
-
-#define DEFINE_ALEFFECT_VTABLE(T) \
-const struct ALeffectVtable T##_vtable = { \
- T##_setParami, T##_setParamiv, \
- T##_setParamf, T##_setParamfv, \
- T##_getParami, T##_getParamiv, \
- T##_getParamf, T##_getParamfv, \
-}
-
-extern const struct ALeffectVtable ALeaxreverb_vtable;
-extern const struct ALeffectVtable ALreverb_vtable;
-extern const struct ALeffectVtable ALautowah_vtable;
-extern const struct ALeffectVtable ALchorus_vtable;
-extern const struct ALeffectVtable ALcompressor_vtable;
-extern const struct ALeffectVtable ALdistortion_vtable;
-extern const struct ALeffectVtable ALecho_vtable;
-extern const struct ALeffectVtable ALequalizer_vtable;
-extern const struct ALeffectVtable ALflanger_vtable;
-extern const struct ALeffectVtable ALfshifter_vtable;
-extern const struct ALeffectVtable ALmodulator_vtable;
-extern const struct ALeffectVtable ALnull_vtable;
-extern const struct ALeffectVtable ALpshifter_vtable;
-extern const struct ALeffectVtable ALdedicated_vtable;
-
-
-typedef union ALeffectProps {
- struct {
- // Shared Reverb Properties
- ALfloat Density;
- ALfloat Diffusion;
- ALfloat Gain;
- ALfloat GainHF;
- ALfloat DecayTime;
- ALfloat DecayHFRatio;
- ALfloat ReflectionsGain;
- ALfloat ReflectionsDelay;
- ALfloat LateReverbGain;
- ALfloat LateReverbDelay;
- ALfloat AirAbsorptionGainHF;
- ALfloat RoomRolloffFactor;
- ALboolean DecayHFLimit;
-
- // Additional EAX Reverb Properties
- ALfloat GainLF;
- ALfloat DecayLFRatio;
- ALfloat ReflectionsPan[3];
- ALfloat LateReverbPan[3];
- ALfloat EchoTime;
- ALfloat EchoDepth;
- ALfloat ModulationTime;
- ALfloat ModulationDepth;
- ALfloat HFReference;
- ALfloat LFReference;
- } Reverb;
-
- struct {
- ALfloat AttackTime;
- ALfloat ReleaseTime;
- ALfloat Resonance;
- ALfloat PeakGain;
- } Autowah;
-
- struct {
- ALint Waveform;
- ALint Phase;
- ALfloat Rate;
- ALfloat Depth;
- ALfloat Feedback;
- ALfloat Delay;
- } Chorus; /* Also Flanger */
-
- struct {
- ALboolean OnOff;
- } Compressor;
-
- struct {
- ALfloat Edge;
- ALfloat Gain;
- ALfloat LowpassCutoff;
- ALfloat EQCenter;
- ALfloat EQBandwidth;
- } Distortion;
-
- struct {
- ALfloat Delay;
- ALfloat LRDelay;
-
- ALfloat Damping;
- ALfloat Feedback;
-
- ALfloat Spread;
- } Echo;
-
- struct {
- ALfloat LowCutoff;
- ALfloat LowGain;
- ALfloat Mid1Center;
- ALfloat Mid1Gain;
- ALfloat Mid1Width;
- ALfloat Mid2Center;
- ALfloat Mid2Gain;
- ALfloat Mid2Width;
- ALfloat HighCutoff;
- ALfloat HighGain;
- } Equalizer;
-
- struct {
- ALfloat Frequency;
- ALint LeftDirection;
- ALint RightDirection;
- } Fshifter;
-
- struct {
- ALfloat Frequency;
- ALfloat HighPassCutoff;
- ALint Waveform;
- } Modulator;
-
- struct {
- ALint CoarseTune;
- ALint FineTune;
- } Pshifter;
-
- struct {
- ALfloat Gain;
- } Dedicated;
-} ALeffectProps;
-
-typedef struct ALeffect {
- // Effect type (AL_EFFECT_NULL, ...)
- ALenum type;
-
- ALeffectProps Props;
-
- const struct ALeffectVtable *vtab;
-
- /* Self ID */
- ALuint id;
-} ALeffect;
-#define ALeffect_setParami(o, c, p, v) ((o)->vtab->setParami(o, c, p, v))
-#define ALeffect_setParamf(o, c, p, v) ((o)->vtab->setParamf(o, c, p, v))
-#define ALeffect_setParamiv(o, c, p, v) ((o)->vtab->setParamiv(o, c, p, v))
-#define ALeffect_setParamfv(o, c, p, v) ((o)->vtab->setParamfv(o, c, p, v))
-#define ALeffect_getParami(o, c, p, v) ((o)->vtab->getParami(o, c, p, v))
-#define ALeffect_getParamf(o, c, p, v) ((o)->vtab->getParamf(o, c, p, v))
-#define ALeffect_getParamiv(o, c, p, v) ((o)->vtab->getParamiv(o, c, p, v))
-#define ALeffect_getParamfv(o, c, p, v) ((o)->vtab->getParamfv(o, c, p, v))
-
-inline ALboolean IsReverbEffect(ALenum type)
-{ return type == AL_EFFECT_REVERB || type == AL_EFFECT_EAXREVERB; }
-
-void InitEffect(ALeffect *effect);
-void ReleaseALEffects(ALCdevice *device);
-
-void LoadReverbPreset(const char *name, ALeffect *effect);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/OpenAL32/Include/alError.h b/OpenAL32/Include/alError.h
deleted file mode 100644
index 858f81de..00000000
--- a/OpenAL32/Include/alError.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef _AL_ERROR_H_
-#define _AL_ERROR_H_
-
-#include "alMain.h"
-#include "logging.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-extern ALboolean TrapALError;
-
-void alSetError(ALCcontext *context, ALenum errorCode, const char *msg, ...) DECL_FORMAT(printf, 3, 4);
-
-#define SETERR_GOTO(ctx, err, lbl, ...) do { \
- alSetError((ctx), (err), __VA_ARGS__); \
- goto lbl; \
-} while(0)
-
-#define SETERR_RETURN(ctx, err, retval, ...) do { \
- alSetError((ctx), (err), __VA_ARGS__); \
- return retval; \
-} while(0)
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/OpenAL32/Include/alFilter.h b/OpenAL32/Include/alFilter.h
deleted file mode 100644
index 2634d5e8..00000000
--- a/OpenAL32/Include/alFilter.h
+++ /dev/null
@@ -1,67 +0,0 @@
-#ifndef _AL_FILTER_H_
-#define _AL_FILTER_H_
-
-#include "AL/alc.h"
-#include "AL/al.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define LOWPASSFREQREF (5000.0f)
-#define HIGHPASSFREQREF (250.0f)
-
-
-struct ALfilter;
-
-typedef struct ALfilterVtable {
- void (*const setParami)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALint val);
- void (*const setParamiv)(struct ALfilter *filter, ALCcontext *context, ALenum param, const ALint *vals);
- void (*const setParamf)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val);
- void (*const setParamfv)(struct ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals);
-
- void (*const getParami)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALint *val);
- void (*const getParamiv)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALint *vals);
- void (*const getParamf)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val);
- void (*const getParamfv)(struct ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals);
-} ALfilterVtable;
-
-#define DEFINE_ALFILTER_VTABLE(T) \
-const struct ALfilterVtable T##_vtable = { \
- T##_setParami, T##_setParamiv, \
- T##_setParamf, T##_setParamfv, \
- T##_getParami, T##_getParamiv, \
- T##_getParamf, T##_getParamfv, \
-}
-
-typedef struct ALfilter {
- // Filter type (AL_FILTER_NULL, ...)
- ALenum type;
-
- ALfloat Gain;
- ALfloat GainHF;
- ALfloat HFReference;
- ALfloat GainLF;
- ALfloat LFReference;
-
- const struct ALfilterVtable *vtab;
-
- /* Self ID */
- ALuint id;
-} ALfilter;
-#define ALfilter_setParami(o, c, p, v) ((o)->vtab->setParami(o, c, p, v))
-#define ALfilter_setParamf(o, c, p, v) ((o)->vtab->setParamf(o, c, p, v))
-#define ALfilter_setParamiv(o, c, p, v) ((o)->vtab->setParamiv(o, c, p, v))
-#define ALfilter_setParamfv(o, c, p, v) ((o)->vtab->setParamfv(o, c, p, v))
-#define ALfilter_getParami(o, c, p, v) ((o)->vtab->getParami(o, c, p, v))
-#define ALfilter_getParamf(o, c, p, v) ((o)->vtab->getParamf(o, c, p, v))
-#define ALfilter_getParamiv(o, c, p, v) ((o)->vtab->getParamiv(o, c, p, v))
-#define ALfilter_getParamfv(o, c, p, v) ((o)->vtab->getParamfv(o, c, p, v))
-
-void ReleaseALFilters(ALCdevice *device);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/OpenAL32/Include/alListener.h b/OpenAL32/Include/alListener.h
deleted file mode 100644
index 0d80a8d7..00000000
--- a/OpenAL32/Include/alListener.h
+++ /dev/null
@@ -1,67 +0,0 @@
-#ifndef _AL_LISTENER_H_
-#define _AL_LISTENER_H_
-
-#include "alMain.h"
-#include "alu.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct ALcontextProps {
- ALfloat DopplerFactor;
- ALfloat DopplerVelocity;
- ALfloat SpeedOfSound;
- ALboolean SourceDistanceModel;
- enum DistanceModel DistanceModel;
- ALfloat MetersPerUnit;
-
- ATOMIC(struct ALcontextProps*) next;
-};
-
-struct ALlistenerProps {
- ALfloat Position[3];
- ALfloat Velocity[3];
- ALfloat Forward[3];
- ALfloat Up[3];
- ALfloat Gain;
-
- ATOMIC(struct ALlistenerProps*) next;
-};
-
-typedef struct ALlistener {
- alignas(16) ALfloat Position[3];
- ALfloat Velocity[3];
- ALfloat Forward[3];
- ALfloat Up[3];
- ALfloat Gain;
-
- ATOMIC_FLAG PropsClean;
-
- /* Pointer to the most recent property values that are awaiting an update.
- */
- ATOMIC(struct ALlistenerProps*) Update;
-
- struct {
- aluMatrixf Matrix;
- aluVector Velocity;
-
- ALfloat Gain;
- ALfloat MetersPerUnit;
-
- ALfloat DopplerFactor;
- ALfloat SpeedOfSound; /* in units per sec! */
- ALfloat ReverbSpeedOfSound; /* in meters per sec! */
-
- ALboolean SourceDistanceModel;
- enum DistanceModel DistanceModel;
- } Params;
-} ALlistener;
-
-void UpdateListenerProps(ALCcontext *context);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/OpenAL32/Include/alMain.h b/OpenAL32/Include/alMain.h
deleted file mode 100644
index 0fd77491..00000000
--- a/OpenAL32/Include/alMain.h
+++ /dev/null
@@ -1,920 +0,0 @@
-#ifndef AL_MAIN_H
-#define AL_MAIN_H
-
-#include <string.h>
-#include <stdio.h>
-#include <stddef.h>
-#include <stdarg.h>
-#include <assert.h>
-#include <math.h>
-#include <limits.h>
-
-#ifdef HAVE_STRINGS_H
-#include <strings.h>
-#endif
-#ifdef HAVE_INTRIN_H
-#include <intrin.h>
-#endif
-
-#include "AL/al.h"
-#include "AL/alc.h"
-#include "AL/alext.h"
-
-#include "inprogext.h"
-#include "logging.h"
-#include "polymorphism.h"
-#include "static_assert.h"
-#include "align.h"
-#include "atomic.h"
-#include "vector.h"
-#include "alstring.h"
-#include "almalloc.h"
-#include "threads.h"
-
-
-#if defined(_WIN64)
-#define SZFMT "%I64u"
-#elif defined(_WIN32)
-#define SZFMT "%u"
-#else
-#define SZFMT "%zu"
-#endif
-
-#ifdef __has_builtin
-#define HAS_BUILTIN __has_builtin
-#else
-#define HAS_BUILTIN(x) (0)
-#endif
-
-#ifdef __GNUC__
-/* LIKELY optimizes the case where the condition is true. The condition is not
- * required to be true, but it can result in more optimal code for the true
- * path at the expense of a less optimal false path.
- */
-#define LIKELY(x) __builtin_expect(!!(x), !0)
-/* The opposite of LIKELY, optimizing the case where the condition is false. */
-#define UNLIKELY(x) __builtin_expect(!!(x), 0)
-/* Unlike LIKELY, ASSUME requires the condition to be true or else it invokes
- * undefined behavior. It's essentially an assert without actually checking the
- * condition at run-time, allowing for stronger optimizations than LIKELY.
- */
-#if HAS_BUILTIN(__builtin_assume)
-#define ASSUME __builtin_assume
-#else
-#define ASSUME(x) do { if(!(x)) __builtin_unreachable(); } while(0)
-#endif
-
-#else
-
-#define LIKELY(x) (!!(x))
-#define UNLIKELY(x) (!!(x))
-#ifdef _MSC_VER
-#define ASSUME __assume
-#else
-#define ASSUME(x) ((void)0)
-#endif
-#endif
-
-#ifndef UINT64_MAX
-#define UINT64_MAX U64(18446744073709551615)
-#endif
-
-#ifndef UNUSED
-#if defined(__cplusplus)
-#define UNUSED(x)
-#elif defined(__GNUC__)
-#define UNUSED(x) UNUSED_##x __attribute__((unused))
-#elif defined(__LCLINT__)
-#define UNUSED(x) /*@unused@*/ x
-#else
-#define UNUSED(x) x
-#endif
-#endif
-
-/* Calculates the size of a struct with N elements of a flexible array member.
- * GCC and Clang allow offsetof(Type, fam[N]) for this, but MSVC seems to have
- * trouble, so a bit more verbose workaround is needed.
- */
-#define FAM_SIZE(T, M, N) (offsetof(T, M) + sizeof(((T*)NULL)->M[0])*(N))
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef ALint64SOFT ALint64;
-typedef ALuint64SOFT ALuint64;
-
-#ifndef U64
-#if defined(_MSC_VER)
-#define U64(x) ((ALuint64)(x##ui64))
-#elif SIZEOF_LONG == 8
-#define U64(x) ((ALuint64)(x##ul))
-#elif SIZEOF_LONG_LONG == 8
-#define U64(x) ((ALuint64)(x##ull))
-#endif
-#endif
-
-#ifndef I64
-#if defined(_MSC_VER)
-#define I64(x) ((ALint64)(x##i64))
-#elif SIZEOF_LONG == 8
-#define I64(x) ((ALint64)(x##l))
-#elif SIZEOF_LONG_LONG == 8
-#define I64(x) ((ALint64)(x##ll))
-#endif
-#endif
-
-/* Define a CTZ64 macro (count trailing zeros, for 64-bit integers). The result
- * is *UNDEFINED* if the value is 0.
- */
-#ifdef __GNUC__
-
-#if SIZEOF_LONG == 8
-#define CTZ64 __builtin_ctzl
-#else
-#define CTZ64 __builtin_ctzll
-#endif
-
-#elif defined(HAVE_BITSCANFORWARD64_INTRINSIC)
-
-inline int msvc64_ctz64(ALuint64 v)
-{
- unsigned long idx = 64;
- _BitScanForward64(&idx, v);
- return (int)idx;
-}
-#define CTZ64 msvc64_ctz64
-
-#elif defined(HAVE_BITSCANFORWARD_INTRINSIC)
-
-inline int msvc_ctz64(ALuint64 v)
-{
- unsigned long idx = 64;
- if(!_BitScanForward(&idx, v&0xffffffff))
- {
- if(_BitScanForward(&idx, v>>32))
- idx += 32;
- }
- return (int)idx;
-}
-#define CTZ64 msvc_ctz64
-
-#else
-
-/* There be black magics here. The popcnt64 method is derived from
- * https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
- * while the ctz-utilizing-popcnt algorithm is shown here
- * http://www.hackersdelight.org/hdcodetxt/ntz.c.txt
- * as the ntz2 variant. These likely aren't the most efficient methods, but
- * they're good enough if the GCC or MSVC intrinsics aren't available.
- */
-inline int fallback_popcnt64(ALuint64 v)
-{
- v = v - ((v >> 1) & U64(0x5555555555555555));
- v = (v & U64(0x3333333333333333)) + ((v >> 2) & U64(0x3333333333333333));
- v = (v + (v >> 4)) & U64(0x0f0f0f0f0f0f0f0f);
- return (int)((v * U64(0x0101010101010101)) >> 56);
-}
-
-inline int fallback_ctz64(ALuint64 value)
-{
- return fallback_popcnt64(~value & (value - 1));
-}
-#define CTZ64 fallback_ctz64
-#endif
-
-#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__)
-#define IS_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
-#else
-static const union {
- ALuint u;
- ALubyte b[sizeof(ALuint)];
-} EndianTest = { 1 };
-#define IS_LITTLE_ENDIAN (EndianTest.b[0] == 1)
-#endif
-
-#define COUNTOF(x) (sizeof(x) / sizeof(0[x]))
-
-
-struct ll_ringbuffer;
-struct Hrtf;
-struct HrtfEntry;
-struct DirectHrtfState;
-struct FrontStablizer;
-struct Compressor;
-struct ALCbackend;
-struct ALbuffer;
-struct ALeffect;
-struct ALfilter;
-struct ALsource;
-struct ALcontextProps;
-struct ALlistenerProps;
-struct ALvoiceProps;
-struct ALeffectslotProps;
-
-
-#define DEFAULT_OUTPUT_RATE (44100)
-#define MIN_OUTPUT_RATE (8000)
-
-
-/* Find the next power-of-2 for non-power-of-2 numbers. */
-inline ALuint NextPowerOf2(ALuint value)
-{
- if(value > 0)
- {
- value--;
- value |= value>>1;
- value |= value>>2;
- value |= value>>4;
- value |= value>>8;
- value |= value>>16;
- }
- return value+1;
-}
-
-/** Round up a value to the next multiple. */
-inline size_t RoundUp(size_t value, size_t r)
-{
- value += r-1;
- return value - (value%r);
-}
-
-/* Fast float-to-int conversion. No particular rounding mode is assumed; the
- * IEEE-754 default is round-to-nearest with ties-to-even, though an app could
- * change it on its own threads. On some systems, a truncating conversion may
- * always be the fastest method.
- */
-inline ALint fastf2i(ALfloat f)
-{
-#if defined(HAVE_INTRIN_H) && ((defined(_M_IX86_FP) && (_M_IX86_FP > 0)) || defined(_M_X64))
- return _mm_cvt_ss2si(_mm_set1_ps(f));
-
-#elif defined(_MSC_VER) && defined(_M_IX86_FP)
-
- ALint i;
- __asm fld f
- __asm fistp i
- return i;
-
-#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
-
- ALint i;
-#ifdef __SSE_MATH__
- __asm__("cvtss2si %1, %0" : "=r"(i) : "x"(f));
-#else
- __asm__ __volatile__("fistpl %0" : "=m"(i) : "t"(f) : "st");
-#endif
- return i;
-
- /* On GCC when compiling with -fno-math-errno, lrintf can be inlined to
- * some simple instructions. Clang does not inline it, always generating a
- * libc call, while MSVC's implementation is horribly slow, so always fall
- * back to a normal integer conversion for them.
- */
-#elif defined(HAVE_LRINTF) && !defined(_MSC_VER) && !defined(__clang__)
-
- return lrintf(f);
-
-#else
-
- return (ALint)f;
-#endif
-}
-
-/* Converts float-to-int using standard behavior (truncation). */
-inline int float2int(float f)
-{
-#if ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \
- !defined(__SSE_MATH__)) || (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0)
- ALint sign, shift, mant;
- union {
- ALfloat f;
- ALint i;
- } conv;
-
- conv.f = f;
- sign = (conv.i>>31) | 1;
- shift = ((conv.i>>23)&0xff) - (127+23);
-
- /* Over/underflow */
- if(UNLIKELY(shift >= 31 || shift < -23))
- return 0;
-
- mant = (conv.i&0x7fffff) | 0x800000;
- if(LIKELY(shift < 0))
- return (mant >> -shift) * sign;
- return (mant << shift) * sign;
-
-#else
-
- return (ALint)f;
-#endif
-}
-
-/* Rounds a float to the nearest integral value, according to the current
- * rounding mode. This is essentially an inlined version of rintf, although
- * makes fewer promises (e.g. -0 or -0.25 rounded to 0 may result in +0).
- */
-inline float fast_roundf(float f)
-{
-#if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \
- !defined(__SSE_MATH__)
-
- float out;
- __asm__ __volatile__("frndint" : "=t"(out) : "0"(f));
- return out;
-
-#else
-
- /* Integral limit, where sub-integral precision is not available for
- * floats.
- */
- static const float ilim[2] = {
- 8388608.0f /* 0x1.0p+23 */,
- -8388608.0f /* -0x1.0p+23 */
- };
- ALuint sign, expo;
- union {
- ALfloat f;
- ALuint i;
- } conv;
-
- conv.f = f;
- sign = (conv.i>>31)&0x01;
- expo = (conv.i>>23)&0xff;
-
- if(UNLIKELY(expo >= 150/*+23*/))
- {
- /* An exponent (base-2) of 23 or higher is incapable of sub-integral
- * precision, so it's already an integral value. We don't need to worry
- * about infinity or NaN here.
- */
- return f;
- }
- /* Adding the integral limit to the value (with a matching sign) forces a
- * result that has no sub-integral precision, and is consequently forced to
- * round to an integral value. Removing the integral limit then restores
- * the initial value rounded to the integral. The compiler should not
- * optimize this out because of non-associative rules on floating-point
- * math (as long as you don't use -fassociative-math,
- * -funsafe-math-optimizations, -ffast-math, or -Ofast, in which case this
- * may break).
- */
- f += ilim[sign];
- return f - ilim[sign];
-#endif
-}
-
-
-enum DevProbe {
- ALL_DEVICE_PROBE,
- CAPTURE_DEVICE_PROBE
-};
-
-
-enum DistanceModel {
- InverseDistanceClamped = AL_INVERSE_DISTANCE_CLAMPED,
- LinearDistanceClamped = AL_LINEAR_DISTANCE_CLAMPED,
- ExponentDistanceClamped = AL_EXPONENT_DISTANCE_CLAMPED,
- InverseDistance = AL_INVERSE_DISTANCE,
- LinearDistance = AL_LINEAR_DISTANCE,
- ExponentDistance = AL_EXPONENT_DISTANCE,
- DisableDistance = AL_NONE,
-
- DefaultDistanceModel = InverseDistanceClamped
-};
-
-enum Channel {
- FrontLeft = 0,
- FrontRight,
- FrontCenter,
- LFE,
- BackLeft,
- BackRight,
- BackCenter,
- SideLeft,
- SideRight,
-
- UpperFrontLeft,
- UpperFrontRight,
- UpperBackLeft,
- UpperBackRight,
- LowerFrontLeft,
- LowerFrontRight,
- LowerBackLeft,
- LowerBackRight,
-
- Aux0,
- Aux1,
- Aux2,
- Aux3,
- Aux4,
- Aux5,
- Aux6,
- Aux7,
- Aux8,
- Aux9,
- Aux10,
- Aux11,
- Aux12,
- Aux13,
- Aux14,
- Aux15,
-
- InvalidChannel
-};
-
-
-/* Device formats */
-enum DevFmtType {
- DevFmtByte = ALC_BYTE_SOFT,
- DevFmtUByte = ALC_UNSIGNED_BYTE_SOFT,
- DevFmtShort = ALC_SHORT_SOFT,
- DevFmtUShort = ALC_UNSIGNED_SHORT_SOFT,
- DevFmtInt = ALC_INT_SOFT,
- DevFmtUInt = ALC_UNSIGNED_INT_SOFT,
- DevFmtFloat = ALC_FLOAT_SOFT,
-
- DevFmtTypeDefault = DevFmtFloat
-};
-enum DevFmtChannels {
- DevFmtMono = ALC_MONO_SOFT,
- DevFmtStereo = ALC_STEREO_SOFT,
- DevFmtQuad = ALC_QUAD_SOFT,
- DevFmtX51 = ALC_5POINT1_SOFT,
- DevFmtX61 = ALC_6POINT1_SOFT,
- DevFmtX71 = ALC_7POINT1_SOFT,
- DevFmtAmbi3D = ALC_BFORMAT3D_SOFT,
-
- /* Similar to 5.1, except using rear channels instead of sides */
- DevFmtX51Rear = 0x80000000,
-
- DevFmtChannelsDefault = DevFmtStereo
-};
-#define MAX_OUTPUT_CHANNELS (16)
-
-ALsizei BytesFromDevFmt(enum DevFmtType type);
-ALsizei ChannelsFromDevFmt(enum DevFmtChannels chans, ALsizei ambiorder);
-inline ALsizei FrameSizeFromDevFmt(enum DevFmtChannels chans, enum DevFmtType type, ALsizei ambiorder)
-{
- return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type);
-}
-
-enum AmbiLayout {
- AmbiLayout_FuMa = ALC_FUMA_SOFT, /* FuMa channel order */
- AmbiLayout_ACN = ALC_ACN_SOFT, /* ACN channel order */
-
- AmbiLayout_Default = AmbiLayout_ACN
-};
-
-enum AmbiNorm {
- AmbiNorm_FuMa = ALC_FUMA_SOFT, /* FuMa normalization */
- AmbiNorm_SN3D = ALC_SN3D_SOFT, /* SN3D normalization */
- AmbiNorm_N3D = ALC_N3D_SOFT, /* N3D normalization */
-
- AmbiNorm_Default = AmbiNorm_SN3D
-};
-
-
-enum DeviceType {
- Playback,
- Capture,
- Loopback
-};
-
-
-enum RenderMode {
- NormalRender,
- StereoPair,
- HrtfRender
-};
-
-
-/* The maximum number of Ambisonics coefficients. For a given order (o), the
- * size needed will be (o+1)**2, thus zero-order has 1, first-order has 4,
- * second-order has 9, third-order has 16, and fourth-order has 25.
- */
-#define MAX_AMBI_ORDER 3
-#define MAX_AMBI_COEFFS ((MAX_AMBI_ORDER+1) * (MAX_AMBI_ORDER+1))
-
-/* A bitmask of ambisonic channels with height information. If none of these
- * channels are used/needed, there's no height (e.g. with most surround sound
- * speaker setups). This only specifies up to 4th order, which is the highest
- * order a 32-bit mask value can specify (a 64-bit mask could handle up to 7th
- * order). This is ACN ordering, with bit 0 being ACN 0, etc.
- */
-#define AMBI_PERIPHONIC_MASK (0xfe7ce4)
-
-/* The maximum number of Ambisonic coefficients for 2D (non-periphonic)
- * representation. This is 2 per each order above zero-order, plus 1 for zero-
- * order. Or simply, o*2 + 1.
- */
-#define MAX_AMBI2D_COEFFS (MAX_AMBI_ORDER*2 + 1)
-
-
-typedef ALfloat ChannelConfig[MAX_AMBI_COEFFS];
-typedef struct BFChannelConfig {
- ALfloat Scale;
- ALsizei Index;
-} BFChannelConfig;
-
-typedef union AmbiConfig {
- /* Ambisonic coefficients for mixing to the dry buffer. */
- ChannelConfig Coeffs[MAX_OUTPUT_CHANNELS];
- /* Coefficient channel mapping for mixing to the dry buffer. */
- BFChannelConfig Map[MAX_OUTPUT_CHANNELS];
-} AmbiConfig;
-
-
-typedef struct BufferSubList {
- ALuint64 FreeMask;
- struct ALbuffer *Buffers; /* 64 */
-} BufferSubList;
-TYPEDEF_VECTOR(BufferSubList, vector_BufferSubList)
-
-typedef struct EffectSubList {
- ALuint64 FreeMask;
- struct ALeffect *Effects; /* 64 */
-} EffectSubList;
-TYPEDEF_VECTOR(EffectSubList, vector_EffectSubList)
-
-typedef struct FilterSubList {
- ALuint64 FreeMask;
- struct ALfilter *Filters; /* 64 */
-} FilterSubList;
-TYPEDEF_VECTOR(FilterSubList, vector_FilterSubList)
-
-typedef struct SourceSubList {
- ALuint64 FreeMask;
- struct ALsource *Sources; /* 64 */
-} SourceSubList;
-TYPEDEF_VECTOR(SourceSubList, vector_SourceSubList)
-
-/* Effect slots are rather large, and apps aren't likely to have more than one
- * or two (let alone 64), so hold them individually.
- */
-typedef struct ALeffectslot *ALeffectslotPtr;
-TYPEDEF_VECTOR(ALeffectslotPtr, vector_ALeffectslotPtr)
-
-
-typedef struct EnumeratedHrtf {
- al_string name;
-
- struct HrtfEntry *hrtf;
-} EnumeratedHrtf;
-TYPEDEF_VECTOR(EnumeratedHrtf, vector_EnumeratedHrtf)
-
-
-/* Maximum delay in samples for speaker distance compensation. */
-#define MAX_DELAY_LENGTH 1024
-
-typedef struct DistanceComp {
- ALfloat Gain;
- ALsizei Length; /* Valid range is [0...MAX_DELAY_LENGTH). */
- ALfloat *Buffer;
-} DistanceComp;
-
-/* Size for temporary storage of buffer data, in ALfloats. Larger values need
- * more memory, while smaller values may need more iterations. The value needs
- * to be a sensible size, however, as it constrains the max stepping value used
- * for mixing, as well as the maximum number of samples per mixing iteration.
- */
-#define BUFFERSIZE 2048
-
-typedef struct MixParams {
- AmbiConfig Ambi;
- /* Number of coefficients in each Ambi.Coeffs to mix together (4 for first-
- * order, 9 for second-order, etc). If the count is 0, Ambi.Map is used
- * instead to map each output to a coefficient index.
- */
- ALsizei CoeffCount;
-
- ALfloat (*Buffer)[BUFFERSIZE];
- ALsizei NumChannels;
-} MixParams;
-
-typedef struct RealMixParams {
- enum Channel ChannelName[MAX_OUTPUT_CHANNELS];
-
- ALfloat (*Buffer)[BUFFERSIZE];
- ALsizei NumChannels;
-} RealMixParams;
-
-typedef void (*POSTPROCESS)(ALCdevice *device, ALsizei SamplesToDo);
-
-struct ALCdevice_struct {
- RefCount ref;
-
- ATOMIC(ALenum) Connected;
- enum DeviceType Type;
-
- ALuint Frequency;
- ALuint UpdateSize;
- ALuint NumUpdates;
- enum DevFmtChannels FmtChans;
- enum DevFmtType FmtType;
- ALboolean IsHeadphones;
- ALsizei AmbiOrder;
- /* For DevFmtAmbi* output only, specifies the channel order and
- * normalization.
- */
- enum AmbiLayout AmbiLayout;
- enum AmbiNorm AmbiScale;
-
- ALCenum LimiterState;
-
- al_string DeviceName;
-
- ATOMIC(ALCenum) LastError;
-
- // Maximum number of sources that can be created
- ALuint SourcesMax;
- // Maximum number of slots that can be created
- ALuint AuxiliaryEffectSlotMax;
-
- ALCuint NumMonoSources;
- ALCuint NumStereoSources;
- ALsizei NumAuxSends;
-
- // Map of Buffers for this device
- vector_BufferSubList BufferList;
- almtx_t BufferLock;
-
- // Map of Effects for this device
- vector_EffectSubList EffectList;
- almtx_t EffectLock;
-
- // Map of Filters for this device
- vector_FilterSubList FilterList;
- almtx_t FilterLock;
-
- POSTPROCESS PostProcess;
-
- /* HRTF state and info */
- struct DirectHrtfState *Hrtf;
- al_string HrtfName;
- struct Hrtf *HrtfHandle;
- vector_EnumeratedHrtf HrtfList;
- ALCenum HrtfStatus;
-
- /* UHJ encoder state */
- struct Uhj2Encoder *Uhj_Encoder;
-
- /* High quality Ambisonic decoder */
- struct BFormatDec *AmbiDecoder;
-
- /* Stereo-to-binaural filter */
- struct bs2b *Bs2b;
-
- /* First-order ambisonic upsampler for higher-order output */
- struct AmbiUpsampler *AmbiUp;
-
- /* Rendering mode. */
- enum RenderMode Render_Mode;
-
- // Device flags
- ALuint Flags;
-
- ALuint64 ClockBase;
- ALuint SamplesDone;
- ALuint FixedLatency;
-
- /* Temp storage used for mixer processing. */
- alignas(16) ALfloat TempBuffer[4][BUFFERSIZE];
-
- /* The "dry" path corresponds to the main output. */
- MixParams Dry;
- ALsizei NumChannelsPerOrder[MAX_AMBI_ORDER+1];
-
- /* First-order ambisonics output, to be upsampled to the dry buffer if different. */
- MixParams FOAOut;
-
- /* "Real" output, which will be written to the device buffer. May alias the
- * dry buffer.
- */
- RealMixParams RealOut;
-
- struct FrontStablizer *Stablizer;
-
- struct Compressor *Limiter;
-
- /* The average speaker distance as determined by the ambdec configuration
- * (or alternatively, by the NFC-HOA reference delay). Only used for NFC.
- */
- ALfloat AvgSpeakerDist;
-
- /* Delay buffers used to compensate for speaker distances. */
- DistanceComp ChannelDelay[MAX_OUTPUT_CHANNELS];
-
- /* Dithering control. */
- ALfloat DitherDepth;
- ALuint DitherSeed;
-
- /* 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;
-
- // Contexts created on this device
- ATOMIC(ALCcontext*) ContextList;
-
- almtx_t BackendLock;
- struct ALCbackend *Backend;
-
- ATOMIC(ALCdevice*) next;
-};
-
-// Frequency was requested by the app or config file
-#define DEVICE_FREQUENCY_REQUEST (1u<<1)
-// Channel configuration was requested by the config file
-#define DEVICE_CHANNELS_REQUEST (1u<<2)
-// Sample type was requested by the config file
-#define DEVICE_SAMPLE_TYPE_REQUEST (1u<<3)
-
-// Specifies if the DSP is paused at user request
-#define DEVICE_PAUSED (1u<<30)
-
-// Specifies if the device is currently running
-#define DEVICE_RUNNING (1u<<31)
-
-
-/* Nanosecond resolution for the device clock time. */
-#define DEVICE_CLOCK_RES U64(1000000000)
-
-
-/* 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"
-
-
-enum {
- /* End event thread processing. */
- EventType_KillThread = 0,
-
- /* User event types. */
- EventType_SourceStateChange = 1<<0,
- EventType_BufferCompleted = 1<<1,
- EventType_Error = 1<<2,
- EventType_Performance = 1<<3,
- EventType_Deprecated = 1<<4,
- EventType_Disconnected = 1<<5,
-
- /* Internal events. */
- EventType_ReleaseEffectState = 65536,
-};
-
-typedef struct AsyncEvent {
- unsigned int EnumType;
- union {
- char dummy;
- struct {
- ALenum type;
- ALuint id;
- ALuint param;
- ALchar msg[1008];
- } user;
- struct ALeffectState *EffectState;
- } u;
-} AsyncEvent;
-#define ASYNC_EVENT(t) { t, { 0 } }
-
-struct ALCcontext_struct {
- RefCount ref;
-
- struct ALlistener *Listener;
-
- vector_SourceSubList SourceList;
- ALuint NumSources;
- almtx_t SourceLock;
-
- vector_ALeffectslotPtr EffectSlotList;
- almtx_t EffectSlotLock;
-
- ATOMIC(ALenum) LastError;
-
- enum DistanceModel DistanceModel;
- ALboolean SourceDistanceModel;
-
- ALfloat DopplerFactor;
- ALfloat DopplerVelocity;
- ALfloat SpeedOfSound;
- ALfloat MetersPerUnit;
-
- ATOMIC_FLAG PropsClean;
- ATOMIC(ALenum) DeferUpdates;
-
- almtx_t PropLock;
-
- /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
- * indicates if updates are currently happening).
- */
- RefCount UpdateCount;
- ATOMIC(ALenum) HoldUpdates;
-
- ALfloat GainBoost;
-
- ATOMIC(struct ALcontextProps*) Update;
-
- /* Linked lists of unused property containers, free to use for future
- * updates.
- */
- ATOMIC(struct ALcontextProps*) FreeContextProps;
- ATOMIC(struct ALlistenerProps*) FreeListenerProps;
- ATOMIC(struct ALvoiceProps*) FreeVoiceProps;
- ATOMIC(struct ALeffectslotProps*) FreeEffectslotProps;
-
- struct ALvoice **Voices;
- ALsizei VoiceCount;
- ALsizei MaxVoices;
-
- ATOMIC(struct ALeffectslotArray*) ActiveAuxSlots;
-
- althrd_t EventThread;
- alsem_t EventSem;
- struct ll_ringbuffer *AsyncEvents;
- ATOMIC(ALbitfieldSOFT) EnabledEvts;
- almtx_t EventCbLock;
- ALEVENTPROCSOFT EventCb;
- void *EventParam;
-
- /* Default effect slot */
- struct ALeffectslot *DefaultSlot;
-
- ALCdevice *Device;
- const ALCchar *ExtensionList;
-
- ATOMIC(ALCcontext*) next;
-
- /* Memory space used by the listener (and possibly default effect slot) */
- alignas(16) ALCbyte _listener_mem[];
-};
-
-ALCcontext *GetContextRef(void);
-
-void ALCcontext_DecRef(ALCcontext *context);
-
-void ALCcontext_DeferUpdates(ALCcontext *context);
-void ALCcontext_ProcessUpdates(ALCcontext *context);
-
-void AllocateVoices(ALCcontext *context, ALsizei num_voices, ALsizei old_sends);
-
-
-extern ALint RTPrioLevel;
-void SetRTPriority(void);
-
-void SetDefaultChannelOrder(ALCdevice *device);
-void SetDefaultWFXChannelOrder(ALCdevice *device);
-
-const ALCchar *DevFmtTypeString(enum DevFmtType type);
-const ALCchar *DevFmtChannelsString(enum DevFmtChannels chans);
-
-inline ALint GetChannelIndex(const enum Channel names[MAX_OUTPUT_CHANNELS], enum Channel chan)
-{
- ALint i;
- for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
- {
- if(names[i] == chan)
- return i;
- }
- return -1;
-}
-/**
- * GetChannelIdxByName
- *
- * Returns the index for the given channel name (e.g. FrontCenter), or -1 if it
- * doesn't exist.
- */
-inline ALint GetChannelIdxByName(const RealMixParams *real, enum Channel chan)
-{ return GetChannelIndex(real->ChannelName, chan); }
-
-
-inline void LockBufferList(ALCdevice *device) { almtx_lock(&device->BufferLock); }
-inline void UnlockBufferList(ALCdevice *device) { almtx_unlock(&device->BufferLock); }
-
-inline void LockEffectList(ALCdevice *device) { almtx_lock(&device->EffectLock); }
-inline void UnlockEffectList(ALCdevice *device) { almtx_unlock(&device->EffectLock); }
-
-inline void LockFilterList(ALCdevice *device) { almtx_lock(&device->FilterLock); }
-inline void UnlockFilterList(ALCdevice *device) { almtx_unlock(&device->FilterLock); }
-
-inline void LockEffectSlotList(ALCcontext *context)
-{ almtx_lock(&context->EffectSlotLock); }
-inline void UnlockEffectSlotList(ALCcontext *context)
-{ almtx_unlock(&context->EffectSlotLock); }
-
-
-int EventThread(void *arg);
-
-
-vector_al_string SearchDataFiles(const char *match, const char *subdir);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/OpenAL32/Include/alSource.h b/OpenAL32/Include/alSource.h
deleted file mode 100644
index 5f07c09d..00000000
--- a/OpenAL32/Include/alSource.h
+++ /dev/null
@@ -1,120 +0,0 @@
-#ifndef _AL_SOURCE_H_
-#define _AL_SOURCE_H_
-
-#include "bool.h"
-#include "alMain.h"
-#include "alu.h"
-#include "hrtf.h"
-#include "atomic.h"
-
-#define MAX_SENDS 16
-#define DEFAULT_SENDS 2
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct ALbuffer;
-struct ALsource;
-
-
-typedef struct ALbufferlistitem {
- ATOMIC(struct ALbufferlistitem*) next;
- ALsizei max_samples;
- ALsizei num_buffers;
- struct ALbuffer *buffers[];
-} ALbufferlistitem;
-
-
-typedef struct ALsource {
- /** Source properties. */
- ALfloat Pitch;
- ALfloat Gain;
- ALfloat OuterGain;
- ALfloat MinGain;
- ALfloat MaxGain;
- ALfloat InnerAngle;
- ALfloat OuterAngle;
- ALfloat RefDistance;
- ALfloat MaxDistance;
- ALfloat RolloffFactor;
- ALfloat Position[3];
- ALfloat Velocity[3];
- ALfloat Direction[3];
- ALfloat Orientation[2][3];
- ALboolean HeadRelative;
- ALboolean Looping;
- enum DistanceModel DistanceModel;
- enum Resampler Resampler;
- ALboolean DirectChannels;
- enum SpatializeMode Spatialize;
-
- ALboolean DryGainHFAuto;
- ALboolean WetGainAuto;
- ALboolean WetGainHFAuto;
- ALfloat OuterGainHF;
-
- ALfloat AirAbsorptionFactor;
- ALfloat RoomRolloffFactor;
- ALfloat DopplerFactor;
-
- /* NOTE: Stereo pan angles are specified in radians, counter-clockwise
- * rather than clockwise.
- */
- ALfloat StereoPan[2];
-
- ALfloat Radius;
-
- /** Direct filter and auxiliary send info. */
- struct {
- ALfloat Gain;
- ALfloat GainHF;
- ALfloat HFReference;
- ALfloat GainLF;
- ALfloat LFReference;
- } Direct;
- struct {
- struct ALeffectslot *Slot;
- ALfloat Gain;
- ALfloat GainHF;
- ALfloat HFReference;
- ALfloat GainLF;
- ALfloat LFReference;
- } *Send;
-
- /**
- * Last user-specified offset, and the offset type (bytes, samples, or
- * seconds).
- */
- ALdouble Offset;
- ALenum OffsetType;
-
- /** Source type (static, streaming, or undetermined) */
- ALint SourceType;
-
- /** Source state (initial, playing, paused, or stopped) */
- ALenum state;
-
- /** Source Buffer Queue head. */
- ALbufferlistitem *queue;
-
- ATOMIC_FLAG PropsClean;
-
- /* Index into the context's Voices array. Lazily updated, only checked and
- * reset when looking up the voice.
- */
- ALint VoiceIdx;
-
- /** Self ID */
- ALuint id;
-} ALsource;
-
-void UpdateAllSourceProps(ALCcontext *context);
-
-ALvoid ReleaseALSources(ALCcontext *Context);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/OpenAL32/Include/alu.h b/OpenAL32/Include/alu.h
deleted file mode 100644
index c572fd71..00000000
--- a/OpenAL32/Include/alu.h
+++ /dev/null
@@ -1,534 +0,0 @@
-#ifndef _ALU_H_
-#define _ALU_H_
-
-#include <limits.h>
-#include <math.h>
-#ifdef HAVE_FLOAT_H
-#include <float.h>
-#endif
-#ifdef HAVE_IEEEFP_H
-#include <ieeefp.h>
-#endif
-
-#include "alMain.h"
-#include "alBuffer.h"
-
-#include "hrtf.h"
-#include "align.h"
-#include "math_defs.h"
-#include "filters/defs.h"
-#include "filters/nfc.h"
-
-
-#define MAX_PITCH (255)
-
-/* Maximum number of samples to pad on either end of a buffer for resampling.
- * Note that both the beginning and end need padding!
- */
-#define MAX_RESAMPLE_PADDING 24
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct BSincTable;
-struct ALsource;
-struct ALbufferlistitem;
-struct ALvoice;
-struct ALeffectslot;
-
-
-#define DITHER_RNG_SEED 22222
-
-
-enum SpatializeMode {
- SpatializeOff = AL_FALSE,
- SpatializeOn = AL_TRUE,
- SpatializeAuto = AL_AUTO_SOFT
-};
-
-enum Resampler {
- PointResampler,
- LinearResampler,
- FIR4Resampler,
- BSinc12Resampler,
- BSinc24Resampler,
-
- ResamplerMax = BSinc24Resampler
-};
-extern enum Resampler ResamplerDefault;
-
-/* The number of distinct scale and phase intervals within the bsinc filter
- * table.
- */
-#define BSINC_SCALE_BITS 4
-#define BSINC_SCALE_COUNT (1<<BSINC_SCALE_BITS)
-#define BSINC_PHASE_BITS 4
-#define BSINC_PHASE_COUNT (1<<BSINC_PHASE_BITS)
-
-/* Interpolator state. Kind of a misnomer since the interpolator itself is
- * stateless. This just keeps it from having to recompute scale-related
- * mappings for every sample.
- */
-typedef struct BsincState {
- ALfloat sf; /* Scale interpolation factor. */
- ALsizei m; /* Coefficient count. */
- ALsizei l; /* Left coefficient offset. */
- /* Filter coefficients, followed by the scale, phase, and scale-phase
- * delta coefficients. Starting at phase index 0, each subsequent phase
- * index follows contiguously.
- */
- const ALfloat *filter;
-} BsincState;
-
-typedef union InterpState {
- BsincState bsinc;
-} InterpState;
-
-typedef const ALfloat* (*ResamplerFunc)(const InterpState *state,
- const ALfloat *restrict src, ALsizei frac, ALint increment,
- ALfloat *restrict dst, ALsizei dstlen
-);
-
-void BsincPrepare(const ALuint increment, BsincState *state, const struct BSincTable *table);
-
-extern const struct BSincTable bsinc12;
-extern const struct BSincTable bsinc24;
-
-
-typedef union aluVector {
- alignas(16) ALfloat v[4];
-} aluVector;
-
-inline void aluVectorSet(aluVector *vector, ALfloat x, ALfloat y, ALfloat z, ALfloat w)
-{
- vector->v[0] = x;
- vector->v[1] = y;
- vector->v[2] = z;
- vector->v[3] = w;
-}
-
-
-typedef union aluMatrixf {
- alignas(16) ALfloat m[4][4];
-} aluMatrixf;
-extern const aluMatrixf IdentityMatrixf;
-
-inline void aluMatrixfSetRow(aluMatrixf *matrix, ALuint row,
- ALfloat m0, ALfloat m1, ALfloat m2, ALfloat m3)
-{
- matrix->m[row][0] = m0;
- matrix->m[row][1] = m1;
- matrix->m[row][2] = m2;
- matrix->m[row][3] = m3;
-}
-
-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)
-{
- aluMatrixfSetRow(matrix, 0, m00, m01, m02, m03);
- aluMatrixfSetRow(matrix, 1, m10, m11, m12, m13);
- aluMatrixfSetRow(matrix, 2, m20, m21, m22, m23);
- aluMatrixfSetRow(matrix, 3, m30, m31, m32, m33);
-}
-
-
-enum ActiveFilters {
- AF_None = 0,
- AF_LowPass = 1,
- AF_HighPass = 2,
- AF_BandPass = AF_LowPass | AF_HighPass
-};
-
-
-typedef struct MixHrtfParams {
- const ALfloat (*Coeffs)[2];
- ALsizei Delay[2];
- ALfloat Gain;
- ALfloat GainStep;
-} MixHrtfParams;
-
-
-typedef struct DirectParams {
- BiquadFilter LowPass;
- BiquadFilter HighPass;
-
- NfcFilter NFCtrlFilter;
-
- struct {
- HrtfParams Old;
- HrtfParams Target;
- HrtfState State;
- } Hrtf;
-
- struct {
- ALfloat Current[MAX_OUTPUT_CHANNELS];
- ALfloat Target[MAX_OUTPUT_CHANNELS];
- } Gains;
-} DirectParams;
-
-typedef struct SendParams {
- BiquadFilter LowPass;
- BiquadFilter HighPass;
-
- struct {
- ALfloat Current[MAX_OUTPUT_CHANNELS];
- ALfloat Target[MAX_OUTPUT_CHANNELS];
- } Gains;
-} SendParams;
-
-
-struct ALvoiceProps {
- ATOMIC(struct ALvoiceProps*) next;
-
- ALfloat Pitch;
- ALfloat Gain;
- ALfloat OuterGain;
- ALfloat MinGain;
- ALfloat MaxGain;
- ALfloat InnerAngle;
- ALfloat OuterAngle;
- ALfloat RefDistance;
- ALfloat MaxDistance;
- ALfloat RolloffFactor;
- ALfloat Position[3];
- ALfloat Velocity[3];
- ALfloat Direction[3];
- ALfloat Orientation[2][3];
- ALboolean HeadRelative;
- enum DistanceModel DistanceModel;
- enum Resampler Resampler;
- ALboolean DirectChannels;
- enum SpatializeMode SpatializeMode;
-
- ALboolean DryGainHFAuto;
- ALboolean WetGainAuto;
- ALboolean WetGainHFAuto;
- ALfloat OuterGainHF;
-
- ALfloat AirAbsorptionFactor;
- ALfloat RoomRolloffFactor;
- ALfloat DopplerFactor;
-
- ALfloat StereoPan[2];
-
- ALfloat Radius;
-
- /** Direct filter and auxiliary send info. */
- struct {
- ALfloat Gain;
- ALfloat GainHF;
- ALfloat HFReference;
- ALfloat GainLF;
- ALfloat LFReference;
- } Direct;
- struct {
- struct ALeffectslot *Slot;
- ALfloat Gain;
- ALfloat GainHF;
- ALfloat HFReference;
- ALfloat GainLF;
- ALfloat LFReference;
- } Send[];
-};
-
-#define VOICE_IS_STATIC (1<<0)
-#define VOICE_IS_FADING (1<<1) /* Fading sources use gain stepping for smooth transitions. */
-#define VOICE_HAS_HRTF (1<<2)
-#define VOICE_HAS_NFC (1<<3)
-
-typedef struct ALvoice {
- struct ALvoiceProps *Props;
-
- ATOMIC(struct ALvoiceProps*) Update;
-
- ATOMIC(struct ALsource*) Source;
- ATOMIC(bool) Playing;
-
- /**
- * Source offset in samples, relative to the currently playing buffer, NOT
- * the whole queue, and the fractional (fixed-point) offset to the next
- * sample.
- */
- ATOMIC(ALuint) position;
- ATOMIC(ALsizei) position_fraction;
-
- /* Current buffer queue item being played. */
- ATOMIC(struct ALbufferlistitem*) current_buffer;
-
- /* Buffer queue item to loop to at end of queue (will be NULL for non-
- * looping voices).
- */
- ATOMIC(struct ALbufferlistitem*) loop_buffer;
-
- /**
- * Number of channels and bytes-per-sample for the attached source's
- * buffer(s).
- */
- ALsizei NumChannels;
- ALsizei SampleSize;
-
- /** Current target parameters used for mixing. */
- ALint Step;
-
- ResamplerFunc Resampler;
-
- ALuint Flags;
-
- ALuint Offset; /* Number of output samples mixed since starting. */
-
- alignas(16) ALfloat PrevSamples[MAX_INPUT_CHANNELS][MAX_RESAMPLE_PADDING];
-
- InterpState ResampleState;
-
- struct {
- enum ActiveFilters FilterType;
- DirectParams Params[MAX_INPUT_CHANNELS];
-
- ALfloat (*Buffer)[BUFFERSIZE];
- ALsizei Channels;
- ALsizei ChannelsPerOrder[MAX_AMBI_ORDER+1];
- } Direct;
-
- struct {
- enum ActiveFilters FilterType;
- SendParams Params[MAX_INPUT_CHANNELS];
-
- ALfloat (*Buffer)[BUFFERSIZE];
- ALsizei Channels;
- } Send[];
-} ALvoice;
-
-void DeinitVoice(ALvoice *voice);
-
-
-typedef void (*MixerFunc)(const ALfloat *data, ALsizei OutChans,
- ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALfloat *CurrentGains,
- const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,
- ALsizei BufferSize);
-typedef void (*RowMixerFunc)(ALfloat *OutBuffer, const ALfloat *gains,
- const ALfloat (*restrict data)[BUFFERSIZE], ALsizei InChans,
- ALsizei InPos, ALsizei BufferSize);
-typedef void (*HrtfMixerFunc)(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, ALsizei OutPos,
- const ALsizei IrSize, MixHrtfParams *hrtfparams,
- HrtfState *hrtfstate, ALsizei BufferSize);
-typedef void (*HrtfMixerBlendFunc)(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, ALsizei OutPos,
- const ALsizei IrSize, const HrtfParams *oldparams,
- MixHrtfParams *newparams, HrtfState *hrtfstate,
- ALsizei BufferSize);
-typedef void (*HrtfDirectMixerFunc)(ALfloat *restrict LeftOut, ALfloat *restrict RightOut,
- const ALfloat *data, ALsizei Offset, const ALsizei IrSize,
- const ALfloat (*restrict Coeffs)[2],
- ALfloat (*restrict Values)[2], ALsizei BufferSize);
-
-
-#define GAIN_MIX_MAX (16.0f) /* +24dB */
-
-#define GAIN_SILENCE_THRESHOLD (0.00001f) /* -100dB */
-
-#define SPEEDOFSOUNDMETRESPERSEC (343.3f)
-#define AIRABSORBGAINHF (0.99426f) /* -0.05dB */
-
-/* Target gain for the reverb decay feedback reaching the decay time. */
-#define REVERB_DECAY_GAIN (0.001f) /* -60 dB */
-
-#define FRACTIONBITS (12)
-#define FRACTIONONE (1<<FRACTIONBITS)
-#define FRACTIONMASK (FRACTIONONE-1)
-
-
-inline ALfloat minf(ALfloat a, ALfloat b)
-{ return ((a > b) ? b : a); }
-inline ALfloat maxf(ALfloat a, ALfloat b)
-{ return ((a > b) ? a : b); }
-inline ALfloat clampf(ALfloat val, ALfloat min, ALfloat max)
-{ return minf(max, maxf(min, val)); }
-
-inline ALdouble mind(ALdouble a, ALdouble b)
-{ return ((a > b) ? b : a); }
-inline ALdouble maxd(ALdouble a, ALdouble b)
-{ return ((a > b) ? a : b); }
-inline ALdouble clampd(ALdouble val, ALdouble min, ALdouble max)
-{ return mind(max, maxd(min, val)); }
-
-inline ALuint minu(ALuint a, ALuint b)
-{ return ((a > b) ? b : a); }
-inline ALuint maxu(ALuint a, ALuint b)
-{ return ((a > b) ? a : b); }
-inline ALuint clampu(ALuint val, ALuint min, ALuint max)
-{ return minu(max, maxu(min, val)); }
-
-inline ALint mini(ALint a, ALint b)
-{ return ((a > b) ? b : a); }
-inline ALint maxi(ALint a, ALint b)
-{ return ((a > b) ? a : b); }
-inline ALint clampi(ALint val, ALint min, ALint max)
-{ return mini(max, maxi(min, val)); }
-
-inline ALint64 mini64(ALint64 a, ALint64 b)
-{ return ((a > b) ? b : a); }
-inline ALint64 maxi64(ALint64 a, ALint64 b)
-{ return ((a > b) ? a : b); }
-inline ALint64 clampi64(ALint64 val, ALint64 min, ALint64 max)
-{ return mini64(max, maxi64(min, val)); }
-
-inline ALuint64 minu64(ALuint64 a, ALuint64 b)
-{ return ((a > b) ? b : a); }
-inline ALuint64 maxu64(ALuint64 a, ALuint64 b)
-{ return ((a > b) ? a : b); }
-inline ALuint64 clampu64(ALuint64 val, ALuint64 min, ALuint64 max)
-{ return minu64(max, maxu64(min, val)); }
-
-inline size_t minz(size_t a, size_t b)
-{ return ((a > b) ? b : a); }
-inline size_t maxz(size_t a, size_t b)
-{ return ((a > b) ? a : b); }
-inline size_t clampz(size_t val, size_t min, size_t max)
-{ return minz(max, maxz(min, val)); }
-
-
-inline ALfloat lerp(ALfloat val1, ALfloat val2, ALfloat mu)
-{
- return val1 + (val2-val1)*mu;
-}
-inline ALfloat cubic(ALfloat val1, ALfloat val2, ALfloat val3, ALfloat val4, ALfloat mu)
-{
- ALfloat mu2 = mu*mu, mu3 = mu2*mu;
- ALfloat a0 = -0.5f*mu3 + mu2 + -0.5f*mu;
- ALfloat a1 = 1.5f*mu3 + -2.5f*mu2 + 1.0f;
- ALfloat a2 = -1.5f*mu3 + 2.0f*mu2 + 0.5f*mu;
- ALfloat a3 = 0.5f*mu3 + -0.5f*mu2;
- return val1*a0 + val2*a1 + val3*a2 + val4*a3;
-}
-
-
-enum HrtfRequestMode {
- Hrtf_Default = 0,
- Hrtf_Enable = 1,
- Hrtf_Disable = 2,
-};
-
-void aluInit(void);
-
-void aluInitMixer(void);
-
-ResamplerFunc SelectResampler(enum Resampler resampler);
-
-/* aluInitRenderer
- *
- * Set up the appropriate panning method and mixing method given the device
- * properties.
- */
-void aluInitRenderer(ALCdevice *device, ALint hrtf_id, enum HrtfRequestMode hrtf_appreq, enum HrtfRequestMode hrtf_userreq);
-
-void aluInitEffectPanning(struct ALeffectslot *slot);
-
-void aluSelectPostProcess(ALCdevice *device);
-
-/**
- * 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.
- */
-void CalcAmbiCoeffs(const ALfloat y, const ALfloat z, const ALfloat x, const ALfloat spread,
- ALfloat coeffs[MAX_AMBI_COEFFS]);
-
-/**
- * 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 void CalcDirectionCoeffs(const ALfloat dir[3], ALfloat spread, ALfloat coeffs[MAX_AMBI_COEFFS])
-{
- /* Convert from OpenAL coords to Ambisonics. */
- CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread, coeffs);
-}
-
-/**
- * CalcAngleCoeffs
- *
- * Calculates ambisonic coefficients based on azimuth and elevation. The
- * azimuth and elevation parameters are in radians, going right and up
- * respectively.
- */
-inline void CalcAngleCoeffs(ALfloat azimuth, ALfloat elevation, ALfloat spread, ALfloat coeffs[MAX_AMBI_COEFFS])
-{
- ALfloat x = -sinf(azimuth) * cosf(elevation);
- ALfloat y = sinf(elevation);
- ALfloat z = cosf(azimuth) * cosf(elevation);
-
- CalcAmbiCoeffs(x, y, z, spread, coeffs);
-}
-
-/**
- * ScaleAzimuthFront
- *
- * Scales the given azimuth toward the side (+/- pi/2 radians) for positions in
- * front.
- */
-inline float ScaleAzimuthFront(float azimuth, float scale)
-{
- ALfloat sign = copysignf(1.0f, azimuth);
- if(!(fabsf(azimuth) > F_PI_2))
- return minf(fabsf(azimuth) * scale, F_PI_2) * sign;
- return azimuth;
-}
-
-
-void ComputePanningGainsMC(const ChannelConfig *chancoeffs, ALsizei numchans, ALsizei numcoeffs, const ALfloat*restrict coeffs, ALfloat ingain, ALfloat gains[MAX_OUTPUT_CHANNELS]);
-void ComputePanningGainsBF(const BFChannelConfig *chanmap, ALsizei numchans, const ALfloat*restrict coeffs, ALfloat ingain, ALfloat gains[MAX_OUTPUT_CHANNELS]);
-
-/**
- * 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.
- */
-inline void ComputePanGains(const MixParams *dry, const ALfloat*restrict coeffs, ALfloat ingain, ALfloat gains[MAX_OUTPUT_CHANNELS])
-{
- if(dry->CoeffCount > 0)
- ComputePanningGainsMC(dry->Ambi.Coeffs, dry->NumChannels, dry->CoeffCount,
- coeffs, ingain, gains);
- else
- ComputePanningGainsBF(dry->Ambi.Map, dry->NumChannels, coeffs, ingain, gains);
-}
-
-
-ALboolean MixSource(struct ALvoice *voice, ALuint SourceID, ALCcontext *Context, ALsizei SamplesToDo);
-
-void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples);
-/* Caller must lock the device, and the mixer must not be running. */
-void aluHandleDisconnect(ALCdevice *device, const char *msg, ...) DECL_FORMAT(printf, 2, 3);
-
-void UpdateContextProps(ALCcontext *context);
-
-extern MixerFunc MixSamples;
-extern RowMixerFunc MixRowSamples;
-
-extern ALfloat ConeScale;
-extern ALfloat ZScale;
-extern ALboolean OverrideReverbSpeedOfSound;
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/OpenAL32/Include/sample_cvt.h b/OpenAL32/Include/sample_cvt.h
deleted file mode 100644
index c041760e..00000000
--- a/OpenAL32/Include/sample_cvt.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef SAMPLE_CVT_H
-#define SAMPLE_CVT_H
-
-#include "AL/al.h"
-#include "alBuffer.h"
-
-extern const ALshort muLawDecompressionTable[256];
-extern const ALshort aLawDecompressionTable[256];
-
-void Convert_ALshort_ALima4(ALshort *dst, const ALubyte *src, ALsizei numchans, ALsizei len,
- ALsizei align);
-void Convert_ALshort_ALmsadpcm(ALshort *dst, const ALubyte *src, ALsizei numchans, ALsizei len,
- ALsizei align);
-
-#endif /* SAMPLE_CVT_H */
diff --git a/OpenAL32/alAuxEffectSlot.c b/OpenAL32/alAuxEffectSlot.c
deleted file mode 100644
index 8141e0f6..00000000
--- a/OpenAL32/alAuxEffectSlot.c
+++ /dev/null
@@ -1,802 +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 <math.h>
-
-#include "AL/al.h"
-#include "AL/alc.h"
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
-#include "alListener.h"
-#include "alSource.h"
-
-#include "fpu_modes.h"
-#include "almalloc.h"
-
-
-extern inline void LockEffectSlotList(ALCcontext *context);
-extern inline void UnlockEffectSlotList(ALCcontext *context);
-
-static void AddActiveEffectSlots(const ALuint *slotids, ALsizei count, ALCcontext *context);
-static void RemoveActiveEffectSlots(const ALuint *slotids, ALsizei count, ALCcontext *context);
-
-static const struct {
- ALenum Type;
- EffectStateFactory* (*GetFactory)(void);
-} FactoryList[] = {
- { AL_EFFECT_NULL, NullStateFactory_getFactory },
- { AL_EFFECT_EAXREVERB, ReverbStateFactory_getFactory },
- { AL_EFFECT_REVERB, ReverbStateFactory_getFactory },
- { AL_EFFECT_AUTOWAH, AutowahStateFactory_getFactory },
- { AL_EFFECT_CHORUS, ChorusStateFactory_getFactory },
- { AL_EFFECT_COMPRESSOR, CompressorStateFactory_getFactory },
- { AL_EFFECT_DISTORTION, DistortionStateFactory_getFactory },
- { AL_EFFECT_ECHO, EchoStateFactory_getFactory },
- { AL_EFFECT_EQUALIZER, EqualizerStateFactory_getFactory },
- { AL_EFFECT_FLANGER, FlangerStateFactory_getFactory },
- { AL_EFFECT_FREQUENCY_SHIFTER, FshifterStateFactory_getFactory },
- { AL_EFFECT_RING_MODULATOR, ModulatorStateFactory_getFactory },
- { AL_EFFECT_PITCH_SHIFTER, PshifterStateFactory_getFactory},
- { AL_EFFECT_DEDICATED_DIALOGUE, DedicatedStateFactory_getFactory },
- { AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedStateFactory_getFactory }
-};
-
-static inline EffectStateFactory *getFactoryByType(ALenum type)
-{
- size_t i;
- for(i = 0;i < COUNTOF(FactoryList);i++)
- {
- if(FactoryList[i].Type == type)
- return FactoryList[i].GetFactory();
- }
- return NULL;
-}
-
-static void ALeffectState_IncRef(ALeffectState *state);
-
-
-static inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id)
-{
- id--;
- if(UNLIKELY(id >= VECTOR_SIZE(context->EffectSlotList)))
- return NULL;
- return VECTOR_ELEM(context->EffectSlotList, id);
-}
-
-static inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
-{
- EffectSubList *sublist;
- ALuint lidx = (id-1) >> 6;
- ALsizei slidx = (id-1) & 0x3f;
-
- if(UNLIKELY(lidx >= VECTOR_SIZE(device->EffectList)))
- return NULL;
- sublist = &VECTOR_ELEM(device->EffectList, lidx);
- if(UNLIKELY(sublist->FreeMask & (U64(1)<<slidx)))
- return NULL;
- return sublist->Effects + slidx;
-}
-
-
-#define DO_UPDATEPROPS() do { \
- if(!ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire)) \
- UpdateEffectSlotProps(slot, context); \
- else \
- ATOMIC_FLAG_CLEAR(&slot->PropsClean, almemory_order_release); \
-} while(0)
-
-
-AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALsizei cur;
-
- context = GetContextRef();
- if(!context) return;
-
- if(!(n >= 0))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Generating %d effect slots", n);
- if(n == 0) goto done;
-
- LockEffectSlotList(context);
- device = context->Device;
- for(cur = 0;cur < n;cur++)
- {
- ALeffectslotPtr *iter = VECTOR_BEGIN(context->EffectSlotList);
- ALeffectslotPtr *end = VECTOR_END(context->EffectSlotList);
- ALeffectslot *slot = NULL;
- ALenum err = AL_OUT_OF_MEMORY;
-
- for(;iter != end;iter++)
- {
- if(!*iter)
- break;
- }
- if(iter == end)
- {
- if(device->AuxiliaryEffectSlotMax == VECTOR_SIZE(context->EffectSlotList))
- {
- UnlockEffectSlotList(context);
- alDeleteAuxiliaryEffectSlots(cur, effectslots);
- SETERR_GOTO(context, AL_OUT_OF_MEMORY, done,
- "Exceeding %u auxiliary effect slot limit", device->AuxiliaryEffectSlotMax);
- }
- VECTOR_PUSH_BACK(context->EffectSlotList, NULL);
- iter = &VECTOR_BACK(context->EffectSlotList);
- }
- slot = al_calloc(16, sizeof(ALeffectslot));
- if(!slot || (err=InitEffectSlot(slot)) != AL_NO_ERROR)
- {
- al_free(slot);
- UnlockEffectSlotList(context);
-
- alDeleteAuxiliaryEffectSlots(cur, effectslots);
- SETERR_GOTO(context, err, done, "Effect slot object allocation failed");
- }
- aluInitEffectPanning(slot);
-
- slot->id = (iter - VECTOR_BEGIN(context->EffectSlotList)) + 1;
- *iter = slot;
-
- effectslots[cur] = slot->id;
- }
- AddActiveEffectSlots(effectslots, n, context);
- UnlockEffectSlotList(context);
-
-done:
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots)
-{
- ALCcontext *context;
- ALeffectslot *slot;
- ALsizei i;
-
- context = GetContextRef();
- if(!context) return;
-
- LockEffectSlotList(context);
- if(!(n >= 0))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Deleting %d effect slots", n);
- if(n == 0) goto done;
-
- for(i = 0;i < n;i++)
- {
- if((slot=LookupEffectSlot(context, effectslots[i])) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u",
- effectslots[i]);
- if(ReadRef(&slot->ref) != 0)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Deleting in-use effect slot %u",
- effectslots[i]);
- }
-
- // All effectslots are valid
- RemoveActiveEffectSlots(effectslots, n, context);
- for(i = 0;i < n;i++)
- {
- if((slot=LookupEffectSlot(context, effectslots[i])) == NULL)
- continue;
- VECTOR_ELEM(context->EffectSlotList, effectslots[i]-1) = NULL;
-
- DeinitEffectSlot(slot);
-
- memset(slot, 0, sizeof(*slot));
- al_free(slot);
- }
-
-done:
- UnlockEffectSlotList(context);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot)
-{
- ALCcontext *context;
- ALboolean ret;
-
- context = GetContextRef();
- if(!context) return AL_FALSE;
-
- LockEffectSlotList(context);
- ret = (LookupEffectSlot(context, effectslot) ? AL_TRUE : AL_FALSE);
- UnlockEffectSlotList(context);
-
- ALCcontext_DecRef(context);
-
- return ret;
-}
-
-AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint value)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALeffectslot *slot;
- ALeffect *effect = NULL;
- ALenum err;
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- LockEffectSlotList(context);
- if((slot=LookupEffectSlot(context, effectslot)) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
- switch(param)
- {
- case AL_EFFECTSLOT_EFFECT:
- device = context->Device;
-
- LockEffectList(device);
- effect = (value ? LookupEffect(device, value) : NULL);
- if(!(value == 0 || effect != NULL))
- {
- UnlockEffectList(device);
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Invalid effect ID %u", value);
- }
- err = InitializeEffect(context, slot, effect);
- UnlockEffectList(device);
-
- if(err != AL_NO_ERROR)
- SETERR_GOTO(context, err, done, "Effect initialization failed");
- break;
-
- case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
- if(!(value == AL_TRUE || value == AL_FALSE))
- SETERR_GOTO(context, AL_INVALID_VALUE, done,
- "Effect slot auxiliary send auto out of range");
- slot->AuxSendAuto = value;
- break;
-
- default:
- SETERR_GOTO(context, AL_INVALID_ENUM, done, "Invalid effect slot integer property 0x%04x",
- param);
- }
- DO_UPDATEPROPS();
-
-done:
- UnlockEffectSlotList(context);
- almtx_unlock(&context->PropLock);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *values)
-{
- ALCcontext *context;
-
- switch(param)
- {
- case AL_EFFECTSLOT_EFFECT:
- case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
- alAuxiliaryEffectSloti(effectslot, param, values[0]);
- return;
- }
-
- context = GetContextRef();
- if(!context) return;
-
- LockEffectSlotList(context);
- if(LookupEffectSlot(context, effectslot) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
- switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
- param);
- }
-
-done:
- UnlockEffectSlotList(context);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat value)
-{
- ALCcontext *context;
- ALeffectslot *slot;
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- LockEffectSlotList(context);
- if((slot=LookupEffectSlot(context, effectslot)) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
- switch(param)
- {
- case AL_EFFECTSLOT_GAIN:
- if(!(value >= 0.0f && value <= 1.0f))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Effect slot gain out of range");
- slot->Gain = value;
- break;
-
- default:
- SETERR_GOTO(context, AL_INVALID_ENUM, done, "Invalid effect slot float property 0x%04x",
- param);
- }
- DO_UPDATEPROPS();
-
-done:
- UnlockEffectSlotList(context);
- almtx_unlock(&context->PropLock);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *values)
-{
- ALCcontext *context;
-
- switch(param)
- {
- case AL_EFFECTSLOT_GAIN:
- alAuxiliaryEffectSlotf(effectslot, param, values[0]);
- return;
- }
-
- context = GetContextRef();
- if(!context) return;
-
- LockEffectSlotList(context);
- if(LookupEffectSlot(context, effectslot) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
- switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
- param);
- }
-
-done:
- UnlockEffectSlotList(context);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *value)
-{
- ALCcontext *context;
- ALeffectslot *slot;
-
- context = GetContextRef();
- if(!context) return;
-
- LockEffectSlotList(context);
- if((slot=LookupEffectSlot(context, effectslot)) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
- switch(param)
- {
- case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
- *value = slot->AuxSendAuto;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param);
- }
-
-done:
- UnlockEffectSlotList(context);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *values)
-{
- ALCcontext *context;
-
- switch(param)
- {
- case AL_EFFECTSLOT_EFFECT:
- case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
- alGetAuxiliaryEffectSloti(effectslot, param, values);
- return;
- }
-
- context = GetContextRef();
- if(!context) return;
-
- LockEffectSlotList(context);
- if(LookupEffectSlot(context, effectslot) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
- switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
- param);
- }
-
-done:
- UnlockEffectSlotList(context);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *value)
-{
- ALCcontext *context;
- ALeffectslot *slot;
-
- context = GetContextRef();
- if(!context) return;
-
- LockEffectSlotList(context);
- if((slot=LookupEffectSlot(context, effectslot)) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
- switch(param)
- {
- case AL_EFFECTSLOT_GAIN:
- *value = slot->Gain;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param);
- }
-
-done:
- UnlockEffectSlotList(context);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *values)
-{
- ALCcontext *context;
-
- switch(param)
- {
- case AL_EFFECTSLOT_GAIN:
- alGetAuxiliaryEffectSlotf(effectslot, param, values);
- return;
- }
-
- context = GetContextRef();
- if(!context) return;
-
- LockEffectSlotList(context);
- if(LookupEffectSlot(context, effectslot) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect slot ID %u", effectslot);
- switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
- param);
- }
-
-done:
- UnlockEffectSlotList(context);
- ALCcontext_DecRef(context);
-}
-
-
-ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect)
-{
- ALCdevice *Device = Context->Device;
- ALenum newtype = (effect ? effect->type : AL_EFFECT_NULL);
- struct ALeffectslotProps *props;
- ALeffectState *State;
-
- if(newtype != EffectSlot->Effect.Type)
- {
- EffectStateFactory *factory;
-
- factory = getFactoryByType(newtype);
- if(!factory)
- {
- ERR("Failed to find factory for effect type 0x%04x\n", newtype);
- return AL_INVALID_ENUM;
- }
- State = EffectStateFactory_create(factory);
- if(!State) return AL_OUT_OF_MEMORY;
-
- START_MIXER_MODE();
- almtx_lock(&Device->BackendLock);
- State->OutBuffer = Device->Dry.Buffer;
- State->OutChannels = Device->Dry.NumChannels;
- if(V(State,deviceUpdate)(Device) == AL_FALSE)
- {
- almtx_unlock(&Device->BackendLock);
- LEAVE_MIXER_MODE();
- ALeffectState_DecRef(State);
- return AL_OUT_OF_MEMORY;
- }
- almtx_unlock(&Device->BackendLock);
- END_MIXER_MODE();
-
- if(!effect)
- {
- EffectSlot->Effect.Type = AL_EFFECT_NULL;
- memset(&EffectSlot->Effect.Props, 0, sizeof(EffectSlot->Effect.Props));
- }
- else
- {
- EffectSlot->Effect.Type = effect->type;
- EffectSlot->Effect.Props = effect->Props;
- }
-
- ALeffectState_DecRef(EffectSlot->Effect.State);
- EffectSlot->Effect.State = State;
- }
- else if(effect)
- EffectSlot->Effect.Props = effect->Props;
-
- /* Remove state references from old effect slot property updates. */
- props = ATOMIC_LOAD_SEQ(&Context->FreeEffectslotProps);
- while(props)
- {
- if(props->State)
- ALeffectState_DecRef(props->State);
- props->State = NULL;
- props = ATOMIC_LOAD(&props->next, almemory_order_relaxed);
- }
-
- return AL_NO_ERROR;
-}
-
-
-static void ALeffectState_IncRef(ALeffectState *state)
-{
- uint ref;
- ref = IncrementRef(&state->Ref);
- TRACEREF("%p increasing refcount to %u\n", state, ref);
-}
-
-void ALeffectState_DecRef(ALeffectState *state)
-{
- uint ref;
- ref = DecrementRef(&state->Ref);
- TRACEREF("%p decreasing refcount to %u\n", state, ref);
- if(ref == 0) DELETE_OBJ(state);
-}
-
-
-void ALeffectState_Construct(ALeffectState *state)
-{
- InitRef(&state->Ref, 1);
-
- state->OutBuffer = NULL;
- state->OutChannels = 0;
-}
-
-void ALeffectState_Destruct(ALeffectState *UNUSED(state))
-{
-}
-
-
-static void AddActiveEffectSlots(const ALuint *slotids, ALsizei count, ALCcontext *context)
-{
- struct ALeffectslotArray *curarray = ATOMIC_LOAD(&context->ActiveAuxSlots,
- almemory_order_acquire);
- struct ALeffectslotArray *newarray = NULL;
- ALsizei newcount = curarray->count + count;
- ALCdevice *device = context->Device;
- ALsizei i, j;
-
- /* Insert the new effect slots into the head of the array, followed by the
- * existing ones.
- */
- newarray = al_calloc(DEF_ALIGN, FAM_SIZE(struct ALeffectslotArray, slot, newcount));
- newarray->count = newcount;
- for(i = 0;i < count;i++)
- newarray->slot[i] = LookupEffectSlot(context, slotids[i]);
- for(j = 0;i < newcount;)
- newarray->slot[i++] = curarray->slot[j++];
- /* Remove any duplicates (first instance of each will be kept). */
- for(i = 1;i < newcount;i++)
- {
- for(j = i;j != 0;)
- {
- if(UNLIKELY(newarray->slot[i] == newarray->slot[--j]))
- {
- newcount--;
- for(j = i;j < newcount;j++)
- newarray->slot[j] = newarray->slot[j+1];
- i--;
- break;
- }
- }
- }
-
- /* Reallocate newarray if the new size ended up smaller from duplicate
- * removal.
- */
- if(UNLIKELY(newcount < newarray->count))
- {
- struct ALeffectslotArray *tmpnewarray = al_calloc(DEF_ALIGN,
- FAM_SIZE(struct ALeffectslotArray, slot, newcount));
- memcpy(tmpnewarray, newarray, FAM_SIZE(struct ALeffectslotArray, slot, newcount));
- al_free(newarray);
- newarray = tmpnewarray;
- newarray->count = newcount;
- }
-
- curarray = ATOMIC_EXCHANGE_PTR(&context->ActiveAuxSlots, newarray, almemory_order_acq_rel);
- while((ATOMIC_LOAD(&device->MixCount, almemory_order_acquire)&1))
- althrd_yield();
- al_free(curarray);
-}
-
-static void RemoveActiveEffectSlots(const ALuint *slotids, ALsizei count, ALCcontext *context)
-{
- struct ALeffectslotArray *curarray = ATOMIC_LOAD(&context->ActiveAuxSlots,
- almemory_order_acquire);
- struct ALeffectslotArray *newarray = NULL;
- ALCdevice *device = context->Device;
- ALsizei i, j;
-
- /* Don't shrink the allocated array size since we don't know how many (if
- * any) of the effect slots to remove are in the array.
- */
- newarray = al_calloc(DEF_ALIGN, FAM_SIZE(struct ALeffectslotArray, slot, curarray->count));
- newarray->count = 0;
- for(i = 0;i < curarray->count;i++)
- {
- /* Insert this slot into the new array only if it's not one to remove. */
- ALeffectslot *slot = curarray->slot[i];
- for(j = count;j != 0;)
- {
- if(slot->id == slotids[--j])
- goto skip_ins;
- }
- newarray->slot[newarray->count++] = slot;
- skip_ins: ;
- }
-
- /* TODO: Could reallocate newarray now that we know it's needed size. */
-
- curarray = ATOMIC_EXCHANGE_PTR(&context->ActiveAuxSlots, newarray, almemory_order_acq_rel);
- while((ATOMIC_LOAD(&device->MixCount, almemory_order_acquire)&1))
- althrd_yield();
- al_free(curarray);
-}
-
-
-ALenum InitEffectSlot(ALeffectslot *slot)
-{
- EffectStateFactory *factory;
-
- slot->Effect.Type = AL_EFFECT_NULL;
-
- factory = getFactoryByType(AL_EFFECT_NULL);
- slot->Effect.State = EffectStateFactory_create(factory);
- if(!slot->Effect.State) return AL_OUT_OF_MEMORY;
-
- slot->Gain = 1.0;
- slot->AuxSendAuto = AL_TRUE;
- ATOMIC_FLAG_TEST_AND_SET(&slot->PropsClean, almemory_order_relaxed);
- InitRef(&slot->ref, 0);
-
- ATOMIC_INIT(&slot->Update, NULL);
-
- slot->Params.Gain = 1.0f;
- slot->Params.AuxSendAuto = AL_TRUE;
- ALeffectState_IncRef(slot->Effect.State);
- slot->Params.EffectState = slot->Effect.State;
- 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;
-
- return AL_NO_ERROR;
-}
-
-void DeinitEffectSlot(ALeffectslot *slot)
-{
- struct ALeffectslotProps *props;
-
- props = ATOMIC_LOAD_SEQ(&slot->Update);
- if(props)
- {
- if(props->State) ALeffectState_DecRef(props->State);
- TRACE("Freed unapplied AuxiliaryEffectSlot update %p\n", props);
- al_free(props);
- }
-
- ALeffectState_DecRef(slot->Effect.State);
- if(slot->Params.EffectState)
- ALeffectState_DecRef(slot->Params.EffectState);
-}
-
-void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context)
-{
- struct ALeffectslotProps *props;
- ALeffectState *oldstate;
-
- /* Get an unused property container, or allocate a new one as needed. */
- props = ATOMIC_LOAD(&context->FreeEffectslotProps, almemory_order_relaxed);
- if(!props)
- props = al_calloc(16, sizeof(*props));
- else
- {
- struct ALeffectslotProps *next;
- do {
- next = ATOMIC_LOAD(&props->next, almemory_order_relaxed);
- } while(ATOMIC_COMPARE_EXCHANGE_PTR_WEAK(&context->FreeEffectslotProps, &props, next,
- almemory_order_seq_cst, almemory_order_acquire) == 0);
- }
-
- /* Copy in current property values. */
- props->Gain = slot->Gain;
- props->AuxSendAuto = slot->AuxSendAuto;
-
- props->Type = slot->Effect.Type;
- props->Props = slot->Effect.Props;
- /* Swap out any stale effect state object there may be in the container, to
- * delete it.
- */
- ALeffectState_IncRef(slot->Effect.State);
- oldstate = props->State;
- props->State = slot->Effect.State;
-
- /* Set the new container for updating internal parameters. */
- props = ATOMIC_EXCHANGE_PTR(&slot->Update, props, almemory_order_acq_rel);
- if(props)
- {
- /* If there was an unused update container, put it back in the
- * freelist.
- */
- if(props->State)
- ALeffectState_DecRef(props->State);
- props->State = NULL;
- ATOMIC_REPLACE_HEAD(struct ALeffectslotProps*, &context->FreeEffectslotProps, props);
- }
-
- if(oldstate)
- ALeffectState_DecRef(oldstate);
-}
-
-void UpdateAllEffectSlotProps(ALCcontext *context)
-{
- struct ALeffectslotArray *auxslots;
- ALsizei i;
-
- LockEffectSlotList(context);
- auxslots = ATOMIC_LOAD(&context->ActiveAuxSlots, almemory_order_acquire);
- for(i = 0;i < auxslots->count;i++)
- {
- ALeffectslot *slot = auxslots->slot[i];
- if(!ATOMIC_FLAG_TEST_AND_SET(&slot->PropsClean, almemory_order_acq_rel))
- UpdateEffectSlotProps(slot, context);
- }
- UnlockEffectSlotList(context);
-}
-
-ALvoid ReleaseALAuxiliaryEffectSlots(ALCcontext *context)
-{
- ALeffectslotPtr *iter = VECTOR_BEGIN(context->EffectSlotList);
- ALeffectslotPtr *end = VECTOR_END(context->EffectSlotList);
- size_t leftover = 0;
-
- for(;iter != end;iter++)
- {
- ALeffectslot *slot = *iter;
- if(!slot) continue;
- *iter = NULL;
-
- DeinitEffectSlot(slot);
-
- memset(slot, 0, sizeof(*slot));
- al_free(slot);
- ++leftover;
- }
- if(leftover > 0)
- WARN("(%p) Deleted "SZFMT" AuxiliaryEffectSlot%s\n", context, leftover, (leftover==1)?"":"s");
-}
diff --git a/OpenAL32/alBuffer.c b/OpenAL32/alBuffer.c
deleted file mode 100644
index ed712434..00000000
--- a/OpenAL32/alBuffer.c
+++ /dev/null
@@ -1,1305 +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 <assert.h>
-#include <limits.h>
-#ifdef HAVE_MALLOC_H
-#include <malloc.h>
-#endif
-
-#include "alMain.h"
-#include "alu.h"
-#include "alError.h"
-#include "alBuffer.h"
-#include "sample_cvt.h"
-
-
-extern inline void LockBufferList(ALCdevice *device);
-extern inline void UnlockBufferList(ALCdevice *device);
-extern inline ALsizei FrameSizeFromUserFmt(enum UserFmtChannels chans, enum UserFmtType type);
-extern inline ALsizei FrameSizeFromFmt(enum FmtChannels chans, enum FmtType type);
-
-static ALbuffer *AllocBuffer(ALCcontext *context);
-static void FreeBuffer(ALCdevice *device, ALbuffer *buffer);
-static const ALchar *NameFromUserFmtType(enum UserFmtType type);
-static void LoadData(ALCcontext *context, ALbuffer *buffer, ALuint freq, ALsizei size,
- enum UserFmtChannels SrcChannels, enum UserFmtType SrcType,
- const ALvoid *data, ALbitfieldSOFT access);
-static ALboolean DecomposeUserFormat(ALenum format, enum UserFmtChannels *chans, enum UserFmtType *type);
-static ALsizei SanitizeAlignment(enum UserFmtType type, ALsizei align);
-
-static inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id)
-{
- BufferSubList *sublist;
- ALuint lidx = (id-1) >> 6;
- ALsizei slidx = (id-1) & 0x3f;
-
- if(UNLIKELY(lidx >= VECTOR_SIZE(device->BufferList)))
- return NULL;
- sublist = &VECTOR_ELEM(device->BufferList, lidx);
- if(UNLIKELY(sublist->FreeMask & (U64(1)<<slidx)))
- return NULL;
- return sublist->Buffers + slidx;
-}
-
-
-#define INVALID_STORAGE_MASK ~(AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT | AL_PRESERVE_DATA_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT)
-#define MAP_READ_WRITE_FLAGS (AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT)
-#define INVALID_MAP_FLAGS ~(AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT)
-
-
-AL_API ALvoid AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers)
-{
- ALCcontext *context;
- ALsizei cur = 0;
-
- context = GetContextRef();
- if(!context) return;
-
- if(!(n >= 0))
- alSetError(context, AL_INVALID_VALUE, "Generating %d buffers", n);
- else for(cur = 0;cur < n;cur++)
- {
- ALbuffer *buffer = AllocBuffer(context);
- if(!buffer)
- {
- alDeleteBuffers(cur, buffers);
- break;
- }
-
- buffers[cur] = buffer->id;
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALbuffer *ALBuf;
- ALsizei i;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
-
- LockBufferList(device);
- if(UNLIKELY(n < 0))
- {
- alSetError(context, AL_INVALID_VALUE, "Deleting %d buffers", n);
- goto done;
- }
-
- for(i = 0;i < n;i++)
- {
- if(!buffers[i])
- continue;
-
- /* Check for valid Buffer ID, and make sure it's not in use. */
- if((ALBuf=LookupBuffer(device, buffers[i])) == NULL)
- {
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffers[i]);
- goto done;
- }
- if(ReadRef(&ALBuf->ref) != 0)
- {
- alSetError(context, AL_INVALID_OPERATION, "Deleting in-use buffer %u", buffers[i]);
- goto done;
- }
- }
- for(i = 0;i < n;i++)
- {
- if((ALBuf=LookupBuffer(device, buffers[i])) != NULL)
- FreeBuffer(device, ALBuf);
- }
-
-done:
- UnlockBufferList(device);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer)
-{
- ALCcontext *context;
- ALboolean ret;
-
- context = GetContextRef();
- if(!context) return AL_FALSE;
-
- LockBufferList(context->Device);
- ret = ((!buffer || LookupBuffer(context->Device, buffer)) ?
- AL_TRUE : AL_FALSE);
- UnlockBufferList(context->Device);
-
- ALCcontext_DecRef(context);
-
- return ret;
-}
-
-
-AL_API ALvoid AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq)
-{ alBufferStorageSOFT(buffer, format, data, size, freq, 0); }
-
-AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags)
-{
- enum UserFmtChannels srcchannels = UserFmtMono;
- enum UserFmtType srctype = UserFmtUByte;
- ALCdevice *device;
- ALCcontext *context;
- ALbuffer *albuf;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY((albuf=LookupBuffer(device, buffer)) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY(size < 0))
- alSetError(context, AL_INVALID_VALUE, "Negative storage size %d", size);
- else if(UNLIKELY(freq < 1))
- alSetError(context, AL_INVALID_VALUE, "Invalid sample rate %d", freq);
- else if(UNLIKELY((flags&INVALID_STORAGE_MASK) != 0))
- alSetError(context, AL_INVALID_VALUE, "Invalid storage flags 0x%x",
- flags&INVALID_STORAGE_MASK);
- else if(UNLIKELY((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS)))
- alSetError(context, AL_INVALID_VALUE,
- "Declaring persistently mapped storage without read or write access");
- else if(UNLIKELY(DecomposeUserFormat(format, &srcchannels, &srctype) == AL_FALSE))
- alSetError(context, AL_INVALID_ENUM, "Invalid format 0x%04x", format);
- else
- LoadData(context, albuf, freq, size, srcchannels, srctype, data, flags);
-
- UnlockBufferList(device);
- ALCcontext_DecRef(context);
-}
-
-AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access)
-{
- void *retval = NULL;
- ALCdevice *device;
- ALCcontext *context;
- ALbuffer *albuf;
-
- context = GetContextRef();
- if(!context) return retval;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY((albuf=LookupBuffer(device, buffer)) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY((access&INVALID_MAP_FLAGS) != 0))
- alSetError(context, AL_INVALID_VALUE, "Invalid map flags 0x%x", access&INVALID_MAP_FLAGS);
- else if(UNLIKELY(!(access&MAP_READ_WRITE_FLAGS)))
- alSetError(context, AL_INVALID_VALUE, "Mapping buffer %u without read or write access",
- buffer);
- else
- {
- ALbitfieldSOFT unavailable = (albuf->Access^access) & access;
- if(UNLIKELY(ReadRef(&albuf->ref) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)))
- alSetError(context, AL_INVALID_OPERATION,
- "Mapping in-use buffer %u without persistent mapping", buffer);
- else if(UNLIKELY(albuf->MappedAccess != 0))
- alSetError(context, AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer);
- else if(UNLIKELY((unavailable&AL_MAP_READ_BIT_SOFT)))
- alSetError(context, AL_INVALID_VALUE,
- "Mapping buffer %u for reading without read access", buffer);
- else if(UNLIKELY((unavailable&AL_MAP_WRITE_BIT_SOFT)))
- alSetError(context, AL_INVALID_VALUE,
- "Mapping buffer %u for writing without write access", buffer);
- else if(UNLIKELY((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)))
- alSetError(context, AL_INVALID_VALUE,
- "Mapping buffer %u persistently without persistent access", buffer);
- else if(UNLIKELY(offset < 0 || offset >= albuf->OriginalSize ||
- length <= 0 || length > albuf->OriginalSize - offset))
- alSetError(context, AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u",
- offset, length, buffer);
- else
- {
- retval = (ALbyte*)albuf->data + offset;
- albuf->MappedAccess = access;
- albuf->MappedOffset = offset;
- albuf->MappedSize = length;
- }
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
- return retval;
-}
-
-AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALbuffer *albuf;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if((albuf=LookupBuffer(device, buffer)) == NULL)
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(albuf->MappedAccess == 0)
- alSetError(context, AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer);
- else
- {
- albuf->MappedAccess = 0;
- albuf->MappedOffset = 0;
- albuf->MappedSize = 0;
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALbuffer *albuf;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY((albuf=LookupBuffer(device, buffer)) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)))
- alSetError(context, AL_INVALID_OPERATION,
- "Flushing buffer %u while not mapped for writing", buffer);
- else if(UNLIKELY(offset < albuf->MappedOffset ||
- offset >= albuf->MappedOffset+albuf->MappedSize ||
- length <= 0 || length > albuf->MappedOffset+albuf->MappedSize-offset))
- alSetError(context, AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u",
- offset, length, buffer);
- else
- {
- /* FIXME: Need to use some method of double-buffering for the mixer and
- * app to hold separate memory, which can be safely transfered
- * asynchronously. Currently we just say the app shouldn't write where
- * OpenAL's reading, and hope for the best...
- */
- ATOMIC_THREAD_FENCE(almemory_order_seq_cst);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alBufferSubDataSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length)
-{
- enum UserFmtChannels srcchannels = UserFmtMono;
- enum UserFmtType srctype = UserFmtUByte;
- ALCdevice *device;
- ALCcontext *context;
- ALbuffer *albuf;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY((albuf=LookupBuffer(device, buffer)) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY(DecomposeUserFormat(format, &srcchannels, &srctype) == AL_FALSE))
- alSetError(context, AL_INVALID_ENUM, "Invalid format 0x%04x", format);
- else
- {
- ALsizei unpack_align, align;
- ALsizei byte_align;
- ALsizei frame_size;
- ALsizei num_chans;
- void *dst;
-
- unpack_align = ATOMIC_LOAD_SEQ(&albuf->UnpackAlign);
- align = SanitizeAlignment(srctype, unpack_align);
- if(UNLIKELY(align < 1))
- alSetError(context, AL_INVALID_VALUE, "Invalid unpack alignment %d", unpack_align);
- else if(UNLIKELY((long)srcchannels != (long)albuf->FmtChannels ||
- srctype != albuf->OriginalType))
- alSetError(context, AL_INVALID_ENUM, "Unpacking data with mismatched format");
- else if(UNLIKELY(align != albuf->OriginalAlign))
- alSetError(context, AL_INVALID_VALUE,
- "Unpacking data with alignment %u does not match original alignment %u",
- align, albuf->OriginalAlign);
- else if(UNLIKELY(albuf->MappedAccess != 0))
- alSetError(context, AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u",
- buffer);
- else
- {
- num_chans = ChannelsFromFmt(albuf->FmtChannels);
- frame_size = num_chans * BytesFromFmt(albuf->FmtType);
- if(albuf->OriginalType == UserFmtIMA4)
- byte_align = ((align-1)/2 + 4) * num_chans;
- else if(albuf->OriginalType == UserFmtMSADPCM)
- byte_align = ((align-2)/2 + 7) * num_chans;
- else
- byte_align = align * frame_size;
-
- if(UNLIKELY(offset < 0 || length < 0 || offset > albuf->OriginalSize ||
- length > albuf->OriginalSize-offset))
- alSetError(context, AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u",
- offset, length, buffer);
- else if(UNLIKELY((offset%byte_align) != 0))
- alSetError(context, AL_INVALID_VALUE,
- "Sub-range offset %d is not a multiple of frame size %d (%d unpack alignment)",
- offset, byte_align, align);
- else if(UNLIKELY((length%byte_align) != 0))
- alSetError(context, AL_INVALID_VALUE,
- "Sub-range length %d is not a multiple of frame size %d (%d unpack alignment)",
- length, byte_align, align);
- else
- {
- /* offset -> byte offset, length -> sample count */
- offset = offset/byte_align * align * frame_size;
- length = length/byte_align * align;
-
- dst = (ALbyte*)albuf->data + offset;
- if(srctype == UserFmtIMA4 && albuf->FmtType == FmtShort)
- Convert_ALshort_ALima4(dst, data, num_chans, length, align);
- else if(srctype == UserFmtMSADPCM && albuf->FmtType == FmtShort)
- Convert_ALshort_ALmsadpcm(dst, data, num_chans, length, align);
- else
- {
- assert((long)srctype == (long)albuf->FmtType);
- memcpy(dst, data, length*frame_size);
- }
- }
- }
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint UNUSED(buffer),
- ALuint UNUSED(samplerate), ALenum UNUSED(internalformat), ALsizei UNUSED(samples),
- ALenum UNUSED(channels), ALenum UNUSED(type), const ALvoid *UNUSED(data))
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- alSetError(context, AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported");
-
- ALCcontext_DecRef(context);
-}
-
-AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint UNUSED(buffer),
- ALsizei UNUSED(offset), ALsizei UNUSED(samples),
- ALenum UNUSED(channels), ALenum UNUSED(type), const ALvoid *UNUSED(data))
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- alSetError(context, AL_INVALID_OPERATION, "alBufferSubSamplesSOFT not supported");
-
- ALCcontext_DecRef(context);
-}
-
-AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint UNUSED(buffer),
- ALsizei UNUSED(offset), ALsizei UNUSED(samples),
- ALenum UNUSED(channels), ALenum UNUSED(type), ALvoid *UNUSED(data))
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- alSetError(context, AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported");
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum UNUSED(format))
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return AL_FALSE;
-
- alSetError(context, AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported");
-
- ALCcontext_DecRef(context);
- return AL_FALSE;
-}
-
-
-AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat UNUSED(value))
-{
- ALCdevice *device;
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY(LookupBuffer(device, buffer) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, ALfloat UNUSED(value1), ALfloat UNUSED(value2), ALfloat UNUSED(value3))
-{
- ALCdevice *device;
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY(LookupBuffer(device, buffer) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values)
-{
- ALCdevice *device;
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY(LookupBuffer(device, buffer) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY(!values))
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALbuffer *albuf;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY((albuf=LookupBuffer(device, buffer)) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else switch(param)
- {
- case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
- if(UNLIKELY(value < 0))
- alSetError(context, AL_INVALID_VALUE, "Invalid unpack block alignment %d", value);
- else
- ATOMIC_STORE_SEQ(&albuf->UnpackAlign, value);
- break;
-
- case AL_PACK_BLOCK_ALIGNMENT_SOFT:
- if(UNLIKELY(value < 0))
- alSetError(context, AL_INVALID_VALUE, "Invalid pack block alignment %d", value);
- else
- ATOMIC_STORE_SEQ(&albuf->PackAlign, value);
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, ALint UNUSED(value1), ALint UNUSED(value2), ALint UNUSED(value3))
-{
- ALCdevice *device;
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY(LookupBuffer(device, buffer) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALbuffer *albuf;
-
- if(values)
- {
- switch(param)
- {
- case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
- case AL_PACK_BLOCK_ALIGNMENT_SOFT:
- alBufferi(buffer, param, values[0]);
- return;
- }
- }
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY((albuf=LookupBuffer(device, buffer)) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY(!values))
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- case AL_LOOP_POINTS_SOFT:
- if(UNLIKELY(ReadRef(&albuf->ref) != 0))
- alSetError(context, AL_INVALID_OPERATION, "Modifying in-use buffer %u's loop points",
- buffer);
- else if(UNLIKELY(values[0] >= values[1] || values[0] < 0 || values[1] > albuf->SampleLen))
- alSetError(context, AL_INVALID_VALUE, "Invalid loop point range %d -> %d o buffer %u",
- values[0], values[1], buffer);
- else
- {
- albuf->LoopStart = values[0];
- albuf->LoopEnd = values[1];
- }
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x",
- param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALbuffer *albuf;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY((albuf=LookupBuffer(device, buffer)) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY(!value))
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
-{
- ALCdevice *device;
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY(LookupBuffer(device, buffer) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY(!value1 || !value2 || !value3))
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values)
-{
- ALCdevice *device;
- ALCcontext *context;
-
- switch(param)
- {
- case AL_SEC_LENGTH_SOFT:
- alGetBufferf(buffer, param, values);
- return;
- }
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY(LookupBuffer(device, buffer) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY(!values))
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALbuffer *albuf;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY((albuf=LookupBuffer(device, buffer)) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY(!value))
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- case AL_FREQUENCY:
- *value = albuf->Frequency;
- break;
-
- case AL_BITS:
- *value = BytesFromFmt(albuf->FmtType) * 8;
- break;
-
- case AL_CHANNELS:
- *value = ChannelsFromFmt(albuf->FmtChannels);
- break;
-
- case AL_SIZE:
- *value = albuf->SampleLen * FrameSizeFromFmt(albuf->FmtChannels,
- albuf->FmtType);
- break;
-
- case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
- *value = ATOMIC_LOAD_SEQ(&albuf->UnpackAlign);
- break;
-
- case AL_PACK_BLOCK_ALIGNMENT_SOFT:
- *value = ATOMIC_LOAD_SEQ(&albuf->PackAlign);
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3)
-{
- ALCdevice *device;
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY(LookupBuffer(device, buffer) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY(!value1 || !value2 || !value3))
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALbuffer *albuf;
-
- switch(param)
- {
- case AL_FREQUENCY:
- case AL_BITS:
- case AL_CHANNELS:
- case AL_SIZE:
- case AL_INTERNAL_FORMAT_SOFT:
- case AL_BYTE_LENGTH_SOFT:
- case AL_SAMPLE_LENGTH_SOFT:
- case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
- case AL_PACK_BLOCK_ALIGNMENT_SOFT:
- alGetBufferi(buffer, param, values);
- return;
- }
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockBufferList(device);
- if(UNLIKELY((albuf=LookupBuffer(device, buffer)) == NULL))
- alSetError(context, AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
- else if(UNLIKELY(!values))
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- case AL_LOOP_POINTS_SOFT:
- values[0] = albuf->LoopStart;
- values[1] = albuf->LoopEnd;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x",
- param);
- }
- UnlockBufferList(device);
-
- ALCcontext_DecRef(context);
-}
-
-
-static const ALchar *NameFromUserFmtType(enum UserFmtType type)
-{
- switch(type)
- {
- case UserFmtUByte: return "Unsigned Byte";
- case UserFmtShort: return "Signed Short";
- case UserFmtFloat: return "Float32";
- case UserFmtDouble: return "Float64";
- case UserFmtMulaw: return "muLaw";
- case UserFmtAlaw: return "aLaw";
- case UserFmtIMA4: return "IMA4 ADPCM";
- case UserFmtMSADPCM: return "MSADPCM";
- }
- return "<internal type error>";
-}
-
-/*
- * LoadData
- *
- * Loads the specified data into the buffer, using the specified format.
- */
-static void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALuint freq, ALsizei size, enum UserFmtChannels SrcChannels, enum UserFmtType SrcType, const ALvoid *data, ALbitfieldSOFT access)
-{
- enum FmtChannels DstChannels = FmtMono;
- enum FmtType DstType = FmtUByte;
- ALsizei NumChannels, FrameSize;
- ALsizei SrcByteAlign;
- ALsizei unpackalign;
- ALsizei newsize;
- ALsizei frames;
- ALsizei align;
-
- if(UNLIKELY(ReadRef(&ALBuf->ref) != 0 || ALBuf->MappedAccess != 0))
- SETERR_RETURN(context, AL_INVALID_OPERATION,, "Modifying storage for in-use buffer %u",
- ALBuf->id);
-
- /* Currently no channel configurations need to be converted. */
- switch(SrcChannels)
- {
- case UserFmtMono: DstChannels = FmtMono; break;
- case UserFmtStereo: DstChannels = FmtStereo; break;
- case UserFmtRear: DstChannels = FmtRear; break;
- case UserFmtQuad: DstChannels = FmtQuad; break;
- case UserFmtX51: DstChannels = FmtX51; break;
- case UserFmtX61: DstChannels = FmtX61; break;
- case UserFmtX71: DstChannels = FmtX71; break;
- case UserFmtBFormat2D: DstChannels = FmtBFormat2D; break;
- case UserFmtBFormat3D: DstChannels = FmtBFormat3D; break;
- }
- if(UNLIKELY((long)SrcChannels != (long)DstChannels))
- SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid format");
-
- /* IMA4 and MSADPCM convert to 16-bit short. */
- switch(SrcType)
- {
- case UserFmtUByte: DstType = FmtUByte; break;
- case UserFmtShort: DstType = FmtShort; break;
- case UserFmtFloat: DstType = FmtFloat; break;
- case UserFmtDouble: DstType = FmtDouble; break;
- case UserFmtAlaw: DstType = FmtAlaw; break;
- case UserFmtMulaw: DstType = FmtMulaw; break;
- case UserFmtIMA4: DstType = FmtShort; break;
- case UserFmtMSADPCM: DstType = FmtShort; break;
- }
-
- /* TODO: Currently we can only map samples when they're not converted. To
- * allow it would need some kind of double-buffering to hold onto a copy of
- * the original data.
- */
- if((access&MAP_READ_WRITE_FLAGS))
- {
- if(UNLIKELY((long)SrcType != (long)DstType))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "%s samples cannot be mapped",
- NameFromUserFmtType(SrcType));
- }
-
- unpackalign = ATOMIC_LOAD_SEQ(&ALBuf->UnpackAlign);
- if(UNLIKELY((align=SanitizeAlignment(SrcType, unpackalign)) < 1))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid unpack alignment %d for %s samples",
- unpackalign, NameFromUserFmtType(SrcType));
-
- if((access&AL_PRESERVE_DATA_BIT_SOFT))
- {
- /* Can only preserve data with the same format and alignment. */
- if(UNLIKELY(ALBuf->FmtChannels != DstChannels || ALBuf->OriginalType != SrcType))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched format");
- if(UNLIKELY(ALBuf->OriginalAlign != align))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched alignment");
- }
-
- /* Convert the input/source size in bytes to sample frames using the unpack
- * block alignment.
- */
- if(SrcType == UserFmtIMA4)
- SrcByteAlign = ((align-1)/2 + 4) * ChannelsFromUserFmt(SrcChannels);
- else if(SrcType == UserFmtMSADPCM)
- SrcByteAlign = ((align-2)/2 + 7) * ChannelsFromUserFmt(SrcChannels);
- else
- SrcByteAlign = align * FrameSizeFromUserFmt(SrcChannels, SrcType);
- if(UNLIKELY((size%SrcByteAlign) != 0))
- SETERR_RETURN(context, AL_INVALID_VALUE,,
- "Data size %d is not a multiple of frame size %d (%d unpack alignment)",
- size, SrcByteAlign, align);
-
- if(UNLIKELY(size / SrcByteAlign > INT_MAX / align))
- SETERR_RETURN(context, AL_OUT_OF_MEMORY,,
- "Buffer size overflow, %d blocks x %d samples per block", size/SrcByteAlign, align);
- frames = size / SrcByteAlign * align;
-
- /* Convert the sample frames to the number of bytes needed for internal
- * storage.
- */
- NumChannels = ChannelsFromFmt(DstChannels);
- FrameSize = NumChannels * BytesFromFmt(DstType);
- if(UNLIKELY(frames > INT_MAX/FrameSize))
- SETERR_RETURN(context, AL_OUT_OF_MEMORY,,
- "Buffer size overflow, %d frames x %d bytes per frame", frames, FrameSize);
- newsize = frames*FrameSize;
-
- /* Round up to the next 16-byte multiple. This could reallocate only when
- * increasing or the new size is less than half the current, but then the
- * buffer's AL_SIZE would not be very reliable for accounting buffer memory
- * usage, and reporting the real size could cause problems for apps that
- * use AL_SIZE to try to get the buffer's play length.
- */
- if(LIKELY(newsize <= INT_MAX-15))
- newsize = (newsize+15) & ~0xf;
- if(newsize != ALBuf->BytesAlloc)
- {
- void *temp = al_malloc(16, (size_t)newsize);
- if(UNLIKELY(!temp && newsize))
- SETERR_RETURN(context, AL_OUT_OF_MEMORY,, "Failed to allocate %d bytes of storage",
- newsize);
- if((access&AL_PRESERVE_DATA_BIT_SOFT))
- {
- ALsizei tocopy = mini(newsize, ALBuf->BytesAlloc);
- if(tocopy > 0) memcpy(temp, ALBuf->data, tocopy);
- }
- al_free(ALBuf->data);
- ALBuf->data = temp;
- ALBuf->BytesAlloc = newsize;
- }
-
- if(SrcType == UserFmtIMA4)
- {
- assert(DstType == FmtShort);
- if(data != NULL && ALBuf->data != NULL)
- Convert_ALshort_ALima4(ALBuf->data, data, NumChannels, frames, align);
- ALBuf->OriginalAlign = align;
- }
- else if(SrcType == UserFmtMSADPCM)
- {
- assert(DstType == FmtShort);
- if(data != NULL && ALBuf->data != NULL)
- Convert_ALshort_ALmsadpcm(ALBuf->data, data, NumChannels, frames, align);
- ALBuf->OriginalAlign = align;
- }
- else
- {
- assert((long)SrcType == (long)DstType);
- if(data != NULL && ALBuf->data != NULL)
- memcpy(ALBuf->data, data, frames*FrameSize);
- ALBuf->OriginalAlign = 1;
- }
- ALBuf->OriginalSize = size;
- ALBuf->OriginalType = SrcType;
-
- ALBuf->Frequency = freq;
- ALBuf->FmtChannels = DstChannels;
- ALBuf->FmtType = DstType;
- ALBuf->Access = access;
-
- ALBuf->SampleLen = frames;
- ALBuf->LoopStart = 0;
- ALBuf->LoopEnd = ALBuf->SampleLen;
-}
-
-
-ALsizei BytesFromUserFmt(enum UserFmtType type)
-{
- switch(type)
- {
- case UserFmtUByte: return sizeof(ALubyte);
- case UserFmtShort: return sizeof(ALshort);
- case UserFmtFloat: return sizeof(ALfloat);
- case UserFmtDouble: return sizeof(ALdouble);
- case UserFmtMulaw: return sizeof(ALubyte);
- case UserFmtAlaw: return sizeof(ALubyte);
- case UserFmtIMA4: break; /* not handled here */
- case UserFmtMSADPCM: break; /* not handled here */
- }
- return 0;
-}
-ALsizei ChannelsFromUserFmt(enum UserFmtChannels chans)
-{
- switch(chans)
- {
- case UserFmtMono: return 1;
- case UserFmtStereo: return 2;
- case UserFmtRear: return 2;
- case UserFmtQuad: return 4;
- case UserFmtX51: return 6;
- case UserFmtX61: return 7;
- case UserFmtX71: return 8;
- case UserFmtBFormat2D: return 3;
- case UserFmtBFormat3D: return 4;
- }
- return 0;
-}
-static ALboolean DecomposeUserFormat(ALenum format, enum UserFmtChannels *chans,
- enum UserFmtType *type)
-{
- static const struct {
- ALenum format;
- enum UserFmtChannels channels;
- enum UserFmtType type;
- } list[] = {
- { AL_FORMAT_MONO8, UserFmtMono, UserFmtUByte },
- { AL_FORMAT_MONO16, UserFmtMono, UserFmtShort },
- { AL_FORMAT_MONO_FLOAT32, UserFmtMono, UserFmtFloat },
- { AL_FORMAT_MONO_DOUBLE_EXT, UserFmtMono, UserFmtDouble },
- { AL_FORMAT_MONO_IMA4, UserFmtMono, UserFmtIMA4 },
- { AL_FORMAT_MONO_MSADPCM_SOFT, UserFmtMono, UserFmtMSADPCM },
- { AL_FORMAT_MONO_MULAW, UserFmtMono, UserFmtMulaw },
- { AL_FORMAT_MONO_ALAW_EXT, UserFmtMono, UserFmtAlaw },
-
- { AL_FORMAT_STEREO8, UserFmtStereo, UserFmtUByte },
- { AL_FORMAT_STEREO16, UserFmtStereo, UserFmtShort },
- { AL_FORMAT_STEREO_FLOAT32, UserFmtStereo, UserFmtFloat },
- { AL_FORMAT_STEREO_DOUBLE_EXT, UserFmtStereo, UserFmtDouble },
- { AL_FORMAT_STEREO_IMA4, UserFmtStereo, UserFmtIMA4 },
- { AL_FORMAT_STEREO_MSADPCM_SOFT, UserFmtStereo, UserFmtMSADPCM },
- { AL_FORMAT_STEREO_MULAW, UserFmtStereo, UserFmtMulaw },
- { AL_FORMAT_STEREO_ALAW_EXT, UserFmtStereo, UserFmtAlaw },
-
- { AL_FORMAT_REAR8, UserFmtRear, UserFmtUByte },
- { AL_FORMAT_REAR16, UserFmtRear, UserFmtShort },
- { AL_FORMAT_REAR32, UserFmtRear, UserFmtFloat },
- { AL_FORMAT_REAR_MULAW, UserFmtRear, UserFmtMulaw },
-
- { AL_FORMAT_QUAD8_LOKI, UserFmtQuad, UserFmtUByte },
- { AL_FORMAT_QUAD16_LOKI, UserFmtQuad, UserFmtShort },
-
- { AL_FORMAT_QUAD8, UserFmtQuad, UserFmtUByte },
- { AL_FORMAT_QUAD16, UserFmtQuad, UserFmtShort },
- { AL_FORMAT_QUAD32, UserFmtQuad, UserFmtFloat },
- { AL_FORMAT_QUAD_MULAW, UserFmtQuad, UserFmtMulaw },
-
- { AL_FORMAT_51CHN8, UserFmtX51, UserFmtUByte },
- { AL_FORMAT_51CHN16, UserFmtX51, UserFmtShort },
- { AL_FORMAT_51CHN32, UserFmtX51, UserFmtFloat },
- { AL_FORMAT_51CHN_MULAW, UserFmtX51, UserFmtMulaw },
-
- { AL_FORMAT_61CHN8, UserFmtX61, UserFmtUByte },
- { AL_FORMAT_61CHN16, UserFmtX61, UserFmtShort },
- { AL_FORMAT_61CHN32, UserFmtX61, UserFmtFloat },
- { AL_FORMAT_61CHN_MULAW, UserFmtX61, UserFmtMulaw },
-
- { AL_FORMAT_71CHN8, UserFmtX71, UserFmtUByte },
- { AL_FORMAT_71CHN16, UserFmtX71, UserFmtShort },
- { AL_FORMAT_71CHN32, UserFmtX71, UserFmtFloat },
- { AL_FORMAT_71CHN_MULAW, UserFmtX71, UserFmtMulaw },
-
- { AL_FORMAT_BFORMAT2D_8, UserFmtBFormat2D, UserFmtUByte },
- { AL_FORMAT_BFORMAT2D_16, UserFmtBFormat2D, UserFmtShort },
- { AL_FORMAT_BFORMAT2D_FLOAT32, UserFmtBFormat2D, UserFmtFloat },
- { AL_FORMAT_BFORMAT2D_MULAW, UserFmtBFormat2D, UserFmtMulaw },
-
- { AL_FORMAT_BFORMAT3D_8, UserFmtBFormat3D, UserFmtUByte },
- { AL_FORMAT_BFORMAT3D_16, UserFmtBFormat3D, UserFmtShort },
- { AL_FORMAT_BFORMAT3D_FLOAT32, UserFmtBFormat3D, UserFmtFloat },
- { AL_FORMAT_BFORMAT3D_MULAW, UserFmtBFormat3D, UserFmtMulaw },
- };
- 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;
-}
-
-ALsizei BytesFromFmt(enum FmtType type)
-{
- switch(type)
- {
- case FmtUByte: return sizeof(ALubyte);
- case FmtShort: return sizeof(ALshort);
- case FmtFloat: return sizeof(ALfloat);
- case FmtDouble: return sizeof(ALdouble);
- case FmtMulaw: return sizeof(ALubyte);
- case FmtAlaw: return sizeof(ALubyte);
- }
- return 0;
-}
-ALsizei ChannelsFromFmt(enum FmtChannels chans)
-{
- 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 3;
- case FmtBFormat3D: return 4;
- }
- return 0;
-}
-
-static ALsizei SanitizeAlignment(enum UserFmtType type, ALsizei align)
-{
- if(align < 0)
- return 0;
-
- if(align == 0)
- {
- if(type == UserFmtIMA4)
- {
- /* Here is where things vary:
- * nVidia and Apple use 64+1 sample frames per block -> block_size=36 bytes per channel
- * Most PC sound software uses 2040+1 sample frames per block -> block_size=1024 bytes per channel
- */
- return 65;
- }
- if(type == UserFmtMSADPCM)
- return 64;
- return 1;
- }
-
- if(type == UserFmtIMA4)
- {
- /* IMA4 block alignment must be a multiple of 8, plus 1. */
- if((align&7) == 1) return align;
- return 0;
- }
- if(type == UserFmtMSADPCM)
- {
- /* MSADPCM block alignment must be a multiple of 2. */
- if((align&1) == 0) return align;
- return 0;
- }
-
- return align;
-}
-
-
-static ALbuffer *AllocBuffer(ALCcontext *context)
-{
- ALCdevice *device = context->Device;
- BufferSubList *sublist, *subend;
- ALbuffer *buffer = NULL;
- ALsizei lidx = 0;
- ALsizei slidx;
-
- almtx_lock(&device->BufferLock);
- sublist = VECTOR_BEGIN(device->BufferList);
- subend = VECTOR_END(device->BufferList);
- for(;sublist != subend;++sublist)
- {
- if(sublist->FreeMask)
- {
- slidx = CTZ64(sublist->FreeMask);
- buffer = sublist->Buffers + slidx;
- break;
- }
- ++lidx;
- }
- if(UNLIKELY(!buffer))
- {
- const BufferSubList empty_sublist = { 0, NULL };
- /* Don't allocate so many list entries that the 32-bit ID could
- * overflow...
- */
- if(UNLIKELY(VECTOR_SIZE(device->BufferList) >= 1<<25))
- {
- almtx_unlock(&device->BufferLock);
- alSetError(context, AL_OUT_OF_MEMORY, "Too many buffers allocated");
- return NULL;
- }
- lidx = (ALsizei)VECTOR_SIZE(device->BufferList);
- VECTOR_PUSH_BACK(device->BufferList, empty_sublist);
- sublist = &VECTOR_BACK(device->BufferList);
- sublist->FreeMask = ~U64(0);
- sublist->Buffers = al_calloc(16, sizeof(ALbuffer)*64);
- if(UNLIKELY(!sublist->Buffers))
- {
- VECTOR_POP_BACK(device->BufferList);
- almtx_unlock(&device->BufferLock);
- alSetError(context, AL_OUT_OF_MEMORY, "Failed to allocate buffer batch");
- return NULL;
- }
-
- slidx = 0;
- buffer = sublist->Buffers + slidx;
- }
-
- memset(buffer, 0, sizeof(*buffer));
-
- /* Add 1 to avoid buffer ID 0. */
- buffer->id = ((lidx<<6) | slidx) + 1;
-
- sublist->FreeMask &= ~(U64(1)<<slidx);
- almtx_unlock(&device->BufferLock);
-
- return buffer;
-}
-
-static void FreeBuffer(ALCdevice *device, ALbuffer *buffer)
-{
- ALuint id = buffer->id - 1;
- ALsizei lidx = id >> 6;
- ALsizei slidx = id & 0x3f;
-
- al_free(buffer->data);
- memset(buffer, 0, sizeof(*buffer));
-
- VECTOR_ELEM(device->BufferList, lidx).FreeMask |= U64(1) << slidx;
-}
-
-
-/*
- * ReleaseALBuffers()
- *
- * INTERNAL: Called to destroy any buffers that still exist on the device
- */
-ALvoid ReleaseALBuffers(ALCdevice *device)
-{
- BufferSubList *sublist = VECTOR_BEGIN(device->BufferList);
- BufferSubList *subend = VECTOR_END(device->BufferList);
- size_t leftover = 0;
- for(;sublist != subend;++sublist)
- {
- ALuint64 usemask = ~sublist->FreeMask;
- while(usemask)
- {
- ALsizei idx = CTZ64(usemask);
- ALbuffer *buffer = sublist->Buffers + idx;
-
- al_free(buffer->data);
- memset(buffer, 0, sizeof(*buffer));
- ++leftover;
-
- usemask &= ~(U64(1) << idx);
- }
- sublist->FreeMask = ~usemask;
- }
- if(leftover > 0)
- WARN("(%p) Deleted "SZFMT" Buffer%s\n", device, leftover, (leftover==1)?"":"s");
-}
diff --git a/OpenAL32/alEffect.c b/OpenAL32/alEffect.c
deleted file mode 100644
index c2a78a0c..00000000
--- a/OpenAL32/alEffect.c
+++ /dev/null
@@ -1,819 +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 <math.h>
-#include <float.h>
-
-#include "AL/al.h"
-#include "AL/alc.h"
-#include "alMain.h"
-#include "alEffect.h"
-#include "alError.h"
-
-
-extern inline void LockEffectList(ALCdevice *device);
-extern inline void UnlockEffectList(ALCdevice *device);
-extern inline ALboolean IsReverbEffect(ALenum type);
-
-const struct EffectList EffectList[EFFECTLIST_SIZE] = {
- { "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB },
- { "reverb", REVERB_EFFECT, AL_EFFECT_REVERB },
- { "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH },
- { "chorus", CHORUS_EFFECT, AL_EFFECT_CHORUS },
- { "compressor", COMPRESSOR_EFFECT, AL_EFFECT_COMPRESSOR },
- { "distortion", DISTORTION_EFFECT, AL_EFFECT_DISTORTION },
- { "echo", ECHO_EFFECT, AL_EFFECT_ECHO },
- { "equalizer", EQUALIZER_EFFECT, AL_EFFECT_EQUALIZER },
- { "flanger", FLANGER_EFFECT, AL_EFFECT_FLANGER },
- { "fshifter", FSHIFTER_EFFECT, AL_EFFECT_FREQUENCY_SHIFTER },
- { "modulator", MODULATOR_EFFECT, AL_EFFECT_RING_MODULATOR },
- { "pshifter", PSHIFTER_EFFECT, AL_EFFECT_PITCH_SHIFTER },
- { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT },
- { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE },
-};
-
-ALboolean DisabledEffects[MAX_EFFECTS];
-
-static ALeffect *AllocEffect(ALCcontext *context);
-static void FreeEffect(ALCdevice *device, ALeffect *effect);
-static void InitEffectParams(ALeffect *effect, ALenum type);
-
-static inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
-{
- EffectSubList *sublist;
- ALuint lidx = (id-1) >> 6;
- ALsizei slidx = (id-1) & 0x3f;
-
- if(UNLIKELY(lidx >= VECTOR_SIZE(device->EffectList)))
- return NULL;
- sublist = &VECTOR_ELEM(device->EffectList, lidx);
- if(UNLIKELY(sublist->FreeMask & (U64(1)<<slidx)))
- return NULL;
- return sublist->Effects + slidx;
-}
-
-
-AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects)
-{
- ALCcontext *context;
- ALsizei cur;
-
- context = GetContextRef();
- if(!context) return;
-
- if(!(n >= 0))
- alSetError(context, AL_INVALID_VALUE, "Generating %d effects", n);
- else for(cur = 0;cur < n;cur++)
- {
- ALeffect *effect = AllocEffect(context);
- if(!effect)
- {
- alDeleteEffects(cur, effects);
- break;
- }
- effects[cur] = effect->id;
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALeffect *effect;
- ALsizei i;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockEffectList(device);
- if(!(n >= 0))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Deleting %d effects", n);
- for(i = 0;i < n;i++)
- {
- if(effects[i] && LookupEffect(device, effects[i]) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid effect ID %u", effects[i]);
- }
- for(i = 0;i < n;i++)
- {
- if((effect=LookupEffect(device, effects[i])) != NULL)
- FreeEffect(device, effect);
- }
-
-done:
- UnlockEffectList(device);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect)
-{
- ALCcontext *Context;
- ALboolean result;
-
- Context = GetContextRef();
- if(!Context) return AL_FALSE;
-
- LockEffectList(Context->Device);
- result = ((!effect || LookupEffect(Context->Device, effect)) ?
- AL_TRUE : AL_FALSE);
- UnlockEffectList(Context->Device);
-
- ALCcontext_DecRef(Context);
-
- return result;
-}
-
-AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALeffect *ALEffect;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockEffectList(Device);
- if((ALEffect=LookupEffect(Device, effect)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
- else
- {
- if(param == AL_EFFECT_TYPE)
- {
- ALboolean isOk = (value == AL_EFFECT_NULL);
- ALint i;
- for(i = 0;!isOk && i < EFFECTLIST_SIZE;i++)
- {
- if(value == EffectList[i].val &&
- !DisabledEffects[EffectList[i].type])
- isOk = AL_TRUE;
- }
-
- if(isOk)
- InitEffectParams(ALEffect, value);
- else
- alSetError(Context, AL_INVALID_VALUE, "Effect type 0x%04x not supported", value);
- }
- else
- {
- /* Call the appropriate handler */
- ALeffect_setParami(ALEffect, Context, param, value);
- }
- }
- UnlockEffectList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *values)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALeffect *ALEffect;
-
- switch(param)
- {
- case AL_EFFECT_TYPE:
- alEffecti(effect, param, values[0]);
- return;
- }
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockEffectList(Device);
- if((ALEffect=LookupEffect(Device, effect)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
- else
- {
- /* Call the appropriate handler */
- ALeffect_setParamiv(ALEffect, Context, param, values);
- }
- UnlockEffectList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALeffect *ALEffect;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockEffectList(Device);
- if((ALEffect=LookupEffect(Device, effect)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
- else
- {
- /* Call the appropriate handler */
- ALeffect_setParamf(ALEffect, Context, param, value);
- }
- UnlockEffectList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALeffect *ALEffect;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockEffectList(Device);
- if((ALEffect=LookupEffect(Device, effect)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
- else
- {
- /* Call the appropriate handler */
- ALeffect_setParamfv(ALEffect, Context, param, values);
- }
- UnlockEffectList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALeffect *ALEffect;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockEffectList(Device);
- if((ALEffect=LookupEffect(Device, effect)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
- else
- {
- if(param == AL_EFFECT_TYPE)
- *value = ALEffect->type;
- else
- {
- /* Call the appropriate handler */
- ALeffect_getParami(ALEffect, Context, param, value);
- }
- }
- UnlockEffectList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *values)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALeffect *ALEffect;
-
- switch(param)
- {
- case AL_EFFECT_TYPE:
- alGetEffecti(effect, param, values);
- return;
- }
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockEffectList(Device);
- if((ALEffect=LookupEffect(Device, effect)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
- else
- {
- /* Call the appropriate handler */
- ALeffect_getParamiv(ALEffect, Context, param, values);
- }
- UnlockEffectList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALeffect *ALEffect;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockEffectList(Device);
- if((ALEffect=LookupEffect(Device, effect)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
- else
- {
- /* Call the appropriate handler */
- ALeffect_getParamf(ALEffect, Context, param, value);
- }
- UnlockEffectList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALeffect *ALEffect;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockEffectList(Device);
- if((ALEffect=LookupEffect(Device, effect)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid effect ID %u", effect);
- else
- {
- /* Call the appropriate handler */
- ALeffect_getParamfv(ALEffect, Context, param, values);
- }
- UnlockEffectList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-
-void InitEffect(ALeffect *effect)
-{
- InitEffectParams(effect, AL_EFFECT_NULL);
-}
-
-static ALeffect *AllocEffect(ALCcontext *context)
-{
- ALCdevice *device = context->Device;
- EffectSubList *sublist, *subend;
- ALeffect *effect = NULL;
- ALsizei lidx = 0;
- ALsizei slidx;
-
- almtx_lock(&device->EffectLock);
- sublist = VECTOR_BEGIN(device->EffectList);
- subend = VECTOR_END(device->EffectList);
- for(;sublist != subend;++sublist)
- {
- if(sublist->FreeMask)
- {
- slidx = CTZ64(sublist->FreeMask);
- effect = sublist->Effects + slidx;
- break;
- }
- ++lidx;
- }
- if(UNLIKELY(!effect))
- {
- const EffectSubList empty_sublist = { 0, NULL };
- /* Don't allocate so many list entries that the 32-bit ID could
- * overflow...
- */
- if(UNLIKELY(VECTOR_SIZE(device->EffectList) >= 1<<25))
- {
- almtx_unlock(&device->EffectLock);
- alSetError(context, AL_OUT_OF_MEMORY, "Too many effects allocated");
- return NULL;
- }
- lidx = (ALsizei)VECTOR_SIZE(device->EffectList);
- VECTOR_PUSH_BACK(device->EffectList, empty_sublist);
- sublist = &VECTOR_BACK(device->EffectList);
- sublist->FreeMask = ~U64(0);
- sublist->Effects = al_calloc(16, sizeof(ALeffect)*64);
- if(UNLIKELY(!sublist->Effects))
- {
- VECTOR_POP_BACK(device->EffectList);
- almtx_unlock(&device->EffectLock);
- alSetError(context, AL_OUT_OF_MEMORY, "Failed to allocate effect batch");
- return NULL;
- }
-
- slidx = 0;
- effect = sublist->Effects + slidx;
- }
-
- memset(effect, 0, sizeof(*effect));
- InitEffectParams(effect, AL_EFFECT_NULL);
-
- /* Add 1 to avoid effect ID 0. */
- effect->id = ((lidx<<6) | slidx) + 1;
-
- sublist->FreeMask &= ~(U64(1)<<slidx);
- almtx_unlock(&device->EffectLock);
-
- return effect;
-}
-
-static void FreeEffect(ALCdevice *device, ALeffect *effect)
-{
- ALuint id = effect->id - 1;
- ALsizei lidx = id >> 6;
- ALsizei slidx = id & 0x3f;
-
- memset(effect, 0, sizeof(*effect));
-
- VECTOR_ELEM(device->EffectList, lidx).FreeMask |= U64(1) << slidx;
-}
-
-void ReleaseALEffects(ALCdevice *device)
-{
- EffectSubList *sublist = VECTOR_BEGIN(device->EffectList);
- EffectSubList *subend = VECTOR_END(device->EffectList);
- size_t leftover = 0;
- for(;sublist != subend;++sublist)
- {
- ALuint64 usemask = ~sublist->FreeMask;
- while(usemask)
- {
- ALsizei idx = CTZ64(usemask);
- ALeffect *effect = sublist->Effects + idx;
-
- memset(effect, 0, sizeof(*effect));
- ++leftover;
-
- usemask &= ~(U64(1) << idx);
- }
- sublist->FreeMask = ~usemask;
- }
- if(leftover > 0)
- WARN("(%p) Deleted "SZFMT" Effect%s\n", device, leftover, (leftover==1)?"":"s");
-}
-
-
-static void InitEffectParams(ALeffect *effect, ALenum type)
-{
- switch(type)
- {
- case AL_EFFECT_EAXREVERB:
- effect->Props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY;
- effect->Props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION;
- effect->Props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN;
- effect->Props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF;
- effect->Props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF;
- effect->Props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME;
- effect->Props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO;
- effect->Props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO;
- effect->Props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN;
- effect->Props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY;
- effect->Props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
- effect->Props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
- effect->Props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
- effect->Props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN;
- effect->Props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY;
- effect->Props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
- effect->Props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
- effect->Props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
- effect->Props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME;
- effect->Props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH;
- effect->Props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME;
- effect->Props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH;
- effect->Props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
- effect->Props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE;
- effect->Props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE;
- effect->Props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
- effect->Props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT;
- effect->vtab = &ALeaxreverb_vtable;
- break;
- case AL_EFFECT_REVERB:
- effect->Props.Reverb.Density = AL_REVERB_DEFAULT_DENSITY;
- effect->Props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION;
- effect->Props.Reverb.Gain = AL_REVERB_DEFAULT_GAIN;
- effect->Props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF;
- effect->Props.Reverb.GainLF = 1.0f;
- effect->Props.Reverb.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME;
- effect->Props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO;
- effect->Props.Reverb.DecayLFRatio = 1.0f;
- effect->Props.Reverb.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN;
- effect->Props.Reverb.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY;
- effect->Props.Reverb.ReflectionsPan[0] = 0.0f;
- effect->Props.Reverb.ReflectionsPan[1] = 0.0f;
- effect->Props.Reverb.ReflectionsPan[2] = 0.0f;
- effect->Props.Reverb.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN;
- effect->Props.Reverb.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY;
- effect->Props.Reverb.LateReverbPan[0] = 0.0f;
- effect->Props.Reverb.LateReverbPan[1] = 0.0f;
- effect->Props.Reverb.LateReverbPan[2] = 0.0f;
- effect->Props.Reverb.EchoTime = 0.25f;
- effect->Props.Reverb.EchoDepth = 0.0f;
- effect->Props.Reverb.ModulationTime = 0.25f;
- effect->Props.Reverb.ModulationDepth = 0.0f;
- effect->Props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
- effect->Props.Reverb.HFReference = 5000.0f;
- effect->Props.Reverb.LFReference = 250.0f;
- effect->Props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
- effect->Props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT;
- effect->vtab = &ALreverb_vtable;
- break;
- case AL_EFFECT_AUTOWAH:
- effect->Props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
- effect->Props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
- effect->Props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
- effect->Props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
- effect->vtab = &ALautowah_vtable;
- break;
- case AL_EFFECT_CHORUS:
- effect->Props.Chorus.Waveform = AL_CHORUS_DEFAULT_WAVEFORM;
- effect->Props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE;
- effect->Props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE;
- effect->Props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH;
- effect->Props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
- effect->Props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY;
- effect->vtab = &ALchorus_vtable;
- break;
- case AL_EFFECT_COMPRESSOR:
- effect->Props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
- effect->vtab = &ALcompressor_vtable;
- break;
- case AL_EFFECT_DISTORTION:
- effect->Props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE;
- effect->Props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN;
- effect->Props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
- effect->Props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
- effect->Props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
- effect->vtab = &ALdistortion_vtable;
- break;
- case AL_EFFECT_ECHO:
- effect->Props.Echo.Delay = AL_ECHO_DEFAULT_DELAY;
- effect->Props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY;
- effect->Props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING;
- effect->Props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
- effect->Props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD;
- effect->vtab = &ALecho_vtable;
- break;
- case AL_EFFECT_EQUALIZER:
- effect->Props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
- effect->Props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
- effect->Props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
- effect->Props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
- effect->Props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
- effect->Props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
- effect->Props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
- effect->Props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
- effect->Props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
- effect->Props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
- effect->vtab = &ALequalizer_vtable;
- break;
- case AL_EFFECT_FLANGER:
- effect->Props.Chorus.Waveform = AL_FLANGER_DEFAULT_WAVEFORM;
- effect->Props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE;
- effect->Props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE;
- effect->Props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH;
- effect->Props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
- effect->Props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY;
- effect->vtab = &ALflanger_vtable;
- break;
- case AL_EFFECT_FREQUENCY_SHIFTER:
- effect->Props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
- effect->Props.Fshifter.LeftDirection = AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION;
- effect->Props.Fshifter.RightDirection = AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION;
- effect->vtab = &ALfshifter_vtable;
- break;
- case AL_EFFECT_RING_MODULATOR:
- effect->Props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
- effect->Props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
- effect->Props.Modulator.Waveform = AL_RING_MODULATOR_DEFAULT_WAVEFORM;
- effect->vtab = &ALmodulator_vtable;
- break;
- case AL_EFFECT_PITCH_SHIFTER:
- effect->Props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
- effect->Props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
- effect->vtab = &ALpshifter_vtable;
- break;
- case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT:
- case AL_EFFECT_DEDICATED_DIALOGUE:
- effect->Props.Dedicated.Gain = 1.0f;
- effect->vtab = &ALdedicated_vtable;
- break;
- default:
- effect->vtab = &ALnull_vtable;
- break;
- }
- effect->type = type;
-}
-
-
-#include "AL/efx-presets.h"
-
-#define DECL(x) { #x, EFX_REVERB_PRESET_##x }
-static const struct {
- const char name[32];
- EFXEAXREVERBPROPERTIES props;
-} reverblist[] = {
- DECL(GENERIC),
- DECL(PADDEDCELL),
- DECL(ROOM),
- DECL(BATHROOM),
- DECL(LIVINGROOM),
- DECL(STONEROOM),
- DECL(AUDITORIUM),
- DECL(CONCERTHALL),
- DECL(CAVE),
- DECL(ARENA),
- DECL(HANGAR),
- DECL(CARPETEDHALLWAY),
- DECL(HALLWAY),
- DECL(STONECORRIDOR),
- DECL(ALLEY),
- DECL(FOREST),
- DECL(CITY),
- DECL(MOUNTAINS),
- DECL(QUARRY),
- DECL(PLAIN),
- DECL(PARKINGLOT),
- DECL(SEWERPIPE),
- DECL(UNDERWATER),
- DECL(DRUGGED),
- DECL(DIZZY),
- DECL(PSYCHOTIC),
-
- DECL(CASTLE_SMALLROOM),
- DECL(CASTLE_SHORTPASSAGE),
- DECL(CASTLE_MEDIUMROOM),
- DECL(CASTLE_LARGEROOM),
- DECL(CASTLE_LONGPASSAGE),
- DECL(CASTLE_HALL),
- DECL(CASTLE_CUPBOARD),
- DECL(CASTLE_COURTYARD),
- DECL(CASTLE_ALCOVE),
-
- DECL(FACTORY_SMALLROOM),
- DECL(FACTORY_SHORTPASSAGE),
- DECL(FACTORY_MEDIUMROOM),
- DECL(FACTORY_LARGEROOM),
- DECL(FACTORY_LONGPASSAGE),
- DECL(FACTORY_HALL),
- DECL(FACTORY_CUPBOARD),
- DECL(FACTORY_COURTYARD),
- DECL(FACTORY_ALCOVE),
-
- DECL(ICEPALACE_SMALLROOM),
- DECL(ICEPALACE_SHORTPASSAGE),
- DECL(ICEPALACE_MEDIUMROOM),
- DECL(ICEPALACE_LARGEROOM),
- DECL(ICEPALACE_LONGPASSAGE),
- DECL(ICEPALACE_HALL),
- DECL(ICEPALACE_CUPBOARD),
- DECL(ICEPALACE_COURTYARD),
- DECL(ICEPALACE_ALCOVE),
-
- DECL(SPACESTATION_SMALLROOM),
- DECL(SPACESTATION_SHORTPASSAGE),
- DECL(SPACESTATION_MEDIUMROOM),
- DECL(SPACESTATION_LARGEROOM),
- DECL(SPACESTATION_LONGPASSAGE),
- DECL(SPACESTATION_HALL),
- DECL(SPACESTATION_CUPBOARD),
- DECL(SPACESTATION_ALCOVE),
-
- DECL(WOODEN_SMALLROOM),
- DECL(WOODEN_SHORTPASSAGE),
- DECL(WOODEN_MEDIUMROOM),
- DECL(WOODEN_LARGEROOM),
- DECL(WOODEN_LONGPASSAGE),
- DECL(WOODEN_HALL),
- DECL(WOODEN_CUPBOARD),
- DECL(WOODEN_COURTYARD),
- DECL(WOODEN_ALCOVE),
-
- DECL(SPORT_EMPTYSTADIUM),
- DECL(SPORT_SQUASHCOURT),
- DECL(SPORT_SMALLSWIMMINGPOOL),
- DECL(SPORT_LARGESWIMMINGPOOL),
- DECL(SPORT_GYMNASIUM),
- DECL(SPORT_FULLSTADIUM),
- DECL(SPORT_STADIUMTANNOY),
-
- DECL(PREFAB_WORKSHOP),
- DECL(PREFAB_SCHOOLROOM),
- DECL(PREFAB_PRACTISEROOM),
- DECL(PREFAB_OUTHOUSE),
- DECL(PREFAB_CARAVAN),
-
- DECL(DOME_TOMB),
- DECL(PIPE_SMALL),
- DECL(DOME_SAINTPAULS),
- DECL(PIPE_LONGTHIN),
- DECL(PIPE_LARGE),
- DECL(PIPE_RESONANT),
-
- DECL(OUTDOORS_BACKYARD),
- DECL(OUTDOORS_ROLLINGPLAINS),
- DECL(OUTDOORS_DEEPCANYON),
- DECL(OUTDOORS_CREEK),
- DECL(OUTDOORS_VALLEY),
-
- DECL(MOOD_HEAVEN),
- DECL(MOOD_HELL),
- DECL(MOOD_MEMORY),
-
- DECL(DRIVING_COMMENTATOR),
- DECL(DRIVING_PITGARAGE),
- DECL(DRIVING_INCAR_RACER),
- DECL(DRIVING_INCAR_SPORTS),
- DECL(DRIVING_INCAR_LUXURY),
- DECL(DRIVING_FULLGRANDSTAND),
- DECL(DRIVING_EMPTYGRANDSTAND),
- DECL(DRIVING_TUNNEL),
-
- DECL(CITY_STREETS),
- DECL(CITY_SUBWAY),
- DECL(CITY_MUSEUM),
- DECL(CITY_LIBRARY),
- DECL(CITY_UNDERPASS),
- DECL(CITY_ABANDONED),
-
- DECL(DUSTYROOM),
- DECL(CHAPEL),
- DECL(SMALLWATERROOM),
-};
-#undef DECL
-
-void LoadReverbPreset(const char *name, ALeffect *effect)
-{
- size_t i;
-
- if(strcasecmp(name, "NONE") == 0)
- {
- InitEffectParams(effect, AL_EFFECT_NULL);
- TRACE("Loading reverb '%s'\n", "NONE");
- return;
- }
-
- if(!DisabledEffects[EAXREVERB_EFFECT])
- InitEffectParams(effect, AL_EFFECT_EAXREVERB);
- else if(!DisabledEffects[REVERB_EFFECT])
- InitEffectParams(effect, AL_EFFECT_REVERB);
- else
- InitEffectParams(effect, AL_EFFECT_NULL);
- for(i = 0;i < COUNTOF(reverblist);i++)
- {
- const EFXEAXREVERBPROPERTIES *props;
-
- if(strcasecmp(name, reverblist[i].name) != 0)
- continue;
-
- TRACE("Loading reverb '%s'\n", reverblist[i].name);
- props = &reverblist[i].props;
- effect->Props.Reverb.Density = props->flDensity;
- effect->Props.Reverb.Diffusion = props->flDiffusion;
- effect->Props.Reverb.Gain = props->flGain;
- effect->Props.Reverb.GainHF = props->flGainHF;
- effect->Props.Reverb.GainLF = props->flGainLF;
- effect->Props.Reverb.DecayTime = props->flDecayTime;
- effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio;
- effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio;
- effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain;
- effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay;
- effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0];
- effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1];
- effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2];
- effect->Props.Reverb.LateReverbGain = props->flLateReverbGain;
- effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay;
- effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0];
- effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1];
- effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2];
- effect->Props.Reverb.EchoTime = props->flEchoTime;
- effect->Props.Reverb.EchoDepth = props->flEchoDepth;
- effect->Props.Reverb.ModulationTime = props->flModulationTime;
- effect->Props.Reverb.ModulationDepth = props->flModulationDepth;
- effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF;
- effect->Props.Reverb.HFReference = props->flHFReference;
- effect->Props.Reverb.LFReference = props->flLFReference;
- effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor;
- effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit;
- return;
- }
-
- WARN("Reverb preset '%s' not found\n", name);
-}
diff --git a/OpenAL32/alFilter.c b/OpenAL32/alFilter.c
deleted file mode 100644
index e57653e0..00000000
--- a/OpenAL32/alFilter.c
+++ /dev/null
@@ -1,671 +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 "alMain.h"
-#include "alu.h"
-#include "alFilter.h"
-#include "alError.h"
-
-
-#define FILTER_MIN_GAIN 0.0f
-#define FILTER_MAX_GAIN 4.0f /* +12dB */
-
-extern inline void LockFilterList(ALCdevice *device);
-extern inline void UnlockFilterList(ALCdevice *device);
-
-static ALfilter *AllocFilter(ALCcontext *context);
-static void FreeFilter(ALCdevice *device, ALfilter *filter);
-static void InitFilterParams(ALfilter *filter, ALenum type);
-
-static inline ALfilter *LookupFilter(ALCdevice *device, ALuint id)
-{
- FilterSubList *sublist;
- ALuint lidx = (id-1) >> 6;
- ALsizei slidx = (id-1) & 0x3f;
-
- if(UNLIKELY(lidx >= VECTOR_SIZE(device->FilterList)))
- return NULL;
- sublist = &VECTOR_ELEM(device->FilterList, lidx);
- if(UNLIKELY(sublist->FreeMask & (U64(1)<<slidx)))
- return NULL;
- return sublist->Filters + slidx;
-}
-
-
-AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters)
-{
- ALCcontext *context;
- ALsizei cur = 0;
-
- context = GetContextRef();
- if(!context) return;
-
- if(!(n >= 0))
- alSetError(context, AL_INVALID_VALUE, "Generating %d filters", n);
- else for(cur = 0;cur < n;cur++)
- {
- ALfilter *filter = AllocFilter(context);
- if(!filter)
- {
- alDeleteFilters(cur, filters);
- break;
- }
-
- filters[cur] = filter->id;
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALfilter *filter;
- ALsizei i;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
- LockFilterList(device);
- if(!(n >= 0))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Deleting %d filters", n);
- for(i = 0;i < n;i++)
- {
- if(filters[i] && LookupFilter(device, filters[i]) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid filter ID %u", filters[i]);
- }
- for(i = 0;i < n;i++)
- {
- if((filter=LookupFilter(device, filters[i])) != NULL)
- FreeFilter(device, filter);
- }
-
-done:
- UnlockFilterList(device);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter)
-{
- ALCcontext *Context;
- ALboolean result;
-
- Context = GetContextRef();
- if(!Context) return AL_FALSE;
-
- LockFilterList(Context->Device);
- result = ((!filter || LookupFilter(Context->Device, filter)) ?
- AL_TRUE : AL_FALSE);
- UnlockFilterList(Context->Device);
-
- ALCcontext_DecRef(Context);
-
- return result;
-}
-
-AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALfilter *ALFilter;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockFilterList(Device);
- if((ALFilter=LookupFilter(Device, filter)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
- else
- {
- if(param == AL_FILTER_TYPE)
- {
- if(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS ||
- value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)
- InitFilterParams(ALFilter, value);
- else
- alSetError(Context, AL_INVALID_VALUE, "Invalid filter type 0x%04x", value);
- }
- else
- {
- /* Call the appropriate handler */
- ALfilter_setParami(ALFilter, Context, param, value);
- }
- }
- UnlockFilterList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *values)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALfilter *ALFilter;
-
- switch(param)
- {
- case AL_FILTER_TYPE:
- alFilteri(filter, param, values[0]);
- return;
- }
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockFilterList(Device);
- if((ALFilter=LookupFilter(Device, filter)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
- else
- {
- /* Call the appropriate handler */
- ALfilter_setParamiv(ALFilter, Context, param, values);
- }
- UnlockFilterList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALfilter *ALFilter;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockFilterList(Device);
- if((ALFilter=LookupFilter(Device, filter)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
- else
- {
- /* Call the appropriate handler */
- ALfilter_setParamf(ALFilter, Context, param, value);
- }
- UnlockFilterList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *values)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALfilter *ALFilter;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockFilterList(Device);
- if((ALFilter=LookupFilter(Device, filter)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
- else
- {
- /* Call the appropriate handler */
- ALfilter_setParamfv(ALFilter, Context, param, values);
- }
- UnlockFilterList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALfilter *ALFilter;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockFilterList(Device);
- if((ALFilter=LookupFilter(Device, filter)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
- else
- {
- if(param == AL_FILTER_TYPE)
- *value = ALFilter->type;
- else
- {
- /* Call the appropriate handler */
- ALfilter_getParami(ALFilter, Context, param, value);
- }
- }
- UnlockFilterList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *values)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALfilter *ALFilter;
-
- switch(param)
- {
- case AL_FILTER_TYPE:
- alGetFilteri(filter, param, values);
- return;
- }
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockFilterList(Device);
- if((ALFilter=LookupFilter(Device, filter)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
- else
- {
- /* Call the appropriate handler */
- ALfilter_getParamiv(ALFilter, Context, param, values);
- }
- UnlockFilterList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALfilter *ALFilter;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockFilterList(Device);
- if((ALFilter=LookupFilter(Device, filter)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
- else
- {
- /* Call the appropriate handler */
- ALfilter_getParamf(ALFilter, Context, param, value);
- }
- UnlockFilterList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *values)
-{
- ALCcontext *Context;
- ALCdevice *Device;
- ALfilter *ALFilter;
-
- Context = GetContextRef();
- if(!Context) return;
-
- Device = Context->Device;
- LockFilterList(Device);
- if((ALFilter=LookupFilter(Device, filter)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid filter ID %u", filter);
- else
- {
- /* Call the appropriate handler */
- ALfilter_getParamfv(ALFilter, Context, param, values);
- }
- UnlockFilterList(Device);
-
- ALCcontext_DecRef(Context);
-}
-
-
-static void ALlowpass_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); }
-static void ALlowpass_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); }
-static void ALlowpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
-{
- switch(param)
- {
- case AL_LOWPASS_GAIN:
- if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gain %f out of range", val);
- filter->Gain = val;
- break;
-
- case AL_LOWPASS_GAINHF:
- if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gainhf %f out of range", val);
- filter->GainHF = val;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param);
- }
-}
-static void ALlowpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
-{ ALlowpass_setParamf(filter, context, param, vals[0]); }
-
-static void ALlowpass_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); }
-static void ALlowpass_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); }
-static void ALlowpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
-{
- switch(param)
- {
- case AL_LOWPASS_GAIN:
- *val = filter->Gain;
- break;
-
- case AL_LOWPASS_GAINHF:
- *val = filter->GainHF;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param);
- }
-}
-static void ALlowpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
-{ ALlowpass_getParamf(filter, context, param, vals); }
-
-DEFINE_ALFILTER_VTABLE(ALlowpass);
-
-
-static void ALhighpass_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); }
-static void ALhighpass_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); }
-static void ALhighpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
-{
- switch(param)
- {
- case AL_HIGHPASS_GAIN:
- if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gain out of range");
- filter->Gain = val;
- break;
-
- case AL_HIGHPASS_GAINLF:
- if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gainlf out of range");
- filter->GainLF = val;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param);
- }
-}
-static void ALhighpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
-{ ALhighpass_setParamf(filter, context, param, vals[0]); }
-
-static void ALhighpass_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); }
-static void ALhighpass_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); }
-static void ALhighpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
-{
- switch(param)
- {
- case AL_HIGHPASS_GAIN:
- *val = filter->Gain;
- break;
-
- case AL_HIGHPASS_GAINLF:
- *val = filter->GainLF;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param);
- }
-}
-static void ALhighpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
-{ ALhighpass_getParamf(filter, context, param, vals); }
-
-DEFINE_ALFILTER_VTABLE(ALhighpass);
-
-
-static void ALbandpass_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); }
-static void ALbandpass_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); }
-static void ALbandpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
-{
- switch(param)
- {
- case AL_BANDPASS_GAIN:
- if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gain out of range");
- filter->Gain = val;
- break;
-
- case AL_BANDPASS_GAINHF:
- if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainhf out of range");
- filter->GainHF = val;
- break;
-
- case AL_BANDPASS_GAINLF:
- if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF))
- SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainlf out of range");
- filter->GainLF = val;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param);
- }
-}
-static void ALbandpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
-{ ALbandpass_setParamf(filter, context, param, vals[0]); }
-
-static void ALbandpass_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); }
-static void ALbandpass_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); }
-static void ALbandpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
-{
- switch(param)
- {
- case AL_BANDPASS_GAIN:
- *val = filter->Gain;
- break;
-
- case AL_BANDPASS_GAINHF:
- *val = filter->GainHF;
- break;
-
- case AL_BANDPASS_GAINLF:
- *val = filter->GainLF;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param);
- }
-}
-static void ALbandpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
-{ ALbandpass_getParamf(filter, context, param, vals); }
-
-DEFINE_ALFILTER_VTABLE(ALbandpass);
-
-
-static void ALnullfilter_setParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
-static void ALnullfilter_setParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
-static void ALnullfilter_setParamf(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALfloat UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
-static void ALnullfilter_setParamfv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, const ALfloat *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
-
-static void ALnullfilter_getParami(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
-static void ALnullfilter_getParamiv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALint *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
-static void ALnullfilter_getParamf(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALfloat *UNUSED(val))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
-static void ALnullfilter_getParamfv(ALfilter *UNUSED(filter), ALCcontext *context, ALenum param, ALfloat *UNUSED(vals))
-{ alSetError(context, AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
-
-DEFINE_ALFILTER_VTABLE(ALnullfilter);
-
-
-static ALfilter *AllocFilter(ALCcontext *context)
-{
- ALCdevice *device = context->Device;
- FilterSubList *sublist, *subend;
- ALfilter *filter = NULL;
- ALsizei lidx = 0;
- ALsizei slidx;
-
- almtx_lock(&device->FilterLock);
- sublist = VECTOR_BEGIN(device->FilterList);
- subend = VECTOR_END(device->FilterList);
- for(;sublist != subend;++sublist)
- {
- if(sublist->FreeMask)
- {
- slidx = CTZ64(sublist->FreeMask);
- filter = sublist->Filters + slidx;
- break;
- }
- ++lidx;
- }
- if(UNLIKELY(!filter))
- {
- const FilterSubList empty_sublist = { 0, NULL };
- /* Don't allocate so many list entries that the 32-bit ID could
- * overflow...
- */
- if(UNLIKELY(VECTOR_SIZE(device->FilterList) >= 1<<25))
- {
- almtx_unlock(&device->FilterLock);
- alSetError(context, AL_OUT_OF_MEMORY, "Too many filters allocated");
- return NULL;
- }
- lidx = (ALsizei)VECTOR_SIZE(device->FilterList);
- VECTOR_PUSH_BACK(device->FilterList, empty_sublist);
- sublist = &VECTOR_BACK(device->FilterList);
- sublist->FreeMask = ~U64(0);
- sublist->Filters = al_calloc(16, sizeof(ALfilter)*64);
- if(UNLIKELY(!sublist->Filters))
- {
- VECTOR_POP_BACK(device->FilterList);
- almtx_unlock(&device->FilterLock);
- alSetError(context, AL_OUT_OF_MEMORY, "Failed to allocate filter batch");
- return NULL;
- }
-
- slidx = 0;
- filter = sublist->Filters + slidx;
- }
-
- memset(filter, 0, sizeof(*filter));
- InitFilterParams(filter, AL_FILTER_NULL);
-
- /* Add 1 to avoid filter ID 0. */
- filter->id = ((lidx<<6) | slidx) + 1;
-
- sublist->FreeMask &= ~(U64(1)<<slidx);
- almtx_unlock(&device->FilterLock);
-
- return filter;
-}
-
-static void FreeFilter(ALCdevice *device, ALfilter *filter)
-{
- ALuint id = filter->id - 1;
- ALsizei lidx = id >> 6;
- ALsizei slidx = id & 0x3f;
-
- memset(filter, 0, sizeof(*filter));
-
- VECTOR_ELEM(device->FilterList, lidx).FreeMask |= U64(1) << slidx;
-}
-
-void ReleaseALFilters(ALCdevice *device)
-{
- FilterSubList *sublist = VECTOR_BEGIN(device->FilterList);
- FilterSubList *subend = VECTOR_END(device->FilterList);
- size_t leftover = 0;
- for(;sublist != subend;++sublist)
- {
- ALuint64 usemask = ~sublist->FreeMask;
- while(usemask)
- {
- ALsizei idx = CTZ64(usemask);
- ALfilter *filter = sublist->Filters + idx;
-
- memset(filter, 0, sizeof(*filter));
- ++leftover;
-
- usemask &= ~(U64(1) << idx);
- }
- sublist->FreeMask = ~usemask;
- }
- if(leftover > 0)
- WARN("(%p) Deleted "SZFMT" Filter%s\n", device, leftover, (leftover==1)?"":"s");
-}
-
-
-static void InitFilterParams(ALfilter *filter, ALenum type)
-{
- if(type == AL_FILTER_LOWPASS)
- {
- filter->Gain = AL_LOWPASS_DEFAULT_GAIN;
- filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF;
- filter->HFReference = LOWPASSFREQREF;
- filter->GainLF = 1.0f;
- filter->LFReference = HIGHPASSFREQREF;
- filter->vtab = &ALlowpass_vtable;
- }
- else if(type == AL_FILTER_HIGHPASS)
- {
- filter->Gain = AL_HIGHPASS_DEFAULT_GAIN;
- filter->GainHF = 1.0f;
- filter->HFReference = LOWPASSFREQREF;
- filter->GainLF = AL_HIGHPASS_DEFAULT_GAINLF;
- filter->LFReference = HIGHPASSFREQREF;
- filter->vtab = &ALhighpass_vtable;
- }
- else if(type == AL_FILTER_BANDPASS)
- {
- filter->Gain = AL_BANDPASS_DEFAULT_GAIN;
- filter->GainHF = AL_BANDPASS_DEFAULT_GAINHF;
- filter->HFReference = LOWPASSFREQREF;
- filter->GainLF = AL_BANDPASS_DEFAULT_GAINLF;
- filter->LFReference = HIGHPASSFREQREF;
- filter->vtab = &ALbandpass_vtable;
- }
- else
- {
- filter->Gain = 1.0f;
- filter->GainHF = 1.0f;
- filter->HFReference = LOWPASSFREQREF;
- filter->GainLF = 1.0f;
- filter->LFReference = HIGHPASSFREQREF;
- filter->vtab = &ALnullfilter_vtable;
- }
- filter->type = type;
-}
diff --git a/OpenAL32/alListener.c b/OpenAL32/alListener.c
deleted file mode 100644
index f1ac3ff4..00000000
--- a/OpenAL32/alListener.c
+++ /dev/null
@@ -1,502 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 1999-2000 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 "alMain.h"
-#include "alu.h"
-#include "alError.h"
-#include "alListener.h"
-#include "alSource.h"
-
-#define DO_UPDATEPROPS() do { \
- if(!ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire)) \
- UpdateListenerProps(context); \
- else \
- ATOMIC_FLAG_CLEAR(&listener->PropsClean, almemory_order_release); \
-} while(0)
-
-
-AL_API ALvoid AL_APIENTRY alListenerf(ALenum param, ALfloat value)
-{
- ALlistener *listener;
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- listener = context->Listener;
- almtx_lock(&context->PropLock);
- switch(param)
- {
- case AL_GAIN:
- if(!(value >= 0.0f && isfinite(value)))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Listener gain out of range");
- listener->Gain = value;
- DO_UPDATEPROPS();
- break;
-
- case AL_METERS_PER_UNIT:
- if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Listener meters per unit out of range");
- context->MetersPerUnit = value;
- if(!ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
- UpdateContextProps(context);
- else
- ATOMIC_FLAG_CLEAR(&context->PropsClean, almemory_order_release);
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener float property");
- }
-
-done:
- almtx_unlock(&context->PropLock);
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
-{
- ALlistener *listener;
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- listener = context->Listener;
- almtx_lock(&context->PropLock);
- switch(param)
- {
- case AL_POSITION:
- if(!(isfinite(value1) && isfinite(value2) && isfinite(value3)))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Listener position out of range");
- listener->Position[0] = value1;
- listener->Position[1] = value2;
- listener->Position[2] = value3;
- DO_UPDATEPROPS();
- break;
-
- case AL_VELOCITY:
- if(!(isfinite(value1) && isfinite(value2) && isfinite(value3)))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Listener velocity out of range");
- listener->Velocity[0] = value1;
- listener->Velocity[1] = value2;
- listener->Velocity[2] = value3;
- DO_UPDATEPROPS();
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener 3-float property");
- }
-
-done:
- almtx_unlock(&context->PropLock);
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values)
-{
- ALlistener *listener;
- ALCcontext *context;
-
- if(values)
- {
- switch(param)
- {
- case AL_GAIN:
- case AL_METERS_PER_UNIT:
- alListenerf(param, values[0]);
- return;
-
- case AL_POSITION:
- case AL_VELOCITY:
- alListener3f(param, values[0], values[1], values[2]);
- return;
- }
- }
-
- context = GetContextRef();
- if(!context) return;
-
- listener = context->Listener;
- almtx_lock(&context->PropLock);
- if(!values) SETERR_GOTO(context, AL_INVALID_VALUE, done, "NULL pointer");
- switch(param)
- {
- case AL_ORIENTATION:
- if(!(isfinite(values[0]) && isfinite(values[1]) && isfinite(values[2]) &&
- isfinite(values[3]) && isfinite(values[4]) && isfinite(values[5])))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Listener orientation out of range");
- /* AT then UP */
- listener->Forward[0] = values[0];
- listener->Forward[1] = values[1];
- listener->Forward[2] = values[2];
- listener->Up[0] = values[3];
- listener->Up[1] = values[4];
- listener->Up[2] = values[5];
- DO_UPDATEPROPS();
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener float-vector property");
- }
-
-done:
- almtx_unlock(&context->PropLock);
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alListeneri(ALenum param, ALint UNUSED(value))
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener integer property");
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3)
-{
- ALCcontext *context;
-
- switch(param)
- {
- case AL_POSITION:
- case AL_VELOCITY:
- alListener3f(param, (ALfloat)value1, (ALfloat)value2, (ALfloat)value3);
- return;
- }
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener 3-integer property");
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values)
-{
- ALCcontext *context;
-
- if(values)
- {
- ALfloat fvals[6];
- switch(param)
- {
- case AL_POSITION:
- case AL_VELOCITY:
- alListener3f(param, (ALfloat)values[0], (ALfloat)values[1], (ALfloat)values[2]);
- return;
-
- case AL_ORIENTATION:
- fvals[0] = (ALfloat)values[0];
- fvals[1] = (ALfloat)values[1];
- fvals[2] = (ALfloat)values[2];
- fvals[3] = (ALfloat)values[3];
- fvals[4] = (ALfloat)values[4];
- fvals[5] = (ALfloat)values[5];
- alListenerfv(param, fvals);
- return;
- }
- }
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- if(!values)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener integer-vector property");
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- if(!value)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- case AL_GAIN:
- *value = context->Listener->Gain;
- break;
-
- case AL_METERS_PER_UNIT:
- *value = context->MetersPerUnit;
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener float property");
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- if(!value1 || !value2 || !value3)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- case AL_POSITION:
- *value1 = context->Listener->Position[0];
- *value2 = context->Listener->Position[1];
- *value3 = context->Listener->Position[2];
- break;
-
- case AL_VELOCITY:
- *value1 = context->Listener->Velocity[0];
- *value2 = context->Listener->Velocity[1];
- *value3 = context->Listener->Velocity[2];
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener 3-float property");
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values)
-{
- ALCcontext *context;
-
- switch(param)
- {
- case AL_GAIN:
- case AL_METERS_PER_UNIT:
- alGetListenerf(param, values);
- return;
-
- case AL_POSITION:
- case AL_VELOCITY:
- alGetListener3f(param, values+0, values+1, values+2);
- return;
- }
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- if(!values)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- case AL_ORIENTATION:
- // AT then UP
- values[0] = context->Listener->Forward[0];
- values[1] = context->Listener->Forward[1];
- values[2] = context->Listener->Forward[2];
- values[3] = context->Listener->Up[0];
- values[4] = context->Listener->Up[1];
- values[5] = context->Listener->Up[2];
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener float-vector property");
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alGetListeneri(ALenum param, ALint *value)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- if(!value)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener integer property");
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- if(!value1 || !value2 || !value3)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- case AL_POSITION:
- *value1 = (ALint)context->Listener->Position[0];
- *value2 = (ALint)context->Listener->Position[1];
- *value3 = (ALint)context->Listener->Position[2];
- break;
-
- case AL_VELOCITY:
- *value1 = (ALint)context->Listener->Velocity[0];
- *value2 = (ALint)context->Listener->Velocity[1];
- *value3 = (ALint)context->Listener->Velocity[2];
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener 3-integer property");
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint* values)
-{
- ALCcontext *context;
-
- switch(param)
- {
- case AL_POSITION:
- case AL_VELOCITY:
- alGetListener3i(param, values+0, values+1, values+2);
- return;
- }
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- if(!values)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- else switch(param)
- {
- case AL_ORIENTATION:
- // AT then UP
- values[0] = (ALint)context->Listener->Forward[0];
- values[1] = (ALint)context->Listener->Forward[1];
- values[2] = (ALint)context->Listener->Forward[2];
- values[3] = (ALint)context->Listener->Up[0];
- values[4] = (ALint)context->Listener->Up[1];
- values[5] = (ALint)context->Listener->Up[2];
- break;
-
- default:
- alSetError(context, AL_INVALID_ENUM, "Invalid listener integer-vector property");
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
-
-
-void UpdateListenerProps(ALCcontext *context)
-{
- ALlistener *listener = context->Listener;
- struct ALlistenerProps *props;
-
- /* Get an unused proprty container, or allocate a new one as needed. */
- props = ATOMIC_LOAD(&context->FreeListenerProps, almemory_order_acquire);
- if(!props)
- props = al_calloc(16, sizeof(*props));
- else
- {
- struct ALlistenerProps *next;
- do {
- next = ATOMIC_LOAD(&props->next, almemory_order_relaxed);
- } while(ATOMIC_COMPARE_EXCHANGE_PTR_WEAK(&context->FreeListenerProps, &props, next,
- almemory_order_seq_cst, almemory_order_acquire) == 0);
- }
-
- /* Copy in current property values. */
- props->Position[0] = listener->Position[0];
- props->Position[1] = listener->Position[1];
- props->Position[2] = listener->Position[2];
-
- props->Velocity[0] = listener->Velocity[0];
- props->Velocity[1] = listener->Velocity[1];
- props->Velocity[2] = listener->Velocity[2];
-
- props->Forward[0] = listener->Forward[0];
- props->Forward[1] = listener->Forward[1];
- props->Forward[2] = listener->Forward[2];
- props->Up[0] = listener->Up[0];
- props->Up[1] = listener->Up[1];
- props->Up[2] = listener->Up[2];
-
- props->Gain = listener->Gain;
-
- /* Set the new container for updating internal parameters. */
- props = ATOMIC_EXCHANGE_PTR(&listener->Update, props, almemory_order_acq_rel);
- if(props)
- {
- /* If there was an unused update container, put it back in the
- * freelist.
- */
- ATOMIC_REPLACE_HEAD(struct ALlistenerProps*, &context->FreeListenerProps, props);
- }
-}
diff --git a/OpenAL32/alSource.c b/OpenAL32/alSource.c
deleted file mode 100644
index d7c68e4e..00000000
--- a/OpenAL32/alSource.c
+++ /dev/null
@@ -1,3705 +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 <limits.h>
-#include <math.h>
-#include <float.h>
-
-#include "AL/al.h"
-#include "AL/alc.h"
-#include "alMain.h"
-#include "alError.h"
-#include "alSource.h"
-#include "alBuffer.h"
-#include "alFilter.h"
-#include "alAuxEffectSlot.h"
-#include "ringbuffer.h"
-
-#include "backends/base.h"
-
-#include "threads.h"
-#include "almalloc.h"
-
-
-static ALsource *AllocSource(ALCcontext *context);
-static void FreeSource(ALCcontext *context, ALsource *source);
-static void InitSourceParams(ALsource *Source, ALsizei num_sends);
-static void DeinitSource(ALsource *source, ALsizei num_sends);
-static void UpdateSourceProps(ALsource *source, ALvoice *voice, ALsizei num_sends, ALCcontext *context);
-static ALint64 GetSourceSampleOffset(ALsource *Source, ALCcontext *context, ALuint64 *clocktime);
-static ALdouble GetSourceSecOffset(ALsource *Source, ALCcontext *context, ALuint64 *clocktime);
-static ALdouble GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context);
-static ALboolean GetSampleOffset(ALsource *Source, ALuint *offset, ALsizei *frac);
-static ALboolean ApplyOffset(ALsource *Source, ALvoice *voice);
-
-static inline void LockSourceList(ALCcontext *context)
-{ almtx_lock(&context->SourceLock); }
-static inline void UnlockSourceList(ALCcontext *context)
-{ almtx_unlock(&context->SourceLock); }
-
-static inline ALsource *LookupSource(ALCcontext *context, ALuint id)
-{
- SourceSubList *sublist;
- ALuint lidx = (id-1) >> 6;
- ALsizei slidx = (id-1) & 0x3f;
-
- if(UNLIKELY(lidx >= VECTOR_SIZE(context->SourceList)))
- return NULL;
- sublist = &VECTOR_ELEM(context->SourceList, lidx);
- if(UNLIKELY(sublist->FreeMask & (U64(1)<<slidx)))
- return NULL;
- return sublist->Sources + slidx;
-}
-
-static inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id)
-{
- BufferSubList *sublist;
- ALuint lidx = (id-1) >> 6;
- ALsizei slidx = (id-1) & 0x3f;
-
- if(UNLIKELY(lidx >= VECTOR_SIZE(device->BufferList)))
- return NULL;
- sublist = &VECTOR_ELEM(device->BufferList, lidx);
- if(UNLIKELY(sublist->FreeMask & (U64(1)<<slidx)))
- return NULL;
- return sublist->Buffers + slidx;
-}
-
-static inline ALfilter *LookupFilter(ALCdevice *device, ALuint id)
-{
- FilterSubList *sublist;
- ALuint lidx = (id-1) >> 6;
- ALsizei slidx = (id-1) & 0x3f;
-
- if(UNLIKELY(lidx >= VECTOR_SIZE(device->FilterList)))
- return NULL;
- sublist = &VECTOR_ELEM(device->FilterList, lidx);
- if(UNLIKELY(sublist->FreeMask & (U64(1)<<slidx)))
- return NULL;
- return sublist->Filters + slidx;
-}
-
-static inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id)
-{
- id--;
- if(UNLIKELY(id >= VECTOR_SIZE(context->EffectSlotList)))
- return NULL;
- return VECTOR_ELEM(context->EffectSlotList, id);
-}
-
-
-typedef enum SourceProp {
- srcPitch = AL_PITCH,
- srcGain = AL_GAIN,
- srcMinGain = AL_MIN_GAIN,
- srcMaxGain = AL_MAX_GAIN,
- srcMaxDistance = AL_MAX_DISTANCE,
- srcRolloffFactor = AL_ROLLOFF_FACTOR,
- srcDopplerFactor = AL_DOPPLER_FACTOR,
- srcConeOuterGain = AL_CONE_OUTER_GAIN,
- srcSecOffset = AL_SEC_OFFSET,
- srcSampleOffset = AL_SAMPLE_OFFSET,
- srcByteOffset = AL_BYTE_OFFSET,
- srcConeInnerAngle = AL_CONE_INNER_ANGLE,
- srcConeOuterAngle = AL_CONE_OUTER_ANGLE,
- srcRefDistance = AL_REFERENCE_DISTANCE,
-
- srcPosition = AL_POSITION,
- srcVelocity = AL_VELOCITY,
- srcDirection = AL_DIRECTION,
-
- srcSourceRelative = AL_SOURCE_RELATIVE,
- srcLooping = AL_LOOPING,
- srcBuffer = AL_BUFFER,
- srcSourceState = AL_SOURCE_STATE,
- srcBuffersQueued = AL_BUFFERS_QUEUED,
- srcBuffersProcessed = AL_BUFFERS_PROCESSED,
- srcSourceType = AL_SOURCE_TYPE,
-
- /* ALC_EXT_EFX */
- srcConeOuterGainHF = AL_CONE_OUTER_GAINHF,
- srcAirAbsorptionFactor = AL_AIR_ABSORPTION_FACTOR,
- srcRoomRolloffFactor = AL_ROOM_ROLLOFF_FACTOR,
- srcDirectFilterGainHFAuto = AL_DIRECT_FILTER_GAINHF_AUTO,
- srcAuxSendFilterGainAuto = AL_AUXILIARY_SEND_FILTER_GAIN_AUTO,
- srcAuxSendFilterGainHFAuto = AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO,
- srcDirectFilter = AL_DIRECT_FILTER,
- srcAuxSendFilter = AL_AUXILIARY_SEND_FILTER,
-
- /* AL_SOFT_direct_channels */
- srcDirectChannelsSOFT = AL_DIRECT_CHANNELS_SOFT,
-
- /* AL_EXT_source_distance_model */
- srcDistanceModel = AL_DISTANCE_MODEL,
-
- /* AL_SOFT_source_latency */
- srcSampleOffsetLatencySOFT = AL_SAMPLE_OFFSET_LATENCY_SOFT,
- srcSecOffsetLatencySOFT = AL_SEC_OFFSET_LATENCY_SOFT,
-
- /* AL_EXT_STEREO_ANGLES */
- srcAngles = AL_STEREO_ANGLES,
-
- /* AL_EXT_SOURCE_RADIUS */
- srcRadius = AL_SOURCE_RADIUS,
-
- /* AL_EXT_BFORMAT */
- srcOrientation = AL_ORIENTATION,
-
- /* AL_SOFT_source_resampler */
- srcResampler = AL_SOURCE_RESAMPLER_SOFT,
-
- /* AL_SOFT_source_spatialize */
- srcSpatialize = AL_SOURCE_SPATIALIZE_SOFT,
-
- /* ALC_SOFT_device_clock */
- srcSampleOffsetClockSOFT = AL_SAMPLE_OFFSET_CLOCK_SOFT,
- srcSecOffsetClockSOFT = AL_SEC_OFFSET_CLOCK_SOFT,
-} SourceProp;
-
-static ALboolean SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALfloat *values);
-static ALboolean SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALint *values);
-static ALboolean SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALint64SOFT *values);
-
-static ALboolean GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, ALdouble *values);
-static ALboolean GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, ALint *values);
-static ALboolean GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, ALint64 *values);
-
-static inline ALvoice *GetSourceVoice(ALsource *source, ALCcontext *context)
-{
- ALint idx = source->VoiceIdx;
- if(idx >= 0 && idx < context->VoiceCount)
- {
- ALvoice *voice = context->Voices[idx];
- if(ATOMIC_LOAD(&voice->Source, almemory_order_acquire) == source)
- return voice;
- }
- source->VoiceIdx = -1;
- return NULL;
-}
-
-/**
- * Returns if the last known state for the source was playing or paused. Does
- * not sync with the mixer voice.
- */
-static inline bool IsPlayingOrPaused(ALsource *source)
-{ return source->state == AL_PLAYING || source->state == AL_PAUSED; }
-
-/**
- * Returns an updated source state using the matching voice's status (or lack
- * thereof).
- */
-static inline ALenum GetSourceState(ALsource *source, ALvoice *voice)
-{
- if(!voice && source->state == AL_PLAYING)
- source->state = AL_STOPPED;
- return source->state;
-}
-
-/**
- * Returns if the source should specify an update, given the context's
- * deferring state and the source's last known state.
- */
-static inline bool SourceShouldUpdate(ALsource *source, ALCcontext *context)
-{
- return !ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire) &&
- IsPlayingOrPaused(source);
-}
-
-
-/** Can only be called while the mixer is locked! */
-static void SendStateChangeEvent(ALCcontext *context, ALuint id, ALenum state)
-{
- AsyncEvent evt = ASYNC_EVENT(EventType_SourceStateChange);
- ALbitfieldSOFT enabledevt;
-
- enabledevt = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_acquire);
- if(!(enabledevt&EventType_SourceStateChange)) return;
-
- evt.u.user.type = AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT;
- evt.u.user.id = id;
- evt.u.user.param = state;
- snprintf(evt.u.user.msg, sizeof(evt.u.user.msg), "Source ID %u state changed to %s", id,
- (state==AL_INITIAL) ? "AL_INITIAL" :
- (state==AL_PLAYING) ? "AL_PLAYING" :
- (state==AL_PAUSED) ? "AL_PAUSED" :
- (state==AL_STOPPED) ? "AL_STOPPED" : "<unknown>"
- );
- /* The mixer may have queued a state change that's not yet been processed,
- * and we don't want state change messages to occur out of order, so send
- * it through the async queue to ensure proper ordering.
- */
- if(ll_ringbuffer_write(context->AsyncEvents, (const char*)&evt, 1) == 1)
- alsem_post(&context->EventSem);
-}
-
-
-static ALint FloatValsByProp(ALenum prop)
-{
- if(prop != (ALenum)((SourceProp)prop))
- return 0;
- switch((SourceProp)prop)
- {
- case AL_PITCH:
- case AL_GAIN:
- case AL_MIN_GAIN:
- case AL_MAX_GAIN:
- case AL_MAX_DISTANCE:
- case AL_ROLLOFF_FACTOR:
- case AL_DOPPLER_FACTOR:
- case AL_CONE_OUTER_GAIN:
- case AL_SEC_OFFSET:
- case AL_SAMPLE_OFFSET:
- case AL_BYTE_OFFSET:
- case AL_CONE_INNER_ANGLE:
- case AL_CONE_OUTER_ANGLE:
- case AL_REFERENCE_DISTANCE:
- case AL_CONE_OUTER_GAINHF:
- case AL_AIR_ABSORPTION_FACTOR:
- case AL_ROOM_ROLLOFF_FACTOR:
- case AL_DIRECT_FILTER_GAINHF_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
- case AL_DIRECT_CHANNELS_SOFT:
- case AL_DISTANCE_MODEL:
- case AL_SOURCE_RELATIVE:
- case AL_LOOPING:
- case AL_SOURCE_STATE:
- case AL_BUFFERS_QUEUED:
- case AL_BUFFERS_PROCESSED:
- case AL_SOURCE_TYPE:
- case AL_SOURCE_RADIUS:
- case AL_SOURCE_RESAMPLER_SOFT:
- case AL_SOURCE_SPATIALIZE_SOFT:
- return 1;
-
- case AL_STEREO_ANGLES:
- return 2;
-
- case AL_POSITION:
- case AL_VELOCITY:
- case AL_DIRECTION:
- return 3;
-
- case AL_ORIENTATION:
- return 6;
-
- case AL_SEC_OFFSET_LATENCY_SOFT:
- case AL_SEC_OFFSET_CLOCK_SOFT:
- break; /* Double only */
-
- case AL_BUFFER:
- case AL_DIRECT_FILTER:
- case AL_AUXILIARY_SEND_FILTER:
- break; /* i/i64 only */
- case AL_SAMPLE_OFFSET_LATENCY_SOFT:
- case AL_SAMPLE_OFFSET_CLOCK_SOFT:
- break; /* i64 only */
- }
- return 0;
-}
-static ALint DoubleValsByProp(ALenum prop)
-{
- if(prop != (ALenum)((SourceProp)prop))
- return 0;
- switch((SourceProp)prop)
- {
- case AL_PITCH:
- case AL_GAIN:
- case AL_MIN_GAIN:
- case AL_MAX_GAIN:
- case AL_MAX_DISTANCE:
- case AL_ROLLOFF_FACTOR:
- case AL_DOPPLER_FACTOR:
- case AL_CONE_OUTER_GAIN:
- case AL_SEC_OFFSET:
- case AL_SAMPLE_OFFSET:
- case AL_BYTE_OFFSET:
- case AL_CONE_INNER_ANGLE:
- case AL_CONE_OUTER_ANGLE:
- case AL_REFERENCE_DISTANCE:
- case AL_CONE_OUTER_GAINHF:
- case AL_AIR_ABSORPTION_FACTOR:
- case AL_ROOM_ROLLOFF_FACTOR:
- case AL_DIRECT_FILTER_GAINHF_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
- case AL_DIRECT_CHANNELS_SOFT:
- case AL_DISTANCE_MODEL:
- case AL_SOURCE_RELATIVE:
- case AL_LOOPING:
- case AL_SOURCE_STATE:
- case AL_BUFFERS_QUEUED:
- case AL_BUFFERS_PROCESSED:
- case AL_SOURCE_TYPE:
- case AL_SOURCE_RADIUS:
- case AL_SOURCE_RESAMPLER_SOFT:
- case AL_SOURCE_SPATIALIZE_SOFT:
- return 1;
-
- case AL_SEC_OFFSET_LATENCY_SOFT:
- case AL_SEC_OFFSET_CLOCK_SOFT:
- case AL_STEREO_ANGLES:
- return 2;
-
- case AL_POSITION:
- case AL_VELOCITY:
- case AL_DIRECTION:
- return 3;
-
- case AL_ORIENTATION:
- return 6;
-
- case AL_BUFFER:
- case AL_DIRECT_FILTER:
- case AL_AUXILIARY_SEND_FILTER:
- break; /* i/i64 only */
- case AL_SAMPLE_OFFSET_LATENCY_SOFT:
- case AL_SAMPLE_OFFSET_CLOCK_SOFT:
- break; /* i64 only */
- }
- return 0;
-}
-
-static ALint IntValsByProp(ALenum prop)
-{
- if(prop != (ALenum)((SourceProp)prop))
- return 0;
- switch((SourceProp)prop)
- {
- case AL_PITCH:
- case AL_GAIN:
- case AL_MIN_GAIN:
- case AL_MAX_GAIN:
- case AL_MAX_DISTANCE:
- case AL_ROLLOFF_FACTOR:
- case AL_DOPPLER_FACTOR:
- case AL_CONE_OUTER_GAIN:
- case AL_SEC_OFFSET:
- case AL_SAMPLE_OFFSET:
- case AL_BYTE_OFFSET:
- case AL_CONE_INNER_ANGLE:
- case AL_CONE_OUTER_ANGLE:
- case AL_REFERENCE_DISTANCE:
- case AL_CONE_OUTER_GAINHF:
- case AL_AIR_ABSORPTION_FACTOR:
- case AL_ROOM_ROLLOFF_FACTOR:
- case AL_DIRECT_FILTER_GAINHF_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
- case AL_DIRECT_CHANNELS_SOFT:
- case AL_DISTANCE_MODEL:
- case AL_SOURCE_RELATIVE:
- case AL_LOOPING:
- case AL_BUFFER:
- case AL_SOURCE_STATE:
- case AL_BUFFERS_QUEUED:
- case AL_BUFFERS_PROCESSED:
- case AL_SOURCE_TYPE:
- case AL_DIRECT_FILTER:
- case AL_SOURCE_RADIUS:
- case AL_SOURCE_RESAMPLER_SOFT:
- case AL_SOURCE_SPATIALIZE_SOFT:
- return 1;
-
- case AL_POSITION:
- case AL_VELOCITY:
- case AL_DIRECTION:
- case AL_AUXILIARY_SEND_FILTER:
- return 3;
-
- case AL_ORIENTATION:
- return 6;
-
- case AL_SAMPLE_OFFSET_LATENCY_SOFT:
- case AL_SAMPLE_OFFSET_CLOCK_SOFT:
- break; /* i64 only */
- case AL_SEC_OFFSET_LATENCY_SOFT:
- case AL_SEC_OFFSET_CLOCK_SOFT:
- break; /* Double only */
- case AL_STEREO_ANGLES:
- break; /* Float/double only */
- }
- return 0;
-}
-static ALint Int64ValsByProp(ALenum prop)
-{
- if(prop != (ALenum)((SourceProp)prop))
- return 0;
- switch((SourceProp)prop)
- {
- case AL_PITCH:
- case AL_GAIN:
- case AL_MIN_GAIN:
- case AL_MAX_GAIN:
- case AL_MAX_DISTANCE:
- case AL_ROLLOFF_FACTOR:
- case AL_DOPPLER_FACTOR:
- case AL_CONE_OUTER_GAIN:
- case AL_SEC_OFFSET:
- case AL_SAMPLE_OFFSET:
- case AL_BYTE_OFFSET:
- case AL_CONE_INNER_ANGLE:
- case AL_CONE_OUTER_ANGLE:
- case AL_REFERENCE_DISTANCE:
- case AL_CONE_OUTER_GAINHF:
- case AL_AIR_ABSORPTION_FACTOR:
- case AL_ROOM_ROLLOFF_FACTOR:
- case AL_DIRECT_FILTER_GAINHF_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
- case AL_DIRECT_CHANNELS_SOFT:
- case AL_DISTANCE_MODEL:
- case AL_SOURCE_RELATIVE:
- case AL_LOOPING:
- case AL_BUFFER:
- case AL_SOURCE_STATE:
- case AL_BUFFERS_QUEUED:
- case AL_BUFFERS_PROCESSED:
- case AL_SOURCE_TYPE:
- case AL_DIRECT_FILTER:
- case AL_SOURCE_RADIUS:
- case AL_SOURCE_RESAMPLER_SOFT:
- case AL_SOURCE_SPATIALIZE_SOFT:
- return 1;
-
- case AL_SAMPLE_OFFSET_LATENCY_SOFT:
- case AL_SAMPLE_OFFSET_CLOCK_SOFT:
- return 2;
-
- case AL_POSITION:
- case AL_VELOCITY:
- case AL_DIRECTION:
- case AL_AUXILIARY_SEND_FILTER:
- return 3;
-
- case AL_ORIENTATION:
- return 6;
-
- case AL_SEC_OFFSET_LATENCY_SOFT:
- case AL_SEC_OFFSET_CLOCK_SOFT:
- break; /* Double only */
- case AL_STEREO_ANGLES:
- break; /* Float/double only */
- }
- return 0;
-}
-
-
-#define CHECKVAL(x) do { \
- if(!(x)) \
- { \
- alSetError(Context, AL_INVALID_VALUE, "Value out of range"); \
- return AL_FALSE; \
- } \
-} while(0)
-
-#define DO_UPDATEPROPS() do { \
- ALvoice *voice; \
- if(SourceShouldUpdate(Source, Context) && \
- (voice=GetSourceVoice(Source, Context)) != NULL) \
- UpdateSourceProps(Source, voice, device->NumAuxSends, Context); \
- else \
- ATOMIC_FLAG_CLEAR(&Source->PropsClean, almemory_order_release); \
-} while(0)
-
-static ALboolean SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALfloat *values)
-{
- ALCdevice *device = Context->Device;
- ALint ival;
-
- switch(prop)
- {
- case AL_SEC_OFFSET_LATENCY_SOFT:
- case AL_SEC_OFFSET_CLOCK_SOFT:
- /* Query only */
- SETERR_RETURN(Context, AL_INVALID_OPERATION, AL_FALSE,
- "Setting read-only source property 0x%04x", prop);
-
- case AL_PITCH:
- CHECKVAL(*values >= 0.0f);
-
- Source->Pitch = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_CONE_INNER_ANGLE:
- CHECKVAL(*values >= 0.0f && *values <= 360.0f);
-
- Source->InnerAngle = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_CONE_OUTER_ANGLE:
- CHECKVAL(*values >= 0.0f && *values <= 360.0f);
-
- Source->OuterAngle = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_GAIN:
- CHECKVAL(*values >= 0.0f);
-
- Source->Gain = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_MAX_DISTANCE:
- CHECKVAL(*values >= 0.0f);
-
- Source->MaxDistance = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_ROLLOFF_FACTOR:
- CHECKVAL(*values >= 0.0f);
-
- Source->RolloffFactor = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_REFERENCE_DISTANCE:
- CHECKVAL(*values >= 0.0f);
-
- Source->RefDistance = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_MIN_GAIN:
- CHECKVAL(*values >= 0.0f);
-
- Source->MinGain = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_MAX_GAIN:
- CHECKVAL(*values >= 0.0f);
-
- Source->MaxGain = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_CONE_OUTER_GAIN:
- CHECKVAL(*values >= 0.0f && *values <= 1.0f);
-
- Source->OuterGain = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_CONE_OUTER_GAINHF:
- CHECKVAL(*values >= 0.0f && *values <= 1.0f);
-
- Source->OuterGainHF = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_AIR_ABSORPTION_FACTOR:
- CHECKVAL(*values >= 0.0f && *values <= 10.0f);
-
- Source->AirAbsorptionFactor = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_ROOM_ROLLOFF_FACTOR:
- CHECKVAL(*values >= 0.0f && *values <= 10.0f);
-
- Source->RoomRolloffFactor = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_DOPPLER_FACTOR:
- CHECKVAL(*values >= 0.0f && *values <= 1.0f);
-
- Source->DopplerFactor = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_SEC_OFFSET:
- case AL_SAMPLE_OFFSET:
- case AL_BYTE_OFFSET:
- CHECKVAL(*values >= 0.0f);
-
- Source->OffsetType = prop;
- Source->Offset = *values;
-
- if(IsPlayingOrPaused(Source))
- {
- ALvoice *voice;
-
- ALCdevice_Lock(Context->Device);
- /* Double-check that the source is still playing while we have
- * the lock.
- */
- voice = GetSourceVoice(Source, Context);
- if(voice)
- {
- if(ApplyOffset(Source, voice) == AL_FALSE)
- {
- ALCdevice_Unlock(Context->Device);
- SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid offset");
- }
- }
- ALCdevice_Unlock(Context->Device);
- }
- return AL_TRUE;
-
- case AL_SOURCE_RADIUS:
- CHECKVAL(*values >= 0.0f && isfinite(*values));
-
- Source->Radius = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_STEREO_ANGLES:
- CHECKVAL(isfinite(values[0]) && isfinite(values[1]));
-
- Source->StereoPan[0] = values[0];
- Source->StereoPan[1] = values[1];
- DO_UPDATEPROPS();
- return AL_TRUE;
-
-
- case AL_POSITION:
- CHECKVAL(isfinite(values[0]) && isfinite(values[1]) && isfinite(values[2]));
-
- Source->Position[0] = values[0];
- Source->Position[1] = values[1];
- Source->Position[2] = values[2];
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_VELOCITY:
- CHECKVAL(isfinite(values[0]) && isfinite(values[1]) && isfinite(values[2]));
-
- Source->Velocity[0] = values[0];
- Source->Velocity[1] = values[1];
- Source->Velocity[2] = values[2];
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_DIRECTION:
- CHECKVAL(isfinite(values[0]) && isfinite(values[1]) && isfinite(values[2]));
-
- Source->Direction[0] = values[0];
- Source->Direction[1] = values[1];
- Source->Direction[2] = values[2];
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_ORIENTATION:
- CHECKVAL(isfinite(values[0]) && isfinite(values[1]) && isfinite(values[2]) &&
- isfinite(values[3]) && isfinite(values[4]) && isfinite(values[5]));
-
- Source->Orientation[0][0] = values[0];
- Source->Orientation[0][1] = values[1];
- Source->Orientation[0][2] = values[2];
- Source->Orientation[1][0] = values[3];
- Source->Orientation[1][1] = values[4];
- Source->Orientation[1][2] = values[5];
- DO_UPDATEPROPS();
- return AL_TRUE;
-
-
- case AL_SOURCE_RELATIVE:
- case AL_LOOPING:
- case AL_SOURCE_STATE:
- case AL_SOURCE_TYPE:
- case AL_DISTANCE_MODEL:
- case AL_DIRECT_FILTER_GAINHF_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
- case AL_DIRECT_CHANNELS_SOFT:
- case AL_SOURCE_RESAMPLER_SOFT:
- case AL_SOURCE_SPATIALIZE_SOFT:
- ival = (ALint)values[0];
- return SetSourceiv(Source, Context, prop, &ival);
-
- case AL_BUFFERS_QUEUED:
- case AL_BUFFERS_PROCESSED:
- ival = (ALint)((ALuint)values[0]);
- return SetSourceiv(Source, Context, prop, &ival);
-
- case AL_BUFFER:
- case AL_DIRECT_FILTER:
- case AL_AUXILIARY_SEND_FILTER:
- case AL_SAMPLE_OFFSET_LATENCY_SOFT:
- case AL_SAMPLE_OFFSET_CLOCK_SOFT:
- break;
- }
-
- ERR("Unexpected property: 0x%04x\n", prop);
- SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source float property 0x%04x", prop);
-}
-
-static ALboolean SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALint *values)
-{
- ALCdevice *device = Context->Device;
- ALbuffer *buffer = NULL;
- ALfilter *filter = NULL;
- ALeffectslot *slot = NULL;
- ALbufferlistitem *oldlist;
- ALfloat fvals[6];
-
- switch(prop)
- {
- case AL_SOURCE_STATE:
- case AL_SOURCE_TYPE:
- case AL_BUFFERS_QUEUED:
- case AL_BUFFERS_PROCESSED:
- /* Query only */
- SETERR_RETURN(Context, AL_INVALID_OPERATION, AL_FALSE,
- "Setting read-only source property 0x%04x", prop);
-
- case AL_SOURCE_RELATIVE:
- CHECKVAL(*values == AL_FALSE || *values == AL_TRUE);
-
- Source->HeadRelative = (ALboolean)*values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_LOOPING:
- CHECKVAL(*values == AL_FALSE || *values == AL_TRUE);
-
- Source->Looping = (ALboolean)*values;
- if(IsPlayingOrPaused(Source))
- {
- ALvoice *voice = GetSourceVoice(Source, Context);
- if(voice)
- {
- if(Source->Looping)
- ATOMIC_STORE(&voice->loop_buffer, Source->queue, almemory_order_release);
- else
- ATOMIC_STORE(&voice->loop_buffer, NULL, almemory_order_release);
-
- /* If the source is playing, wait for the current mix to finish
- * to ensure it isn't currently looping back or reaching the
- * end.
- */
- while((ATOMIC_LOAD(&device->MixCount, almemory_order_acquire)&1))
- althrd_yield();
- }
- }
- return AL_TRUE;
-
- case AL_BUFFER:
- LockBufferList(device);
- if(!(*values == 0 || (buffer=LookupBuffer(device, *values)) != NULL))
- {
- UnlockBufferList(device);
- SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid buffer ID %u",
- *values);
- }
-
- if(buffer && buffer->MappedAccess != 0 &&
- !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
- {
- UnlockBufferList(device);
- SETERR_RETURN(Context, AL_INVALID_OPERATION, AL_FALSE,
- "Setting non-persistently mapped buffer %u", buffer->id);
- }
- else
- {
- ALenum state = GetSourceState(Source, GetSourceVoice(Source, Context));
- if(state == AL_PLAYING || state == AL_PAUSED)
- {
- UnlockBufferList(device);
- SETERR_RETURN(Context, AL_INVALID_OPERATION, AL_FALSE,
- "Setting buffer on playing or paused source %u", Source->id);
- }
- }
-
- oldlist = Source->queue;
- if(buffer != NULL)
- {
- /* Add the selected buffer to a one-item queue */
- ALbufferlistitem *newlist = al_calloc(DEF_ALIGN,
- FAM_SIZE(ALbufferlistitem, buffers, 1));
- ATOMIC_INIT(&newlist->next, NULL);
- newlist->max_samples = buffer->SampleLen;
- newlist->num_buffers = 1;
- newlist->buffers[0] = buffer;
- IncrementRef(&buffer->ref);
-
- /* Source is now Static */
- Source->SourceType = AL_STATIC;
- Source->queue = newlist;
- }
- else
- {
- /* Source is now Undetermined */
- Source->SourceType = AL_UNDETERMINED;
- Source->queue = NULL;
- }
- UnlockBufferList(device);
-
- /* Delete all elements in the previous queue */
- while(oldlist != NULL)
- {
- ALsizei i;
- ALbufferlistitem *temp = oldlist;
- oldlist = ATOMIC_LOAD(&temp->next, almemory_order_relaxed);
-
- for(i = 0;i < temp->num_buffers;i++)
- {
- if(temp->buffers[i])
- DecrementRef(&temp->buffers[i]->ref);
- }
- al_free(temp);
- }
- return AL_TRUE;
-
- case AL_SEC_OFFSET:
- case AL_SAMPLE_OFFSET:
- case AL_BYTE_OFFSET:
- CHECKVAL(*values >= 0);
-
- Source->OffsetType = prop;
- Source->Offset = *values;
-
- if(IsPlayingOrPaused(Source))
- {
- ALvoice *voice;
-
- ALCdevice_Lock(Context->Device);
- voice = GetSourceVoice(Source, Context);
- if(voice)
- {
- if(ApplyOffset(Source, voice) == AL_FALSE)
- {
- ALCdevice_Unlock(Context->Device);
- SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE,
- "Invalid source offset");
- }
- }
- ALCdevice_Unlock(Context->Device);
- }
- return AL_TRUE;
-
- case AL_DIRECT_FILTER:
- LockFilterList(device);
- if(!(*values == 0 || (filter=LookupFilter(device, *values)) != NULL))
- {
- UnlockFilterList(device);
- SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid filter ID %u",
- *values);
- }
-
- if(!filter)
- {
- Source->Direct.Gain = 1.0f;
- Source->Direct.GainHF = 1.0f;
- Source->Direct.HFReference = LOWPASSFREQREF;
- Source->Direct.GainLF = 1.0f;
- Source->Direct.LFReference = HIGHPASSFREQREF;
- }
- else
- {
- Source->Direct.Gain = filter->Gain;
- Source->Direct.GainHF = filter->GainHF;
- Source->Direct.HFReference = filter->HFReference;
- Source->Direct.GainLF = filter->GainLF;
- Source->Direct.LFReference = filter->LFReference;
- }
- UnlockFilterList(device);
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_DIRECT_FILTER_GAINHF_AUTO:
- CHECKVAL(*values == AL_FALSE || *values == AL_TRUE);
-
- Source->DryGainHFAuto = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
- CHECKVAL(*values == AL_FALSE || *values == AL_TRUE);
-
- Source->WetGainAuto = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
- CHECKVAL(*values == AL_FALSE || *values == AL_TRUE);
-
- Source->WetGainHFAuto = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_DIRECT_CHANNELS_SOFT:
- CHECKVAL(*values == AL_FALSE || *values == AL_TRUE);
-
- Source->DirectChannels = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_DISTANCE_MODEL:
- CHECKVAL(*values == AL_NONE ||
- *values == AL_INVERSE_DISTANCE ||
- *values == AL_INVERSE_DISTANCE_CLAMPED ||
- *values == AL_LINEAR_DISTANCE ||
- *values == AL_LINEAR_DISTANCE_CLAMPED ||
- *values == AL_EXPONENT_DISTANCE ||
- *values == AL_EXPONENT_DISTANCE_CLAMPED);
-
- Source->DistanceModel = *values;
- if(Context->SourceDistanceModel)
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_SOURCE_RESAMPLER_SOFT:
- CHECKVAL(*values >= 0 && *values <= ResamplerMax);
-
- Source->Resampler = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
- case AL_SOURCE_SPATIALIZE_SOFT:
- CHECKVAL(*values >= AL_FALSE && *values <= AL_AUTO_SOFT);
-
- Source->Spatialize = *values;
- DO_UPDATEPROPS();
- return AL_TRUE;
-
-
- case AL_AUXILIARY_SEND_FILTER:
- LockEffectSlotList(Context);
- if(!(values[0] == 0 || (slot=LookupEffectSlot(Context, values[0])) != NULL))
- {
- UnlockEffectSlotList(Context);
- SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid effect ID %u",
- values[0]);
- }
- if(!((ALuint)values[1] < (ALuint)device->NumAuxSends))
- {
- UnlockEffectSlotList(Context);
- SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid send %u", values[1]);
- }
- LockFilterList(device);
- if(!(values[2] == 0 || (filter=LookupFilter(device, values[2])) != NULL))
- {
- UnlockFilterList(device);
- UnlockEffectSlotList(Context);
- SETERR_RETURN(Context, AL_INVALID_VALUE, AL_FALSE, "Invalid filter ID %u",
- values[2]);
- }
-
- if(!filter)
- {
- /* Disable filter */
- Source->Send[values[1]].Gain = 1.0f;
- Source->Send[values[1]].GainHF = 1.0f;
- Source->Send[values[1]].HFReference = LOWPASSFREQREF;
- Source->Send[values[1]].GainLF = 1.0f;
- Source->Send[values[1]].LFReference = HIGHPASSFREQREF;
- }
- else
- {
- Source->Send[values[1]].Gain = filter->Gain;
- Source->Send[values[1]].GainHF = filter->GainHF;
- Source->Send[values[1]].HFReference = filter->HFReference;
- Source->Send[values[1]].GainLF = filter->GainLF;
- Source->Send[values[1]].LFReference = filter->LFReference;
- }
- UnlockFilterList(device);
-
- if(slot != Source->Send[values[1]].Slot && IsPlayingOrPaused(Source))
- {
- ALvoice *voice;
- /* Add refcount on the new slot, and release the previous slot */
- if(slot) IncrementRef(&slot->ref);
- if(Source->Send[values[1]].Slot)
- DecrementRef(&Source->Send[values[1]].Slot->ref);
- Source->Send[values[1]].Slot = slot;
-
- /* We must force an update if the auxiliary slot changed on an
- * active source, in case the slot is about to be deleted.
- */
- if((voice=GetSourceVoice(Source, Context)) != NULL)
- UpdateSourceProps(Source, voice, device->NumAuxSends, Context);
- else
- ATOMIC_FLAG_CLEAR(&Source->PropsClean, almemory_order_release);
- }
- else
- {
- if(slot) IncrementRef(&slot->ref);
- if(Source->Send[values[1]].Slot)
- DecrementRef(&Source->Send[values[1]].Slot->ref);
- Source->Send[values[1]].Slot = slot;
- DO_UPDATEPROPS();
- }
- UnlockEffectSlotList(Context);
-
- return AL_TRUE;
-
-
- /* 1x float */
- case AL_CONE_INNER_ANGLE:
- case AL_CONE_OUTER_ANGLE:
- case AL_PITCH:
- case AL_GAIN:
- case AL_MIN_GAIN:
- case AL_MAX_GAIN:
- case AL_REFERENCE_DISTANCE:
- case AL_ROLLOFF_FACTOR:
- case AL_CONE_OUTER_GAIN:
- case AL_MAX_DISTANCE:
- case AL_DOPPLER_FACTOR:
- case AL_CONE_OUTER_GAINHF:
- case AL_AIR_ABSORPTION_FACTOR:
- case AL_ROOM_ROLLOFF_FACTOR:
- case AL_SOURCE_RADIUS:
- fvals[0] = (ALfloat)*values;
- return SetSourcefv(Source, Context, (int)prop, fvals);
-
- /* 3x float */
- case AL_POSITION:
- case AL_VELOCITY:
- case AL_DIRECTION:
- fvals[0] = (ALfloat)values[0];
- fvals[1] = (ALfloat)values[1];
- fvals[2] = (ALfloat)values[2];
- return SetSourcefv(Source, Context, (int)prop, fvals);
-
- /* 6x float */
- case AL_ORIENTATION:
- fvals[0] = (ALfloat)values[0];
- fvals[1] = (ALfloat)values[1];
- fvals[2] = (ALfloat)values[2];
- fvals[3] = (ALfloat)values[3];
- fvals[4] = (ALfloat)values[4];
- fvals[5] = (ALfloat)values[5];
- return SetSourcefv(Source, Context, (int)prop, fvals);
-
- case AL_SAMPLE_OFFSET_LATENCY_SOFT:
- case AL_SEC_OFFSET_LATENCY_SOFT:
- case AL_SEC_OFFSET_CLOCK_SOFT:
- case AL_SAMPLE_OFFSET_CLOCK_SOFT:
- case AL_STEREO_ANGLES:
- break;
- }
-
- ERR("Unexpected property: 0x%04x\n", prop);
- SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source integer property 0x%04x",
- prop);
-}
-
-static ALboolean SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const ALint64SOFT *values)
-{
- ALfloat fvals[6];
- ALint ivals[3];
-
- switch(prop)
- {
- case AL_SOURCE_TYPE:
- case AL_BUFFERS_QUEUED:
- case AL_BUFFERS_PROCESSED:
- case AL_SOURCE_STATE:
- case AL_SAMPLE_OFFSET_LATENCY_SOFT:
- case AL_SAMPLE_OFFSET_CLOCK_SOFT:
- /* Query only */
- SETERR_RETURN(Context, AL_INVALID_OPERATION, AL_FALSE,
- "Setting read-only source property 0x%04x", prop);
-
- /* 1x int */
- case AL_SOURCE_RELATIVE:
- case AL_LOOPING:
- case AL_SEC_OFFSET:
- case AL_SAMPLE_OFFSET:
- case AL_BYTE_OFFSET:
- case AL_DIRECT_FILTER_GAINHF_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
- case AL_DIRECT_CHANNELS_SOFT:
- case AL_DISTANCE_MODEL:
- case AL_SOURCE_RESAMPLER_SOFT:
- case AL_SOURCE_SPATIALIZE_SOFT:
- CHECKVAL(*values <= INT_MAX && *values >= INT_MIN);
-
- ivals[0] = (ALint)*values;
- return SetSourceiv(Source, Context, (int)prop, ivals);
-
- /* 1x uint */
- case AL_BUFFER:
- case AL_DIRECT_FILTER:
- CHECKVAL(*values <= UINT_MAX && *values >= 0);
-
- ivals[0] = (ALuint)*values;
- return SetSourceiv(Source, Context, (int)prop, ivals);
-
- /* 3x uint */
- case AL_AUXILIARY_SEND_FILTER:
- CHECKVAL(values[0] <= UINT_MAX && values[0] >= 0 &&
- values[1] <= UINT_MAX && values[1] >= 0 &&
- values[2] <= UINT_MAX && values[2] >= 0);
-
- ivals[0] = (ALuint)values[0];
- ivals[1] = (ALuint)values[1];
- ivals[2] = (ALuint)values[2];
- return SetSourceiv(Source, Context, (int)prop, ivals);
-
- /* 1x float */
- case AL_CONE_INNER_ANGLE:
- case AL_CONE_OUTER_ANGLE:
- case AL_PITCH:
- case AL_GAIN:
- case AL_MIN_GAIN:
- case AL_MAX_GAIN:
- case AL_REFERENCE_DISTANCE:
- case AL_ROLLOFF_FACTOR:
- case AL_CONE_OUTER_GAIN:
- case AL_MAX_DISTANCE:
- case AL_DOPPLER_FACTOR:
- case AL_CONE_OUTER_GAINHF:
- case AL_AIR_ABSORPTION_FACTOR:
- case AL_ROOM_ROLLOFF_FACTOR:
- case AL_SOURCE_RADIUS:
- fvals[0] = (ALfloat)*values;
- return SetSourcefv(Source, Context, (int)prop, fvals);
-
- /* 3x float */
- case AL_POSITION:
- case AL_VELOCITY:
- case AL_DIRECTION:
- fvals[0] = (ALfloat)values[0];
- fvals[1] = (ALfloat)values[1];
- fvals[2] = (ALfloat)values[2];
- return SetSourcefv(Source, Context, (int)prop, fvals);
-
- /* 6x float */
- case AL_ORIENTATION:
- fvals[0] = (ALfloat)values[0];
- fvals[1] = (ALfloat)values[1];
- fvals[2] = (ALfloat)values[2];
- fvals[3] = (ALfloat)values[3];
- fvals[4] = (ALfloat)values[4];
- fvals[5] = (ALfloat)values[5];
- return SetSourcefv(Source, Context, (int)prop, fvals);
-
- case AL_SEC_OFFSET_LATENCY_SOFT:
- case AL_SEC_OFFSET_CLOCK_SOFT:
- case AL_STEREO_ANGLES:
- break;
- }
-
- ERR("Unexpected property: 0x%04x\n", prop);
- SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source integer64 property 0x%04x",
- prop);
-}
-
-#undef CHECKVAL
-
-
-static ALboolean GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, ALdouble *values)
-{
- ALCdevice *device = Context->Device;
- ClockLatency clocktime;
- ALuint64 srcclock;
- ALint ivals[3];
- ALboolean err;
-
- switch(prop)
- {
- case AL_GAIN:
- *values = Source->Gain;
- return AL_TRUE;
-
- case AL_PITCH:
- *values = Source->Pitch;
- return AL_TRUE;
-
- case AL_MAX_DISTANCE:
- *values = Source->MaxDistance;
- return AL_TRUE;
-
- case AL_ROLLOFF_FACTOR:
- *values = Source->RolloffFactor;
- return AL_TRUE;
-
- case AL_REFERENCE_DISTANCE:
- *values = Source->RefDistance;
- return AL_TRUE;
-
- case AL_CONE_INNER_ANGLE:
- *values = Source->InnerAngle;
- return AL_TRUE;
-
- case AL_CONE_OUTER_ANGLE:
- *values = Source->OuterAngle;
- return AL_TRUE;
-
- case AL_MIN_GAIN:
- *values = Source->MinGain;
- return AL_TRUE;
-
- case AL_MAX_GAIN:
- *values = Source->MaxGain;
- return AL_TRUE;
-
- case AL_CONE_OUTER_GAIN:
- *values = Source->OuterGain;
- return AL_TRUE;
-
- case AL_SEC_OFFSET:
- case AL_SAMPLE_OFFSET:
- case AL_BYTE_OFFSET:
- *values = GetSourceOffset(Source, prop, Context);
- return AL_TRUE;
-
- case AL_CONE_OUTER_GAINHF:
- *values = Source->OuterGainHF;
- return AL_TRUE;
-
- case AL_AIR_ABSORPTION_FACTOR:
- *values = Source->AirAbsorptionFactor;
- return AL_TRUE;
-
- case AL_ROOM_ROLLOFF_FACTOR:
- *values = Source->RoomRolloffFactor;
- return AL_TRUE;
-
- case AL_DOPPLER_FACTOR:
- *values = Source->DopplerFactor;
- return AL_TRUE;
-
- case AL_SOURCE_RADIUS:
- *values = Source->Radius;
- return AL_TRUE;
-
- case AL_STEREO_ANGLES:
- values[0] = Source->StereoPan[0];
- values[1] = Source->StereoPan[1];
- return AL_TRUE;
-
- case AL_SEC_OFFSET_LATENCY_SOFT:
- /* Get the source offset with the clock time first. Then get the
- * clock time with the device latency. Order is important.
- */
- values[0] = GetSourceSecOffset(Source, Context, &srcclock);
- almtx_lock(&device->BackendLock);
- clocktime = GetClockLatency(device);
- almtx_unlock(&device->BackendLock);
- if(srcclock == (ALuint64)clocktime.ClockTime)
- values[1] = (ALdouble)clocktime.Latency / 1000000000.0;
- else
- {
- /* If the clock time incremented, reduce the latency by that
- * much since it's that much closer to the source offset it got
- * earlier.
- */
- ALuint64 diff = clocktime.ClockTime - srcclock;
- values[1] = (ALdouble)(clocktime.Latency - minu64(clocktime.Latency, diff)) /
- 1000000000.0;
- }
- return AL_TRUE;
-
- case AL_SEC_OFFSET_CLOCK_SOFT:
- values[0] = GetSourceSecOffset(Source, Context, &srcclock);
- values[1] = srcclock / 1000000000.0;
- return AL_TRUE;
-
- case AL_POSITION:
- values[0] = Source->Position[0];
- values[1] = Source->Position[1];
- values[2] = Source->Position[2];
- return AL_TRUE;
-
- case AL_VELOCITY:
- values[0] = Source->Velocity[0];
- values[1] = Source->Velocity[1];
- values[2] = Source->Velocity[2];
- return AL_TRUE;
-
- case AL_DIRECTION:
- values[0] = Source->Direction[0];
- values[1] = Source->Direction[1];
- values[2] = Source->Direction[2];
- return AL_TRUE;
-
- case AL_ORIENTATION:
- values[0] = Source->Orientation[0][0];
- values[1] = Source->Orientation[0][1];
- values[2] = Source->Orientation[0][2];
- values[3] = Source->Orientation[1][0];
- values[4] = Source->Orientation[1][1];
- values[5] = Source->Orientation[1][2];
- return AL_TRUE;
-
- /* 1x int */
- case AL_SOURCE_RELATIVE:
- case AL_LOOPING:
- case AL_SOURCE_STATE:
- case AL_BUFFERS_QUEUED:
- case AL_BUFFERS_PROCESSED:
- case AL_SOURCE_TYPE:
- case AL_DIRECT_FILTER_GAINHF_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
- case AL_DIRECT_CHANNELS_SOFT:
- case AL_DISTANCE_MODEL:
- case AL_SOURCE_RESAMPLER_SOFT:
- case AL_SOURCE_SPATIALIZE_SOFT:
- if((err=GetSourceiv(Source, Context, (int)prop, ivals)) != AL_FALSE)
- *values = (ALdouble)ivals[0];
- return err;
-
- case AL_BUFFER:
- case AL_DIRECT_FILTER:
- case AL_AUXILIARY_SEND_FILTER:
- case AL_SAMPLE_OFFSET_LATENCY_SOFT:
- case AL_SAMPLE_OFFSET_CLOCK_SOFT:
- break;
- }
-
- ERR("Unexpected property: 0x%04x\n", prop);
- SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source double property 0x%04x",
- prop);
-}
-
-static ALboolean GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, ALint *values)
-{
- ALbufferlistitem *BufferList;
- ALdouble dvals[6];
- ALboolean err;
-
- switch(prop)
- {
- case AL_SOURCE_RELATIVE:
- *values = Source->HeadRelative;
- return AL_TRUE;
-
- case AL_LOOPING:
- *values = Source->Looping;
- return AL_TRUE;
-
- case AL_BUFFER:
- BufferList = (Source->SourceType == AL_STATIC) ? Source->queue : NULL;
- *values = (BufferList && BufferList->num_buffers >= 1 && BufferList->buffers[0]) ?
- BufferList->buffers[0]->id : 0;
- return AL_TRUE;
-
- case AL_SOURCE_STATE:
- *values = GetSourceState(Source, GetSourceVoice(Source, Context));
- return AL_TRUE;
-
- case AL_BUFFERS_QUEUED:
- if(!(BufferList=Source->queue))
- *values = 0;
- else
- {
- ALsizei count = 0;
- do {
- count += BufferList->num_buffers;
- BufferList = ATOMIC_LOAD(&BufferList->next, almemory_order_relaxed);
- } while(BufferList != NULL);
- *values = count;
- }
- return AL_TRUE;
-
- case AL_BUFFERS_PROCESSED:
- if(Source->Looping || Source->SourceType != AL_STREAMING)
- {
- /* Buffers on a looping source are in a perpetual state of
- * PENDING, so don't report any as PROCESSED */
- *values = 0;
- }
- else
- {
- const ALbufferlistitem *BufferList = Source->queue;
- const ALbufferlistitem *Current = NULL;
- ALsizei played = 0;
- ALvoice *voice;
-
- if((voice=GetSourceVoice(Source, Context)) != NULL)
- Current = ATOMIC_LOAD(&voice->current_buffer, almemory_order_relaxed);
- else if(Source->state == AL_INITIAL)
- Current = BufferList;
-
- while(BufferList && BufferList != Current)
- {
- played += BufferList->num_buffers;
- BufferList = ATOMIC_LOAD(&CONST_CAST(ALbufferlistitem*,BufferList)->next,
- almemory_order_relaxed);
- }
- *values = played;
- }
- return AL_TRUE;
-
- case AL_SOURCE_TYPE:
- *values = Source->SourceType;
- return AL_TRUE;
-
- case AL_DIRECT_FILTER_GAINHF_AUTO:
- *values = Source->DryGainHFAuto;
- return AL_TRUE;
-
- case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
- *values = Source->WetGainAuto;
- return AL_TRUE;
-
- case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
- *values = Source->WetGainHFAuto;
- return AL_TRUE;
-
- case AL_DIRECT_CHANNELS_SOFT:
- *values = Source->DirectChannels;
- return AL_TRUE;
-
- case AL_DISTANCE_MODEL:
- *values = Source->DistanceModel;
- return AL_TRUE;
-
- case AL_SOURCE_RESAMPLER_SOFT:
- *values = Source->Resampler;
- return AL_TRUE;
-
- case AL_SOURCE_SPATIALIZE_SOFT:
- *values = Source->Spatialize;
- return AL_TRUE;
-
- /* 1x float/double */
- case AL_CONE_INNER_ANGLE:
- case AL_CONE_OUTER_ANGLE:
- case AL_PITCH:
- case AL_GAIN:
- case AL_MIN_GAIN:
- case AL_MAX_GAIN:
- case AL_REFERENCE_DISTANCE:
- case AL_ROLLOFF_FACTOR:
- case AL_CONE_OUTER_GAIN:
- case AL_MAX_DISTANCE:
- case AL_SEC_OFFSET:
- case AL_SAMPLE_OFFSET:
- case AL_BYTE_OFFSET:
- case AL_DOPPLER_FACTOR:
- case AL_AIR_ABSORPTION_FACTOR:
- case AL_ROOM_ROLLOFF_FACTOR:
- case AL_CONE_OUTER_GAINHF:
- case AL_SOURCE_RADIUS:
- if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE)
- *values = (ALint)dvals[0];
- return err;
-
- /* 3x float/double */
- case AL_POSITION:
- case AL_VELOCITY:
- case AL_DIRECTION:
- if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE)
- {
- values[0] = (ALint)dvals[0];
- values[1] = (ALint)dvals[1];
- values[2] = (ALint)dvals[2];
- }
- return err;
-
- /* 6x float/double */
- case AL_ORIENTATION:
- if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE)
- {
- values[0] = (ALint)dvals[0];
- values[1] = (ALint)dvals[1];
- values[2] = (ALint)dvals[2];
- values[3] = (ALint)dvals[3];
- values[4] = (ALint)dvals[4];
- values[5] = (ALint)dvals[5];
- }
- return err;
-
- case AL_SAMPLE_OFFSET_LATENCY_SOFT:
- case AL_SAMPLE_OFFSET_CLOCK_SOFT:
- break; /* i64 only */
- case AL_SEC_OFFSET_LATENCY_SOFT:
- case AL_SEC_OFFSET_CLOCK_SOFT:
- break; /* Double only */
- case AL_STEREO_ANGLES:
- break; /* Float/double only */
-
- case AL_DIRECT_FILTER:
- case AL_AUXILIARY_SEND_FILTER:
- break; /* ??? */
- }
-
- ERR("Unexpected property: 0x%04x\n", prop);
- SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source integer property 0x%04x",
- prop);
-}
-
-static ALboolean GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, ALint64 *values)
-{
- ALCdevice *device = Context->Device;
- ClockLatency clocktime;
- ALuint64 srcclock;
- ALdouble dvals[6];
- ALint ivals[3];
- ALboolean err;
-
- switch(prop)
- {
- case AL_SAMPLE_OFFSET_LATENCY_SOFT:
- /* Get the source offset with the clock time first. Then get the
- * clock time with the device latency. Order is important.
- */
- values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
- almtx_lock(&device->BackendLock);
- clocktime = GetClockLatency(device);
- almtx_unlock(&device->BackendLock);
- if(srcclock == (ALuint64)clocktime.ClockTime)
- values[1] = clocktime.Latency;
- else
- {
- /* If the clock time incremented, reduce the latency by that
- * much since it's that much closer to the source offset it got
- * earlier.
- */
- ALuint64 diff = clocktime.ClockTime - srcclock;
- values[1] = clocktime.Latency - minu64(clocktime.Latency, diff);
- }
- return AL_TRUE;
-
- case AL_SAMPLE_OFFSET_CLOCK_SOFT:
- values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
- values[1] = srcclock;
- return AL_TRUE;
-
- /* 1x float/double */
- case AL_CONE_INNER_ANGLE:
- case AL_CONE_OUTER_ANGLE:
- case AL_PITCH:
- case AL_GAIN:
- case AL_MIN_GAIN:
- case AL_MAX_GAIN:
- case AL_REFERENCE_DISTANCE:
- case AL_ROLLOFF_FACTOR:
- case AL_CONE_OUTER_GAIN:
- case AL_MAX_DISTANCE:
- case AL_SEC_OFFSET:
- case AL_SAMPLE_OFFSET:
- case AL_BYTE_OFFSET:
- case AL_DOPPLER_FACTOR:
- case AL_AIR_ABSORPTION_FACTOR:
- case AL_ROOM_ROLLOFF_FACTOR:
- case AL_CONE_OUTER_GAINHF:
- case AL_SOURCE_RADIUS:
- if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE)
- *values = (ALint64)dvals[0];
- return err;
-
- /* 3x float/double */
- case AL_POSITION:
- case AL_VELOCITY:
- case AL_DIRECTION:
- if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE)
- {
- values[0] = (ALint64)dvals[0];
- values[1] = (ALint64)dvals[1];
- values[2] = (ALint64)dvals[2];
- }
- return err;
-
- /* 6x float/double */
- case AL_ORIENTATION:
- if((err=GetSourcedv(Source, Context, prop, dvals)) != AL_FALSE)
- {
- values[0] = (ALint64)dvals[0];
- values[1] = (ALint64)dvals[1];
- values[2] = (ALint64)dvals[2];
- values[3] = (ALint64)dvals[3];
- values[4] = (ALint64)dvals[4];
- values[5] = (ALint64)dvals[5];
- }
- return err;
-
- /* 1x int */
- case AL_SOURCE_RELATIVE:
- case AL_LOOPING:
- case AL_SOURCE_STATE:
- case AL_BUFFERS_QUEUED:
- case AL_BUFFERS_PROCESSED:
- case AL_SOURCE_TYPE:
- case AL_DIRECT_FILTER_GAINHF_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
- case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
- case AL_DIRECT_CHANNELS_SOFT:
- case AL_DISTANCE_MODEL:
- case AL_SOURCE_RESAMPLER_SOFT:
- case AL_SOURCE_SPATIALIZE_SOFT:
- if((err=GetSourceiv(Source, Context, prop, ivals)) != AL_FALSE)
- *values = ivals[0];
- return err;
-
- /* 1x uint */
- case AL_BUFFER:
- case AL_DIRECT_FILTER:
- if((err=GetSourceiv(Source, Context, prop, ivals)) != AL_FALSE)
- *values = (ALuint)ivals[0];
- return err;
-
- /* 3x uint */
- case AL_AUXILIARY_SEND_FILTER:
- if((err=GetSourceiv(Source, Context, prop, ivals)) != AL_FALSE)
- {
- values[0] = (ALuint)ivals[0];
- values[1] = (ALuint)ivals[1];
- values[2] = (ALuint)ivals[2];
- }
- return err;
-
- case AL_SEC_OFFSET_LATENCY_SOFT:
- case AL_SEC_OFFSET_CLOCK_SOFT:
- break; /* Double only */
- case AL_STEREO_ANGLES:
- break; /* Float/double only */
- }
-
- ERR("Unexpected property: 0x%04x\n", prop);
- SETERR_RETURN(Context, AL_INVALID_ENUM, AL_FALSE, "Invalid source integer64 property 0x%04x",
- prop);
-}
-
-
-AL_API ALvoid AL_APIENTRY alGenSources(ALsizei n, ALuint *sources)
-{
- ALCcontext *context;
- ALsizei cur = 0;
-
- context = GetContextRef();
- if(!context) return;
-
- if(!(n >= 0))
- alSetError(context, AL_INVALID_VALUE, "Generating %d sources", n);
- else for(cur = 0;cur < n;cur++)
- {
- ALsource *source = AllocSource(context);
- if(!source)
- {
- alDeleteSources(cur, sources);
- break;
- }
- sources[cur] = source->id;
- }
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources)
-{
- ALCcontext *context;
- ALsource *Source;
- ALsizei i;
-
- context = GetContextRef();
- if(!context) return;
-
- LockSourceList(context);
- if(!(n >= 0))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Deleting %d sources", n);
-
- /* Check that all Sources are valid */
- for(i = 0;i < n;i++)
- {
- if(LookupSource(context, sources[i]) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid source ID %u", sources[i]);
- }
- for(i = 0;i < n;i++)
- {
- if((Source=LookupSource(context, sources[i])) != NULL)
- FreeSource(context, Source);
- }
-
-done:
- UnlockSourceList(context);
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALboolean AL_APIENTRY alIsSource(ALuint source)
-{
- ALCcontext *context;
- ALboolean ret;
-
- context = GetContextRef();
- if(!context) return AL_FALSE;
-
- LockSourceList(context);
- ret = (LookupSource(context, source) ? AL_TRUE : AL_FALSE);
- UnlockSourceList(context);
-
- ALCcontext_DecRef(context);
-
- return ret;
-}
-
-
-AL_API ALvoid AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(FloatValsByProp(param) == 1))
- alSetError(Context, AL_INVALID_ENUM, "Invalid float property 0x%04x", param);
- else
- SetSourcefv(Source, Context, param, &value);
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(FloatValsByProp(param) == 3))
- alSetError(Context, AL_INVALID_ENUM, "Invalid 3-float property 0x%04x", param);
- else
- {
- ALfloat fvals[3] = { value1, value2, value3 };
- SetSourcefv(Source, Context, param, fvals);
- }
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!values)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(FloatValsByProp(param) > 0))
- alSetError(Context, AL_INVALID_ENUM, "Invalid float-vector property 0x%04x", param);
- else
- SetSourcefv(Source, Context, param, values);
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(DoubleValsByProp(param) == 1))
- alSetError(Context, AL_INVALID_ENUM, "Invalid double property 0x%04x", param);
- else
- {
- ALfloat fval = (ALfloat)value;
- SetSourcefv(Source, Context, param, &fval);
- }
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(DoubleValsByProp(param) == 3))
- alSetError(Context, AL_INVALID_ENUM, "Invalid 3-double property 0x%04x", param);
- else
- {
- ALfloat fvals[3] = { (ALfloat)value1, (ALfloat)value2, (ALfloat)value3 };
- SetSourcefv(Source, Context, param, fvals);
- }
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API ALvoid AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values)
-{
- ALCcontext *Context;
- ALsource *Source;
- ALint count;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!values)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!((count=DoubleValsByProp(param)) > 0 && count <= 6))
- alSetError(Context, AL_INVALID_ENUM, "Invalid double-vector property 0x%04x", param);
- else
- {
- ALfloat fvals[6];
- ALint i;
-
- for(i = 0;i < count;i++)
- fvals[i] = (ALfloat)values[i];
- SetSourcefv(Source, Context, param, fvals);
- }
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(IntValsByProp(param) == 1))
- alSetError(Context, AL_INVALID_ENUM, "Invalid integer property 0x%04x", param);
- else
- SetSourceiv(Source, Context, param, &value);
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(IntValsByProp(param) == 3))
- alSetError(Context, AL_INVALID_ENUM, "Invalid 3-integer property 0x%04x", param);
- else
- {
- ALint ivals[3] = { value1, value2, value3 };
- SetSourceiv(Source, Context, param, ivals);
- }
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!values)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(IntValsByProp(param) > 0))
- alSetError(Context, AL_INVALID_ENUM, "Invalid integer-vector property 0x%04x", param);
- else
- SetSourceiv(Source, Context, param, values);
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(Int64ValsByProp(param) == 1))
- alSetError(Context, AL_INVALID_ENUM, "Invalid integer64 property 0x%04x", param);
- else
- SetSourcei64v(Source, Context, param, &value);
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(Int64ValsByProp(param) == 3))
- alSetError(Context, AL_INVALID_ENUM, "Invalid 3-integer64 property 0x%04x", param);
- else
- {
- ALint64SOFT i64vals[3] = { value1, value2, value3 };
- SetSourcei64v(Source, Context, param, i64vals);
- }
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- almtx_lock(&Context->PropLock);
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!values)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(Int64ValsByProp(param) > 0))
- alSetError(Context, AL_INVALID_ENUM, "Invalid integer64-vector property 0x%04x", param);
- else
- SetSourcei64v(Source, Context, param, values);
- UnlockSourceList(Context);
- almtx_unlock(&Context->PropLock);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!value)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(FloatValsByProp(param) == 1))
- alSetError(Context, AL_INVALID_ENUM, "Invalid float property 0x%04x", param);
- else
- {
- ALdouble dval;
- if(GetSourcedv(Source, Context, param, &dval))
- *value = (ALfloat)dval;
- }
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(value1 && value2 && value3))
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(FloatValsByProp(param) == 3))
- alSetError(Context, AL_INVALID_ENUM, "Invalid 3-float property 0x%04x", param);
- else
- {
- ALdouble dvals[3];
- if(GetSourcedv(Source, Context, param, dvals))
- {
- *value1 = (ALfloat)dvals[0];
- *value2 = (ALfloat)dvals[1];
- *value3 = (ALfloat)dvals[2];
- }
- }
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values)
-{
- ALCcontext *Context;
- ALsource *Source;
- ALint count;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!values)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!((count=FloatValsByProp(param)) > 0 && count <= 6))
- alSetError(Context, AL_INVALID_ENUM, "Invalid float-vector property 0x%04x", param);
- else
- {
- ALdouble dvals[6];
- if(GetSourcedv(Source, Context, param, dvals))
- {
- ALint i;
- for(i = 0;i < count;i++)
- values[i] = (ALfloat)dvals[i];
- }
- }
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!value)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(DoubleValsByProp(param) == 1))
- alSetError(Context, AL_INVALID_ENUM, "Invalid double property 0x%04x", param);
- else
- GetSourcedv(Source, Context, param, value);
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(value1 && value2 && value3))
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(DoubleValsByProp(param) == 3))
- alSetError(Context, AL_INVALID_ENUM, "Invalid 3-double property 0x%04x", param);
- else
- {
- ALdouble dvals[3];
- if(GetSourcedv(Source, Context, param, dvals))
- {
- *value1 = dvals[0];
- *value2 = dvals[1];
- *value3 = dvals[2];
- }
- }
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!values)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(DoubleValsByProp(param) > 0))
- alSetError(Context, AL_INVALID_ENUM, "Invalid double-vector property 0x%04x", param);
- else
- GetSourcedv(Source, Context, param, values);
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!value)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(IntValsByProp(param) == 1))
- alSetError(Context, AL_INVALID_ENUM, "Invalid integer property 0x%04x", param);
- else
- GetSourceiv(Source, Context, param, value);
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(value1 && value2 && value3))
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(IntValsByProp(param) == 3))
- alSetError(Context, AL_INVALID_ENUM, "Invalid 3-integer property 0x%04x", param);
- else
- {
- ALint ivals[3];
- if(GetSourceiv(Source, Context, param, ivals))
- {
- *value1 = ivals[0];
- *value2 = ivals[1];
- *value3 = ivals[2];
- }
- }
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!values)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(IntValsByProp(param) > 0))
- alSetError(Context, AL_INVALID_ENUM, "Invalid integer-vector property 0x%04x", param);
- else
- GetSourceiv(Source, Context, param, values);
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!value)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(Int64ValsByProp(param) == 1))
- alSetError(Context, AL_INVALID_ENUM, "Invalid integer64 property 0x%04x", param);
- else
- GetSourcei64v(Source, Context, param, value);
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!(value1 && value2 && value3))
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(Int64ValsByProp(param) == 3))
- alSetError(Context, AL_INVALID_ENUM, "Invalid 3-integer64 property 0x%04x", param);
- else
- {
- ALint64 i64vals[3];
- if(GetSourcei64v(Source, Context, param, i64vals))
- {
- *value1 = i64vals[0];
- *value2 = i64vals[1];
- *value3 = i64vals[2];
- }
- }
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values)
-{
- ALCcontext *Context;
- ALsource *Source;
-
- Context = GetContextRef();
- if(!Context) return;
-
- LockSourceList(Context);
- if((Source=LookupSource(Context, source)) == NULL)
- alSetError(Context, AL_INVALID_NAME, "Invalid source ID %u", source);
- else if(!values)
- alSetError(Context, AL_INVALID_VALUE, "NULL pointer");
- else if(!(Int64ValsByProp(param) > 0))
- alSetError(Context, AL_INVALID_ENUM, "Invalid integer64-vector property 0x%04x", param);
- else
- GetSourcei64v(Source, Context, param, values);
- UnlockSourceList(Context);
-
- ALCcontext_DecRef(Context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alSourcePlay(ALuint source)
-{
- alSourcePlayv(1, &source);
-}
-AL_API ALvoid AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources)
-{
- ALCcontext *context;
- ALCdevice *device;
- ALsource *source;
- ALvoice *voice;
- ALsizei i, j;
-
- context = GetContextRef();
- if(!context) return;
-
- LockSourceList(context);
- if(!(n >= 0))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Playing %d sources", n);
- for(i = 0;i < n;i++)
- {
- if(!LookupSource(context, sources[i]))
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid source ID %u", sources[i]);
- }
-
- device = context->Device;
- ALCdevice_Lock(device);
- /* If the device is disconnected, go right to stopped. */
- if(!ATOMIC_LOAD(&device->Connected, almemory_order_acquire))
- {
- /* TODO: Send state change event? */
- for(i = 0;i < n;i++)
- {
- source = LookupSource(context, sources[i]);
- source->OffsetType = AL_NONE;
- source->Offset = 0.0;
- source->state = AL_STOPPED;
- }
- ALCdevice_Unlock(device);
- goto done;
- }
-
- while(n > context->MaxVoices-context->VoiceCount)
- {
- ALsizei newcount = context->MaxVoices << 1;
- if(context->MaxVoices >= newcount)
- {
- ALCdevice_Unlock(device);
- SETERR_GOTO(context, AL_OUT_OF_MEMORY, done,
- "Overflow increasing voice count %d -> %d", context->MaxVoices, newcount);
- }
- AllocateVoices(context, newcount, device->NumAuxSends);
- }
-
- for(i = 0;i < n;i++)
- {
- ALbufferlistitem *BufferList;
- bool start_fading = false;
- ALint vidx = -1;
-
- source = LookupSource(context, sources[i]);
- /* Check that there is a queue containing at least one valid, non zero
- * length buffer.
- */
- BufferList = source->queue;
- while(BufferList && BufferList->max_samples == 0)
- BufferList = ATOMIC_LOAD(&BufferList->next, almemory_order_relaxed);
-
- /* If there's nothing to play, go right to stopped. */
- if(UNLIKELY(!BufferList))
- {
- /* NOTE: A source without any playable buffers should not have an
- * ALvoice since it shouldn't be in a playing or paused state. So
- * there's no need to look up its voice and clear the source.
- */
- ALenum oldstate = GetSourceState(source, NULL);
- source->OffsetType = AL_NONE;
- source->Offset = 0.0;
- if(oldstate != AL_STOPPED)
- {
- source->state = AL_STOPPED;
- SendStateChangeEvent(context, source->id, AL_STOPPED);
- }
- continue;
- }
-
- voice = GetSourceVoice(source, context);
- switch(GetSourceState(source, voice))
- {
- case AL_PLAYING:
- assert(voice != NULL);
- /* A source that's already playing is restarted from the beginning. */
- ATOMIC_STORE(&voice->current_buffer, BufferList, almemory_order_relaxed);
- ATOMIC_STORE(&voice->position, 0, almemory_order_relaxed);
- ATOMIC_STORE(&voice->position_fraction, 0, almemory_order_release);
- continue;
-
- case AL_PAUSED:
- assert(voice != NULL);
- /* A source that's paused simply resumes. */
- ATOMIC_STORE(&voice->Playing, true, almemory_order_release);
- source->state = AL_PLAYING;
- SendStateChangeEvent(context, source->id, AL_PLAYING);
- continue;
-
- default:
- break;
- }
-
- /* Look for an unused voice to play this source with. */
- assert(voice == NULL);
- for(j = 0;j < context->VoiceCount;j++)
- {
- if(ATOMIC_LOAD(&context->Voices[j]->Source, almemory_order_acquire) == NULL)
- {
- vidx = j;
- break;
- }
- }
- if(vidx == -1)
- vidx = context->VoiceCount++;
- voice = context->Voices[vidx];
- ATOMIC_STORE(&voice->Playing, false, almemory_order_release);
-
- ATOMIC_FLAG_TEST_AND_SET(&source->PropsClean, almemory_order_acquire);
- UpdateSourceProps(source, voice, device->NumAuxSends, context);
-
- /* A source that's not playing or paused has any offset applied when it
- * starts playing.
- */
- if(source->Looping)
- ATOMIC_STORE(&voice->loop_buffer, source->queue, almemory_order_relaxed);
- else
- ATOMIC_STORE(&voice->loop_buffer, NULL, almemory_order_relaxed);
- ATOMIC_STORE(&voice->current_buffer, BufferList, almemory_order_relaxed);
- ATOMIC_STORE(&voice->position, 0, almemory_order_relaxed);
- ATOMIC_STORE(&voice->position_fraction, 0, almemory_order_relaxed);
- if(ApplyOffset(source, voice) != AL_FALSE)
- start_fading = ATOMIC_LOAD(&voice->position, almemory_order_relaxed) != 0 ||
- ATOMIC_LOAD(&voice->position_fraction, almemory_order_relaxed) != 0 ||
- ATOMIC_LOAD(&voice->current_buffer, almemory_order_relaxed) != BufferList;
-
- for(j = 0;j < BufferList->num_buffers;j++)
- {
- ALbuffer *buffer = BufferList->buffers[j];
- if(buffer)
- {
- voice->NumChannels = ChannelsFromFmt(buffer->FmtChannels);
- voice->SampleSize = BytesFromFmt(buffer->FmtType);
- break;
- }
- }
-
- /* Clear previous samples. */
- memset(voice->PrevSamples, 0, sizeof(voice->PrevSamples));
-
- /* Clear the stepping value so the mixer knows not to mix this until
- * the update gets applied.
- */
- voice->Step = 0;
-
- voice->Flags = start_fading ? VOICE_IS_FADING : 0;
- if(source->SourceType == AL_STATIC) voice->Flags |= VOICE_IS_STATIC;
- memset(voice->Direct.Params, 0, sizeof(voice->Direct.Params[0])*voice->NumChannels);
- for(j = 0;j < device->NumAuxSends;j++)
- memset(voice->Send[j].Params, 0, sizeof(voice->Send[j].Params[0])*voice->NumChannels);
- if(device->AvgSpeakerDist > 0.0f)
- {
- ALfloat w1 = SPEEDOFSOUNDMETRESPERSEC /
- (device->AvgSpeakerDist * device->Frequency);
- for(j = 0;j < voice->NumChannels;j++)
- NfcFilterCreate(&voice->Direct.Params[j].NFCtrlFilter, 0.0f, w1);
- }
-
- ATOMIC_STORE(&voice->Source, source, almemory_order_relaxed);
- ATOMIC_STORE(&voice->Playing, true, almemory_order_release);
- source->state = AL_PLAYING;
- source->VoiceIdx = vidx;
-
- SendStateChangeEvent(context, source->id, AL_PLAYING);
- }
- ALCdevice_Unlock(device);
-
-done:
- UnlockSourceList(context);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alSourcePause(ALuint source)
-{
- alSourcePausev(1, &source);
-}
-AL_API ALvoid AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources)
-{
- ALCcontext *context;
- ALCdevice *device;
- ALsource *source;
- ALvoice *voice;
- ALsizei i;
-
- context = GetContextRef();
- if(!context) return;
-
- LockSourceList(context);
- if(!(n >= 0))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Pausing %d sources", n);
- for(i = 0;i < n;i++)
- {
- if(!LookupSource(context, sources[i]))
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid source ID %u", sources[i]);
- }
-
- device = context->Device;
- ALCdevice_Lock(device);
- for(i = 0;i < n;i++)
- {
- source = LookupSource(context, sources[i]);
- if((voice=GetSourceVoice(source, context)) != NULL)
- ATOMIC_STORE(&voice->Playing, false, almemory_order_release);
- if(GetSourceState(source, voice) == AL_PLAYING)
- {
- source->state = AL_PAUSED;
- SendStateChangeEvent(context, source->id, AL_PAUSED);
- }
- }
- ALCdevice_Unlock(device);
-
-done:
- UnlockSourceList(context);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alSourceStop(ALuint source)
-{
- alSourceStopv(1, &source);
-}
-AL_API ALvoid AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources)
-{
- ALCcontext *context;
- ALCdevice *device;
- ALsource *source;
- ALvoice *voice;
- ALsizei i;
-
- context = GetContextRef();
- if(!context) return;
-
- LockSourceList(context);
- if(!(n >= 0))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Stopping %d sources", n);
- for(i = 0;i < n;i++)
- {
- if(!LookupSource(context, sources[i]))
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid source ID %u", sources[i]);
- }
-
- device = context->Device;
- ALCdevice_Lock(device);
- for(i = 0;i < n;i++)
- {
- ALenum oldstate;
- source = LookupSource(context, sources[i]);
- if((voice=GetSourceVoice(source, context)) != NULL)
- {
- ATOMIC_STORE(&voice->Source, NULL, almemory_order_relaxed);
- ATOMIC_STORE(&voice->Playing, false, almemory_order_release);
- voice = NULL;
- }
- oldstate = GetSourceState(source, voice);
- if(oldstate != AL_INITIAL && oldstate != AL_STOPPED)
- {
- source->state = AL_STOPPED;
- SendStateChangeEvent(context, source->id, AL_STOPPED);
- }
- source->OffsetType = AL_NONE;
- source->Offset = 0.0;
- }
- ALCdevice_Unlock(device);
-
-done:
- UnlockSourceList(context);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alSourceRewind(ALuint source)
-{
- alSourceRewindv(1, &source);
-}
-AL_API ALvoid AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources)
-{
- ALCcontext *context;
- ALCdevice *device;
- ALsource *source;
- ALvoice *voice;
- ALsizei i;
-
- context = GetContextRef();
- if(!context) return;
-
- LockSourceList(context);
- if(!(n >= 0))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Rewinding %d sources", n);
- for(i = 0;i < n;i++)
- {
- if(!LookupSource(context, sources[i]))
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid source ID %u", sources[i]);
- }
-
- device = context->Device;
- ALCdevice_Lock(device);
- for(i = 0;i < n;i++)
- {
- source = LookupSource(context, sources[i]);
- if((voice=GetSourceVoice(source, context)) != NULL)
- {
- ATOMIC_STORE(&voice->Source, NULL, almemory_order_relaxed);
- ATOMIC_STORE(&voice->Playing, false, almemory_order_release);
- voice = NULL;
- }
- if(GetSourceState(source, voice) != AL_INITIAL)
- {
- source->state = AL_INITIAL;
- SendStateChangeEvent(context, source->id, AL_INITIAL);
- }
- source->OffsetType = AL_NONE;
- source->Offset = 0.0;
- }
- ALCdevice_Unlock(device);
-
-done:
- UnlockSourceList(context);
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alSourceQueueBuffers(ALuint src, ALsizei nb, const ALuint *buffers)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALsource *source;
- ALsizei i;
- ALbufferlistitem *BufferListStart;
- ALbufferlistitem *BufferList;
- ALbuffer *BufferFmt = NULL;
-
- if(nb == 0)
- return;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
-
- LockSourceList(context);
- if(!(nb >= 0))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Queueing %d buffers", nb);
- if((source=LookupSource(context, src)) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid source ID %u", src);
-
- if(source->SourceType == AL_STATIC)
- {
- /* Can't queue on a Static Source */
- SETERR_GOTO(context, AL_INVALID_OPERATION, done, "Queueing onto static source %u", src);
- }
-
- /* Check for a valid Buffer, for its frequency and format */
- BufferList = source->queue;
- while(BufferList)
- {
- for(i = 0;i < BufferList->num_buffers;i++)
- {
- if((BufferFmt=BufferList->buffers[i]) != NULL)
- break;
- }
- if(BufferFmt) break;
- BufferList = ATOMIC_LOAD(&BufferList->next, almemory_order_relaxed);
- }
-
- LockBufferList(device);
- BufferListStart = NULL;
- BufferList = NULL;
- for(i = 0;i < nb;i++)
- {
- ALbuffer *buffer = NULL;
- if(buffers[i] && (buffer=LookupBuffer(device, buffers[i])) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, buffer_error, "Queueing invalid buffer ID %u",
- buffers[i]);
-
- if(!BufferListStart)
- {
- BufferListStart = al_calloc(DEF_ALIGN,
- FAM_SIZE(ALbufferlistitem, buffers, 1));
- BufferList = BufferListStart;
- }
- else
- {
- ALbufferlistitem *item = al_calloc(DEF_ALIGN,
- FAM_SIZE(ALbufferlistitem, buffers, 1));
- ATOMIC_STORE(&BufferList->next, item, almemory_order_relaxed);
- BufferList = item;
- }
- ATOMIC_INIT(&BufferList->next, NULL);
- BufferList->max_samples = buffer ? buffer->SampleLen : 0;
- BufferList->num_buffers = 1;
- BufferList->buffers[0] = buffer;
- if(!buffer) continue;
-
- IncrementRef(&buffer->ref);
-
- if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
- SETERR_GOTO(context, AL_INVALID_OPERATION, buffer_error,
- "Queueing non-persistently mapped buffer %u", buffer->id);
-
- if(BufferFmt == NULL)
- BufferFmt = buffer;
- else if(BufferFmt->Frequency != buffer->Frequency ||
- BufferFmt->FmtChannels != buffer->FmtChannels ||
- BufferFmt->OriginalType != buffer->OriginalType)
- {
- alSetError(context, AL_INVALID_OPERATION, "Queueing buffer with mismatched format");
-
- buffer_error:
- /* A buffer failed (invalid ID or format), so unlock and release
- * each buffer we had. */
- while(BufferListStart)
- {
- ALbufferlistitem *next = ATOMIC_LOAD(&BufferListStart->next,
- almemory_order_relaxed);
- for(i = 0;i < BufferListStart->num_buffers;i++)
- {
- if((buffer=BufferListStart->buffers[i]) != NULL)
- DecrementRef(&buffer->ref);
- }
- al_free(BufferListStart);
- BufferListStart = next;
- }
- UnlockBufferList(device);
- goto done;
- }
- }
- /* All buffers good. */
- UnlockBufferList(device);
-
- /* Source is now streaming */
- source->SourceType = AL_STREAMING;
-
- if(!(BufferList=source->queue))
- source->queue = BufferListStart;
- else
- {
- ALbufferlistitem *next;
- while((next=ATOMIC_LOAD(&BufferList->next, almemory_order_relaxed)) != NULL)
- BufferList = next;
- ATOMIC_STORE(&BufferList->next, BufferListStart, almemory_order_release);
- }
-
-done:
- UnlockSourceList(context);
- ALCcontext_DecRef(context);
-}
-
-AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, const ALuint *buffers)
-{
- ALCdevice *device;
- ALCcontext *context;
- ALbufferlistitem *BufferListStart;
- ALbufferlistitem *BufferList;
- ALbuffer *BufferFmt = NULL;
- ALsource *source;
- ALsizei i;
-
- if(nb == 0)
- return;
-
- context = GetContextRef();
- if(!context) return;
-
- device = context->Device;
-
- LockSourceList(context);
- if(!(nb >= 0 && nb < 16))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Queueing %d buffer layers", nb);
- if((source=LookupSource(context, src)) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid source ID %u", src);
-
- if(source->SourceType == AL_STATIC)
- {
- /* Can't queue on a Static Source */
- SETERR_GOTO(context, AL_INVALID_OPERATION, done, "Queueing onto static source %u", src);
- }
-
- /* Check for a valid Buffer, for its frequency and format */
- BufferList = source->queue;
- while(BufferList)
- {
- for(i = 0;i < BufferList->num_buffers;i++)
- {
- if((BufferFmt=BufferList->buffers[i]) != NULL)
- break;
- }
- if(BufferFmt) break;
- BufferList = ATOMIC_LOAD(&BufferList->next, almemory_order_relaxed);
- }
-
- LockBufferList(device);
- BufferListStart = al_calloc(DEF_ALIGN, FAM_SIZE(ALbufferlistitem, buffers, nb));
- BufferList = BufferListStart;
- ATOMIC_INIT(&BufferList->next, NULL);
- BufferList->max_samples = 0;
- BufferList->num_buffers = 0;
- for(i = 0;i < nb;i++)
- {
- ALbuffer *buffer = NULL;
- if(buffers[i] && (buffer=LookupBuffer(device, buffers[i])) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, buffer_error, "Queueing invalid buffer ID %u",
- buffers[i]);
-
- BufferList->buffers[BufferList->num_buffers++] = buffer;
- if(!buffer) continue;
-
- IncrementRef(&buffer->ref);
-
- BufferList->max_samples = maxi(BufferList->max_samples, buffer->SampleLen);
-
- if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
- SETERR_GOTO(context, AL_INVALID_OPERATION, buffer_error,
- "Queueing non-persistently mapped buffer %u", buffer->id);
-
- if(BufferFmt == NULL)
- BufferFmt = buffer;
- else if(BufferFmt->Frequency != buffer->Frequency ||
- BufferFmt->FmtChannels != buffer->FmtChannels ||
- BufferFmt->OriginalType != buffer->OriginalType)
- {
- alSetError(context, AL_INVALID_OPERATION, "Queueing buffer with mismatched format");
-
- buffer_error:
- /* A buffer failed (invalid ID or format), so unlock and release
- * each buffer we had. */
- while(BufferListStart)
- {
- ALbufferlistitem *next = ATOMIC_LOAD(&BufferListStart->next,
- almemory_order_relaxed);
- for(i = 0;i < BufferListStart->num_buffers;i++)
- {
- if((buffer=BufferListStart->buffers[i]) != NULL)
- DecrementRef(&buffer->ref);
- }
- al_free(BufferListStart);
- BufferListStart = next;
- }
- UnlockBufferList(device);
- goto done;
- }
- }
- /* All buffers good. */
- UnlockBufferList(device);
-
- /* Source is now streaming */
- source->SourceType = AL_STREAMING;
-
- if(!(BufferList=source->queue))
- source->queue = BufferListStart;
- else
- {
- ALbufferlistitem *next;
- while((next=ATOMIC_LOAD(&BufferList->next, almemory_order_relaxed)) != NULL)
- BufferList = next;
- ATOMIC_STORE(&BufferList->next, BufferListStart, almemory_order_release);
- }
-
-done:
- UnlockSourceList(context);
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alSourceUnqueueBuffers(ALuint src, ALsizei nb, ALuint *buffers)
-{
- ALCcontext *context;
- ALsource *source;
- ALbufferlistitem *BufferList;
- ALbufferlistitem *Current;
- ALvoice *voice;
- ALsizei i;
-
- context = GetContextRef();
- if(!context) return;
-
- LockSourceList(context);
- if(!(nb >= 0))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Unqueueing %d buffers", nb);
- if((source=LookupSource(context, src)) == NULL)
- SETERR_GOTO(context, AL_INVALID_NAME, done, "Invalid source ID %u", src);
-
- /* Nothing to unqueue. */
- if(nb == 0) goto done;
-
- if(source->Looping)
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Unqueueing from looping source %u", src);
- if(source->SourceType != AL_STREAMING)
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Unqueueing from a non-streaming source %u",
- src);
-
- /* Make sure enough buffers have been processed to unqueue. */
- BufferList = source->queue;
- Current = NULL;
- if((voice=GetSourceVoice(source, context)) != NULL)
- Current = ATOMIC_LOAD(&voice->current_buffer, almemory_order_relaxed);
- else if(source->state == AL_INITIAL)
- Current = BufferList;
- if(BufferList == Current)
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Unqueueing pending buffers");
-
- i = BufferList->num_buffers;
- while(i < nb)
- {
- /* If the next bufferlist to check is NULL or is the current one, it's
- * trying to unqueue pending buffers.
- */
- ALbufferlistitem *next = ATOMIC_LOAD(&BufferList->next, almemory_order_relaxed);
- if(!next || next == Current)
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Unqueueing pending buffers");
- BufferList = next;
-
- i += BufferList->num_buffers;
- }
-
- while(nb > 0)
- {
- ALbufferlistitem *head = source->queue;
- ALbufferlistitem *next = ATOMIC_LOAD(&head->next, almemory_order_relaxed);
- for(i = 0;i < head->num_buffers && nb > 0;i++,nb--)
- {
- ALbuffer *buffer = head->buffers[i];
- if(!buffer)
- *(buffers++) = 0;
- else
- {
- *(buffers++) = buffer->id;
- DecrementRef(&buffer->ref);
- }
- }
- if(i < head->num_buffers)
- {
- /* This head has some buffers left over, so move them to the front
- * and update the sample and buffer count.
- */
- ALsizei max_length = 0;
- ALsizei j = 0;
- while(i < head->num_buffers)
- {
- ALbuffer *buffer = head->buffers[i++];
- if(buffer) max_length = maxi(max_length, buffer->SampleLen);
- head->buffers[j++] = buffer;
- }
- head->max_samples = max_length;
- head->num_buffers = j;
- break;
- }
-
- /* Otherwise, free this item and set the source queue head to the next
- * one.
- */
- al_free(head);
- source->queue = next;
- }
-
-done:
- UnlockSourceList(context);
- ALCcontext_DecRef(context);
-}
-
-
-static void InitSourceParams(ALsource *Source, ALsizei num_sends)
-{
- ALsizei i;
-
- Source->InnerAngle = 360.0f;
- Source->OuterAngle = 360.0f;
- Source->Pitch = 1.0f;
- Source->Position[0] = 0.0f;
- Source->Position[1] = 0.0f;
- Source->Position[2] = 0.0f;
- Source->Velocity[0] = 0.0f;
- Source->Velocity[1] = 0.0f;
- Source->Velocity[2] = 0.0f;
- Source->Direction[0] = 0.0f;
- Source->Direction[1] = 0.0f;
- Source->Direction[2] = 0.0f;
- Source->Orientation[0][0] = 0.0f;
- Source->Orientation[0][1] = 0.0f;
- Source->Orientation[0][2] = -1.0f;
- Source->Orientation[1][0] = 0.0f;
- Source->Orientation[1][1] = 1.0f;
- Source->Orientation[1][2] = 0.0f;
- Source->RefDistance = 1.0f;
- Source->MaxDistance = FLT_MAX;
- Source->RolloffFactor = 1.0f;
- Source->Gain = 1.0f;
- Source->MinGain = 0.0f;
- Source->MaxGain = 1.0f;
- Source->OuterGain = 0.0f;
- Source->OuterGainHF = 1.0f;
-
- Source->DryGainHFAuto = AL_TRUE;
- Source->WetGainAuto = AL_TRUE;
- Source->WetGainHFAuto = AL_TRUE;
- Source->AirAbsorptionFactor = 0.0f;
- Source->RoomRolloffFactor = 0.0f;
- Source->DopplerFactor = 1.0f;
- Source->HeadRelative = AL_FALSE;
- Source->Looping = AL_FALSE;
- Source->DistanceModel = DefaultDistanceModel;
- Source->Resampler = ResamplerDefault;
- Source->DirectChannels = AL_FALSE;
- Source->Spatialize = SpatializeAuto;
-
- Source->StereoPan[0] = DEG2RAD( 30.0f);
- Source->StereoPan[1] = DEG2RAD(-30.0f);
-
- Source->Radius = 0.0f;
-
- Source->Direct.Gain = 1.0f;
- Source->Direct.GainHF = 1.0f;
- Source->Direct.HFReference = LOWPASSFREQREF;
- Source->Direct.GainLF = 1.0f;
- Source->Direct.LFReference = HIGHPASSFREQREF;
- Source->Send = al_calloc(16, num_sends*sizeof(Source->Send[0]));
- for(i = 0;i < num_sends;i++)
- {
- Source->Send[i].Slot = NULL;
- Source->Send[i].Gain = 1.0f;
- Source->Send[i].GainHF = 1.0f;
- Source->Send[i].HFReference = LOWPASSFREQREF;
- Source->Send[i].GainLF = 1.0f;
- Source->Send[i].LFReference = HIGHPASSFREQREF;
- }
-
- Source->Offset = 0.0;
- Source->OffsetType = AL_NONE;
- Source->SourceType = AL_UNDETERMINED;
- Source->state = AL_INITIAL;
-
- Source->queue = NULL;
-
- /* No way to do an 'init' here, so just test+set with relaxed ordering and
- * ignore the test.
- */
- ATOMIC_FLAG_TEST_AND_SET(&Source->PropsClean, almemory_order_relaxed);
-
- Source->VoiceIdx = -1;
-}
-
-static void DeinitSource(ALsource *source, ALsizei num_sends)
-{
- ALbufferlistitem *BufferList;
- ALsizei i;
-
- BufferList = source->queue;
- while(BufferList != NULL)
- {
- ALbufferlistitem *next = ATOMIC_LOAD(&BufferList->next, almemory_order_relaxed);
- for(i = 0;i < BufferList->num_buffers;i++)
- {
- if(BufferList->buffers[i] != NULL)
- DecrementRef(&BufferList->buffers[i]->ref);
- }
- al_free(BufferList);
- BufferList = next;
- }
- source->queue = NULL;
-
- if(source->Send)
- {
- for(i = 0;i < num_sends;i++)
- {
- if(source->Send[i].Slot)
- DecrementRef(&source->Send[i].Slot->ref);
- source->Send[i].Slot = NULL;
- }
- al_free(source->Send);
- source->Send = NULL;
- }
-}
-
-static void UpdateSourceProps(ALsource *source, ALvoice *voice, ALsizei num_sends, ALCcontext *context)
-{
- struct ALvoiceProps *props;
- ALsizei i;
-
- /* Get an unused property container, or allocate a new one as needed. */
- props = ATOMIC_LOAD(&context->FreeVoiceProps, almemory_order_acquire);
- if(!props)
- props = al_calloc(16, FAM_SIZE(struct ALvoiceProps, Send, num_sends));
- else
- {
- struct ALvoiceProps *next;
- do {
- next = ATOMIC_LOAD(&props->next, almemory_order_relaxed);
- } while(ATOMIC_COMPARE_EXCHANGE_PTR_WEAK(&context->FreeVoiceProps, &props, next,
- almemory_order_acq_rel, almemory_order_acquire) == 0);
- }
-
- /* Copy in current property values. */
- props->Pitch = source->Pitch;
- props->Gain = source->Gain;
- props->OuterGain = source->OuterGain;
- props->MinGain = source->MinGain;
- props->MaxGain = source->MaxGain;
- props->InnerAngle = source->InnerAngle;
- props->OuterAngle = source->OuterAngle;
- props->RefDistance = source->RefDistance;
- props->MaxDistance = source->MaxDistance;
- props->RolloffFactor = source->RolloffFactor;
- for(i = 0;i < 3;i++)
- props->Position[i] = source->Position[i];
- for(i = 0;i < 3;i++)
- props->Velocity[i] = source->Velocity[i];
- for(i = 0;i < 3;i++)
- props->Direction[i] = source->Direction[i];
- for(i = 0;i < 2;i++)
- {
- ALsizei j;
- for(j = 0;j < 3;j++)
- props->Orientation[i][j] = source->Orientation[i][j];
- }
- props->HeadRelative = source->HeadRelative;
- props->DistanceModel = source->DistanceModel;
- props->Resampler = source->Resampler;
- props->DirectChannels = source->DirectChannels;
- props->SpatializeMode = source->Spatialize;
-
- props->DryGainHFAuto = source->DryGainHFAuto;
- props->WetGainAuto = source->WetGainAuto;
- props->WetGainHFAuto = source->WetGainHFAuto;
- props->OuterGainHF = source->OuterGainHF;
-
- props->AirAbsorptionFactor = source->AirAbsorptionFactor;
- props->RoomRolloffFactor = source->RoomRolloffFactor;
- props->DopplerFactor = source->DopplerFactor;
-
- props->StereoPan[0] = source->StereoPan[0];
- props->StereoPan[1] = source->StereoPan[1];
-
- props->Radius = source->Radius;
-
- props->Direct.Gain = source->Direct.Gain;
- props->Direct.GainHF = source->Direct.GainHF;
- props->Direct.HFReference = source->Direct.HFReference;
- props->Direct.GainLF = source->Direct.GainLF;
- props->Direct.LFReference = source->Direct.LFReference;
-
- for(i = 0;i < num_sends;i++)
- {
- props->Send[i].Slot = source->Send[i].Slot;
- props->Send[i].Gain = source->Send[i].Gain;
- props->Send[i].GainHF = source->Send[i].GainHF;
- props->Send[i].HFReference = source->Send[i].HFReference;
- props->Send[i].GainLF = source->Send[i].GainLF;
- props->Send[i].LFReference = source->Send[i].LFReference;
- }
-
- /* Set the new container for updating internal parameters. */
- props = ATOMIC_EXCHANGE_PTR(&voice->Update, props, almemory_order_acq_rel);
- if(props)
- {
- /* If there was an unused update container, put it back in the
- * freelist.
- */
- ATOMIC_REPLACE_HEAD(struct ALvoiceProps*, &context->FreeVoiceProps, props);
- }
-}
-
-void UpdateAllSourceProps(ALCcontext *context)
-{
- ALsizei num_sends = context->Device->NumAuxSends;
- ALsizei pos;
-
- for(pos = 0;pos < context->VoiceCount;pos++)
- {
- ALvoice *voice = context->Voices[pos];
- ALsource *source = ATOMIC_LOAD(&voice->Source, almemory_order_acquire);
- if(source && !ATOMIC_FLAG_TEST_AND_SET(&source->PropsClean, almemory_order_acq_rel))
- UpdateSourceProps(source, voice, num_sends, context);
- }
-}
-
-
-/* GetSourceSampleOffset
- *
- * Gets the current read offset for the given Source, in 32.32 fixed-point
- * samples. The offset is relative to the start of the queue (not the start of
- * the current buffer).
- */
-static ALint64 GetSourceSampleOffset(ALsource *Source, ALCcontext *context, ALuint64 *clocktime)
-{
- ALCdevice *device = context->Device;
- const ALbufferlistitem *Current;
- ALuint64 readPos;
- ALuint refcount;
- ALvoice *voice;
-
- do {
- Current = NULL;
- readPos = 0;
- while(((refcount=ATOMIC_LOAD(&device->MixCount, almemory_order_acquire))&1))
- althrd_yield();
- *clocktime = GetDeviceClockTime(device);
-
- voice = GetSourceVoice(Source, context);
- if(voice)
- {
- Current = ATOMIC_LOAD(&voice->current_buffer, almemory_order_relaxed);
-
- readPos = (ALuint64)ATOMIC_LOAD(&voice->position, almemory_order_relaxed) << 32;
- readPos |= (ALuint64)ATOMIC_LOAD(&voice->position_fraction, almemory_order_relaxed) <<
- (32-FRACTIONBITS);
- }
- ATOMIC_THREAD_FENCE(almemory_order_acquire);
- } while(refcount != ATOMIC_LOAD(&device->MixCount, almemory_order_relaxed));
-
- if(voice)
- {
- const ALbufferlistitem *BufferList = Source->queue;
- while(BufferList && BufferList != Current)
- {
- readPos += (ALuint64)BufferList->max_samples << 32;
- BufferList = ATOMIC_LOAD(&CONST_CAST(ALbufferlistitem*,BufferList)->next,
- almemory_order_relaxed);
- }
- readPos = minu64(readPos, U64(0x7fffffffffffffff));
- }
-
- return (ALint64)readPos;
-}
-
-/* GetSourceSecOffset
- *
- * Gets the current read offset for the given Source, in seconds. The offset is
- * relative to the start of the queue (not the start of the current buffer).
- */
-static ALdouble GetSourceSecOffset(ALsource *Source, ALCcontext *context, ALuint64 *clocktime)
-{
- ALCdevice *device = context->Device;
- const ALbufferlistitem *Current;
- ALuint64 readPos;
- ALuint refcount;
- ALdouble offset;
- ALvoice *voice;
-
- do {
- Current = NULL;
- readPos = 0;
- while(((refcount=ATOMIC_LOAD(&device->MixCount, almemory_order_acquire))&1))
- althrd_yield();
- *clocktime = GetDeviceClockTime(device);
-
- voice = GetSourceVoice(Source, context);
- if(voice)
- {
- Current = ATOMIC_LOAD(&voice->current_buffer, almemory_order_relaxed);
-
- readPos = (ALuint64)ATOMIC_LOAD(&voice->position, almemory_order_relaxed) <<
- FRACTIONBITS;
- readPos |= ATOMIC_LOAD(&voice->position_fraction, almemory_order_relaxed);
- }
- ATOMIC_THREAD_FENCE(almemory_order_acquire);
- } while(refcount != ATOMIC_LOAD(&device->MixCount, almemory_order_relaxed));
-
- offset = 0.0;
- if(voice)
- {
- const ALbufferlistitem *BufferList = Source->queue;
- const ALbuffer *BufferFmt = NULL;
- while(BufferList && BufferList != Current)
- {
- ALsizei i = 0;
- while(!BufferFmt && i < BufferList->num_buffers)
- BufferFmt = BufferList->buffers[i++];
- readPos += (ALuint64)BufferList->max_samples << FRACTIONBITS;
- BufferList = ATOMIC_LOAD(&CONST_CAST(ALbufferlistitem*,BufferList)->next,
- almemory_order_relaxed);
- }
-
- while(BufferList && !BufferFmt)
- {
- ALsizei i = 0;
- while(!BufferFmt && i < BufferList->num_buffers)
- BufferFmt = BufferList->buffers[i++];
- BufferList = ATOMIC_LOAD(&CONST_CAST(ALbufferlistitem*,BufferList)->next,
- almemory_order_relaxed);
- }
- assert(BufferFmt != NULL);
-
- offset = (ALdouble)readPos / (ALdouble)FRACTIONONE /
- (ALdouble)BufferFmt->Frequency;
- }
-
- return offset;
-}
-
-/* GetSourceOffset
- *
- * Gets the current read offset for the given Source, in the appropriate format
- * (Bytes, Samples or Seconds). The offset is relative to the start of the
- * queue (not the start of the current buffer).
- */
-static ALdouble GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context)
-{
- ALCdevice *device = context->Device;
- const ALbufferlistitem *Current;
- ALuint readPos;
- ALsizei readPosFrac;
- ALuint refcount;
- ALdouble offset;
- ALvoice *voice;
-
- do {
- Current = NULL;
- readPos = readPosFrac = 0;
- while(((refcount=ATOMIC_LOAD(&device->MixCount, almemory_order_acquire))&1))
- althrd_yield();
- voice = GetSourceVoice(Source, context);
- if(voice)
- {
- Current = ATOMIC_LOAD(&voice->current_buffer, almemory_order_relaxed);
-
- readPos = ATOMIC_LOAD(&voice->position, almemory_order_relaxed);
- readPosFrac = ATOMIC_LOAD(&voice->position_fraction, almemory_order_relaxed);
- }
- ATOMIC_THREAD_FENCE(almemory_order_acquire);
- } while(refcount != ATOMIC_LOAD(&device->MixCount, almemory_order_relaxed));
-
- offset = 0.0;
- if(voice)
- {
- const ALbufferlistitem *BufferList = Source->queue;
- const ALbuffer *BufferFmt = NULL;
- ALboolean readFin = AL_FALSE;
- ALuint totalBufferLen = 0;
-
- while(BufferList != NULL)
- {
- ALsizei i = 0;
- while(!BufferFmt && i < BufferList->num_buffers)
- BufferFmt = BufferList->buffers[i++];
-
- readFin |= (BufferList == Current);
- totalBufferLen += BufferList->max_samples;
- if(!readFin) readPos += BufferList->max_samples;
-
- BufferList = ATOMIC_LOAD(&CONST_CAST(ALbufferlistitem*,BufferList)->next,
- almemory_order_relaxed);
- }
- assert(BufferFmt != NULL);
-
- if(Source->Looping)
- readPos %= totalBufferLen;
- else
- {
- /* Wrap back to 0 */
- if(readPos >= totalBufferLen)
- readPos = readPosFrac = 0;
- }
-
- offset = 0.0;
- switch(name)
- {
- case AL_SEC_OFFSET:
- offset = (readPos + (ALdouble)readPosFrac/FRACTIONONE) / BufferFmt->Frequency;
- break;
-
- case AL_SAMPLE_OFFSET:
- offset = readPos + (ALdouble)readPosFrac/FRACTIONONE;
- break;
-
- case AL_BYTE_OFFSET:
- if(BufferFmt->OriginalType == UserFmtIMA4)
- {
- ALsizei align = (BufferFmt->OriginalAlign-1)/2 + 4;
- ALuint BlockSize = align * ChannelsFromFmt(BufferFmt->FmtChannels);
- ALuint FrameBlockSize = BufferFmt->OriginalAlign;
-
- /* Round down to nearest ADPCM block */
- offset = (ALdouble)(readPos / FrameBlockSize * BlockSize);
- }
- else if(BufferFmt->OriginalType == UserFmtMSADPCM)
- {
- ALsizei align = (BufferFmt->OriginalAlign-2)/2 + 7;
- ALuint BlockSize = align * ChannelsFromFmt(BufferFmt->FmtChannels);
- ALuint FrameBlockSize = BufferFmt->OriginalAlign;
-
- /* Round down to nearest ADPCM block */
- offset = (ALdouble)(readPos / FrameBlockSize * BlockSize);
- }
- else
- {
- ALuint FrameSize = FrameSizeFromFmt(BufferFmt->FmtChannels,
- BufferFmt->FmtType);
- offset = (ALdouble)(readPos * FrameSize);
- }
- break;
- }
- }
-
- return offset;
-}
-
-
-/* ApplyOffset
- *
- * Apply the stored playback offset to the Source. This function will update
- * the number of buffers "played" given the stored offset.
- */
-static ALboolean ApplyOffset(ALsource *Source, ALvoice *voice)
-{
- ALbufferlistitem *BufferList;
- ALuint totalBufferLen;
- ALuint offset = 0;
- ALsizei frac = 0;
-
- /* Get sample frame offset */
- if(!GetSampleOffset(Source, &offset, &frac))
- return AL_FALSE;
-
- totalBufferLen = 0;
- BufferList = Source->queue;
- while(BufferList && totalBufferLen <= offset)
- {
- if((ALuint)BufferList->max_samples > offset-totalBufferLen)
- {
- /* Offset is in this buffer */
- ATOMIC_STORE(&voice->position, offset - totalBufferLen, almemory_order_relaxed);
- ATOMIC_STORE(&voice->position_fraction, frac, almemory_order_relaxed);
- ATOMIC_STORE(&voice->current_buffer, BufferList, almemory_order_release);
- return AL_TRUE;
- }
- totalBufferLen += BufferList->max_samples;
-
- BufferList = ATOMIC_LOAD(&BufferList->next, almemory_order_relaxed);
- }
-
- /* Offset is out of range of the queue */
- return AL_FALSE;
-}
-
-
-/* GetSampleOffset
- *
- * Retrieves the sample offset into the Source's queue (from the Sample, Byte
- * or Second offset supplied by the application). This takes into account the
- * fact that the buffer format may have been modifed since.
- */
-static ALboolean GetSampleOffset(ALsource *Source, ALuint *offset, ALsizei *frac)
-{
- const ALbuffer *BufferFmt = NULL;
- const ALbufferlistitem *BufferList;
- ALdouble dbloff, dblfrac;
-
- /* Find the first valid Buffer in the Queue */
- BufferList = Source->queue;
- while(BufferList)
- {
- ALsizei i;
- for(i = 0;i < BufferList->num_buffers && !BufferFmt;i++)
- BufferFmt = BufferList->buffers[i];
- if(BufferFmt) break;
- BufferList = ATOMIC_LOAD(&CONST_CAST(ALbufferlistitem*,BufferList)->next,
- almemory_order_relaxed);
- }
- if(!BufferFmt)
- {
- Source->OffsetType = AL_NONE;
- Source->Offset = 0.0;
- return AL_FALSE;
- }
-
- switch(Source->OffsetType)
- {
- case AL_BYTE_OFFSET:
- /* Determine the ByteOffset (and ensure it is block aligned) */
- *offset = (ALuint)Source->Offset;
- if(BufferFmt->OriginalType == UserFmtIMA4)
- {
- ALsizei align = (BufferFmt->OriginalAlign-1)/2 + 4;
- *offset /= align * ChannelsFromFmt(BufferFmt->FmtChannels);
- *offset *= BufferFmt->OriginalAlign;
- }
- else if(BufferFmt->OriginalType == UserFmtMSADPCM)
- {
- ALsizei align = (BufferFmt->OriginalAlign-2)/2 + 7;
- *offset /= align * ChannelsFromFmt(BufferFmt->FmtChannels);
- *offset *= BufferFmt->OriginalAlign;
- }
- else
- *offset /= FrameSizeFromFmt(BufferFmt->FmtChannels, BufferFmt->FmtType);
- *frac = 0;
- break;
-
- case AL_SAMPLE_OFFSET:
- dblfrac = modf(Source->Offset, &dbloff);
- *offset = (ALuint)mind(dbloff, UINT_MAX);
- *frac = (ALsizei)mind(dblfrac*FRACTIONONE, FRACTIONONE-1.0);
- break;
-
- case AL_SEC_OFFSET:
- dblfrac = modf(Source->Offset*BufferFmt->Frequency, &dbloff);
- *offset = (ALuint)mind(dbloff, UINT_MAX);
- *frac = (ALsizei)mind(dblfrac*FRACTIONONE, FRACTIONONE-1.0);
- break;
- }
- Source->OffsetType = AL_NONE;
- Source->Offset = 0.0;
-
- return AL_TRUE;
-}
-
-
-static ALsource *AllocSource(ALCcontext *context)
-{
- ALCdevice *device = context->Device;
- SourceSubList *sublist, *subend;
- ALsource *source = NULL;
- ALsizei lidx = 0;
- ALsizei slidx;
-
- almtx_lock(&context->SourceLock);
- if(context->NumSources >= device->SourcesMax)
- {
- almtx_unlock(&context->SourceLock);
- alSetError(context, AL_OUT_OF_MEMORY, "Exceeding %u source limit", device->SourcesMax);
- return NULL;
- }
- sublist = VECTOR_BEGIN(context->SourceList);
- subend = VECTOR_END(context->SourceList);
- for(;sublist != subend;++sublist)
- {
- if(sublist->FreeMask)
- {
- slidx = CTZ64(sublist->FreeMask);
- source = sublist->Sources + slidx;
- break;
- }
- ++lidx;
- }
- if(UNLIKELY(!source))
- {
- const SourceSubList empty_sublist = { 0, NULL };
- /* Don't allocate so many list entries that the 32-bit ID could
- * overflow...
- */
- if(UNLIKELY(VECTOR_SIZE(context->SourceList) >= 1<<25))
- {
- almtx_unlock(&device->BufferLock);
- alSetError(context, AL_OUT_OF_MEMORY, "Too many sources allocated");
- return NULL;
- }
- lidx = (ALsizei)VECTOR_SIZE(context->SourceList);
- VECTOR_PUSH_BACK(context->SourceList, empty_sublist);
- sublist = &VECTOR_BACK(context->SourceList);
- sublist->FreeMask = ~U64(0);
- sublist->Sources = al_calloc(16, sizeof(ALsource)*64);
- if(UNLIKELY(!sublist->Sources))
- {
- VECTOR_POP_BACK(context->SourceList);
- almtx_unlock(&context->SourceLock);
- alSetError(context, AL_OUT_OF_MEMORY, "Failed to allocate source batch");
- return NULL;
- }
-
- slidx = 0;
- source = sublist->Sources + slidx;
- }
-
- memset(source, 0, sizeof(*source));
- InitSourceParams(source, device->NumAuxSends);
-
- /* Add 1 to avoid source ID 0. */
- source->id = ((lidx<<6) | slidx) + 1;
-
- context->NumSources++;
- sublist->FreeMask &= ~(U64(1)<<slidx);
- almtx_unlock(&context->SourceLock);
-
- return source;
-}
-
-static void FreeSource(ALCcontext *context, ALsource *source)
-{
- ALCdevice *device = context->Device;
- ALuint id = source->id - 1;
- ALsizei lidx = id >> 6;
- ALsizei slidx = id & 0x3f;
- ALvoice *voice;
-
- ALCdevice_Lock(device);
- if((voice=GetSourceVoice(source, context)) != NULL)
- {
- ATOMIC_STORE(&voice->Source, NULL, almemory_order_relaxed);
- ATOMIC_STORE(&voice->Playing, false, almemory_order_release);
- }
- ALCdevice_Unlock(device);
-
- DeinitSource(source, device->NumAuxSends);
- memset(source, 0, sizeof(*source));
-
- VECTOR_ELEM(context->SourceList, lidx).FreeMask |= U64(1) << slidx;
- context->NumSources--;
-}
-
-/* ReleaseALSources
- *
- * Destroys all sources in the source map.
- */
-ALvoid ReleaseALSources(ALCcontext *context)
-{
- ALCdevice *device = context->Device;
- SourceSubList *sublist = VECTOR_BEGIN(context->SourceList);
- SourceSubList *subend = VECTOR_END(context->SourceList);
- size_t leftover = 0;
- for(;sublist != subend;++sublist)
- {
- ALuint64 usemask = ~sublist->FreeMask;
- while(usemask)
- {
- ALsizei idx = CTZ64(usemask);
- ALsource *source = sublist->Sources + idx;
-
- DeinitSource(source, device->NumAuxSends);
- memset(source, 0, sizeof(*source));
- ++leftover;
-
- usemask &= ~(U64(1) << idx);
- }
- sublist->FreeMask = ~usemask;
- }
- if(leftover > 0)
- WARN("(%p) Deleted "SZFMT" Source%s\n", device, leftover, (leftover==1)?"":"s");
-}
diff --git a/OpenAL32/alState.c b/OpenAL32/alState.c
deleted file mode 100644
index ce93e143..00000000
--- a/OpenAL32/alState.c
+++ /dev/null
@@ -1,900 +0,0 @@
-/**
- * OpenAL cross platform audio library
- * Copyright (C) 1999-2000 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 <stdlib.h>
-#include "alMain.h"
-#include "AL/alc.h"
-#include "AL/al.h"
-#include "AL/alext.h"
-#include "alError.h"
-#include "alListener.h"
-#include "alSource.h"
-#include "alAuxEffectSlot.h"
-
-#include "backends/base.h"
-
-
-static const ALchar alVendor[] = "OpenAL Community";
-static const ALchar alVersion[] = "1.1 ALSOFT "ALSOFT_VERSION;
-static const ALchar alRenderer[] = "OpenAL Soft";
-
-// Error Messages
-static const ALchar alNoError[] = "No Error";
-static const ALchar alErrInvalidName[] = "Invalid Name";
-static const ALchar alErrInvalidEnum[] = "Invalid Enum";
-static const ALchar alErrInvalidValue[] = "Invalid Value";
-static const ALchar alErrInvalidOp[] = "Invalid Operation";
-static const ALchar alErrOutOfMemory[] = "Out of Memory";
-
-/* Resampler strings */
-static const ALchar alPointResampler[] = "Nearest";
-static const ALchar alLinearResampler[] = "Linear";
-static const ALchar alCubicResampler[] = "Cubic";
-static const ALchar alBSinc12Resampler[] = "11th order Sinc";
-static const ALchar alBSinc24Resampler[] = "23rd order Sinc";
-
-/* WARNING: Non-standard export! Not part of any extension, or exposed in the
- * alcFunctions list.
- */
-AL_API const ALchar* AL_APIENTRY alsoft_get_version(void)
-{
- const char *spoof = getenv("ALSOFT_SPOOF_VERSION");
- if(spoof && spoof[0] != '\0') return spoof;
- return ALSOFT_VERSION;
-}
-
-#define DO_UPDATEPROPS() do { \
- if(!ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire)) \
- UpdateContextProps(context); \
- else \
- ATOMIC_FLAG_CLEAR(&context->PropsClean, almemory_order_release); \
-} while(0)
-
-
-AL_API ALvoid AL_APIENTRY alEnable(ALenum capability)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- switch(capability)
- {
- case AL_SOURCE_DISTANCE_MODEL:
- context->SourceDistanceModel = AL_TRUE;
- DO_UPDATEPROPS();
- break;
-
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability);
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alDisable(ALenum capability)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- switch(capability)
- {
- case AL_SOURCE_DISTANCE_MODEL:
- context->SourceDistanceModel = AL_FALSE;
- DO_UPDATEPROPS();
- break;
-
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability);
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability)
-{
- ALCcontext *context;
- ALboolean value=AL_FALSE;
-
- context = GetContextRef();
- if(!context) return AL_FALSE;
-
- almtx_lock(&context->PropLock);
- switch(capability)
- {
- case AL_SOURCE_DISTANCE_MODEL:
- value = context->SourceDistanceModel;
- break;
-
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability);
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
- return value;
-}
-
-AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum pname)
-{
- ALCcontext *context;
- ALboolean value=AL_FALSE;
-
- context = GetContextRef();
- if(!context) return AL_FALSE;
-
- almtx_lock(&context->PropLock);
- switch(pname)
- {
- case AL_DOPPLER_FACTOR:
- if(context->DopplerFactor != 0.0f)
- value = AL_TRUE;
- break;
-
- case AL_DOPPLER_VELOCITY:
- if(context->DopplerVelocity != 0.0f)
- value = AL_TRUE;
- break;
-
- case AL_DISTANCE_MODEL:
- if(context->DistanceModel == AL_INVERSE_DISTANCE_CLAMPED)
- value = AL_TRUE;
- break;
-
- case AL_SPEED_OF_SOUND:
- if(context->SpeedOfSound != 0.0f)
- value = AL_TRUE;
- break;
-
- case AL_DEFERRED_UPDATES_SOFT:
- if(ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
- value = AL_TRUE;
- break;
-
- case AL_GAIN_LIMIT_SOFT:
- if(GAIN_MIX_MAX/context->GainBoost != 0.0f)
- value = AL_TRUE;
- break;
-
- case AL_NUM_RESAMPLERS_SOFT:
- /* Always non-0. */
- value = AL_TRUE;
- break;
-
- case AL_DEFAULT_RESAMPLER_SOFT:
- value = ResamplerDefault ? AL_TRUE : AL_FALSE;
- break;
-
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid boolean property 0x%04x", pname);
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
- return value;
-}
-
-AL_API ALdouble AL_APIENTRY alGetDouble(ALenum pname)
-{
- ALCcontext *context;
- ALdouble value = 0.0;
-
- context = GetContextRef();
- if(!context) return 0.0;
-
- almtx_lock(&context->PropLock);
- switch(pname)
- {
- case AL_DOPPLER_FACTOR:
- value = (ALdouble)context->DopplerFactor;
- break;
-
- case AL_DOPPLER_VELOCITY:
- value = (ALdouble)context->DopplerVelocity;
- break;
-
- case AL_DISTANCE_MODEL:
- value = (ALdouble)context->DistanceModel;
- break;
-
- case AL_SPEED_OF_SOUND:
- value = (ALdouble)context->SpeedOfSound;
- break;
-
- case AL_DEFERRED_UPDATES_SOFT:
- if(ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
- value = (ALdouble)AL_TRUE;
- break;
-
- case AL_GAIN_LIMIT_SOFT:
- value = (ALdouble)GAIN_MIX_MAX/context->GainBoost;
- break;
-
- case AL_NUM_RESAMPLERS_SOFT:
- value = (ALdouble)(ResamplerMax + 1);
- break;
-
- case AL_DEFAULT_RESAMPLER_SOFT:
- value = (ALdouble)ResamplerDefault;
- break;
-
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid double property 0x%04x", pname);
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
- return value;
-}
-
-AL_API ALfloat AL_APIENTRY alGetFloat(ALenum pname)
-{
- ALCcontext *context;
- ALfloat value = 0.0f;
-
- context = GetContextRef();
- if(!context) return 0.0f;
-
- almtx_lock(&context->PropLock);
- switch(pname)
- {
- case AL_DOPPLER_FACTOR:
- value = context->DopplerFactor;
- break;
-
- case AL_DOPPLER_VELOCITY:
- value = context->DopplerVelocity;
- break;
-
- case AL_DISTANCE_MODEL:
- value = (ALfloat)context->DistanceModel;
- break;
-
- case AL_SPEED_OF_SOUND:
- value = context->SpeedOfSound;
- break;
-
- case AL_DEFERRED_UPDATES_SOFT:
- if(ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
- value = (ALfloat)AL_TRUE;
- break;
-
- case AL_GAIN_LIMIT_SOFT:
- value = GAIN_MIX_MAX/context->GainBoost;
- break;
-
- case AL_NUM_RESAMPLERS_SOFT:
- value = (ALfloat)(ResamplerMax + 1);
- break;
-
- case AL_DEFAULT_RESAMPLER_SOFT:
- value = (ALfloat)ResamplerDefault;
- break;
-
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid float property 0x%04x", pname);
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
- return value;
-}
-
-AL_API ALint AL_APIENTRY alGetInteger(ALenum pname)
-{
- ALCcontext *context;
- ALint value = 0;
-
- context = GetContextRef();
- if(!context) return 0;
-
- almtx_lock(&context->PropLock);
- switch(pname)
- {
- case AL_DOPPLER_FACTOR:
- value = (ALint)context->DopplerFactor;
- break;
-
- case AL_DOPPLER_VELOCITY:
- value = (ALint)context->DopplerVelocity;
- break;
-
- case AL_DISTANCE_MODEL:
- value = (ALint)context->DistanceModel;
- break;
-
- case AL_SPEED_OF_SOUND:
- value = (ALint)context->SpeedOfSound;
- break;
-
- case AL_DEFERRED_UPDATES_SOFT:
- if(ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
- value = (ALint)AL_TRUE;
- break;
-
- case AL_GAIN_LIMIT_SOFT:
- value = (ALint)(GAIN_MIX_MAX/context->GainBoost);
- break;
-
- case AL_NUM_RESAMPLERS_SOFT:
- value = ResamplerMax + 1;
- break;
-
- case AL_DEFAULT_RESAMPLER_SOFT:
- value = ResamplerDefault;
- break;
-
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname);
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
- return value;
-}
-
-AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname)
-{
- ALCcontext *context;
- ALint64SOFT value = 0;
-
- context = GetContextRef();
- if(!context) return 0;
-
- almtx_lock(&context->PropLock);
- switch(pname)
- {
- case AL_DOPPLER_FACTOR:
- value = (ALint64SOFT)context->DopplerFactor;
- break;
-
- case AL_DOPPLER_VELOCITY:
- value = (ALint64SOFT)context->DopplerVelocity;
- break;
-
- case AL_DISTANCE_MODEL:
- value = (ALint64SOFT)context->DistanceModel;
- break;
-
- case AL_SPEED_OF_SOUND:
- value = (ALint64SOFT)context->SpeedOfSound;
- break;
-
- case AL_DEFERRED_UPDATES_SOFT:
- if(ATOMIC_LOAD(&context->DeferUpdates, almemory_order_acquire))
- value = (ALint64SOFT)AL_TRUE;
- break;
-
- case AL_GAIN_LIMIT_SOFT:
- value = (ALint64SOFT)(GAIN_MIX_MAX/context->GainBoost);
- break;
-
- case AL_NUM_RESAMPLERS_SOFT:
- value = (ALint64SOFT)(ResamplerMax + 1);
- break;
-
- case AL_DEFAULT_RESAMPLER_SOFT:
- value = (ALint64SOFT)ResamplerDefault;
- break;
-
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid integer64 property 0x%04x", pname);
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
- return value;
-}
-
-AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname)
-{
- ALCcontext *context;
- void *value = NULL;
-
- context = GetContextRef();
- if(!context) return NULL;
-
- almtx_lock(&context->PropLock);
- switch(pname)
- {
- case AL_EVENT_CALLBACK_FUNCTION_SOFT:
- value = context->EventCb;
- break;
-
- case AL_EVENT_CALLBACK_USER_PARAM_SOFT:
- value = context->EventParam;
- break;
-
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid pointer property 0x%04x", pname);
- }
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
- return value;
-}
-
-AL_API ALvoid AL_APIENTRY alGetBooleanv(ALenum pname, ALboolean *values)
-{
- ALCcontext *context;
-
- if(values)
- {
- switch(pname)
- {
- case AL_DOPPLER_FACTOR:
- case AL_DOPPLER_VELOCITY:
- case AL_DISTANCE_MODEL:
- case AL_SPEED_OF_SOUND:
- case AL_DEFERRED_UPDATES_SOFT:
- case AL_GAIN_LIMIT_SOFT:
- case AL_NUM_RESAMPLERS_SOFT:
- case AL_DEFAULT_RESAMPLER_SOFT:
- values[0] = alGetBoolean(pname);
- return;
- }
- }
-
- context = GetContextRef();
- if(!context) return;
-
- if(!values)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- switch(pname)
- {
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid boolean-vector property 0x%04x", pname);
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetDoublev(ALenum pname, ALdouble *values)
-{
- ALCcontext *context;
-
- if(values)
- {
- switch(pname)
- {
- case AL_DOPPLER_FACTOR:
- case AL_DOPPLER_VELOCITY:
- case AL_DISTANCE_MODEL:
- case AL_SPEED_OF_SOUND:
- case AL_DEFERRED_UPDATES_SOFT:
- case AL_GAIN_LIMIT_SOFT:
- case AL_NUM_RESAMPLERS_SOFT:
- case AL_DEFAULT_RESAMPLER_SOFT:
- values[0] = alGetDouble(pname);
- return;
- }
- }
-
- context = GetContextRef();
- if(!context) return;
-
- if(!values)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- switch(pname)
- {
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid double-vector property 0x%04x", pname);
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetFloatv(ALenum pname, ALfloat *values)
-{
- ALCcontext *context;
-
- if(values)
- {
- switch(pname)
- {
- case AL_DOPPLER_FACTOR:
- case AL_DOPPLER_VELOCITY:
- case AL_DISTANCE_MODEL:
- case AL_SPEED_OF_SOUND:
- case AL_DEFERRED_UPDATES_SOFT:
- case AL_GAIN_LIMIT_SOFT:
- case AL_NUM_RESAMPLERS_SOFT:
- case AL_DEFAULT_RESAMPLER_SOFT:
- values[0] = alGetFloat(pname);
- return;
- }
- }
-
- context = GetContextRef();
- if(!context) return;
-
- if(!values)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- switch(pname)
- {
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid float-vector property 0x%04x", pname);
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alGetIntegerv(ALenum pname, ALint *values)
-{
- ALCcontext *context;
-
- if(values)
- {
- switch(pname)
- {
- case AL_DOPPLER_FACTOR:
- case AL_DOPPLER_VELOCITY:
- case AL_DISTANCE_MODEL:
- case AL_SPEED_OF_SOUND:
- case AL_DEFERRED_UPDATES_SOFT:
- case AL_GAIN_LIMIT_SOFT:
- case AL_NUM_RESAMPLERS_SOFT:
- case AL_DEFAULT_RESAMPLER_SOFT:
- values[0] = alGetInteger(pname);
- return;
- }
- }
-
- context = GetContextRef();
- if(!context) return;
-
- if(!values)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- switch(pname)
- {
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid integer-vector property 0x%04x", pname);
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values)
-{
- ALCcontext *context;
-
- if(values)
- {
- switch(pname)
- {
- case AL_DOPPLER_FACTOR:
- case AL_DOPPLER_VELOCITY:
- case AL_DISTANCE_MODEL:
- case AL_SPEED_OF_SOUND:
- case AL_DEFERRED_UPDATES_SOFT:
- case AL_GAIN_LIMIT_SOFT:
- case AL_NUM_RESAMPLERS_SOFT:
- case AL_DEFAULT_RESAMPLER_SOFT:
- values[0] = alGetInteger64SOFT(pname);
- return;
- }
- }
-
- context = GetContextRef();
- if(!context) return;
-
- if(!values)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- switch(pname)
- {
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid integer64-vector property 0x%04x", pname);
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values)
-{
- ALCcontext *context;
-
- if(values)
- {
- switch(pname)
- {
- case AL_EVENT_CALLBACK_FUNCTION_SOFT:
- case AL_EVENT_CALLBACK_USER_PARAM_SOFT:
- values[0] = alGetPointerSOFT(pname);
- return;
- }
- }
-
- context = GetContextRef();
- if(!context) return;
-
- if(!values)
- alSetError(context, AL_INVALID_VALUE, "NULL pointer");
- switch(pname)
- {
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid pointer-vector property 0x%04x", pname);
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname)
-{
- const ALchar *value = NULL;
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return NULL;
-
- switch(pname)
- {
- case AL_VENDOR:
- value = alVendor;
- break;
-
- case AL_VERSION:
- value = alVersion;
- break;
-
- case AL_RENDERER:
- value = alRenderer;
- break;
-
- case AL_EXTENSIONS:
- value = context->ExtensionList;
- break;
-
- case AL_NO_ERROR:
- value = alNoError;
- break;
-
- case AL_INVALID_NAME:
- value = alErrInvalidName;
- break;
-
- case AL_INVALID_ENUM:
- value = alErrInvalidEnum;
- break;
-
- case AL_INVALID_VALUE:
- value = alErrInvalidValue;
- break;
-
- case AL_INVALID_OPERATION:
- value = alErrInvalidOp;
- break;
-
- case AL_OUT_OF_MEMORY:
- value = alErrOutOfMemory;
- break;
-
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid string property 0x%04x", pname);
- }
-
- ALCcontext_DecRef(context);
- return value;
-}
-
-AL_API ALvoid AL_APIENTRY alDopplerFactor(ALfloat value)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- if(!(value >= 0.0f && isfinite(value)))
- alSetError(context, AL_INVALID_VALUE, "Doppler factor %f out of range", value);
- else
- {
- almtx_lock(&context->PropLock);
- context->DopplerFactor = value;
- DO_UPDATEPROPS();
- almtx_unlock(&context->PropLock);
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alDopplerVelocity(ALfloat value)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- if((ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed)&EventType_Deprecated))
- {
- static const ALCchar msg[] =
- "alDopplerVelocity is deprecated in AL1.1, use alSpeedOfSound";
- const ALsizei msglen = (ALsizei)strlen(msg);
- ALbitfieldSOFT enabledevts;
- almtx_lock(&context->EventCbLock);
- enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed);
- if((enabledevts&EventType_Deprecated) && context->EventCb)
- (*context->EventCb)(AL_EVENT_TYPE_DEPRECATED_SOFT, 0, 0, msglen, msg,
- context->EventParam);
- almtx_unlock(&context->EventCbLock);
- }
-
- if(!(value >= 0.0f && isfinite(value)))
- alSetError(context, AL_INVALID_VALUE, "Doppler velocity %f out of range", value);
- else
- {
- almtx_lock(&context->PropLock);
- context->DopplerVelocity = value;
- DO_UPDATEPROPS();
- almtx_unlock(&context->PropLock);
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alSpeedOfSound(ALfloat value)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- if(!(value > 0.0f && isfinite(value)))
- alSetError(context, AL_INVALID_VALUE, "Speed of sound %f out of range", value);
- else
- {
- almtx_lock(&context->PropLock);
- context->SpeedOfSound = value;
- DO_UPDATEPROPS();
- almtx_unlock(&context->PropLock);
- }
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alDistanceModel(ALenum value)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- if(!(value == AL_INVERSE_DISTANCE || value == AL_INVERSE_DISTANCE_CLAMPED ||
- value == AL_LINEAR_DISTANCE || value == AL_LINEAR_DISTANCE_CLAMPED ||
- value == AL_EXPONENT_DISTANCE || value == AL_EXPONENT_DISTANCE_CLAMPED ||
- value == AL_NONE))
- alSetError(context, AL_INVALID_VALUE, "Distance model 0x%04x out of range", value);
- else
- {
- almtx_lock(&context->PropLock);
- context->DistanceModel = value;
- if(!context->SourceDistanceModel)
- DO_UPDATEPROPS();
- almtx_unlock(&context->PropLock);
- }
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API ALvoid AL_APIENTRY alDeferUpdatesSOFT(void)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- ALCcontext_DeferUpdates(context);
-
- ALCcontext_DecRef(context);
-}
-
-AL_API ALvoid AL_APIENTRY alProcessUpdatesSOFT(void)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- ALCcontext_ProcessUpdates(context);
-
- ALCcontext_DecRef(context);
-}
-
-
-AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index)
-{
- const char *ResamplerNames[] = {
- alPointResampler, alLinearResampler,
- alCubicResampler, alBSinc12Resampler,
- alBSinc24Resampler,
- };
- const ALchar *value = NULL;
- ALCcontext *context;
-
- static_assert(COUNTOF(ResamplerNames) == ResamplerMax+1, "Incorrect ResamplerNames list");
-
- context = GetContextRef();
- if(!context) return NULL;
-
- switch(pname)
- {
- case AL_RESAMPLER_NAME_SOFT:
- if(index < 0 || (size_t)index >= COUNTOF(ResamplerNames))
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "Resampler name index %d out of range",
- index);
- value = ResamplerNames[index];
- break;
-
- default:
- alSetError(context, AL_INVALID_VALUE, "Invalid string indexed property");
- }
-
-done:
- ALCcontext_DecRef(context);
- return value;
-}
-
-
-void UpdateContextProps(ALCcontext *context)
-{
- struct ALcontextProps *props;
-
- /* Get an unused proprty container, or allocate a new one as needed. */
- props = ATOMIC_LOAD(&context->FreeContextProps, almemory_order_acquire);
- if(!props)
- props = al_calloc(16, sizeof(*props));
- else
- {
- struct ALcontextProps *next;
- do {
- next = ATOMIC_LOAD(&props->next, almemory_order_relaxed);
- } while(ATOMIC_COMPARE_EXCHANGE_PTR_WEAK(&context->FreeContextProps, &props, next,
- almemory_order_seq_cst, almemory_order_acquire) == 0);
- }
-
- /* Copy in current property values. */
- props->MetersPerUnit = context->MetersPerUnit;
-
- props->DopplerFactor = context->DopplerFactor;
- props->DopplerVelocity = context->DopplerVelocity;
- props->SpeedOfSound = context->SpeedOfSound;
-
- props->SourceDistanceModel = context->SourceDistanceModel;
- props->DistanceModel = context->DistanceModel;
-
- /* Set the new container for updating internal parameters. */
- props = ATOMIC_EXCHANGE_PTR(&context->Update, props, almemory_order_acq_rel);
- if(props)
- {
- /* If there was an unused update container, put it back in the
- * freelist.
- */
- ATOMIC_REPLACE_HEAD(struct ALcontextProps*, &context->FreeContextProps, props);
- }
-}
diff --git a/OpenAL32/event.c b/OpenAL32/event.c
deleted file mode 100644
index 4c9c0be2..00000000
--- a/OpenAL32/event.c
+++ /dev/null
@@ -1,127 +0,0 @@
-
-#include "config.h"
-
-#include "AL/alc.h"
-#include "AL/al.h"
-#include "AL/alext.h"
-#include "alMain.h"
-#include "alError.h"
-#include "alAuxEffectSlot.h"
-#include "ringbuffer.h"
-
-
-int EventThread(void *arg)
-{
- ALCcontext *context = arg;
- bool quitnow = false;
-
- while(!quitnow)
- {
- ALbitfieldSOFT enabledevts;
- AsyncEvent evt;
-
- if(ll_ringbuffer_read(context->AsyncEvents, (char*)&evt, 1) == 0)
- {
- alsem_wait(&context->EventSem);
- continue;
- }
-
- almtx_lock(&context->EventCbLock);
- do {
- quitnow = evt.EnumType == EventType_KillThread;
- if(quitnow) break;
-
- if(evt.EnumType == EventType_ReleaseEffectState)
- {
- ALeffectState_DecRef(evt.u.EffectState);
- continue;
- }
-
- enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_acquire);
- if(context->EventCb && (enabledevts&evt.EnumType) == evt.EnumType)
- context->EventCb(evt.u.user.type, evt.u.user.id, evt.u.user.param,
- (ALsizei)strlen(evt.u.user.msg), evt.u.user.msg, context->EventParam
- );
- } while(ll_ringbuffer_read(context->AsyncEvents, (char*)&evt, 1) != 0);
- almtx_unlock(&context->EventCbLock);
- }
- return 0;
-}
-
-AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable)
-{
- ALCcontext *context;
- ALbitfieldSOFT enabledevts;
- ALbitfieldSOFT flags = 0;
- ALsizei i;
-
- context = GetContextRef();
- if(!context) return;
-
- if(count < 0) SETERR_GOTO(context, AL_INVALID_VALUE, done, "Controlling %d events", count);
- if(count == 0) goto done;
- if(!types) SETERR_GOTO(context, AL_INVALID_VALUE, done, "NULL pointer");
-
- for(i = 0;i < count;i++)
- {
- if(types[i] == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT)
- flags |= EventType_BufferCompleted;
- else if(types[i] == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT)
- flags |= EventType_SourceStateChange;
- else if(types[i] == AL_EVENT_TYPE_ERROR_SOFT)
- flags |= EventType_Error;
- else if(types[i] == AL_EVENT_TYPE_PERFORMANCE_SOFT)
- flags |= EventType_Performance;
- else if(types[i] == AL_EVENT_TYPE_DEPRECATED_SOFT)
- flags |= EventType_Deprecated;
- else if(types[i] == AL_EVENT_TYPE_DISCONNECTED_SOFT)
- flags |= EventType_Disconnected;
- else
- SETERR_GOTO(context, AL_INVALID_ENUM, done, "Invalid event type 0x%04x", types[i]);
- }
-
- if(enable)
- {
- enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed);
- while(ATOMIC_COMPARE_EXCHANGE_WEAK(&context->EnabledEvts, &enabledevts, enabledevts|flags,
- almemory_order_acq_rel, almemory_order_acquire) == 0)
- {
- /* enabledevts is (re-)filled with the current value on failure, so
- * just try again.
- */
- }
- }
- else
- {
- enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed);
- while(ATOMIC_COMPARE_EXCHANGE_WEAK(&context->EnabledEvts, &enabledevts, enabledevts&~flags,
- almemory_order_acq_rel, almemory_order_acquire) == 0)
- {
- }
- /* Wait to ensure the event handler sees the changed flags before
- * returning.
- */
- almtx_lock(&context->EventCbLock);
- almtx_unlock(&context->EventCbLock);
- }
-
-done:
- ALCcontext_DecRef(context);
-}
-
-AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam)
-{
- ALCcontext *context;
-
- context = GetContextRef();
- if(!context) return;
-
- almtx_lock(&context->PropLock);
- almtx_lock(&context->EventCbLock);
- context->EventCb = callback;
- context->EventParam = userParam;
- almtx_unlock(&context->EventCbLock);
- almtx_unlock(&context->PropLock);
-
- ALCcontext_DecRef(context);
-}
diff --git a/OpenAL32/sample_cvt.c b/OpenAL32/sample_cvt.c
deleted file mode 100644
index 4a85f74a..00000000
--- a/OpenAL32/sample_cvt.c
+++ /dev/null
@@ -1,276 +0,0 @@
-
-#include "config.h"
-
-#include "sample_cvt.h"
-
-#include "AL/al.h"
-#include "alu.h"
-#include "alBuffer.h"
-
-
-/* IMA ADPCM Stepsize table */
-static const int IMAStep_size[89] = {
- 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19,
- 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55,
- 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157,
- 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449,
- 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282,
- 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660,
- 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442,
- 11487,12635,13899,15289,16818,18500,20350,22358,24633,27086,29794,
- 32767
-};
-
-/* IMA4 ADPCM Codeword decode table */
-static const int IMA4Codeword[16] = {
- 1, 3, 5, 7, 9, 11, 13, 15,
- -1,-3,-5,-7,-9,-11,-13,-15,
-};
-
-/* IMA4 ADPCM Step index adjust decode table */
-static const int IMA4Index_adjust[16] = {
- -1,-1,-1,-1, 2, 4, 6, 8,
- -1,-1,-1,-1, 2, 4, 6, 8
-};
-
-
-/* MSADPCM Adaption table */
-static const int MSADPCMAdaption[16] = {
- 230, 230, 230, 230, 307, 409, 512, 614,
- 768, 614, 512, 409, 307, 230, 230, 230
-};
-
-/* MSADPCM Adaption Coefficient tables */
-static const int MSADPCMAdaptionCoeff[7][2] = {
- { 256, 0 },
- { 512, -256 },
- { 0, 0 },
- { 192, 64 },
- { 240, 0 },
- { 460, -208 },
- { 392, -232 }
-};
-
-
-/* A quick'n'dirty lookup table to decode a muLaw-encoded byte sample into a
- * signed 16-bit sample */
-const ALshort muLawDecompressionTable[256] = {
- -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
- -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
- -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
- -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
- -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
- -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
- -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
- -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
- -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
- -1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
- -876, -844, -812, -780, -748, -716, -684, -652,
- -620, -588, -556, -524, -492, -460, -428, -396,
- -372, -356, -340, -324, -308, -292, -276, -260,
- -244, -228, -212, -196, -180, -164, -148, -132,
- -120, -112, -104, -96, -88, -80, -72, -64,
- -56, -48, -40, -32, -24, -16, -8, 0,
- 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
- 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
- 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
- 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
- 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
- 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
- 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
- 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
- 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
- 1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
- 876, 844, 812, 780, 748, 716, 684, 652,
- 620, 588, 556, 524, 492, 460, 428, 396,
- 372, 356, 340, 324, 308, 292, 276, 260,
- 244, 228, 212, 196, 180, 164, 148, 132,
- 120, 112, 104, 96, 88, 80, 72, 64,
- 56, 48, 40, 32, 24, 16, 8, 0
-};
-
-
-/* A quick'n'dirty lookup table to decode an aLaw-encoded byte sample into a
- * signed 16-bit sample */
-const ALshort aLawDecompressionTable[256] = {
- -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
- -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
- -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
- -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
- -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
- -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
- -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472,
- -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
- -344, -328, -376, -360, -280, -264, -312, -296,
- -472, -456, -504, -488, -408, -392, -440, -424,
- -88, -72, -120, -104, -24, -8, -56, -40,
- -216, -200, -248, -232, -152, -136, -184, -168,
- -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
- -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
- -688, -656, -752, -720, -560, -528, -624, -592,
- -944, -912, -1008, -976, -816, -784, -880, -848,
- 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
- 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
- 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
- 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
- 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
- 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
- 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
- 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
- 344, 328, 376, 360, 280, 264, 312, 296,
- 472, 456, 504, 488, 408, 392, 440, 424,
- 88, 72, 120, 104, 24, 8, 56, 40,
- 216, 200, 248, 232, 152, 136, 184, 168,
- 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
- 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
- 688, 656, 752, 720, 560, 528, 624, 592,
- 944, 912, 1008, 976, 816, 784, 880, 848
-};
-
-
-static void DecodeIMA4Block(ALshort *dst, const ALubyte *src, ALint numchans, ALsizei align)
-{
- ALint sample[MAX_INPUT_CHANNELS] = { 0 };
- ALint index[MAX_INPUT_CHANNELS] = { 0 };
- ALuint code[MAX_INPUT_CHANNELS] = { 0 };
- ALsizei c, i;
-
- for(c = 0;c < numchans;c++)
- {
- sample[c] = *(src++);
- sample[c] |= *(src++) << 8;
- sample[c] = (sample[c]^0x8000) - 32768;
- index[c] = *(src++);
- index[c] |= *(src++) << 8;
- index[c] = (index[c]^0x8000) - 32768;
-
- index[c] = clampi(index[c], 0, 88);
-
- dst[c] = sample[c];
- }
-
- for(i = 1;i < align;i++)
- {
- if((i&7) == 1)
- {
- for(c = 0;c < numchans;c++)
- {
- code[c] = *(src++);
- code[c] |= *(src++) << 8;
- code[c] |= *(src++) << 16;
- code[c] |= *(src++) << 24;
- }
- }
-
- for(c = 0;c < numchans;c++)
- {
- int nibble = code[c]&0xf;
- code[c] >>= 4;
-
- sample[c] += IMA4Codeword[nibble] * IMAStep_size[index[c]] / 8;
- sample[c] = clampi(sample[c], -32768, 32767);
-
- index[c] += IMA4Index_adjust[nibble];
- index[c] = clampi(index[c], 0, 88);
-
- *(dst++) = sample[c];
- }
- }
-}
-
-static void DecodeMSADPCMBlock(ALshort *dst, const ALubyte *src, ALint numchans, ALsizei align)
-{
- ALubyte blockpred[MAX_INPUT_CHANNELS] = { 0 };
- ALint delta[MAX_INPUT_CHANNELS] = { 0 };
- ALshort samples[MAX_INPUT_CHANNELS][2] = { { 0, 0 } };
- ALint c, i;
-
- for(c = 0;c < numchans;c++)
- {
- blockpred[c] = *(src++);
- blockpred[c] = minu(blockpred[c], 6);
- }
- for(c = 0;c < numchans;c++)
- {
- delta[c] = *(src++);
- delta[c] |= *(src++) << 8;
- delta[c] = (delta[c]^0x8000) - 32768;
- }
- for(c = 0;c < numchans;c++)
- {
- samples[c][0] = *(src++);
- samples[c][0] |= *(src++) << 8;
- samples[c][0] = (samples[c][0]^0x8000) - 32768;
- }
- for(c = 0;c < numchans;c++)
- {
- samples[c][1] = *(src++);
- samples[c][1] |= *(src++) << 8;
- samples[c][1] = (samples[c][1]^0x8000) - 0x8000;
- }
-
- /* Second sample is written first. */
- for(c = 0;c < numchans;c++)
- *(dst++) = samples[c][1];
- for(c = 0;c < numchans;c++)
- *(dst++) = samples[c][0];
-
- for(i = 2;i < align;i++)
- {
- for(c = 0;c < numchans;c++)
- {
- const ALint num = (i*numchans) + c;
- ALint nibble, pred;
-
- /* Read the nibble (first is in the upper bits). */
- if(!(num&1))
- nibble = (*src>>4)&0x0f;
- else
- nibble = (*(src++))&0x0f;
-
- pred = (samples[c][0]*MSADPCMAdaptionCoeff[blockpred[c]][0] +
- samples[c][1]*MSADPCMAdaptionCoeff[blockpred[c]][1]) / 256;
- pred += ((nibble^0x08) - 0x08) * delta[c];
- pred = clampi(pred, -32768, 32767);
-
- samples[c][1] = samples[c][0];
- samples[c][0] = pred;
-
- delta[c] = (MSADPCMAdaption[nibble] * delta[c]) / 256;
- delta[c] = maxi(16, delta[c]);
-
- *(dst++) = pred;
- }
- }
-}
-
-
-void Convert_ALshort_ALima4(ALshort *dst, const ALubyte *src, ALsizei numchans, ALsizei len,
- ALsizei align)
-{
- ALsizei byte_align = ((align-1)/2 + 4) * numchans;
- ALsizei i;
-
- assert(align > 0 && (len%align) == 0);
- for(i = 0;i < len;i += align)
- {
- DecodeIMA4Block(dst, src, numchans, align);
- src += byte_align;
- dst += align*numchans;
- }
-}
-
-void Convert_ALshort_ALmsadpcm(ALshort *dst, const ALubyte *src, ALsizei numchans, ALsizei len,
- ALsizei align)
-{
- ALsizei byte_align = ((align-2)/2 + 7) * numchans;
- ALsizei i;
-
- assert(align > 1 && (len%align) == 0);
- for(i = 0;i < len;i += align)
- {
- DecodeMSADPCMBlock(dst, src, numchans, align);
- src += byte_align;
- dst += align*numchans;
- }
-}
diff --git a/al/auxeffectslot.cpp b/al/auxeffectslot.cpp
new file mode 100644
index 00000000..3c312c66
--- /dev/null
+++ b/al/auxeffectslot.cpp
@@ -0,0 +1,792 @@
+/**
+ * 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 "auxeffectslot.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <numeric>
+#include <thread>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/efx.h"
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "alu.h"
+#include "effect.h"
+#include "fpu_modes.h"
+#include "inprogext.h"
+#include "logging.h"
+#include "opthelpers.h"
+
+
+namespace {
+
+inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept
+{
+ const size_t lidx{(id-1) >> 6};
+ const ALuint slidx{(id-1) & 0x3f};
+
+ if UNLIKELY(lidx >= context->mEffectSlotList.size())
+ return nullptr;
+ EffectSlotSubList &sublist{context->mEffectSlotList[lidx]};
+ if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
+ return nullptr;
+ return sublist.EffectSlots + slidx;
+}
+
+inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) noexcept
+{
+ const size_t lidx{(id-1) >> 6};
+ const ALuint slidx{(id-1) & 0x3f};
+
+ if UNLIKELY(lidx >= device->EffectList.size())
+ return nullptr;
+ EffectSubList &sublist = device->EffectList[lidx];
+ if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
+ return nullptr;
+ return sublist.Effects + slidx;
+}
+
+
+void AddActiveEffectSlots(const ALuint *slotids, size_t count, ALCcontext *context)
+{
+ if(count < 1) return;
+ ALeffectslotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)};
+ size_t newcount{curarray->size() + count};
+
+ /* Insert the new effect slots into the head of the array, followed by the
+ * existing ones.
+ */
+ ALeffectslotArray *newarray = ALeffectslot::CreatePtrArray(newcount);
+ auto slotiter = std::transform(slotids, slotids+count, newarray->begin(),
+ [context](ALuint id) noexcept -> ALeffectslot*
+ { return LookupEffectSlot(context, id); }
+ );
+ std::copy(curarray->begin(), curarray->end(), slotiter);
+
+ /* Remove any duplicates (first instance of each will be kept). */
+ auto last = newarray->end();
+ for(auto start=newarray->begin()+1;;)
+ {
+ last = std::remove(start, last, *(start-1));
+ if(start == last) break;
+ ++start;
+ }
+ newcount = static_cast<size_t>(std::distance(newarray->begin(), last));
+
+ /* Reallocate newarray if the new size ended up smaller from duplicate
+ * removal.
+ */
+ if UNLIKELY(newcount < newarray->size())
+ {
+ curarray = newarray;
+ newarray = ALeffectslot::CreatePtrArray(newcount);
+ std::copy_n(curarray->begin(), newcount, newarray->begin());
+ delete curarray;
+ curarray = nullptr;
+ }
+
+ curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel);
+ ALCdevice *device{context->mDevice.get()};
+ while((device->MixCount.load(std::memory_order_acquire)&1))
+ std::this_thread::yield();
+ delete curarray;
+}
+
+void RemoveActiveEffectSlots(const ALuint *slotids, size_t count, ALCcontext *context)
+{
+ if(count < 1) return;
+ ALeffectslotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)};
+
+ /* Don't shrink the allocated array size since we don't know how many (if
+ * any) of the effect slots to remove are in the array.
+ */
+ ALeffectslotArray *newarray = ALeffectslot::CreatePtrArray(curarray->size());
+
+ /* Copy each element in curarray to newarray whose ID is not in slotids. */
+ const ALuint *slotids_end{slotids + count};
+ auto slotiter = std::copy_if(curarray->begin(), curarray->end(), newarray->begin(),
+ [slotids, slotids_end](const ALeffectslot *slot) -> bool
+ { return std::find(slotids, slotids_end, slot->id) == slotids_end; }
+ );
+
+ /* Reallocate with the new size. */
+ auto newsize = static_cast<size_t>(std::distance(newarray->begin(), slotiter));
+ if LIKELY(newsize != newarray->size())
+ {
+ curarray = newarray;
+ newarray = ALeffectslot::CreatePtrArray(newsize);
+ std::copy_n(curarray->begin(), newsize, newarray->begin());
+
+ delete curarray;
+ curarray = nullptr;
+ }
+
+ curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel);
+ ALCdevice *device{context->mDevice.get()};
+ while((device->MixCount.load(std::memory_order_acquire)&1))
+ std::this_thread::yield();
+ delete curarray;
+}
+
+
+bool EnsureEffectSlots(ALCcontext *context, size_t needed)
+{
+ size_t count{std::accumulate(context->mEffectSlotList.cbegin(),
+ context->mEffectSlotList.cend(), size_t{0},
+ [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
+ { return cur + static_cast<ALuint>(POPCNT64(sublist.FreeMask)); }
+ )};
+
+ while(needed > count)
+ {
+ if UNLIKELY(context->mEffectSlotList.size() >= 1<<25)
+ return false;
+
+ context->mEffectSlotList.emplace_back();
+ auto sublist = context->mEffectSlotList.end() - 1;
+ sublist->FreeMask = ~0_u64;
+ sublist->EffectSlots = static_cast<ALeffectslot*>(
+ al_calloc(alignof(ALeffectslot), sizeof(ALeffectslot)*64));
+ if UNLIKELY(!sublist->EffectSlots)
+ {
+ context->mEffectSlotList.pop_back();
+ return false;
+ }
+ count += 64;
+ }
+ return true;
+}
+
+ALeffectslot *AllocEffectSlot(ALCcontext *context)
+{
+ auto sublist = std::find_if(context->mEffectSlotList.begin(), context->mEffectSlotList.end(),
+ [](const EffectSlotSubList &entry) noexcept -> bool
+ { return entry.FreeMask != 0; }
+ );
+ auto lidx = static_cast<ALuint>(std::distance(context->mEffectSlotList.begin(), sublist));
+ auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
+
+ ALeffectslot *slot{::new (sublist->EffectSlots + slidx) ALeffectslot{}};
+ if(ALenum err{InitEffectSlot(slot)})
+ {
+ al::destroy_at(slot);
+ context->setError(err, "Effect slot object initialization failed");
+ return nullptr;
+ }
+ aluInitEffectPanning(slot, context->mDevice.get());
+
+ /* Add 1 to avoid source ID 0. */
+ slot->id = ((lidx<<6) | slidx) + 1;
+
+ context->mNumEffectSlots += 1;
+ sublist->FreeMask &= ~(1_u64 << slidx);
+
+ return slot;
+}
+
+void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot)
+{
+ const ALuint id{slot->id - 1};
+ const size_t lidx{id >> 6};
+ const ALuint slidx{id & 0x3f};
+
+ al::destroy_at(slot);
+
+ context->mEffectSlotList[lidx].FreeMask |= 1_u64 << slidx;
+ context->mNumEffectSlots--;
+}
+
+
+#define DO_UPDATEPROPS() do { \
+ if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
+ UpdateEffectSlotProps(slot, context.get()); \
+ else \
+ slot->PropsClean.clear(std::memory_order_release); \
+} while(0)
+
+} // namespace
+
+ALeffectslotArray *ALeffectslot::CreatePtrArray(size_t count) noexcept
+{
+ /* Allocate space for twice as many pointers, so the mixer has scratch
+ * space to store a sorted list during mixing.
+ */
+ void *ptr{al_calloc(alignof(ALeffectslotArray), ALeffectslotArray::Sizeof(count*2))};
+ return new (ptr) ALeffectslotArray{count};
+}
+
+
+AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Generating %d effect slots", n);
+ if UNLIKELY(n <= 0) return;
+
+ std::unique_lock<std::mutex> slotlock{context->mEffectSlotLock};
+ ALCdevice *device{context->mDevice.get()};
+ if(static_cast<ALuint>(n) > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots)
+ {
+ context->setError(AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)",
+ device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n);
+ return;
+ }
+ if(!EnsureEffectSlots(context.get(), static_cast<ALuint>(n)))
+ {
+ context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n,
+ (n==1) ? "" : "s");
+ return;
+ }
+
+ if(n == 1)
+ {
+ ALeffectslot *slot{AllocEffectSlot(context.get())};
+ if(!slot) return;
+ effectslots[0] = slot->id;
+ }
+ else
+ {
+ al::vector<ALuint> ids;
+ ALsizei count{n};
+ ids.reserve(static_cast<ALuint>(count));
+ do {
+ ALeffectslot *slot{AllocEffectSlot(context.get())};
+ if(!slot)
+ {
+ slotlock.unlock();
+ alDeleteAuxiliaryEffectSlots(static_cast<ALsizei>(ids.size()), ids.data());
+ return;
+ }
+ ids.emplace_back(slot->id);
+ } while(--count);
+ std::copy(ids.cbegin(), ids.cend(), effectslots);
+ }
+
+ AddActiveEffectSlots(effectslots, static_cast<ALuint>(n), context.get());
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Deleting %d effect slots", n);
+ if UNLIKELY(n <= 0) return;
+
+ std::lock_guard<std::mutex> _{context->mEffectSlotLock};
+ auto validate_slot = [&context](const ALuint id) -> bool
+ {
+ ALeffectslot *slot{LookupEffectSlot(context.get(), id)};
+ if UNLIKELY(!slot)
+ {
+ context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", id);
+ return false;
+ }
+ if UNLIKELY(ReadRef(slot->ref) != 0)
+ {
+ context->setError(AL_INVALID_OPERATION, "Deleting in-use effect slot %u", id);
+ return false;
+ }
+ return true;
+ };
+ auto effectslots_end = effectslots + n;
+ auto bad_slot = std::find_if_not(effectslots, effectslots_end, validate_slot);
+ if UNLIKELY(bad_slot != effectslots_end) return;
+
+ // All effectslots are valid, remove and delete them
+ RemoveActiveEffectSlots(effectslots, static_cast<ALuint>(n), context.get());
+ auto delete_slot = [&context](const ALuint sid) -> void
+ {
+ ALeffectslot *slot{LookupEffectSlot(context.get(), sid)};
+ if(slot) FreeEffectSlot(context.get(), slot);
+ };
+ std::for_each(effectslots, effectslots_end, delete_slot);
+}
+END_API_FUNC
+
+AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if LIKELY(context)
+ {
+ std::lock_guard<std::mutex> _{context->mEffectSlotLock};
+ if(LookupEffectSlot(context.get(), effectslot) != nullptr)
+ return AL_TRUE;
+ }
+ return AL_FALSE;
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mEffectSlotLock};
+ ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
+ if UNLIKELY(!slot)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
+
+ ALeffectslot *target{};
+ ALCdevice *device{};
+ ALenum err{};
+ switch(param)
+ {
+ case AL_EFFECTSLOT_EFFECT:
+ device = context->mDevice.get();
+
+ { std::lock_guard<std::mutex> ___{device->EffectLock};
+ ALeffect *effect{value ? LookupEffect(device, static_cast<ALuint>(value)) : nullptr};
+ if(!(value == 0 || effect != nullptr))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect ID %u", value);
+ err = InitializeEffect(context.get(), slot, effect);
+ }
+ if(err != AL_NO_ERROR)
+ {
+ context->setError(err, "Effect initialization failed");
+ return;
+ }
+ break;
+
+ case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
+ if(!(value == AL_TRUE || value == AL_FALSE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,
+ "Effect slot auxiliary send auto out of range");
+ slot->AuxSendAuto = static_cast<ALboolean>(value);
+ break;
+
+ case AL_EFFECTSLOT_TARGET_SOFT:
+ target = LookupEffectSlot(context.get(), static_cast<ALuint>(value));
+ if(value && !target)
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect slot target ID");
+ if(target)
+ {
+ ALeffectslot *checker{target};
+ while(checker && checker != slot)
+ checker = checker->Target;
+ if(checker)
+ SETERR_RETURN(context, AL_INVALID_OPERATION,,
+ "Setting target of effect slot ID %u to %u creates circular chain", slot->id,
+ target->id);
+ }
+
+ if(ALeffectslot *oldtarget{slot->Target})
+ {
+ /* We must force an update if there was an existing effect slot
+ * target, in case it's about to be deleted.
+ */
+ if(target) IncrementRef(target->ref);
+ DecrementRef(oldtarget->ref);
+ slot->Target = target;
+ UpdateEffectSlotProps(slot, context.get());
+ return;
+ }
+
+ if(target) IncrementRef(target->ref);
+ slot->Target = target;
+ break;
+
+ default:
+ SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot integer property 0x%04x",
+ param);
+ }
+ DO_UPDATEPROPS();
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_EFFECTSLOT_EFFECT:
+ case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
+ case AL_EFFECTSLOT_TARGET_SOFT:
+ alAuxiliaryEffectSloti(effectslot, param, values[0]);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mEffectSlotLock};
+ ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
+ if UNLIKELY(!slot)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
+
+ switch(param)
+ {
+ default:
+ SETERR_RETURN(context, AL_INVALID_ENUM,,
+ "Invalid effect slot integer-vector property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mEffectSlotLock};
+ ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
+ if UNLIKELY(!slot)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
+
+ switch(param)
+ {
+ case AL_EFFECTSLOT_GAIN:
+ if(!(value >= 0.0f && value <= 1.0f))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Effect slot gain out of range");
+ slot->Gain = value;
+ break;
+
+ default:
+ SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot float property 0x%04x",
+ param);
+ }
+ DO_UPDATEPROPS();
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_EFFECTSLOT_GAIN:
+ alAuxiliaryEffectSlotf(effectslot, param, values[0]);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mEffectSlotLock};
+ ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
+ if UNLIKELY(!slot)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
+
+ switch(param)
+ {
+ default:
+ SETERR_RETURN(context, AL_INVALID_ENUM,,
+ "Invalid effect slot float-vector property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mEffectSlotLock};
+ ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
+ if UNLIKELY(!slot)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
+
+ switch(param)
+ {
+ case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
+ *value = slot->AuxSendAuto;
+ break;
+
+ case AL_EFFECTSLOT_TARGET_SOFT:
+ if(auto *target = slot->Target)
+ *value = static_cast<ALint>(target->id);
+ else
+ *value = 0;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_EFFECTSLOT_EFFECT:
+ case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
+ case AL_EFFECTSLOT_TARGET_SOFT:
+ alGetAuxiliaryEffectSloti(effectslot, param, values);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mEffectSlotLock};
+ ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
+ if UNLIKELY(!slot)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
+
+ switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
+ param);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mEffectSlotLock};
+ ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
+ if UNLIKELY(!slot)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
+
+ switch(param)
+ {
+ case AL_EFFECTSLOT_GAIN:
+ *value = slot->Gain;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_EFFECTSLOT_GAIN:
+ alGetAuxiliaryEffectSlotf(effectslot, param, values);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mEffectSlotLock};
+ ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
+ if UNLIKELY(!slot)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
+
+ switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
+ param);
+ }
+}
+END_API_FUNC
+
+
+ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect)
+{
+ ALenum newtype{effect ? effect->type : AL_EFFECT_NULL};
+ if(newtype != EffectSlot->Effect.Type)
+ {
+ EffectStateFactory *factory{getFactoryByType(newtype)};
+ if(!factory)
+ {
+ ERR("Failed to find factory for effect type 0x%04x\n", newtype);
+ return AL_INVALID_ENUM;
+ }
+ EffectState *State{factory->create()};
+ if(!State) return AL_OUT_OF_MEMORY;
+
+ FPUCtl mixer_mode{};
+ ALCdevice *Device{Context->mDevice.get()};
+ std::unique_lock<std::mutex> statelock{Device->StateLock};
+ State->mOutTarget = Device->Dry.Buffer;
+ if(State->deviceUpdate(Device) == AL_FALSE)
+ {
+ statelock.unlock();
+ mixer_mode.leave();
+ State->release();
+ return AL_OUT_OF_MEMORY;
+ }
+ mixer_mode.leave();
+
+ if(!effect)
+ {
+ EffectSlot->Effect.Type = AL_EFFECT_NULL;
+ EffectSlot->Effect.Props = EffectProps {};
+ }
+ else
+ {
+ EffectSlot->Effect.Type = effect->type;
+ EffectSlot->Effect.Props = effect->Props;
+ }
+
+ EffectSlot->Effect.State->release();
+ EffectSlot->Effect.State = State;
+ }
+ else if(effect)
+ EffectSlot->Effect.Props = effect->Props;
+
+ /* Remove state references from old effect slot property updates. */
+ ALeffectslotProps *props{Context->mFreeEffectslotProps.load()};
+ while(props)
+ {
+ if(props->State)
+ props->State->release();
+ props->State = nullptr;
+ props = props->next.load(std::memory_order_relaxed);
+ }
+
+ return AL_NO_ERROR;
+}
+
+
+ALenum InitEffectSlot(ALeffectslot *slot)
+{
+ EffectStateFactory *factory{getFactoryByType(slot->Effect.Type)};
+ if(!factory) return AL_INVALID_VALUE;
+ slot->Effect.State = factory->create();
+ if(!slot->Effect.State) return AL_OUT_OF_MEMORY;
+
+ slot->Effect.State->add_ref();
+ slot->Params.mEffectState = slot->Effect.State;
+ return AL_NO_ERROR;
+}
+
+ALeffectslot::~ALeffectslot()
+{
+ if(Target)
+ DecrementRef(Target->ref);
+ Target = nullptr;
+
+ ALeffectslotProps *props{Params.Update.load()};
+ if(props)
+ {
+ if(props->State) props->State->release();
+ TRACE("Freed unapplied AuxiliaryEffectSlot update %p\n",
+ decltype(std::declval<void*>()){props});
+ delete props;
+ }
+
+ if(Effect.State)
+ Effect.State->release();
+ if(Params.mEffectState)
+ Params.mEffectState->release();
+}
+
+void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context)
+{
+ /* Get an unused property container, or allocate a new one as needed. */
+ ALeffectslotProps *props{context->mFreeEffectslotProps.load(std::memory_order_relaxed)};
+ if(!props)
+ props = new ALeffectslotProps{};
+ else
+ {
+ ALeffectslotProps *next;
+ do {
+ next = props->next.load(std::memory_order_relaxed);
+ } while(context->mFreeEffectslotProps.compare_exchange_weak(props, next,
+ std::memory_order_seq_cst, std::memory_order_acquire) == 0);
+ }
+
+ /* Copy in current property values. */
+ props->Gain = slot->Gain;
+ props->AuxSendAuto = slot->AuxSendAuto;
+ props->Target = slot->Target;
+
+ props->Type = slot->Effect.Type;
+ props->Props = slot->Effect.Props;
+ /* Swap out any stale effect state object there may be in the container, to
+ * delete it.
+ */
+ EffectState *oldstate{props->State};
+ slot->Effect.State->add_ref();
+ props->State = slot->Effect.State;
+
+ /* Set the new container for updating internal parameters. */
+ props = slot->Params.Update.exchange(props, std::memory_order_acq_rel);
+ if(props)
+ {
+ /* If there was an unused update container, put it back in the
+ * freelist.
+ */
+ if(props->State)
+ props->State->release();
+ props->State = nullptr;
+ AtomicReplaceHead(context->mFreeEffectslotProps, props);
+ }
+
+ if(oldstate)
+ oldstate->release();
+}
+
+void UpdateAllEffectSlotProps(ALCcontext *context)
+{
+ std::lock_guard<std::mutex> _{context->mEffectSlotLock};
+ ALeffectslotArray *auxslots{context->mActiveAuxSlots.load(std::memory_order_acquire)};
+ for(ALeffectslot *slot : *auxslots)
+ {
+ if(!slot->PropsClean.test_and_set(std::memory_order_acq_rel))
+ UpdateEffectSlotProps(slot, context);
+ }
+}
+
+EffectSlotSubList::~EffectSlotSubList()
+{
+ uint64_t usemask{~FreeMask};
+ while(usemask)
+ {
+ ALsizei idx{CTZ64(usemask)};
+ al::destroy_at(EffectSlots+idx);
+ usemask &= ~(1_u64 << idx);
+ }
+ FreeMask = ~usemask;
+ al_free(EffectSlots);
+ EffectSlots = nullptr;
+}
diff --git a/al/auxeffectslot.h b/al/auxeffectslot.h
new file mode 100644
index 00000000..2471447f
--- /dev/null
+++ b/al/auxeffectslot.h
@@ -0,0 +1,105 @@
+#ifndef AL_AUXEFFECTSLOT_H
+#define AL_AUXEFFECTSLOT_H
+
+#include <atomic>
+#include <cstddef>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/efx.h"
+
+#include "alcmain.h"
+#include "almalloc.h"
+#include "atomic.h"
+#include "effects/base.h"
+#include "vector.h"
+
+struct ALeffect;
+struct ALeffectslot;
+
+
+using ALeffectslotArray = al::FlexArray<ALeffectslot*>;
+
+
+struct ALeffectslotProps {
+ ALfloat Gain;
+ ALboolean AuxSendAuto;
+ ALeffectslot *Target;
+
+ ALenum Type;
+ EffectProps Props;
+
+ EffectState *State;
+
+ std::atomic<ALeffectslotProps*> next;
+
+ DEF_NEWDEL(ALeffectslotProps)
+};
+
+
+struct ALeffectslot {
+ ALfloat Gain{1.0f};
+ ALboolean AuxSendAuto{AL_TRUE};
+ ALeffectslot *Target{nullptr};
+
+ struct {
+ ALenum Type{AL_EFFECT_NULL};
+ EffectProps Props{};
+
+ EffectState *State{nullptr};
+ } Effect;
+
+ std::atomic_flag PropsClean;
+
+ RefCount ref{0u};
+
+ struct {
+ std::atomic<ALeffectslotProps*> Update{nullptr};
+
+ ALfloat Gain{1.0f};
+ ALboolean AuxSendAuto{AL_TRUE};
+ ALeffectslot *Target{nullptr};
+
+ ALenum EffectType{AL_EFFECT_NULL};
+ EffectProps mEffectProps{};
+ EffectState *mEffectState{nullptr};
+
+ ALfloat RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */
+ ALfloat DecayTime{0.0f};
+ ALfloat DecayLFRatio{0.0f};
+ ALfloat DecayHFRatio{0.0f};
+ ALboolean DecayHFLimit{AL_FALSE};
+ ALfloat AirAbsorptionGainHF{1.0f};
+ } Params;
+
+ /* Self ID */
+ ALuint id{};
+
+ /* Mixing buffer used by the Wet mix. */
+ al::vector<FloatBufferLine, 16> MixBuffer;
+
+ /* Wet buffer configuration is ACN channel order with N3D scaling.
+ * Consequently, effects that only want to work with mono input can use
+ * channel 0 by itself. Effects that want multichannel can process the
+ * ambisonics signal and make a B-Format source pan.
+ */
+ MixParams Wet;
+
+ ALeffectslot() { PropsClean.test_and_set(std::memory_order_relaxed); }
+ ALeffectslot(const ALeffectslot&) = delete;
+ ALeffectslot& operator=(const ALeffectslot&) = delete;
+ ~ALeffectslot();
+
+ static ALeffectslotArray *CreatePtrArray(size_t count) noexcept;
+
+ DEF_NEWDEL(ALeffectslot)
+};
+
+ALenum InitEffectSlot(ALeffectslot *slot);
+void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context);
+void UpdateAllEffectSlotProps(ALCcontext *context);
+
+
+ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect);
+
+#endif
diff --git a/al/buffer.cpp b/al/buffer.cpp
new file mode 100644
index 00000000..8f4228a8
--- /dev/null
+++ b/al/buffer.cpp
@@ -0,0 +1,1391 @@
+/**
+ * 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 "buffer.h"
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <numeric>
+#include <utility>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#include "albyte.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "atomic.h"
+#include "inprogext.h"
+#include "opthelpers.h"
+
+
+namespace {
+
+/* IMA ADPCM Stepsize table */
+constexpr int IMAStep_size[89] = {
+ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19,
+ 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55,
+ 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157,
+ 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449,
+ 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282,
+ 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660,
+ 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442,
+ 11487,12635,13899,15289,16818,18500,20350,22358,24633,27086,29794,
+ 32767
+};
+
+/* IMA4 ADPCM Codeword decode table */
+constexpr int IMA4Codeword[16] = {
+ 1, 3, 5, 7, 9, 11, 13, 15,
+ -1,-3,-5,-7,-9,-11,-13,-15,
+};
+
+/* IMA4 ADPCM Step index adjust decode table */
+constexpr int IMA4Index_adjust[16] = {
+ -1,-1,-1,-1, 2, 4, 6, 8,
+ -1,-1,-1,-1, 2, 4, 6, 8
+};
+
+
+/* MSADPCM Adaption table */
+constexpr int MSADPCMAdaption[16] = {
+ 230, 230, 230, 230, 307, 409, 512, 614,
+ 768, 614, 512, 409, 307, 230, 230, 230
+};
+
+/* MSADPCM Adaption Coefficient tables */
+constexpr int MSADPCMAdaptionCoeff[7][2] = {
+ { 256, 0 },
+ { 512, -256 },
+ { 0, 0 },
+ { 192, 64 },
+ { 240, 0 },
+ { 460, -208 },
+ { 392, -232 }
+};
+
+
+void DecodeIMA4Block(ALshort *dst, const al::byte *src, size_t numchans, size_t align)
+{
+ ALint sample[MAX_INPUT_CHANNELS]{};
+ ALint index[MAX_INPUT_CHANNELS]{};
+ ALuint code[MAX_INPUT_CHANNELS]{};
+
+ for(size_t c{0};c < numchans;c++)
+ {
+ sample[c] = al::to_integer<int>(src[0]) | (al::to_integer<int>(src[1])<<8);
+ sample[c] = (sample[c]^0x8000) - 32768;
+ src += 2;
+ index[c] = al::to_integer<int>(src[0]) | (al::to_integer<int>(src[1])<<8);
+ index[c] = clampi((index[c]^0x8000) - 32768, 0, 88);
+ src += 2;
+
+ *(dst++) = static_cast<ALshort>(sample[c]);
+ }
+
+ for(size_t i{1};i < align;i++)
+ {
+ if((i&7) == 1)
+ {
+ for(size_t c{0};c < numchans;c++)
+ {
+ code[c] = al::to_integer<ALuint>(src[0]) | (al::to_integer<ALuint>(src[1])<< 8) |
+ (al::to_integer<ALuint>(src[2])<<16) | (al::to_integer<ALuint>(src[3])<<24);
+ src += 4;
+ }
+ }
+
+ for(size_t c{0};c < numchans;c++)
+ {
+ const ALuint nibble{code[c]&0xf};
+ code[c] >>= 4;
+
+ sample[c] += IMA4Codeword[nibble] * IMAStep_size[index[c]] / 8;
+ sample[c] = clampi(sample[c], -32768, 32767);
+
+ index[c] += IMA4Index_adjust[nibble];
+ index[c] = clampi(index[c], 0, 88);
+
+ *(dst++) = static_cast<ALshort>(sample[c]);
+ }
+ }
+}
+
+void DecodeMSADPCMBlock(ALshort *dst, const al::byte *src, size_t numchans, size_t align)
+{
+ ALubyte blockpred[MAX_INPUT_CHANNELS]{};
+ ALint delta[MAX_INPUT_CHANNELS]{};
+ ALshort samples[MAX_INPUT_CHANNELS][2]{};
+
+ for(size_t c{0};c < numchans;c++)
+ {
+ blockpred[c] = std::min<ALubyte>(al::to_integer<ALubyte>(src[0]), 6);
+ ++src;
+ }
+ for(size_t c{0};c < numchans;c++)
+ {
+ delta[c] = al::to_integer<int>(src[0]) | (al::to_integer<int>(src[1])<<8);
+ delta[c] = (delta[c]^0x8000) - 32768;
+ src += 2;
+ }
+ for(size_t c{0};c < numchans;c++)
+ {
+ samples[c][0] = static_cast<ALshort>(al::to_integer<int>(src[0]) |
+ (al::to_integer<int>(src[1])<<8));
+ src += 2;
+ }
+ for(size_t c{0};c < numchans;c++)
+ {
+ samples[c][1] = static_cast<ALshort>(al::to_integer<int>(src[0]) |
+ (al::to_integer<int>(src[1])<<8));
+ src += 2;
+ }
+
+ /* Second sample is written first. */
+ for(size_t c{0};c < numchans;c++)
+ *(dst++) = samples[c][1];
+ for(size_t c{0};c < numchans;c++)
+ *(dst++) = samples[c][0];
+
+ int num{0};
+ for(size_t i{2};i < align;i++)
+ {
+ for(size_t c{0};c < numchans;c++)
+ {
+ /* Read the nibble (first is in the upper bits). */
+ al::byte nibble;
+ if(!(num++ & 1))
+ nibble = *src >> 4;
+ else
+ nibble = *(src++) & 0x0f;
+
+ ALint pred{(samples[c][0]*MSADPCMAdaptionCoeff[blockpred[c]][0] +
+ samples[c][1]*MSADPCMAdaptionCoeff[blockpred[c]][1]) / 256};
+ pred += (al::to_integer<int>(nibble^0x08) - 0x08) * delta[c];
+ pred = clampi(pred, -32768, 32767);
+
+ samples[c][1] = samples[c][0];
+ samples[c][0] = static_cast<ALshort>(pred);
+
+ delta[c] = (MSADPCMAdaption[al::to_integer<ALubyte>(nibble)] * delta[c]) / 256;
+ delta[c] = maxi(16, delta[c]);
+
+ *(dst++) = static_cast<ALshort>(pred);
+ }
+ }
+}
+
+void Convert_ALshort_ALima4(ALshort *dst, const al::byte *src, size_t numchans, size_t len,
+ size_t align)
+{
+ const size_t byte_align{((align-1)/2 + 4) * numchans};
+
+ len /= align;
+ while(len--)
+ {
+ DecodeIMA4Block(dst, src, numchans, align);
+ src += byte_align;
+ dst += align*numchans;
+ }
+}
+
+void Convert_ALshort_ALmsadpcm(ALshort *dst, const al::byte *src, size_t numchans, size_t len,
+ size_t align)
+{
+ const size_t byte_align{((align-2)/2 + 7) * numchans};
+
+ len /= align;
+ while(len--)
+ {
+ DecodeMSADPCMBlock(dst, src, numchans, align);
+ src += byte_align;
+ dst += align*numchans;
+ }
+}
+
+
+ALuint BytesFromUserFmt(UserFmtType type)
+{
+ switch(type)
+ {
+ case UserFmtUByte: return sizeof(ALubyte);
+ case UserFmtShort: return sizeof(ALshort);
+ case UserFmtFloat: return sizeof(ALfloat);
+ case UserFmtDouble: return sizeof(ALdouble);
+ case UserFmtMulaw: return sizeof(ALubyte);
+ case UserFmtAlaw: return sizeof(ALubyte);
+ case UserFmtIMA4: break; /* not handled here */
+ case UserFmtMSADPCM: break; /* not handled here */
+ }
+ return 0;
+}
+ALuint ChannelsFromUserFmt(UserFmtChannels chans)
+{
+ switch(chans)
+ {
+ case UserFmtMono: return 1;
+ case UserFmtStereo: return 2;
+ case UserFmtRear: return 2;
+ case UserFmtQuad: return 4;
+ case UserFmtX51: return 6;
+ case UserFmtX61: return 7;
+ case UserFmtX71: return 8;
+ case UserFmtBFormat2D: return 3;
+ case UserFmtBFormat3D: return 4;
+ }
+ return 0;
+}
+inline ALuint FrameSizeFromUserFmt(UserFmtChannels chans, UserFmtType type)
+{ return ChannelsFromUserFmt(chans) * BytesFromUserFmt(type); }
+
+
+constexpr ALbitfieldSOFT INVALID_STORAGE_MASK{~unsigned(AL_MAP_READ_BIT_SOFT |
+ AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT | AL_PRESERVE_DATA_BIT_SOFT)};
+constexpr ALbitfieldSOFT MAP_READ_WRITE_FLAGS{AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT};
+constexpr ALbitfieldSOFT INVALID_MAP_FLAGS{~unsigned(AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT |
+ AL_MAP_PERSISTENT_BIT_SOFT)};
+
+
+bool EnsureBuffers(ALCdevice *device, size_t needed)
+{
+ size_t count{std::accumulate(device->BufferList.cbegin(), device->BufferList.cend(), size_t{0},
+ [](size_t cur, const BufferSubList &sublist) noexcept -> size_t
+ { return cur + static_cast<ALuint>(POPCNT64(sublist.FreeMask)); }
+ )};
+
+ while(needed > count)
+ {
+ if UNLIKELY(device->BufferList.size() >= 1<<25)
+ return false;
+
+ device->BufferList.emplace_back();
+ auto sublist = device->BufferList.end() - 1;
+ sublist->FreeMask = ~0_u64;
+ sublist->Buffers = static_cast<ALbuffer*>(al_calloc(alignof(ALbuffer), sizeof(ALbuffer)*64));
+ if UNLIKELY(!sublist->Buffers)
+ {
+ device->BufferList.pop_back();
+ return false;
+ }
+ count += 64;
+ }
+ return true;
+}
+
+ALbuffer *AllocBuffer(ALCdevice *device)
+{
+ auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(),
+ [](const BufferSubList &entry) noexcept -> bool
+ { return entry.FreeMask != 0; }
+ );
+
+ auto lidx = static_cast<ALuint>(std::distance(device->BufferList.begin(), sublist));
+ auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
+
+ ALbuffer *buffer{::new (sublist->Buffers + slidx) ALbuffer{}};
+
+ /* Add 1 to avoid buffer ID 0. */
+ buffer->id = ((lidx<<6) | slidx) + 1;
+
+ sublist->FreeMask &= ~(1_u64 << slidx);
+
+ return buffer;
+}
+
+void FreeBuffer(ALCdevice *device, ALbuffer *buffer)
+{
+ const ALuint id{buffer->id - 1};
+ const size_t lidx{id >> 6};
+ const ALuint slidx{id & 0x3f};
+
+ al::destroy_at(buffer);
+
+ device->BufferList[lidx].FreeMask |= 1_u64 << slidx;
+}
+
+inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id)
+{
+ const size_t lidx{(id-1) >> 6};
+ const ALuint slidx{(id-1) & 0x3f};
+
+ if UNLIKELY(lidx >= device->BufferList.size())
+ return nullptr;
+ BufferSubList &sublist = device->BufferList[lidx];
+ if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
+ return nullptr;
+ return sublist.Buffers + slidx;
+}
+
+
+ALuint SanitizeAlignment(UserFmtType type, ALuint align)
+{
+ if(align == 0)
+ {
+ if(type == UserFmtIMA4)
+ {
+ /* Here is where things vary:
+ * nVidia and Apple use 64+1 sample frames per block -> block_size=36 bytes per channel
+ * Most PC sound software uses 2040+1 sample frames per block -> block_size=1024 bytes per channel
+ */
+ return 65;
+ }
+ if(type == UserFmtMSADPCM)
+ return 64;
+ return 1;
+ }
+
+ if(type == UserFmtIMA4)
+ {
+ /* IMA4 block alignment must be a multiple of 8, plus 1. */
+ if((align&7) == 1) return static_cast<ALuint>(align);
+ return 0;
+ }
+ if(type == UserFmtMSADPCM)
+ {
+ /* MSADPCM block alignment must be a multiple of 2. */
+ if((align&1) == 0) return static_cast<ALuint>(align);
+ return 0;
+ }
+
+ return static_cast<ALuint>(align);
+}
+
+
+const ALchar *NameFromUserFmtType(UserFmtType type)
+{
+ switch(type)
+ {
+ case UserFmtUByte: return "Unsigned Byte";
+ case UserFmtShort: return "Signed Short";
+ case UserFmtFloat: return "Float32";
+ case UserFmtDouble: return "Float64";
+ case UserFmtMulaw: return "muLaw";
+ case UserFmtAlaw: return "aLaw";
+ case UserFmtIMA4: return "IMA4 ADPCM";
+ case UserFmtMSADPCM: return "MSADPCM";
+ }
+ return "<internal type error>";
+}
+
+/** Loads the specified data into the buffer, using the specified format. */
+void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
+ UserFmtChannels SrcChannels, UserFmtType SrcType, const al::byte *SrcData,
+ ALbitfieldSOFT access)
+{
+ if UNLIKELY(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0)
+ SETERR_RETURN(context, AL_INVALID_OPERATION,, "Modifying storage for in-use buffer %u",
+ ALBuf->id);
+
+ /* Currently no channel configurations need to be converted. */
+ FmtChannels DstChannels{FmtMono};
+ switch(SrcChannels)
+ {
+ case UserFmtMono: DstChannels = FmtMono; break;
+ case UserFmtStereo: DstChannels = FmtStereo; break;
+ case UserFmtRear: DstChannels = FmtRear; break;
+ case UserFmtQuad: DstChannels = FmtQuad; break;
+ case UserFmtX51: DstChannels = FmtX51; break;
+ case UserFmtX61: DstChannels = FmtX61; break;
+ case UserFmtX71: DstChannels = FmtX71; break;
+ case UserFmtBFormat2D: DstChannels = FmtBFormat2D; break;
+ case UserFmtBFormat3D: DstChannels = FmtBFormat3D; break;
+ }
+ if UNLIKELY(static_cast<long>(SrcChannels) != static_cast<long>(DstChannels))
+ SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format");
+
+ /* IMA4 and MSADPCM convert to 16-bit short. */
+ FmtType DstType{FmtUByte};
+ switch(SrcType)
+ {
+ case UserFmtUByte: DstType = FmtUByte; break;
+ case UserFmtShort: DstType = FmtShort; break;
+ case UserFmtFloat: DstType = FmtFloat; break;
+ case UserFmtDouble: DstType = FmtDouble; break;
+ case UserFmtAlaw: DstType = FmtAlaw; break;
+ case UserFmtMulaw: DstType = FmtMulaw; break;
+ case UserFmtIMA4: DstType = FmtShort; break;
+ case UserFmtMSADPCM: DstType = FmtShort; break;
+ }
+
+ /* TODO: Currently we can only map samples when they're not converted. To
+ * allow it would need some kind of double-buffering to hold onto a copy of
+ * the original data.
+ */
+ if((access&MAP_READ_WRITE_FLAGS))
+ {
+ if UNLIKELY(static_cast<long>(SrcType) != static_cast<long>(DstType))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "%s samples cannot be mapped",
+ NameFromUserFmtType(SrcType));
+ }
+
+ const ALuint unpackalign{ALBuf->UnpackAlign};
+ const ALuint align{SanitizeAlignment(SrcType, unpackalign)};
+ if UNLIKELY(align < 1)
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid unpack alignment %u for %s samples",
+ unpackalign, NameFromUserFmtType(SrcType));
+
+ if((access&AL_PRESERVE_DATA_BIT_SOFT))
+ {
+ /* Can only preserve data with the same format and alignment. */
+ if UNLIKELY(ALBuf->mFmtChannels != DstChannels || ALBuf->OriginalType != SrcType)
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched format");
+ if UNLIKELY(ALBuf->OriginalAlign != align)
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched alignment");
+ }
+
+ /* Convert the input/source size in bytes to sample frames using the unpack
+ * block alignment.
+ */
+ const ALuint SrcByteAlign{
+ (SrcType == UserFmtIMA4) ? ((align-1)/2 + 4) * ChannelsFromUserFmt(SrcChannels) :
+ (SrcType == UserFmtMSADPCM) ? ((align-2)/2 + 7) * ChannelsFromUserFmt(SrcChannels) :
+ (align * FrameSizeFromUserFmt(SrcChannels, SrcType))
+ };
+ if UNLIKELY((size%SrcByteAlign) != 0)
+ SETERR_RETURN(context, AL_INVALID_VALUE,,
+ "Data size %d is not a multiple of frame size %d (%d unpack alignment)",
+ size, SrcByteAlign, align);
+
+ if UNLIKELY(size/SrcByteAlign > std::numeric_limits<ALsizei>::max()/align)
+ SETERR_RETURN(context, AL_OUT_OF_MEMORY,,
+ "Buffer size overflow, %d blocks x %d samples per block", size/SrcByteAlign, align);
+ const ALuint frames{size / SrcByteAlign * align};
+
+ /* Convert the sample frames to the number of bytes needed for internal
+ * storage.
+ */
+ ALuint NumChannels{ChannelsFromFmt(DstChannels)};
+ ALuint FrameSize{NumChannels * BytesFromFmt(DstType)};
+ if UNLIKELY(frames > std::numeric_limits<size_t>::max()/FrameSize)
+ SETERR_RETURN(context, AL_OUT_OF_MEMORY,,
+ "Buffer size overflow, %d frames x %d bytes per frame", frames, FrameSize);
+ size_t newsize{static_cast<size_t>(frames) * FrameSize};
+
+ /* Round up to the next 16-byte multiple. This could reallocate only when
+ * increasing or the new size is less than half the current, but then the
+ * buffer's AL_SIZE would not be very reliable for accounting buffer memory
+ * usage, and reporting the real size could cause problems for apps that
+ * use AL_SIZE to try to get the buffer's play length.
+ */
+ newsize = RoundUp(newsize, 16);
+ if(newsize != ALBuf->mData.size())
+ {
+ auto newdata = al::vector<al::byte,16>(newsize, al::byte{});
+ if((access&AL_PRESERVE_DATA_BIT_SOFT))
+ {
+ const size_t tocopy{minz(newdata.size(), ALBuf->mData.size())};
+ std::copy_n(ALBuf->mData.begin(), tocopy, newdata.begin());
+ }
+ ALBuf->mData = std::move(newdata);
+ }
+
+ if(SrcType == UserFmtIMA4)
+ {
+ assert(DstType == FmtShort);
+ if(SrcData != nullptr && !ALBuf->mData.empty())
+ Convert_ALshort_ALima4(reinterpret_cast<ALshort*>(ALBuf->mData.data()),
+ SrcData, NumChannels, frames, align);
+ ALBuf->OriginalAlign = align;
+ }
+ else if(SrcType == UserFmtMSADPCM)
+ {
+ assert(DstType == FmtShort);
+ if(SrcData != nullptr && !ALBuf->mData.empty())
+ Convert_ALshort_ALmsadpcm(reinterpret_cast<ALshort*>(ALBuf->mData.data()),
+ SrcData, NumChannels, frames, align);
+ ALBuf->OriginalAlign = align;
+ }
+ else
+ {
+ assert(static_cast<long>(SrcType) == static_cast<long>(DstType));
+ if(SrcData != nullptr && !ALBuf->mData.empty())
+ std::copy_n(SrcData, frames*FrameSize, ALBuf->mData.begin());
+ ALBuf->OriginalAlign = 1;
+ }
+ ALBuf->OriginalSize = size;
+ ALBuf->OriginalType = SrcType;
+
+ ALBuf->Frequency = static_cast<ALuint>(freq);
+ ALBuf->mFmtChannels = DstChannels;
+ ALBuf->mFmtType = DstType;
+ ALBuf->Access = access;
+
+ ALBuf->SampleLen = frames;
+ ALBuf->LoopStart = 0;
+ ALBuf->LoopEnd = ALBuf->SampleLen;
+}
+
+struct DecompResult { UserFmtChannels channels; UserFmtType type; };
+al::optional<DecompResult> DecomposeUserFormat(ALenum format)
+{
+ struct FormatMap {
+ ALenum format;
+ UserFmtChannels channels;
+ UserFmtType type;
+ };
+ static const std::array<FormatMap,46> UserFmtList{{
+ { AL_FORMAT_MONO8, UserFmtMono, UserFmtUByte },
+ { AL_FORMAT_MONO16, UserFmtMono, UserFmtShort },
+ { AL_FORMAT_MONO_FLOAT32, UserFmtMono, UserFmtFloat },
+ { AL_FORMAT_MONO_DOUBLE_EXT, UserFmtMono, UserFmtDouble },
+ { AL_FORMAT_MONO_IMA4, UserFmtMono, UserFmtIMA4 },
+ { AL_FORMAT_MONO_MSADPCM_SOFT, UserFmtMono, UserFmtMSADPCM },
+ { AL_FORMAT_MONO_MULAW, UserFmtMono, UserFmtMulaw },
+ { AL_FORMAT_MONO_ALAW_EXT, UserFmtMono, UserFmtAlaw },
+
+ { AL_FORMAT_STEREO8, UserFmtStereo, UserFmtUByte },
+ { AL_FORMAT_STEREO16, UserFmtStereo, UserFmtShort },
+ { AL_FORMAT_STEREO_FLOAT32, UserFmtStereo, UserFmtFloat },
+ { AL_FORMAT_STEREO_DOUBLE_EXT, UserFmtStereo, UserFmtDouble },
+ { AL_FORMAT_STEREO_IMA4, UserFmtStereo, UserFmtIMA4 },
+ { AL_FORMAT_STEREO_MSADPCM_SOFT, UserFmtStereo, UserFmtMSADPCM },
+ { AL_FORMAT_STEREO_MULAW, UserFmtStereo, UserFmtMulaw },
+ { AL_FORMAT_STEREO_ALAW_EXT, UserFmtStereo, UserFmtAlaw },
+
+ { AL_FORMAT_REAR8, UserFmtRear, UserFmtUByte },
+ { AL_FORMAT_REAR16, UserFmtRear, UserFmtShort },
+ { AL_FORMAT_REAR32, UserFmtRear, UserFmtFloat },
+ { AL_FORMAT_REAR_MULAW, UserFmtRear, UserFmtMulaw },
+
+ { AL_FORMAT_QUAD8_LOKI, UserFmtQuad, UserFmtUByte },
+ { AL_FORMAT_QUAD16_LOKI, UserFmtQuad, UserFmtShort },
+
+ { AL_FORMAT_QUAD8, UserFmtQuad, UserFmtUByte },
+ { AL_FORMAT_QUAD16, UserFmtQuad, UserFmtShort },
+ { AL_FORMAT_QUAD32, UserFmtQuad, UserFmtFloat },
+ { AL_FORMAT_QUAD_MULAW, UserFmtQuad, UserFmtMulaw },
+
+ { AL_FORMAT_51CHN8, UserFmtX51, UserFmtUByte },
+ { AL_FORMAT_51CHN16, UserFmtX51, UserFmtShort },
+ { AL_FORMAT_51CHN32, UserFmtX51, UserFmtFloat },
+ { AL_FORMAT_51CHN_MULAW, UserFmtX51, UserFmtMulaw },
+
+ { AL_FORMAT_61CHN8, UserFmtX61, UserFmtUByte },
+ { AL_FORMAT_61CHN16, UserFmtX61, UserFmtShort },
+ { AL_FORMAT_61CHN32, UserFmtX61, UserFmtFloat },
+ { AL_FORMAT_61CHN_MULAW, UserFmtX61, UserFmtMulaw },
+
+ { AL_FORMAT_71CHN8, UserFmtX71, UserFmtUByte },
+ { AL_FORMAT_71CHN16, UserFmtX71, UserFmtShort },
+ { AL_FORMAT_71CHN32, UserFmtX71, UserFmtFloat },
+ { AL_FORMAT_71CHN_MULAW, UserFmtX71, UserFmtMulaw },
+
+ { AL_FORMAT_BFORMAT2D_8, UserFmtBFormat2D, UserFmtUByte },
+ { AL_FORMAT_BFORMAT2D_16, UserFmtBFormat2D, UserFmtShort },
+ { AL_FORMAT_BFORMAT2D_FLOAT32, UserFmtBFormat2D, UserFmtFloat },
+ { AL_FORMAT_BFORMAT2D_MULAW, UserFmtBFormat2D, UserFmtMulaw },
+
+ { AL_FORMAT_BFORMAT3D_8, UserFmtBFormat3D, UserFmtUByte },
+ { AL_FORMAT_BFORMAT3D_16, UserFmtBFormat3D, UserFmtShort },
+ { AL_FORMAT_BFORMAT3D_FLOAT32, UserFmtBFormat3D, UserFmtFloat },
+ { AL_FORMAT_BFORMAT3D_MULAW, UserFmtBFormat3D, UserFmtMulaw },
+ }};
+
+ for(const auto &fmt : UserFmtList)
+ {
+ if(fmt.format == format)
+ return al::make_optional<DecompResult>({fmt.channels, fmt.type});
+ }
+ return al::nullopt;
+}
+
+} // namespace
+
+
+AL_API ALvoid AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Generating %d buffers", n);
+ if UNLIKELY(n <= 0) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+ if(!EnsureBuffers(device, static_cast<ALuint>(n)))
+ {
+ context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d buffer%s", n, (n==1)?"":"s");
+ return;
+ }
+
+ if LIKELY(n == 1)
+ {
+ /* Special handling for the easy and normal case. */
+ ALbuffer *buffer{AllocBuffer(device)};
+ buffers[0] = buffer->id;
+ }
+ else
+ {
+ /* Store the allocated buffer IDs in a separate local list, to avoid
+ * modifying the user storage in case of failure.
+ */
+ al::vector<ALuint> ids;
+ ids.reserve(static_cast<ALuint>(n));
+ do {
+ ALbuffer *buffer{AllocBuffer(device)};
+ ids.emplace_back(buffer->id);
+ } while(--n);
+ std::copy(ids.begin(), ids.end(), buffers);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n);
+ if UNLIKELY(n <= 0) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ /* First try to find any buffers that are invalid or in-use. */
+ auto validate_buffer = [device, &context](const ALuint bid) -> bool
+ {
+ if(!bid) return true;
+ ALbuffer *ALBuf{LookupBuffer(device, bid)};
+ if UNLIKELY(!ALBuf)
+ {
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", bid);
+ return false;
+ }
+ if UNLIKELY(ReadRef(ALBuf->ref) != 0)
+ {
+ context->setError(AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid);
+ return false;
+ }
+ return true;
+ };
+ const ALuint *buffers_end = buffers + n;
+ auto invbuf = std::find_if_not(buffers, buffers_end, validate_buffer);
+ if UNLIKELY(invbuf != buffers_end) return;
+
+ /* All good. Delete non-0 buffer IDs. */
+ auto delete_buffer = [device](const ALuint bid) -> void
+ {
+ ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr};
+ if(buffer) FreeBuffer(device, buffer);
+ };
+ std::for_each(buffers, buffers_end, delete_buffer);
+}
+END_API_FUNC
+
+AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if LIKELY(context)
+ {
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+ if(!buffer || LookupBuffer(device, buffer))
+ return AL_TRUE;
+ }
+ return AL_FALSE;
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq)
+START_API_FUNC
+{ alBufferStorageSOFT(buffer, format, data, size, freq, 0); }
+END_API_FUNC
+
+AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ ALbuffer *albuf = LookupBuffer(device, buffer);
+ if UNLIKELY(!albuf)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY(size < 0)
+ context->setError(AL_INVALID_VALUE, "Negative storage size %d", size);
+ else if UNLIKELY(freq < 1)
+ context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq);
+ else if UNLIKELY((flags&INVALID_STORAGE_MASK) != 0)
+ context->setError(AL_INVALID_VALUE, "Invalid storage flags 0x%x",
+ flags&INVALID_STORAGE_MASK);
+ else if UNLIKELY((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS))
+ context->setError(AL_INVALID_VALUE,
+ "Declaring persistently mapped storage without read or write access");
+ else
+ {
+ auto usrfmt = DecomposeUserFormat(format);
+ if UNLIKELY(!usrfmt)
+ context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format);
+ else
+ LoadData(context.get(), albuf, freq, static_cast<ALuint>(size), usrfmt->channels,
+ usrfmt->type, static_cast<const al::byte*>(data), flags);
+ }
+}
+END_API_FUNC
+
+AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return nullptr;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ ALbuffer *albuf = LookupBuffer(device, buffer);
+ if UNLIKELY(!albuf)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY((access&INVALID_MAP_FLAGS) != 0)
+ context->setError(AL_INVALID_VALUE, "Invalid map flags 0x%x", access&INVALID_MAP_FLAGS);
+ else if UNLIKELY(!(access&MAP_READ_WRITE_FLAGS))
+ context->setError(AL_INVALID_VALUE, "Mapping buffer %u without read or write access",
+ buffer);
+ else
+ {
+ ALbitfieldSOFT unavailable = (albuf->Access^access) & access;
+ if UNLIKELY(ReadRef(albuf->ref) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT))
+ context->setError(AL_INVALID_OPERATION,
+ "Mapping in-use buffer %u without persistent mapping", buffer);
+ else if UNLIKELY(albuf->MappedAccess != 0)
+ context->setError(AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer);
+ else if UNLIKELY((unavailable&AL_MAP_READ_BIT_SOFT))
+ context->setError(AL_INVALID_VALUE,
+ "Mapping buffer %u for reading without read access", buffer);
+ else if UNLIKELY((unavailable&AL_MAP_WRITE_BIT_SOFT))
+ context->setError(AL_INVALID_VALUE,
+ "Mapping buffer %u for writing without write access", buffer);
+ else if UNLIKELY((unavailable&AL_MAP_PERSISTENT_BIT_SOFT))
+ context->setError(AL_INVALID_VALUE,
+ "Mapping buffer %u persistently without persistent access", buffer);
+ else if UNLIKELY(offset < 0 || length <= 0
+ || static_cast<ALuint>(offset) >= albuf->OriginalSize
+ || static_cast<ALuint>(length) > albuf->OriginalSize - static_cast<ALuint>(offset))
+ context->setError(AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u",
+ offset, length, buffer);
+ else
+ {
+ void *retval = albuf->mData.data() + offset;
+ albuf->MappedAccess = access;
+ albuf->MappedOffset = offset;
+ albuf->MappedSize = length;
+ return retval;
+ }
+ }
+
+ return nullptr;
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ ALbuffer *albuf = LookupBuffer(device, buffer);
+ if UNLIKELY(!albuf)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY(albuf->MappedAccess == 0)
+ context->setError(AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer);
+ else
+ {
+ albuf->MappedAccess = 0;
+ albuf->MappedOffset = 0;
+ albuf->MappedSize = 0;
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ ALbuffer *albuf = LookupBuffer(device, buffer);
+ if UNLIKELY(!albuf)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT))
+ context->setError(AL_INVALID_OPERATION, "Flushing buffer %u while not mapped for writing",
+ buffer);
+ else if UNLIKELY(offset < albuf->MappedOffset || length <= 0
+ || offset >= albuf->MappedOffset+albuf->MappedSize
+ || length > albuf->MappedOffset+albuf->MappedSize-offset)
+ context->setError(AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u", offset,
+ length, buffer);
+ else
+ {
+ /* FIXME: Need to use some method of double-buffering for the mixer and
+ * app to hold separate memory, which can be safely transfered
+ * asynchronously. Currently we just say the app shouldn't write where
+ * OpenAL's reading, and hope for the best...
+ */
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alBufferSubDataSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ ALbuffer *albuf = LookupBuffer(device, buffer);
+ if UNLIKELY(!albuf)
+ {
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ return;
+ }
+
+ auto usrfmt = DecomposeUserFormat(format);
+ if UNLIKELY(!usrfmt)
+ {
+ context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format);
+ return;
+ }
+
+ ALuint unpack_align{albuf->UnpackAlign};
+ ALuint align{SanitizeAlignment(usrfmt->type, unpack_align)};
+ if UNLIKELY(align < 1)
+ context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u", unpack_align);
+ else if UNLIKELY(long{usrfmt->channels} != long{albuf->mFmtChannels}
+ || usrfmt->type != albuf->OriginalType)
+ context->setError(AL_INVALID_ENUM, "Unpacking data with mismatched format");
+ else if UNLIKELY(align != albuf->OriginalAlign)
+ context->setError(AL_INVALID_VALUE,
+ "Unpacking data with alignment %u does not match original alignment %u", align,
+ albuf->OriginalAlign);
+ else if UNLIKELY(albuf->MappedAccess != 0)
+ context->setError(AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u", buffer);
+ else
+ {
+ ALuint num_chans{ChannelsFromFmt(albuf->mFmtChannels)};
+ ALuint frame_size{num_chans * BytesFromFmt(albuf->mFmtType)};
+ ALuint byte_align{
+ (albuf->OriginalType == UserFmtIMA4) ? ((align-1)/2 + 4) * num_chans :
+ (albuf->OriginalType == UserFmtMSADPCM) ? ((align-2)/2 + 7) * num_chans :
+ (align * frame_size)
+ };
+
+ if UNLIKELY(offset < 0 || length < 0 || static_cast<ALuint>(offset) > albuf->OriginalSize
+ || static_cast<ALuint>(length) > albuf->OriginalSize-static_cast<ALuint>(offset))
+ context->setError(AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u",
+ offset, length, buffer);
+ else if UNLIKELY((static_cast<ALuint>(offset)%byte_align) != 0)
+ context->setError(AL_INVALID_VALUE,
+ "Sub-range offset %d is not a multiple of frame size %d (%d unpack alignment)",
+ offset, byte_align, align);
+ else if UNLIKELY((static_cast<ALuint>(length)%byte_align) != 0)
+ context->setError(AL_INVALID_VALUE,
+ "Sub-range length %d is not a multiple of frame size %d (%d unpack alignment)",
+ length, byte_align, align);
+ else
+ {
+ /* offset -> byte offset, length -> sample count */
+ size_t byteoff{static_cast<ALuint>(offset)/byte_align * align * frame_size};
+ size_t samplen{static_cast<ALuint>(length)/byte_align * align};
+
+ void *dst = albuf->mData.data() + byteoff;
+ if(usrfmt->type == UserFmtIMA4 && albuf->mFmtType == FmtShort)
+ Convert_ALshort_ALima4(static_cast<ALshort*>(dst),
+ static_cast<const al::byte*>(data), num_chans, samplen, align);
+ else if(usrfmt->type == UserFmtMSADPCM && albuf->mFmtType == FmtShort)
+ Convert_ALshort_ALmsadpcm(static_cast<ALshort*>(dst),
+ static_cast<const al::byte*>(data), num_chans, samplen, align);
+ else
+ {
+ assert(long{usrfmt->type} == long{albuf->mFmtType});
+ memcpy(dst, data, size_t{samplen} * frame_size);
+ }
+ }
+ }
+}
+END_API_FUNC
+
+
+AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint /*buffer*/, ALuint /*samplerate*/,
+ ALenum /*internalformat*/, ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/,
+ const ALvoid* /*data*/)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ context->setError(AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported");
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/,
+ ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, const ALvoid* /*data*/)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ context->setError(AL_INVALID_OPERATION, "alBufferSubSamplesSOFT not supported");
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/,
+ ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, ALvoid* /*data*/)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ context->setError(AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported");
+}
+END_API_FUNC
+
+AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return AL_FALSE;
+
+ context->setError(AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported");
+ return AL_FALSE;
+}
+END_API_FUNC
+
+
+AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat /*value*/)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param,
+ ALfloat /*value1*/, ALfloat /*value2*/, ALfloat /*value3*/)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+
+AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ ALbuffer *albuf = LookupBuffer(device, buffer);
+ if UNLIKELY(!albuf)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else switch(param)
+ {
+ case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
+ if UNLIKELY(value < 0)
+ context->setError(AL_INVALID_VALUE, "Invalid unpack block alignment %d", value);
+ else
+ albuf->UnpackAlign = static_cast<ALuint>(value);
+ break;
+
+ case AL_PACK_BLOCK_ALIGNMENT_SOFT:
+ if UNLIKELY(value < 0)
+ context->setError(AL_INVALID_VALUE, "Invalid pack block alignment %d", value);
+ else
+ albuf->PackAlign = static_cast<ALuint>(value);
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param,
+ ALint /*value1*/, ALint /*value2*/, ALint /*value3*/)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values)
+START_API_FUNC
+{
+ if(values)
+ {
+ switch(param)
+ {
+ case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
+ case AL_PACK_BLOCK_ALIGNMENT_SOFT:
+ alBufferi(buffer, param, values[0]);
+ return;
+ }
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ ALbuffer *albuf = LookupBuffer(device, buffer);
+ if UNLIKELY(!albuf)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ case AL_LOOP_POINTS_SOFT:
+ if UNLIKELY(ReadRef(albuf->ref) != 0)
+ context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's loop points",
+ buffer);
+ else if UNLIKELY(values[0] < 0 || values[0] >= values[1]
+ || static_cast<ALuint>(values[1]) > albuf->SampleLen)
+ context->setError(AL_INVALID_VALUE, "Invalid loop point range %d -> %d on buffer %u",
+ values[0], values[1], buffer);
+ else
+ {
+ albuf->LoopStart = static_cast<ALuint>(values[0]);
+ albuf->LoopEnd = static_cast<ALuint>(values[1]);
+ }
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ ALbuffer *albuf = LookupBuffer(device, buffer);
+ if UNLIKELY(!albuf)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY(!value)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY(!value1 || !value2 || !value3)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_SEC_LENGTH_SOFT:
+ alGetBufferf(buffer, param, values);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+
+ if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+ ALbuffer *albuf = LookupBuffer(device, buffer);
+ if UNLIKELY(!albuf)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY(!value)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ case AL_FREQUENCY:
+ *value = static_cast<ALint>(albuf->Frequency);
+ break;
+
+ case AL_BITS:
+ *value = static_cast<ALint>(BytesFromFmt(albuf->mFmtType) * 8);
+ break;
+
+ case AL_CHANNELS:
+ *value = static_cast<ALint>(ChannelsFromFmt(albuf->mFmtChannels));
+ break;
+
+ case AL_SIZE:
+ *value = static_cast<ALint>(albuf->SampleLen *
+ FrameSizeFromFmt(albuf->mFmtChannels, albuf->mFmtType));
+ break;
+
+ case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
+ *value = static_cast<ALint>(albuf->UnpackAlign);
+ break;
+
+ case AL_PACK_BLOCK_ALIGNMENT_SOFT:
+ *value = static_cast<ALint>(albuf->PackAlign);
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+ if UNLIKELY(LookupBuffer(device, buffer) == nullptr)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY(!value1 || !value2 || !value3)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_FREQUENCY:
+ case AL_BITS:
+ case AL_CHANNELS:
+ case AL_SIZE:
+ case AL_INTERNAL_FORMAT_SOFT:
+ case AL_BYTE_LENGTH_SOFT:
+ case AL_SAMPLE_LENGTH_SOFT:
+ case AL_UNPACK_BLOCK_ALIGNMENT_SOFT:
+ case AL_PACK_BLOCK_ALIGNMENT_SOFT:
+ alGetBufferi(buffer, param, values);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->BufferLock};
+ ALbuffer *albuf = LookupBuffer(device, buffer);
+ if UNLIKELY(!albuf)
+ context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ case AL_LOOP_POINTS_SOFT:
+ values[0] = static_cast<ALint>(albuf->LoopStart);
+ values[1] = static_cast<ALint>(albuf->LoopEnd);
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", param);
+ }
+}
+END_API_FUNC
+
+
+ALuint BytesFromFmt(FmtType type)
+{
+ switch(type)
+ {
+ case FmtUByte: return sizeof(ALubyte);
+ case FmtShort: return sizeof(ALshort);
+ case FmtFloat: return sizeof(ALfloat);
+ case FmtDouble: return sizeof(ALdouble);
+ case FmtMulaw: return sizeof(ALubyte);
+ case FmtAlaw: return sizeof(ALubyte);
+ }
+ return 0;
+}
+ALuint ChannelsFromFmt(FmtChannels chans)
+{
+ 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 3;
+ case FmtBFormat3D: return 4;
+ }
+ return 0;
+}
+
+
+BufferSubList::~BufferSubList()
+{
+ uint64_t usemask{~FreeMask};
+ while(usemask)
+ {
+ ALsizei idx{CTZ64(usemask)};
+ al::destroy_at(Buffers+idx);
+ usemask &= ~(1_u64 << idx);
+ }
+ FreeMask = ~usemask;
+ al_free(Buffers);
+ Buffers = nullptr;
+}
diff --git a/al/buffer.h b/al/buffer.h
new file mode 100644
index 00000000..d41eec5d
--- /dev/null
+++ b/al/buffer.h
@@ -0,0 +1,99 @@
+#ifndef AL_BUFFER_H
+#define AL_BUFFER_H
+
+#include <atomic>
+
+#include "AL/al.h"
+
+#include "albyte.h"
+#include "almalloc.h"
+#include "atomic.h"
+#include "inprogext.h"
+#include "vector.h"
+
+
+/* User formats */
+enum UserFmtType : unsigned char {
+ UserFmtUByte,
+ UserFmtShort,
+ UserFmtFloat,
+ UserFmtDouble,
+ UserFmtMulaw,
+ UserFmtAlaw,
+ UserFmtIMA4,
+ UserFmtMSADPCM,
+};
+enum UserFmtChannels : unsigned char {
+ UserFmtMono,
+ UserFmtStereo,
+ UserFmtRear,
+ UserFmtQuad,
+ UserFmtX51, /* (WFX order) */
+ UserFmtX61, /* (WFX order) */
+ UserFmtX71, /* (WFX order) */
+ UserFmtBFormat2D, /* WXY */
+ UserFmtBFormat3D, /* WXYZ */
+};
+
+
+/* Storable formats */
+enum FmtType : unsigned char {
+ FmtUByte = UserFmtUByte,
+ FmtShort = UserFmtShort,
+ FmtFloat = UserFmtFloat,
+ FmtDouble = UserFmtDouble,
+ FmtMulaw = UserFmtMulaw,
+ FmtAlaw = UserFmtAlaw,
+};
+enum FmtChannels : unsigned char {
+ FmtMono = UserFmtMono,
+ FmtStereo = UserFmtStereo,
+ FmtRear = UserFmtRear,
+ FmtQuad = UserFmtQuad,
+ FmtX51 = UserFmtX51,
+ FmtX61 = UserFmtX61,
+ FmtX71 = UserFmtX71,
+ FmtBFormat2D = UserFmtBFormat2D,
+ FmtBFormat3D = UserFmtBFormat3D,
+};
+#define MAX_INPUT_CHANNELS (8)
+
+
+ALuint BytesFromFmt(FmtType type);
+ALuint ChannelsFromFmt(FmtChannels chans);
+inline ALuint FrameSizeFromFmt(FmtChannels chans, FmtType type)
+{ return ChannelsFromFmt(chans) * BytesFromFmt(type); }
+
+
+struct ALbuffer {
+ al::vector<al::byte,16> mData;
+
+ ALuint Frequency{0u};
+ ALbitfieldSOFT Access{0u};
+ ALuint SampleLen{0u};
+
+ FmtChannels mFmtChannels{};
+ FmtType mFmtType{};
+
+ UserFmtType OriginalType{};
+ ALuint OriginalSize{0};
+ ALuint OriginalAlign{0};
+
+ ALuint LoopStart{0u};
+ ALuint LoopEnd{0u};
+
+ ALuint UnpackAlign{0};
+ ALuint PackAlign{0};
+
+ ALbitfieldSOFT MappedAccess{0u};
+ ALsizei MappedOffset{0};
+ ALsizei MappedSize{0};
+
+ /* Number of times buffer was attached to a source (deletion can only occur when 0) */
+ RefCount ref{0u};
+
+ /* Self ID */
+ ALuint id{0};
+};
+
+#endif
diff --git a/al/effect.cpp b/al/effect.cpp
new file mode 100644
index 00000000..f7d17f50
--- /dev/null
+++ b/al/effect.cpp
@@ -0,0 +1,725 @@
+/**
+ * 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 "effect.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <numeric>
+#include <utility>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+#include "AL/efx-presets.h"
+#include "AL/efx.h"
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alstring.h"
+#include "effects/base.h"
+#include "logging.h"
+#include "opthelpers.h"
+#include "vector.h"
+
+
+const EffectList gEffectList[15]{
+ { "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB },
+ { "reverb", REVERB_EFFECT, AL_EFFECT_REVERB },
+ { "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH },
+ { "chorus", CHORUS_EFFECT, AL_EFFECT_CHORUS },
+ { "compressor", COMPRESSOR_EFFECT, AL_EFFECT_COMPRESSOR },
+ { "distortion", DISTORTION_EFFECT, AL_EFFECT_DISTORTION },
+ { "echo", ECHO_EFFECT, AL_EFFECT_ECHO },
+ { "equalizer", EQUALIZER_EFFECT, AL_EFFECT_EQUALIZER },
+ { "flanger", FLANGER_EFFECT, AL_EFFECT_FLANGER },
+ { "fshifter", FSHIFTER_EFFECT, AL_EFFECT_FREQUENCY_SHIFTER },
+ { "modulator", MODULATOR_EFFECT, AL_EFFECT_RING_MODULATOR },
+ { "pshifter", PSHIFTER_EFFECT, AL_EFFECT_PITCH_SHIFTER },
+ { "vmorpher", VMORPHER_EFFECT, AL_EFFECT_VOCAL_MORPHER },
+ { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT },
+ { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE },
+};
+
+ALboolean DisabledEffects[MAX_EFFECTS];
+
+namespace {
+
+constexpr struct FactoryItem {
+ ALenum Type;
+ EffectStateFactory* (&GetFactory)(void);
+} FactoryList[] = {
+ { AL_EFFECT_NULL, NullStateFactory_getFactory },
+ { AL_EFFECT_EAXREVERB, ReverbStateFactory_getFactory },
+ { AL_EFFECT_REVERB, StdReverbStateFactory_getFactory },
+ { AL_EFFECT_AUTOWAH, AutowahStateFactory_getFactory },
+ { AL_EFFECT_CHORUS, ChorusStateFactory_getFactory },
+ { AL_EFFECT_COMPRESSOR, CompressorStateFactory_getFactory },
+ { AL_EFFECT_DISTORTION, DistortionStateFactory_getFactory },
+ { AL_EFFECT_ECHO, EchoStateFactory_getFactory },
+ { AL_EFFECT_EQUALIZER, EqualizerStateFactory_getFactory },
+ { AL_EFFECT_FLANGER, FlangerStateFactory_getFactory },
+ { AL_EFFECT_FREQUENCY_SHIFTER, FshifterStateFactory_getFactory },
+ { AL_EFFECT_RING_MODULATOR, ModulatorStateFactory_getFactory },
+ { AL_EFFECT_PITCH_SHIFTER, PshifterStateFactory_getFactory},
+ { AL_EFFECT_VOCAL_MORPHER, VmorpherStateFactory_getFactory},
+ { AL_EFFECT_DEDICATED_DIALOGUE, DedicatedStateFactory_getFactory },
+ { AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedStateFactory_getFactory }
+};
+
+
+template<typename... T>
+void ALeffect_setParami(ALeffect *effect, T&& ...args)
+{ effect->vtab->setParami(&effect->Props, std::forward<T>(args)...); }
+template<typename... T>
+void ALeffect_setParamiv(ALeffect *effect, T&& ...args)
+{ effect->vtab->setParamiv(&effect->Props, std::forward<T>(args)...); }
+template<typename... T>
+void ALeffect_setParamf(ALeffect *effect, T&& ...args)
+{ effect->vtab->setParamf(&effect->Props, std::forward<T>(args)...); }
+template<typename... T>
+void ALeffect_setParamfv(ALeffect *effect, T&& ...args)
+{ effect->vtab->setParamfv(&effect->Props, std::forward<T>(args)...); }
+
+template<typename... T>
+void ALeffect_getParami(const ALeffect *effect, T&& ...args)
+{ effect->vtab->getParami(&effect->Props, std::forward<T>(args)...); }
+template<typename... T>
+void ALeffect_getParamiv(const ALeffect *effect, T&& ...args)
+{ effect->vtab->getParamiv(&effect->Props, std::forward<T>(args)...); }
+template<typename... T>
+void ALeffect_getParamf(const ALeffect *effect, T&& ...args)
+{ effect->vtab->getParamf(&effect->Props, std::forward<T>(args)...); }
+template<typename... T>
+void ALeffect_getParamfv(const ALeffect *effect, T&& ...args)
+{ effect->vtab->getParamfv(&effect->Props, std::forward<T>(args)...); }
+
+
+void InitEffectParams(ALeffect *effect, ALenum type)
+{
+ EffectStateFactory *factory = getFactoryByType(type);
+ if(factory)
+ {
+ effect->Props = factory->getDefaultProps();
+ effect->vtab = factory->getEffectVtable();
+ }
+ else
+ {
+ effect->Props = EffectProps{};
+ effect->vtab = nullptr;
+ }
+ effect->type = type;
+}
+
+bool EnsureEffects(ALCdevice *device, size_t needed)
+{
+ size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), size_t{0},
+ [](size_t cur, const EffectSubList &sublist) noexcept -> size_t
+ { return cur + static_cast<ALuint>(POPCNT64(sublist.FreeMask)); }
+ )};
+
+ while(needed > count)
+ {
+ if UNLIKELY(device->EffectList.size() >= 1<<25)
+ return false;
+
+ device->EffectList.emplace_back();
+ auto sublist = device->EffectList.end() - 1;
+ sublist->FreeMask = ~0_u64;
+ sublist->Effects = static_cast<ALeffect*>(al_calloc(alignof(ALeffect), sizeof(ALeffect)*64));
+ if UNLIKELY(!sublist->Effects)
+ {
+ device->EffectList.pop_back();
+ return false;
+ }
+ count += 64;
+ }
+ return true;
+}
+
+ALeffect *AllocEffect(ALCdevice *device)
+{
+ auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(),
+ [](const EffectSubList &entry) noexcept -> bool
+ { return entry.FreeMask != 0; }
+ );
+ auto lidx = static_cast<ALuint>(std::distance(device->EffectList.begin(), sublist));
+ auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
+
+ ALeffect *effect{::new (sublist->Effects + slidx) ALeffect{}};
+ InitEffectParams(effect, AL_EFFECT_NULL);
+
+ /* Add 1 to avoid effect ID 0. */
+ effect->id = ((lidx<<6) | slidx) + 1;
+
+ sublist->FreeMask &= ~(1_u64 << slidx);
+
+ return effect;
+}
+
+void FreeEffect(ALCdevice *device, ALeffect *effect)
+{
+ const ALuint id{effect->id - 1};
+ const size_t lidx{id >> 6};
+ const ALuint slidx{id & 0x3f};
+
+ al::destroy_at(effect);
+
+ device->EffectList[lidx].FreeMask |= 1_u64 << slidx;
+}
+
+inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
+{
+ const size_t lidx{(id-1) >> 6};
+ const ALuint slidx{(id-1) & 0x3f};
+
+ if UNLIKELY(lidx >= device->EffectList.size())
+ return nullptr;
+ EffectSubList &sublist = device->EffectList[lidx];
+ if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
+ return nullptr;
+ return sublist.Effects + slidx;
+}
+
+} // namespace
+
+AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Generating %d effects", n);
+ if UNLIKELY(n <= 0) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+ if(!EnsureEffects(device, static_cast<ALuint>(n)))
+ {
+ context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, (n==1)?"":"s");
+ return;
+ }
+
+ if LIKELY(n == 1)
+ {
+ /* Special handling for the easy and normal case. */
+ ALeffect *effect{AllocEffect(device)};
+ effects[0] = effect->id;
+ }
+ else
+ {
+ /* Store the allocated buffer IDs in a separate local list, to avoid
+ * modifying the user storage in case of failure.
+ */
+ al::vector<ALuint> ids;
+ ids.reserve(static_cast<ALuint>(n));
+ do {
+ ALeffect *effect{AllocEffect(device)};
+ ids.emplace_back(effect->id);
+ } while(--n);
+ std::copy(ids.cbegin(), ids.cend(), effects);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Deleting %d effects", n);
+ if UNLIKELY(n <= 0) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+
+ /* First try to find any effects that are invalid. */
+ auto validate_effect = [device](const ALuint eid) -> bool
+ { return !eid || LookupEffect(device, eid) != nullptr; };
+
+ const ALuint *effects_end = effects + n;
+ auto inveffect = std::find_if_not(effects, effects_end, validate_effect);
+ if UNLIKELY(inveffect != effects_end)
+ {
+ context->setError(AL_INVALID_NAME, "Invalid effect ID %u", *inveffect);
+ return;
+ }
+
+ /* All good. Delete non-0 effect IDs. */
+ auto delete_effect = [device](ALuint eid) -> void
+ {
+ ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr};
+ if(effect) FreeEffect(device, effect);
+ };
+ std::for_each(effects, effects_end, delete_effect);
+}
+END_API_FUNC
+
+AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if LIKELY(context)
+ {
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+ if(!effect || LookupEffect(device, effect))
+ return AL_TRUE;
+ }
+ return AL_FALSE;
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+
+ ALeffect *aleffect{LookupEffect(device, effect)};
+ if UNLIKELY(!aleffect)
+ context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
+ else
+ {
+ if(param == AL_EFFECT_TYPE)
+ {
+ ALboolean isOk{value == AL_EFFECT_NULL};
+ if(!isOk)
+ {
+ for(const EffectList &effectitem : gEffectList)
+ {
+ if(value == effectitem.val && !DisabledEffects[effectitem.type])
+ {
+ isOk = AL_TRUE;
+ break;
+ }
+ }
+ }
+
+ if(isOk)
+ InitEffectParams(aleffect, value);
+ else
+ context->setError(AL_INVALID_VALUE, "Effect type 0x%04x not supported", value);
+ }
+ else
+ {
+ /* Call the appropriate handler */
+ ALeffect_setParami(aleffect, context.get(), param, value);
+ }
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_EFFECT_TYPE:
+ alEffecti(effect, param, values[0]);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+
+ ALeffect *aleffect{LookupEffect(device, effect)};
+ if UNLIKELY(!aleffect)
+ context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
+ else
+ {
+ /* Call the appropriate handler */
+ ALeffect_setParamiv(aleffect, context.get(), param, values);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+
+ ALeffect *aleffect{LookupEffect(device, effect)};
+ if UNLIKELY(!aleffect)
+ context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
+ else
+ {
+ /* Call the appropriate handler */
+ ALeffect_setParamf(aleffect, context.get(), param, value);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+
+ ALeffect *aleffect{LookupEffect(device, effect)};
+ if UNLIKELY(!aleffect)
+ context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
+ else
+ {
+ /* Call the appropriate handler */
+ ALeffect_setParamfv(aleffect, context.get(), param, values);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+
+ const ALeffect *aleffect{LookupEffect(device, effect)};
+ if UNLIKELY(!aleffect)
+ context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
+ else
+ {
+ if(param == AL_EFFECT_TYPE)
+ *value = aleffect->type;
+ else
+ {
+ /* Call the appropriate handler */
+ ALeffect_getParami(aleffect, context.get(), param, value);
+ }
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_EFFECT_TYPE:
+ alGetEffecti(effect, param, values);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+
+ const ALeffect *aleffect{LookupEffect(device, effect)};
+ if UNLIKELY(!aleffect)
+ context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
+ else
+ {
+ /* Call the appropriate handler */
+ ALeffect_getParamiv(aleffect, context.get(), param, values);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+
+ const ALeffect *aleffect{LookupEffect(device, effect)};
+ if UNLIKELY(!aleffect)
+ context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
+ else
+ {
+ /* Call the appropriate handler */
+ ALeffect_getParamf(aleffect, context.get(), param, value);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+
+ const ALeffect *aleffect{LookupEffect(device, effect)};
+ if UNLIKELY(!aleffect)
+ context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
+ else
+ {
+ /* Call the appropriate handler */
+ ALeffect_getParamfv(aleffect, context.get(), param, values);
+ }
+}
+END_API_FUNC
+
+
+void InitEffect(ALeffect *effect)
+{
+ InitEffectParams(effect, AL_EFFECT_NULL);
+}
+
+EffectSubList::~EffectSubList()
+{
+ uint64_t usemask{~FreeMask};
+ while(usemask)
+ {
+ ALsizei idx = CTZ64(usemask);
+ al::destroy_at(Effects+idx);
+ usemask &= ~(1_u64 << idx);
+ }
+ FreeMask = ~usemask;
+ al_free(Effects);
+ Effects = nullptr;
+}
+
+
+EffectStateFactory *getFactoryByType(ALenum type)
+{
+ auto iter = std::find_if(std::begin(FactoryList), std::end(FactoryList),
+ [type](const FactoryItem &item) noexcept -> bool
+ { return item.Type == type; }
+ );
+ return (iter != std::end(FactoryList)) ? iter->GetFactory() : nullptr;
+}
+
+
+#define DECL(x) { #x, EFX_REVERB_PRESET_##x }
+static const struct {
+ const char name[32];
+ EFXEAXREVERBPROPERTIES props;
+} reverblist[] = {
+ DECL(GENERIC),
+ DECL(PADDEDCELL),
+ DECL(ROOM),
+ DECL(BATHROOM),
+ DECL(LIVINGROOM),
+ DECL(STONEROOM),
+ DECL(AUDITORIUM),
+ DECL(CONCERTHALL),
+ DECL(CAVE),
+ DECL(ARENA),
+ DECL(HANGAR),
+ DECL(CARPETEDHALLWAY),
+ DECL(HALLWAY),
+ DECL(STONECORRIDOR),
+ DECL(ALLEY),
+ DECL(FOREST),
+ DECL(CITY),
+ DECL(MOUNTAINS),
+ DECL(QUARRY),
+ DECL(PLAIN),
+ DECL(PARKINGLOT),
+ DECL(SEWERPIPE),
+ DECL(UNDERWATER),
+ DECL(DRUGGED),
+ DECL(DIZZY),
+ DECL(PSYCHOTIC),
+
+ DECL(CASTLE_SMALLROOM),
+ DECL(CASTLE_SHORTPASSAGE),
+ DECL(CASTLE_MEDIUMROOM),
+ DECL(CASTLE_LARGEROOM),
+ DECL(CASTLE_LONGPASSAGE),
+ DECL(CASTLE_HALL),
+ DECL(CASTLE_CUPBOARD),
+ DECL(CASTLE_COURTYARD),
+ DECL(CASTLE_ALCOVE),
+
+ DECL(FACTORY_SMALLROOM),
+ DECL(FACTORY_SHORTPASSAGE),
+ DECL(FACTORY_MEDIUMROOM),
+ DECL(FACTORY_LARGEROOM),
+ DECL(FACTORY_LONGPASSAGE),
+ DECL(FACTORY_HALL),
+ DECL(FACTORY_CUPBOARD),
+ DECL(FACTORY_COURTYARD),
+ DECL(FACTORY_ALCOVE),
+
+ DECL(ICEPALACE_SMALLROOM),
+ DECL(ICEPALACE_SHORTPASSAGE),
+ DECL(ICEPALACE_MEDIUMROOM),
+ DECL(ICEPALACE_LARGEROOM),
+ DECL(ICEPALACE_LONGPASSAGE),
+ DECL(ICEPALACE_HALL),
+ DECL(ICEPALACE_CUPBOARD),
+ DECL(ICEPALACE_COURTYARD),
+ DECL(ICEPALACE_ALCOVE),
+
+ DECL(SPACESTATION_SMALLROOM),
+ DECL(SPACESTATION_SHORTPASSAGE),
+ DECL(SPACESTATION_MEDIUMROOM),
+ DECL(SPACESTATION_LARGEROOM),
+ DECL(SPACESTATION_LONGPASSAGE),
+ DECL(SPACESTATION_HALL),
+ DECL(SPACESTATION_CUPBOARD),
+ DECL(SPACESTATION_ALCOVE),
+
+ DECL(WOODEN_SMALLROOM),
+ DECL(WOODEN_SHORTPASSAGE),
+ DECL(WOODEN_MEDIUMROOM),
+ DECL(WOODEN_LARGEROOM),
+ DECL(WOODEN_LONGPASSAGE),
+ DECL(WOODEN_HALL),
+ DECL(WOODEN_CUPBOARD),
+ DECL(WOODEN_COURTYARD),
+ DECL(WOODEN_ALCOVE),
+
+ DECL(SPORT_EMPTYSTADIUM),
+ DECL(SPORT_SQUASHCOURT),
+ DECL(SPORT_SMALLSWIMMINGPOOL),
+ DECL(SPORT_LARGESWIMMINGPOOL),
+ DECL(SPORT_GYMNASIUM),
+ DECL(SPORT_FULLSTADIUM),
+ DECL(SPORT_STADIUMTANNOY),
+
+ DECL(PREFAB_WORKSHOP),
+ DECL(PREFAB_SCHOOLROOM),
+ DECL(PREFAB_PRACTISEROOM),
+ DECL(PREFAB_OUTHOUSE),
+ DECL(PREFAB_CARAVAN),
+
+ DECL(DOME_TOMB),
+ DECL(PIPE_SMALL),
+ DECL(DOME_SAINTPAULS),
+ DECL(PIPE_LONGTHIN),
+ DECL(PIPE_LARGE),
+ DECL(PIPE_RESONANT),
+
+ DECL(OUTDOORS_BACKYARD),
+ DECL(OUTDOORS_ROLLINGPLAINS),
+ DECL(OUTDOORS_DEEPCANYON),
+ DECL(OUTDOORS_CREEK),
+ DECL(OUTDOORS_VALLEY),
+
+ DECL(MOOD_HEAVEN),
+ DECL(MOOD_HELL),
+ DECL(MOOD_MEMORY),
+
+ DECL(DRIVING_COMMENTATOR),
+ DECL(DRIVING_PITGARAGE),
+ DECL(DRIVING_INCAR_RACER),
+ DECL(DRIVING_INCAR_SPORTS),
+ DECL(DRIVING_INCAR_LUXURY),
+ DECL(DRIVING_FULLGRANDSTAND),
+ DECL(DRIVING_EMPTYGRANDSTAND),
+ DECL(DRIVING_TUNNEL),
+
+ DECL(CITY_STREETS),
+ DECL(CITY_SUBWAY),
+ DECL(CITY_MUSEUM),
+ DECL(CITY_LIBRARY),
+ DECL(CITY_UNDERPASS),
+ DECL(CITY_ABANDONED),
+
+ DECL(DUSTYROOM),
+ DECL(CHAPEL),
+ DECL(SMALLWATERROOM),
+};
+#undef DECL
+
+void LoadReverbPreset(const char *name, ALeffect *effect)
+{
+ if(al::strcasecmp(name, "NONE") == 0)
+ {
+ InitEffectParams(effect, AL_EFFECT_NULL);
+ TRACE("Loading reverb '%s'\n", "NONE");
+ return;
+ }
+
+ if(!DisabledEffects[EAXREVERB_EFFECT])
+ InitEffectParams(effect, AL_EFFECT_EAXREVERB);
+ else if(!DisabledEffects[REVERB_EFFECT])
+ InitEffectParams(effect, AL_EFFECT_REVERB);
+ else
+ InitEffectParams(effect, AL_EFFECT_NULL);
+ for(const auto &reverbitem : reverblist)
+ {
+ const EFXEAXREVERBPROPERTIES *props;
+
+ if(al::strcasecmp(name, reverbitem.name) != 0)
+ continue;
+
+ TRACE("Loading reverb '%s'\n", reverbitem.name);
+ props = &reverbitem.props;
+ effect->Props.Reverb.Density = props->flDensity;
+ effect->Props.Reverb.Diffusion = props->flDiffusion;
+ effect->Props.Reverb.Gain = props->flGain;
+ effect->Props.Reverb.GainHF = props->flGainHF;
+ effect->Props.Reverb.GainLF = props->flGainLF;
+ effect->Props.Reverb.DecayTime = props->flDecayTime;
+ effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio;
+ effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio;
+ effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain;
+ effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay;
+ effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0];
+ effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1];
+ effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2];
+ effect->Props.Reverb.LateReverbGain = props->flLateReverbGain;
+ effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay;
+ effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0];
+ effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1];
+ effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2];
+ effect->Props.Reverb.EchoTime = props->flEchoTime;
+ effect->Props.Reverb.EchoDepth = props->flEchoDepth;
+ effect->Props.Reverb.ModulationTime = props->flModulationTime;
+ effect->Props.Reverb.ModulationDepth = props->flModulationDepth;
+ effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF;
+ effect->Props.Reverb.HFReference = props->flHFReference;
+ effect->Props.Reverb.LFReference = props->flLFReference;
+ effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor;
+ effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit ? AL_TRUE : AL_FALSE;
+ return;
+ }
+
+ WARN("Reverb preset '%s' not found\n", name);
+}
diff --git a/al/effect.h b/al/effect.h
new file mode 100644
index 00000000..270b8e20
--- /dev/null
+++ b/al/effect.h
@@ -0,0 +1,61 @@
+#ifndef AL_EFFECT_H
+#define AL_EFFECT_H
+
+#include "AL/al.h"
+#include "AL/efx.h"
+
+#include "effects/base.h"
+
+
+enum {
+ EAXREVERB_EFFECT = 0,
+ REVERB_EFFECT,
+ AUTOWAH_EFFECT,
+ CHORUS_EFFECT,
+ COMPRESSOR_EFFECT,
+ DISTORTION_EFFECT,
+ ECHO_EFFECT,
+ EQUALIZER_EFFECT,
+ FLANGER_EFFECT,
+ FSHIFTER_EFFECT,
+ MODULATOR_EFFECT,
+ PSHIFTER_EFFECT,
+ VMORPHER_EFFECT,
+ DEDICATED_EFFECT,
+
+ MAX_EFFECTS
+};
+extern ALboolean DisabledEffects[MAX_EFFECTS];
+
+extern ALfloat ReverbBoost;
+
+struct EffectList {
+ const char name[16];
+ int type;
+ ALenum val;
+};
+extern const EffectList gEffectList[15];
+
+
+struct ALeffect {
+ // Effect type (AL_EFFECT_NULL, ...)
+ ALenum type{AL_EFFECT_NULL};
+
+ EffectProps Props{};
+
+ const EffectVtable *vtab{nullptr};
+
+ /* Self ID */
+ ALuint id{0u};
+};
+
+inline ALboolean IsReverbEffect(ALenum type)
+{ return type == AL_EFFECT_REVERB || type == AL_EFFECT_EAXREVERB; }
+
+EffectStateFactory *getFactoryByType(ALenum type);
+
+void InitEffect(ALeffect *effect);
+
+void LoadReverbPreset(const char *name, ALeffect *effect);
+
+#endif
diff --git a/OpenAL32/alError.c b/al/error.cpp
index b6208f77..b667d14f 100644
--- a/OpenAL32/alError.c
+++ b/al/error.cpp
@@ -20,46 +20,55 @@
#include "config.h"
-#include <signal.h>
-#include <stdarg.h>
-
-#ifdef HAVE_WINDOWS_H
+#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
-#include "alMain.h"
+#include <atomic>
+#include <csignal>
+#include <cstdarg>
+#include <cstdio>
+#include <cstring>
+#include <mutex>
+
+#include "AL/al.h"
#include "AL/alc.h"
-#include "alError.h"
-ALboolean TrapALError = AL_FALSE;
+#include "alcontext.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "event.h"
+#include "inprogext.h"
+#include "logging.h"
+#include "opthelpers.h"
+#include "vector.h"
+
-void alSetError(ALCcontext *context, ALenum errorCode, const char *msg, ...)
+bool TrapALError{false};
+
+void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
{
- ALenum curerr = AL_NO_ERROR;
- char message[1024] = { 0 };
- va_list args;
- int msglen;
+ auto message = al::vector<char>(256);
+ va_list args, args2;
va_start(args, msg);
- msglen = vsnprintf(message, sizeof(message), msg, args);
- va_end(args);
-
- if(msglen < 0 || (size_t)msglen >= sizeof(message))
+ va_copy(args2, args);
+ int msglen{std::vsnprintf(message.data(), message.size(), msg, args)};
+ if(msglen >= 0 && static_cast<size_t>(msglen) >= message.size())
{
- message[sizeof(message)-1] = 0;
- msglen = (int)strlen(message);
- }
- if(msglen > 0)
- msg = message;
- else
- {
- msg = "<internal error constructing message>";
- msglen = (int)strlen(msg);
+ message.resize(static_cast<size_t>(msglen) + 1u);
+ msglen = std::vsnprintf(message.data(), message.size(), msg, args2);
}
+ va_end(args2);
+ va_end(args);
+
+ if(msglen >= 0) msg = message.data();
+ else msg = "<internal error constructing message>";
+ msglen = static_cast<int>(strlen(msg));
WARN("Error generated on context %p, code 0x%04x, \"%s\"\n",
- context, errorCode, message);
+ decltype(std::declval<void*>()){this}, errorCode, msg);
if(TrapALError)
{
#ifdef _WIN32
@@ -71,28 +80,25 @@ void alSetError(ALCcontext *context, ALenum errorCode, const char *msg, ...)
#endif
}
- ATOMIC_COMPARE_EXCHANGE_STRONG_SEQ(&context->LastError, &curerr, errorCode);
- if((ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed)&EventType_Error))
+ ALenum curerr{AL_NO_ERROR};
+ mLastError.compare_exchange_strong(curerr, errorCode);
+ if((mEnabledEvts.load(std::memory_order_relaxed)&EventType_Error))
{
- ALbitfieldSOFT enabledevts;
- almtx_lock(&context->EventCbLock);
- enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_relaxed);
- if((enabledevts&EventType_Error) && context->EventCb)
- (*context->EventCb)(AL_EVENT_TYPE_ERROR_SOFT, 0, errorCode, msglen, msg,
- context->EventParam);
- almtx_unlock(&context->EventCbLock);
+ std::lock_guard<std::mutex> _{mEventCbLock};
+ ALbitfieldSOFT enabledevts{mEnabledEvts.load(std::memory_order_relaxed)};
+ if((enabledevts&EventType_Error) && mEventCb)
+ (*mEventCb)(AL_EVENT_TYPE_ERROR_SOFT, 0, static_cast<ALuint>(errorCode), msglen, msg,
+ mEventParam);
}
}
AL_API ALenum AL_APIENTRY alGetError(void)
+START_API_FUNC
{
- ALCcontext *context;
- ALenum errorCode;
-
- context = GetContextRef();
- if(!context)
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context)
{
- const ALenum deferror = AL_INVALID_OPERATION;
+ constexpr ALenum deferror{AL_INVALID_OPERATION};
WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror);
if(TrapALError)
{
@@ -106,8 +112,6 @@ AL_API ALenum AL_APIENTRY alGetError(void)
return deferror;
}
- errorCode = ATOMIC_EXCHANGE_SEQ(&context->LastError, AL_NO_ERROR);
-
- ALCcontext_DecRef(context);
- return errorCode;
+ return context->mLastError.exchange(AL_NO_ERROR);
}
+END_API_FUNC
diff --git a/al/event.cpp b/al/event.cpp
new file mode 100644
index 00000000..0da48cbf
--- /dev/null
+++ b/al/event.cpp
@@ -0,0 +1,205 @@
+
+#include "config.h"
+
+#include "event.h"
+
+#include <algorithm>
+#include <atomic>
+#include <cstring>
+#include <exception>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+
+#include "albyte.h"
+#include "alcontext.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "effects/base.h"
+#include "inprogext.h"
+#include "logging.h"
+#include "opthelpers.h"
+#include "ringbuffer.h"
+#include "threads.h"
+
+
+static int EventThread(ALCcontext *context)
+{
+ RingBuffer *ring{context->mAsyncEvents.get()};
+ bool quitnow{false};
+ while LIKELY(!quitnow)
+ {
+ auto evt_data = ring->getReadVector().first;
+ if(evt_data.len == 0)
+ {
+ context->mEventSem.wait();
+ continue;
+ }
+
+ std::lock_guard<std::mutex> _{context->mEventCbLock};
+ do {
+ auto *evt_ptr = reinterpret_cast<AsyncEvent*>(evt_data.buf);
+ evt_data.buf += sizeof(AsyncEvent);
+ evt_data.len -= 1;
+
+ AsyncEvent evt{*evt_ptr};
+ al::destroy_at(evt_ptr);
+ ring->readAdvance(1);
+
+ quitnow = evt.EnumType == EventType_KillThread;
+ if UNLIKELY(quitnow) break;
+
+ if(evt.EnumType == EventType_ReleaseEffectState)
+ {
+ evt.u.mEffectState->release();
+ continue;
+ }
+
+ ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_acquire)};
+ if(!context->mEventCb) continue;
+
+ if(evt.EnumType == EventType_SourceStateChange)
+ {
+ if(!(enabledevts&EventType_SourceStateChange))
+ continue;
+ std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)};
+ msg += " state has changed to ";
+ msg += (evt.u.srcstate.state==AL_INITIAL) ? "AL_INITIAL" :
+ (evt.u.srcstate.state==AL_PLAYING) ? "AL_PLAYING" :
+ (evt.u.srcstate.state==AL_PAUSED) ? "AL_PAUSED" :
+ (evt.u.srcstate.state==AL_STOPPED) ? "AL_STOPPED" : "<unknown>";
+ context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id,
+ static_cast<ALuint>(evt.u.srcstate.state), static_cast<ALsizei>(msg.length()),
+ msg.c_str(), context->mEventParam);
+ }
+ else if(evt.EnumType == EventType_BufferCompleted)
+ {
+ if(!(enabledevts&EventType_BufferCompleted))
+ continue;
+ std::string msg{std::to_string(evt.u.bufcomp.count)};
+ if(evt.u.bufcomp.count == 1) msg += " buffer completed";
+ else msg += " buffers completed";
+ context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id,
+ evt.u.bufcomp.count, static_cast<ALsizei>(msg.length()), msg.c_str(),
+ context->mEventParam);
+ }
+ else if((enabledevts&evt.EnumType) == evt.EnumType)
+ context->mEventCb(evt.u.user.type, evt.u.user.id, evt.u.user.param,
+ static_cast<ALsizei>(strlen(evt.u.user.msg)), evt.u.user.msg,
+ context->mEventParam);
+ } while(evt_data.len != 0);
+ }
+ return 0;
+}
+
+void StartEventThrd(ALCcontext *ctx)
+{
+ try {
+ ctx->mEventThread = std::thread{EventThread, ctx};
+ }
+ catch(std::exception& e) {
+ ERR("Failed to start event thread: %s\n", e.what());
+ }
+ catch(...) {
+ ERR("Failed to start event thread! Expect problems.\n");
+ }
+}
+
+void StopEventThrd(ALCcontext *ctx)
+{
+ RingBuffer *ring{ctx->mAsyncEvents.get()};
+ auto evt_data = ring->getWriteVector().first;
+ if(evt_data.len == 0)
+ {
+ do {
+ std::this_thread::yield();
+ evt_data = ring->getWriteVector().first;
+ } while(evt_data.len == 0);
+ }
+ new (evt_data.buf) AsyncEvent{EventType_KillThread};
+ ring->writeAdvance(1);
+
+ ctx->mEventSem.post();
+ if(ctx->mEventThread.joinable())
+ ctx->mEventThread.join();
+}
+
+AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count);
+ if(count <= 0) return;
+ if(!types) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer");
+
+ ALbitfieldSOFT flags{0};
+ const ALenum *types_end = types+count;
+ auto bad_type = std::find_if_not(types, types_end,
+ [&flags](ALenum type) noexcept -> bool
+ {
+ if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT)
+ flags |= EventType_BufferCompleted;
+ else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT)
+ flags |= EventType_SourceStateChange;
+ else if(type == AL_EVENT_TYPE_ERROR_SOFT)
+ flags |= EventType_Error;
+ else if(type == AL_EVENT_TYPE_PERFORMANCE_SOFT)
+ flags |= EventType_Performance;
+ else if(type == AL_EVENT_TYPE_DEPRECATED_SOFT)
+ flags |= EventType_Deprecated;
+ else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT)
+ flags |= EventType_Disconnected;
+ else
+ return false;
+ return true;
+ }
+ );
+ if(bad_type != types_end)
+ SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid event type 0x%04x", *bad_type);
+
+ if(enable)
+ {
+ ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
+ while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts|flags,
+ std::memory_order_acq_rel, std::memory_order_acquire) == 0)
+ {
+ /* enabledevts is (re-)filled with the current value on failure, so
+ * just try again.
+ */
+ }
+ }
+ else
+ {
+ ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
+ while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts&~flags,
+ std::memory_order_acq_rel, std::memory_order_acquire) == 0)
+ {
+ }
+ /* Wait to ensure the event handler sees the changed flags before
+ * returning.
+ */
+ std::lock_guard<std::mutex>{context->mEventCbLock};
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mEventCbLock};
+ context->mEventCb = callback;
+ context->mEventParam = userParam;
+}
+END_API_FUNC
diff --git a/al/event.h b/al/event.h
new file mode 100644
index 00000000..9056d577
--- /dev/null
+++ b/al/event.h
@@ -0,0 +1,55 @@
+#ifndef AL_EVENT_H
+#define AL_EVENT_H
+
+#include "AL/al.h"
+#include "AL/alc.h"
+
+struct EffectState;
+
+
+enum {
+ /* End event thread processing. */
+ EventType_KillThread = 0,
+
+ /* User event types. */
+ EventType_SourceStateChange = 1<<0,
+ EventType_BufferCompleted = 1<<1,
+ EventType_Error = 1<<2,
+ EventType_Performance = 1<<3,
+ EventType_Deprecated = 1<<4,
+ EventType_Disconnected = 1<<5,
+
+ /* Internal events. */
+ EventType_ReleaseEffectState = 65536,
+};
+
+struct AsyncEvent {
+ unsigned int EnumType{0u};
+ union {
+ char dummy;
+ struct {
+ ALuint id;
+ ALenum state;
+ } srcstate;
+ struct {
+ ALuint id;
+ ALuint count;
+ } bufcomp;
+ struct {
+ ALenum type;
+ ALuint id;
+ ALuint param;
+ ALchar msg[232];
+ } user;
+ EffectState *mEffectState;
+ } u{};
+
+ AsyncEvent() noexcept = default;
+ constexpr AsyncEvent(unsigned int type) noexcept : EnumType{type} { }
+};
+
+
+void StartEventThrd(ALCcontext *ctx);
+void StopEventThrd(ALCcontext *ctx);
+
+#endif
diff --git a/OpenAL32/alExtension.c b/al/extension.cpp
index f6378c70..35c53136 100644
--- a/OpenAL32/alExtension.c
+++ b/al/extension.cpp
@@ -20,45 +20,36 @@
#include "config.h"
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
+#include <cctype>
+#include <cstdlib>
+#include <cstring>
-#include "alError.h"
-#include "alMain.h"
-#include "alFilter.h"
-#include "alEffect.h"
-#include "alAuxEffectSlot.h"
-#include "alSource.h"
-#include "alBuffer.h"
#include "AL/al.h"
#include "AL/alc.h"
+#include "alcontext.h"
+#include "alexcpt.h"
+#include "alstring.h"
+#include "opthelpers.h"
+
AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName)
+START_API_FUNC
{
- ALboolean ret = AL_FALSE;
- ALCcontext *context;
- const char *ptr;
- size_t len;
-
- context = GetContextRef();
- if(!context) return AL_FALSE;
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return AL_FALSE;
if(!extName)
- SETERR_GOTO(context, AL_INVALID_VALUE, done, "NULL pointer");
+ SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer");
- len = strlen(extName);
- ptr = context->ExtensionList;
+ size_t len{strlen(extName)};
+ const char *ptr{context->mExtensionList};
while(ptr && *ptr)
{
- if(strncasecmp(ptr, extName, len) == 0 &&
- (ptr[len] == '\0' || isspace(ptr[len])))
- {
- ret = AL_TRUE;
- break;
- }
- if((ptr=strchr(ptr, ' ')) != NULL)
+ if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len])))
+ return AL_TRUE;
+
+ if((ptr=strchr(ptr, ' ')) != nullptr)
{
do {
++ptr;
@@ -66,22 +57,23 @@ AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName)
}
}
-done:
- ALCcontext_DecRef(context);
- return ret;
+ return AL_FALSE;
}
+END_API_FUNC
AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName)
+START_API_FUNC
{
- if(!funcName)
- return NULL;
- return alcGetProcAddress(NULL, funcName);
+ if(!funcName) return nullptr;
+ return alcGetProcAddress(nullptr, funcName);
}
+END_API_FUNC
AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName)
+START_API_FUNC
{
- if(!enumName)
- return (ALenum)0;
- return alcGetEnumValue(NULL, enumName);
+ if(!enumName) return static_cast<ALenum>(0);
+ return alcGetEnumValue(nullptr, enumName);
}
+END_API_FUNC
diff --git a/al/filter.cpp b/al/filter.cpp
new file mode 100644
index 00000000..33887254
--- /dev/null
+++ b/al/filter.cpp
@@ -0,0 +1,650 @@
+/**
+ * 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 "filter.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <numeric>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/efx.h"
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "opthelpers.h"
+#include "vector.h"
+
+
+namespace {
+
+#define FILTER_MIN_GAIN 0.0f
+#define FILTER_MAX_GAIN 4.0f /* +12dB */
+
+void ALlowpass_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
+{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); }
+void ALlowpass_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); }
+void ALlowpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_LOWPASS_GAIN:
+ if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gain %f out of range", val);
+ filter->Gain = val;
+ break;
+
+ case AL_LOWPASS_GAINHF:
+ if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gainhf %f out of range", val);
+ filter->GainHF = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param);
+ }
+}
+void ALlowpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ ALlowpass_setParamf(filter, context, param, vals[0]); }
+
+void ALlowpass_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); }
+void ALlowpass_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); }
+void ALlowpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_LOWPASS_GAIN:
+ *val = filter->Gain;
+ break;
+
+ case AL_LOWPASS_GAINHF:
+ *val = filter->GainHF;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param);
+ }
+}
+void ALlowpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
+{ ALlowpass_getParamf(filter, context, param, vals); }
+
+DEFINE_ALFILTER_VTABLE(ALlowpass);
+
+
+void ALhighpass_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
+{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); }
+void ALhighpass_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); }
+void ALhighpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_HIGHPASS_GAIN:
+ if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gain out of range");
+ filter->Gain = val;
+ break;
+
+ case AL_HIGHPASS_GAINLF:
+ if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gainlf out of range");
+ filter->GainLF = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param);
+ }
+}
+void ALhighpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ ALhighpass_setParamf(filter, context, param, vals[0]); }
+
+void ALhighpass_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); }
+void ALhighpass_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); }
+void ALhighpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_HIGHPASS_GAIN:
+ *val = filter->Gain;
+ break;
+
+ case AL_HIGHPASS_GAINLF:
+ *val = filter->GainLF;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param);
+ }
+}
+void ALhighpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
+{ ALhighpass_getParamf(filter, context, param, vals); }
+
+DEFINE_ALFILTER_VTABLE(ALhighpass);
+
+
+void ALbandpass_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
+{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); }
+void ALbandpass_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); }
+void ALbandpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_BANDPASS_GAIN:
+ if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gain out of range");
+ filter->Gain = val;
+ break;
+
+ case AL_BANDPASS_GAINHF:
+ if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainhf out of range");
+ filter->GainHF = val;
+ break;
+
+ case AL_BANDPASS_GAINLF:
+ if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainlf out of range");
+ filter->GainLF = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param);
+ }
+}
+void ALbandpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ ALbandpass_setParamf(filter, context, param, vals[0]); }
+
+void ALbandpass_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); }
+void ALbandpass_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); }
+void ALbandpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_BANDPASS_GAIN:
+ *val = filter->Gain;
+ break;
+
+ case AL_BANDPASS_GAINHF:
+ *val = filter->GainHF;
+ break;
+
+ case AL_BANDPASS_GAINLF:
+ *val = filter->GainLF;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param);
+ }
+}
+void ALbandpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
+{ ALbandpass_getParamf(filter, context, param, vals); }
+
+DEFINE_ALFILTER_VTABLE(ALbandpass);
+
+
+void ALnullfilter_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
+{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
+void ALnullfilter_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
+void ALnullfilter_setParamf(ALfilter*, ALCcontext *context, ALenum param, ALfloat)
+{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
+void ALnullfilter_setParamfv(ALfilter*, ALCcontext *context, ALenum param, const ALfloat*)
+{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
+
+void ALnullfilter_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
+void ALnullfilter_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
+void ALnullfilter_getParamf(ALfilter*, ALCcontext *context, ALenum param, ALfloat*)
+{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
+void ALnullfilter_getParamfv(ALfilter*, ALCcontext *context, ALenum param, ALfloat*)
+{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
+
+DEFINE_ALFILTER_VTABLE(ALnullfilter);
+
+
+void InitFilterParams(ALfilter *filter, ALenum type)
+{
+ if(type == AL_FILTER_LOWPASS)
+ {
+ filter->Gain = AL_LOWPASS_DEFAULT_GAIN;
+ filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF;
+ filter->HFReference = LOWPASSFREQREF;
+ filter->GainLF = 1.0f;
+ filter->LFReference = HIGHPASSFREQREF;
+ filter->vtab = &ALlowpass_vtable;
+ }
+ else if(type == AL_FILTER_HIGHPASS)
+ {
+ filter->Gain = AL_HIGHPASS_DEFAULT_GAIN;
+ filter->GainHF = 1.0f;
+ filter->HFReference = LOWPASSFREQREF;
+ filter->GainLF = AL_HIGHPASS_DEFAULT_GAINLF;
+ filter->LFReference = HIGHPASSFREQREF;
+ filter->vtab = &ALhighpass_vtable;
+ }
+ else if(type == AL_FILTER_BANDPASS)
+ {
+ filter->Gain = AL_BANDPASS_DEFAULT_GAIN;
+ filter->GainHF = AL_BANDPASS_DEFAULT_GAINHF;
+ filter->HFReference = LOWPASSFREQREF;
+ filter->GainLF = AL_BANDPASS_DEFAULT_GAINLF;
+ filter->LFReference = HIGHPASSFREQREF;
+ filter->vtab = &ALbandpass_vtable;
+ }
+ else
+ {
+ filter->Gain = 1.0f;
+ filter->GainHF = 1.0f;
+ filter->HFReference = LOWPASSFREQREF;
+ filter->GainLF = 1.0f;
+ filter->LFReference = HIGHPASSFREQREF;
+ filter->vtab = &ALnullfilter_vtable;
+ }
+ filter->type = type;
+}
+
+bool EnsureFilters(ALCdevice *device, size_t needed)
+{
+ size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), size_t{0},
+ [](size_t cur, const FilterSubList &sublist) noexcept -> size_t
+ { return cur + static_cast<ALuint>(POPCNT64(sublist.FreeMask)); }
+ )};
+
+ while(needed > count)
+ {
+ if UNLIKELY(device->FilterList.size() >= 1<<25)
+ return false;
+
+ device->FilterList.emplace_back();
+ auto sublist = device->FilterList.end() - 1;
+ sublist->FreeMask = ~0_u64;
+ sublist->Filters = static_cast<ALfilter*>(al_calloc(alignof(ALfilter), sizeof(ALfilter)*64));
+ if UNLIKELY(!sublist->Filters)
+ {
+ device->FilterList.pop_back();
+ return false;
+ }
+ count += 64;
+ }
+ return true;
+}
+
+
+ALfilter *AllocFilter(ALCdevice *device)
+{
+ auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(),
+ [](const FilterSubList &entry) noexcept -> bool
+ { return entry.FreeMask != 0; }
+ );
+ auto lidx = static_cast<ALuint>(std::distance(device->FilterList.begin(), sublist));
+ auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
+
+ ALfilter *filter{::new (sublist->Filters + slidx) ALfilter{}};
+ InitFilterParams(filter, AL_FILTER_NULL);
+
+ /* Add 1 to avoid filter ID 0. */
+ filter->id = ((lidx<<6) | slidx) + 1;
+
+ sublist->FreeMask &= ~(1_u64 << slidx);
+
+ return filter;
+}
+
+void FreeFilter(ALCdevice *device, ALfilter *filter)
+{
+ const ALuint id{filter->id - 1};
+ const size_t lidx{id >> 6};
+ const ALuint slidx{id & 0x3f};
+
+ al::destroy_at(filter);
+
+ device->FilterList[lidx].FreeMask |= 1_u64 << slidx;
+}
+
+
+inline ALfilter *LookupFilter(ALCdevice *device, ALuint id)
+{
+ const size_t lidx{(id-1) >> 6};
+ const ALuint slidx{(id-1) & 0x3f};
+
+ if UNLIKELY(lidx >= device->FilterList.size())
+ return nullptr;
+ FilterSubList &sublist = device->FilterList[lidx];
+ if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
+ return nullptr;
+ return sublist.Filters + slidx;
+}
+
+} // namespace
+
+AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Generating %d filters", n);
+ if UNLIKELY(n <= 0) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->EffectLock};
+ if(!EnsureFilters(device, static_cast<ALuint>(n)))
+ {
+ context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, (n==1)?"":"s");
+ return;
+ }
+
+ if LIKELY(n == 1)
+ {
+ /* Special handling for the easy and normal case. */
+ ALfilter *filter{AllocFilter(device)};
+ if(filter) filters[0] = filter->id;
+ }
+ else
+ {
+ /* Store the allocated buffer IDs in a separate local list, to avoid
+ * modifying the user storage in case of failure.
+ */
+ al::vector<ALuint> ids;
+ ids.reserve(static_cast<ALuint>(n));
+ do {
+ ALfilter *filter{AllocFilter(device)};
+ ids.emplace_back(filter->id);
+ } while(--n);
+ std::copy(ids.begin(), ids.end(), filters);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Deleting %d filters", n);
+ if UNLIKELY(n <= 0) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->FilterLock};
+
+ /* First try to find any filters that are invalid. */
+ auto validate_filter = [device](const ALuint fid) -> bool
+ { return !fid || LookupFilter(device, fid) != nullptr; };
+
+ const ALuint *filters_end = filters + n;
+ auto invflt = std::find_if_not(filters, filters_end, validate_filter);
+ if UNLIKELY(invflt != filters_end)
+ {
+ context->setError(AL_INVALID_NAME, "Invalid filter ID %u", *invflt);
+ return;
+ }
+
+ /* All good. Delete non-0 filter IDs. */
+ auto delete_filter = [device](const ALuint fid) -> void
+ {
+ ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr};
+ if(filter) FreeFilter(device, filter);
+ };
+ std::for_each(filters, filters_end, delete_filter);
+}
+END_API_FUNC
+
+AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if LIKELY(context)
+ {
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->FilterLock};
+ if(!filter || LookupFilter(device, filter))
+ return AL_TRUE;
+ }
+ return AL_FALSE;
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->FilterLock};
+
+ ALfilter *alfilt{LookupFilter(device, filter)};
+ if UNLIKELY(!alfilt)
+ context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
+ else
+ {
+ if(param == AL_FILTER_TYPE)
+ {
+ if(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS ||
+ value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)
+ InitFilterParams(alfilt, value);
+ else
+ context->setError(AL_INVALID_VALUE, "Invalid filter type 0x%04x", value);
+ }
+ else
+ {
+ /* Call the appropriate handler */
+ ALfilter_setParami(alfilt, context.get(), param, value);
+ }
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_FILTER_TYPE:
+ alFilteri(filter, param, values[0]);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->FilterLock};
+
+ ALfilter *alfilt{LookupFilter(device, filter)};
+ if UNLIKELY(!alfilt)
+ context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
+ else
+ {
+ /* Call the appropriate handler */
+ ALfilter_setParamiv(alfilt, context.get(), param, values);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->FilterLock};
+
+ ALfilter *alfilt{LookupFilter(device, filter)};
+ if UNLIKELY(!alfilt)
+ context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
+ else
+ {
+ /* Call the appropriate handler */
+ ALfilter_setParamf(alfilt, context.get(), param, value);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->FilterLock};
+
+ ALfilter *alfilt{LookupFilter(device, filter)};
+ if UNLIKELY(!alfilt)
+ context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
+ else
+ {
+ /* Call the appropriate handler */
+ ALfilter_setParamfv(alfilt, context.get(), param, values);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->FilterLock};
+
+ ALfilter *alfilt{LookupFilter(device, filter)};
+ if UNLIKELY(!alfilt)
+ context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
+ else
+ {
+ if(param == AL_FILTER_TYPE)
+ *value = alfilt->type;
+ else
+ {
+ /* Call the appropriate handler */
+ ALfilter_getParami(alfilt, context.get(), param, value);
+ }
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_FILTER_TYPE:
+ alGetFilteri(filter, param, values);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->FilterLock};
+
+ ALfilter *alfilt{LookupFilter(device, filter)};
+ if UNLIKELY(!alfilt)
+ context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
+ else
+ {
+ /* Call the appropriate handler */
+ ALfilter_getParamiv(alfilt, context.get(), param, values);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->FilterLock};
+
+ ALfilter *alfilt{LookupFilter(device, filter)};
+ if UNLIKELY(!alfilt)
+ context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
+ else
+ {
+ /* Call the appropriate handler */
+ ALfilter_getParamf(alfilt, context.get(), param, value);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALCdevice *device{context->mDevice.get()};
+ std::lock_guard<std::mutex> _{device->FilterLock};
+
+ ALfilter *alfilt{LookupFilter(device, filter)};
+ if UNLIKELY(!alfilt)
+ context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
+ else
+ {
+ /* Call the appropriate handler */
+ ALfilter_getParamfv(alfilt, context.get(), param, values);
+ }
+}
+END_API_FUNC
+
+
+FilterSubList::~FilterSubList()
+{
+ uint64_t usemask{~FreeMask};
+ while(usemask)
+ {
+ ALsizei idx = CTZ64(usemask);
+ al::destroy_at(Filters+idx);
+ usemask &= ~(1_u64 << idx);
+ }
+ FreeMask = ~usemask;
+ al_free(Filters);
+ Filters = nullptr;
+}
diff --git a/al/filter.h b/al/filter.h
new file mode 100644
index 00000000..db098d70
--- /dev/null
+++ b/al/filter.h
@@ -0,0 +1,56 @@
+#ifndef AL_FILTER_H
+#define AL_FILTER_H
+
+#include "AL/al.h"
+#include "AL/alc.h"
+
+
+#define LOWPASSFREQREF (5000.0f)
+#define HIGHPASSFREQREF (250.0f)
+
+
+struct ALfilter;
+
+struct ALfilterVtable {
+ void (*const setParami)(ALfilter *filter, ALCcontext *context, ALenum param, ALint val);
+ void (*const setParamiv)(ALfilter *filter, ALCcontext *context, ALenum param, const ALint *vals);
+ void (*const setParamf)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val);
+ void (*const setParamfv)(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals);
+
+ void (*const getParami)(ALfilter *filter, ALCcontext *context, ALenum param, ALint *val);
+ void (*const getParamiv)(ALfilter *filter, ALCcontext *context, ALenum param, ALint *vals);
+ void (*const getParamf)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val);
+ void (*const getParamfv)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals);
+};
+
+#define DEFINE_ALFILTER_VTABLE(T) \
+const ALfilterVtable T##_vtable = { \
+ T##_setParami, T##_setParamiv, T##_setParamf, T##_setParamfv, \
+ T##_getParami, T##_getParamiv, T##_getParamf, T##_getParamfv, \
+}
+
+struct ALfilter {
+ // Filter type (AL_FILTER_NULL, ...)
+ ALenum type;
+
+ ALfloat Gain;
+ ALfloat GainHF;
+ ALfloat HFReference;
+ ALfloat GainLF;
+ ALfloat LFReference;
+
+ const ALfilterVtable *vtab;
+
+ /* Self ID */
+ ALuint id;
+};
+#define ALfilter_setParami(o, c, p, v) ((o)->vtab->setParami(o, c, p, v))
+#define ALfilter_setParamf(o, c, p, v) ((o)->vtab->setParamf(o, c, p, v))
+#define ALfilter_setParamiv(o, c, p, v) ((o)->vtab->setParamiv(o, c, p, v))
+#define ALfilter_setParamfv(o, c, p, v) ((o)->vtab->setParamfv(o, c, p, v))
+#define ALfilter_getParami(o, c, p, v) ((o)->vtab->getParami(o, c, p, v))
+#define ALfilter_getParamf(o, c, p, v) ((o)->vtab->getParamf(o, c, p, v))
+#define ALfilter_getParamiv(o, c, p, v) ((o)->vtab->getParamiv(o, c, p, v))
+#define ALfilter_getParamfv(o, c, p, v) ((o)->vtab->getParamfv(o, c, p, v))
+
+#endif
diff --git a/al/listener.cpp b/al/listener.cpp
new file mode 100644
index 00000000..7a14a9ba
--- /dev/null
+++ b/al/listener.cpp
@@ -0,0 +1,452 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 1999-2000 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 "listener.h"
+
+#include <cmath>
+#include <mutex>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/efx.h"
+
+#include "alcontext.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "atomic.h"
+#include "opthelpers.h"
+
+
+#define DO_UPDATEPROPS() do { \
+ if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
+ UpdateListenerProps(context.get()); \
+ else \
+ listener.PropsClean.clear(std::memory_order_release); \
+} while(0)
+
+
+AL_API ALvoid AL_APIENTRY alListenerf(ALenum param, ALfloat value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALlistener &listener = context->mListener;
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ switch(param)
+ {
+ case AL_GAIN:
+ if(!(value >= 0.0f && std::isfinite(value)))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range");
+ listener.Gain = value;
+ DO_UPDATEPROPS();
+ break;
+
+ case AL_METERS_PER_UNIT:
+ if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range");
+ listener.mMetersPerUnit = value;
+ DO_UPDATEPROPS();
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener float property");
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALlistener &listener = context->mListener;
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ switch(param)
+ {
+ case AL_POSITION:
+ if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener position out of range");
+ listener.Position[0] = value1;
+ listener.Position[1] = value2;
+ listener.Position[2] = value3;
+ DO_UPDATEPROPS();
+ break;
+
+ case AL_VELOCITY:
+ if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener velocity out of range");
+ listener.Velocity[0] = value1;
+ listener.Velocity[1] = value2;
+ listener.Velocity[2] = value3;
+ DO_UPDATEPROPS();
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values)
+START_API_FUNC
+{
+ if(values)
+ {
+ switch(param)
+ {
+ case AL_GAIN:
+ case AL_METERS_PER_UNIT:
+ alListenerf(param, values[0]);
+ return;
+
+ case AL_POSITION:
+ case AL_VELOCITY:
+ alListener3f(param, values[0], values[1], values[2]);
+ return;
+ }
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALlistener &listener = context->mListener;
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ if(!values) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer");
+ switch(param)
+ {
+ case AL_ORIENTATION:
+ if(!(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) &&
+ std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener orientation out of range");
+ /* AT then UP */
+ listener.OrientAt[0] = values[0];
+ listener.OrientAt[1] = values[1];
+ listener.OrientAt[2] = values[2];
+ listener.OrientUp[0] = values[3];
+ listener.OrientUp[1] = values[4];
+ listener.OrientUp[2] = values[5];
+ DO_UPDATEPROPS();
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
+ }
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alListeneri(ALenum param, ALint /*value*/)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_POSITION:
+ case AL_VELOCITY:
+ alListener3f(param, static_cast<ALfloat>(value1), static_cast<ALfloat>(value2), static_cast<ALfloat>(value3));
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values)
+START_API_FUNC
+{
+ if(values)
+ {
+ ALfloat fvals[6];
+ switch(param)
+ {
+ case AL_POSITION:
+ case AL_VELOCITY:
+ alListener3f(param, static_cast<ALfloat>(values[0]), static_cast<ALfloat>(values[1]), static_cast<ALfloat>(values[2]));
+ return;
+
+ case AL_ORIENTATION:
+ fvals[0] = static_cast<ALfloat>(values[0]);
+ fvals[1] = static_cast<ALfloat>(values[1]);
+ fvals[2] = static_cast<ALfloat>(values[2]);
+ fvals[3] = static_cast<ALfloat>(values[3]);
+ fvals[4] = static_cast<ALfloat>(values[4]);
+ fvals[5] = static_cast<ALfloat>(values[5]);
+ alListenerfv(param, fvals);
+ return;
+ }
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ if(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
+ }
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALlistener &listener = context->mListener;
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ if(!value)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ case AL_GAIN:
+ *value = listener.Gain;
+ break;
+
+ case AL_METERS_PER_UNIT:
+ *value = listener.mMetersPerUnit;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener float property");
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALlistener &listener = context->mListener;
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ if(!value1 || !value2 || !value3)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ case AL_POSITION:
+ *value1 = listener.Position[0];
+ *value2 = listener.Position[1];
+ *value3 = listener.Position[2];
+ break;
+
+ case AL_VELOCITY:
+ *value1 = listener.Velocity[0];
+ *value2 = listener.Velocity[1];
+ *value3 = listener.Velocity[2];
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_GAIN:
+ case AL_METERS_PER_UNIT:
+ alGetListenerf(param, values);
+ return;
+
+ case AL_POSITION:
+ case AL_VELOCITY:
+ alGetListener3f(param, values+0, values+1, values+2);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALlistener &listener = context->mListener;
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ if(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ case AL_ORIENTATION:
+ // AT then UP
+ values[0] = listener.OrientAt[0];
+ values[1] = listener.OrientAt[1];
+ values[2] = listener.OrientAt[2];
+ values[3] = listener.OrientUp[0];
+ values[4] = listener.OrientUp[1];
+ values[5] = listener.OrientUp[2];
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
+ }
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alGetListeneri(ALenum param, ALint *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ if(!value)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALlistener &listener = context->mListener;
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ if(!value1 || !value2 || !value3)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ case AL_POSITION:
+ *value1 = static_cast<ALint>(listener.Position[0]);
+ *value2 = static_cast<ALint>(listener.Position[1]);
+ *value3 = static_cast<ALint>(listener.Position[2]);
+ break;
+
+ case AL_VELOCITY:
+ *value1 = static_cast<ALint>(listener.Velocity[0]);
+ *value2 = static_cast<ALint>(listener.Velocity[1]);
+ *value3 = static_cast<ALint>(listener.Velocity[2]);
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint* values)
+START_API_FUNC
+{
+ switch(param)
+ {
+ case AL_POSITION:
+ case AL_VELOCITY:
+ alGetListener3i(param, values+0, values+1, values+2);
+ return;
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ ALlistener &listener = context->mListener;
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ if(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(param)
+ {
+ case AL_ORIENTATION:
+ // AT then UP
+ values[0] = static_cast<ALint>(listener.OrientAt[0]);
+ values[1] = static_cast<ALint>(listener.OrientAt[1]);
+ values[2] = static_cast<ALint>(listener.OrientAt[2]);
+ values[3] = static_cast<ALint>(listener.OrientUp[0]);
+ values[4] = static_cast<ALint>(listener.OrientUp[1]);
+ values[5] = static_cast<ALint>(listener.OrientUp[2]);
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
+ }
+}
+END_API_FUNC
+
+
+void UpdateListenerProps(ALCcontext *context)
+{
+ /* Get an unused proprty container, or allocate a new one as needed. */
+ ALlistenerProps *props{context->mFreeListenerProps.load(std::memory_order_acquire)};
+ if(!props)
+ props = new ALlistenerProps{};
+ else
+ {
+ ALlistenerProps *next;
+ do {
+ next = props->next.load(std::memory_order_relaxed);
+ } while(context->mFreeListenerProps.compare_exchange_weak(props, next,
+ std::memory_order_seq_cst, std::memory_order_acquire) == 0);
+ }
+
+ /* Copy in current property values. */
+ ALlistener &listener = context->mListener;
+ props->Position = listener.Position;
+ props->Velocity = listener.Velocity;
+ props->OrientAt = listener.OrientAt;
+ props->OrientUp = listener.OrientUp;
+ props->Gain = listener.Gain;
+ props->MetersPerUnit = listener.mMetersPerUnit;
+
+ /* Set the new container for updating internal parameters. */
+ props = listener.Params.Update.exchange(props, std::memory_order_acq_rel);
+ if(props)
+ {
+ /* If there was an unused update container, put it back in the
+ * freelist.
+ */
+ AtomicReplaceHead(context->mFreeListenerProps, props);
+ }
+}
diff --git a/al/listener.h b/al/listener.h
new file mode 100644
index 00000000..318ab024
--- /dev/null
+++ b/al/listener.h
@@ -0,0 +1,64 @@
+#ifndef AL_LISTENER_H
+#define AL_LISTENER_H
+
+#include <array>
+#include <atomic>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/efx.h"
+
+#include "almalloc.h"
+#include "vecmat.h"
+
+enum class DistanceModel;
+
+
+struct ALlistenerProps {
+ std::array<ALfloat,3> Position;
+ std::array<ALfloat,3> Velocity;
+ std::array<ALfloat,3> OrientAt;
+ std::array<ALfloat,3> OrientUp;
+ ALfloat Gain;
+ ALfloat MetersPerUnit;
+
+ std::atomic<ALlistenerProps*> next;
+
+ DEF_NEWDEL(ALlistenerProps)
+};
+
+struct ALlistener {
+ std::array<ALfloat,3> Position{{0.0f, 0.0f, 0.0f}};
+ std::array<ALfloat,3> Velocity{{0.0f, 0.0f, 0.0f}};
+ std::array<ALfloat,3> OrientAt{{0.0f, 0.0f, -1.0f}};
+ std::array<ALfloat,3> OrientUp{{0.0f, 1.0f, 0.0f}};
+ ALfloat Gain{1.0f};
+ ALfloat mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT};
+
+ std::atomic_flag PropsClean;
+
+ struct {
+ /* Pointer to the most recent property values that are awaiting an
+ * update.
+ */
+ std::atomic<ALlistenerProps*> Update{nullptr};
+
+ alu::Matrix Matrix;
+ alu::Vector Velocity;
+
+ ALfloat Gain;
+ ALfloat MetersPerUnit;
+
+ ALfloat DopplerFactor;
+ ALfloat SpeedOfSound; /* in units per sec! */
+
+ ALboolean SourceDistanceModel;
+ DistanceModel mDistanceModel;
+ } Params;
+
+ ALlistener() { PropsClean.test_and_set(std::memory_order_relaxed); }
+};
+
+void UpdateListenerProps(ALCcontext *context);
+
+#endif
diff --git a/al/source.cpp b/al/source.cpp
new file mode 100644
index 00000000..8b8e6382
--- /dev/null
+++ b/al/source.cpp
@@ -0,0 +1,3348 @@
+/**
+ * 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 "source.h"
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <climits>
+#include <cmath>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <numeric>
+#include <thread>
+#include <utility>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+#include "AL/efx.h"
+
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alspan.h"
+#include "alu.h"
+#include "ambidefs.h"
+#include "atomic.h"
+#include "auxeffectslot.h"
+#include "backends/base.h"
+#include "bformatdec.h"
+#include "buffer.h"
+#include "event.h"
+#include "filter.h"
+#include "filters/nfc.h"
+#include "filters/splitter.h"
+#include "inprogext.h"
+#include "logging.h"
+#include "math_defs.h"
+#include "opthelpers.h"
+#include "ringbuffer.h"
+#include "threads.h"
+
+
+namespace {
+
+using namespace std::placeholders;
+using std::chrono::nanoseconds;
+
+ALvoice *GetSourceVoice(ALsource *source, ALCcontext *context)
+{
+ ALuint idx{source->VoiceIdx};
+ if(idx < context->mVoices.size())
+ {
+ ALuint sid{source->id};
+ ALvoice &voice = context->mVoices[idx];
+ if(voice.mSourceID.load(std::memory_order_acquire) == sid)
+ return &voice;
+ }
+ source->VoiceIdx = INVALID_VOICE_IDX;
+ return nullptr;
+}
+
+void UpdateSourceProps(const ALsource *source, ALvoice *voice, ALCcontext *context)
+{
+ /* Get an unused property container, or allocate a new one as needed. */
+ ALvoiceProps *props{context->mFreeVoiceProps.load(std::memory_order_acquire)};
+ if(!props)
+ props = new ALvoiceProps{};
+ else
+ {
+ ALvoiceProps *next;
+ do {
+ next = props->next.load(std::memory_order_relaxed);
+ } while(context->mFreeVoiceProps.compare_exchange_weak(props, next,
+ std::memory_order_acq_rel, std::memory_order_acquire) == 0);
+ }
+
+ /* Copy in current property values. */
+ props->Pitch = source->Pitch;
+ props->Gain = source->Gain;
+ props->OuterGain = source->OuterGain;
+ props->MinGain = source->MinGain;
+ props->MaxGain = source->MaxGain;
+ props->InnerAngle = source->InnerAngle;
+ props->OuterAngle = source->OuterAngle;
+ props->RefDistance = source->RefDistance;
+ props->MaxDistance = source->MaxDistance;
+ props->RolloffFactor = source->RolloffFactor;
+ props->Position = source->Position;
+ props->Velocity = source->Velocity;
+ props->Direction = source->Direction;
+ props->OrientAt = source->OrientAt;
+ props->OrientUp = source->OrientUp;
+ props->HeadRelative = source->HeadRelative;
+ props->mDistanceModel = source->mDistanceModel;
+ props->mResampler = source->mResampler;
+ props->DirectChannels = source->DirectChannels;
+ props->mSpatializeMode = source->mSpatialize;
+
+ props->DryGainHFAuto = source->DryGainHFAuto;
+ props->WetGainAuto = source->WetGainAuto;
+ props->WetGainHFAuto = source->WetGainHFAuto;
+ props->OuterGainHF = source->OuterGainHF;
+
+ props->AirAbsorptionFactor = source->AirAbsorptionFactor;
+ props->RoomRolloffFactor = source->RoomRolloffFactor;
+ props->DopplerFactor = source->DopplerFactor;
+
+ props->StereoPan = source->StereoPan;
+
+ props->Radius = source->Radius;
+
+ props->Direct.Gain = source->Direct.Gain;
+ props->Direct.GainHF = source->Direct.GainHF;
+ props->Direct.HFReference = source->Direct.HFReference;
+ props->Direct.GainLF = source->Direct.GainLF;
+ props->Direct.LFReference = source->Direct.LFReference;
+
+ auto copy_send = [](const ALsource::SendData &srcsend) noexcept -> ALvoicePropsBase::SendData
+ {
+ ALvoicePropsBase::SendData ret;
+ ret.Slot = srcsend.Slot;
+ ret.Gain = srcsend.Gain;
+ ret.GainHF = srcsend.GainHF;
+ ret.HFReference = srcsend.HFReference;
+ ret.GainLF = srcsend.GainLF;
+ ret.LFReference = srcsend.LFReference;
+ return ret;
+ };
+ std::transform(source->Send.cbegin(), source->Send.cend(), props->Send, copy_send);
+
+ /* Set the new container for updating internal parameters. */
+ props = voice->mUpdate.exchange(props, std::memory_order_acq_rel);
+ if(props)
+ {
+ /* If there was an unused update container, put it back in the
+ * freelist.
+ */
+ AtomicReplaceHead(context->mFreeVoiceProps, props);
+ }
+}
+
+/* GetSourceSampleOffset
+ *
+ * Gets the current read offset for the given Source, in 32.32 fixed-point
+ * samples. The offset is relative to the start of the queue (not the start of
+ * the current buffer).
+ */
+int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime)
+{
+ ALCdevice *device{context->mDevice.get()};
+ const ALbufferlistitem *Current;
+ uint64_t readPos;
+ ALuint refcount;
+ ALvoice *voice;
+
+ do {
+ Current = nullptr;
+ readPos = 0;
+ while(((refcount=device->MixCount.load(std::memory_order_acquire))&1))
+ std::this_thread::yield();
+ *clocktime = GetDeviceClockTime(device);
+
+ voice = GetSourceVoice(Source, context);
+ if(voice)
+ {
+ Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
+
+ readPos = uint64_t{voice->mPosition.load(std::memory_order_relaxed)} << 32;
+ readPos |= uint64_t{voice->mPositionFrac.load(std::memory_order_relaxed)} <<
+ (32-FRACTIONBITS);
+ }
+ std::atomic_thread_fence(std::memory_order_acquire);
+ } while(refcount != device->MixCount.load(std::memory_order_relaxed));
+
+ if(voice)
+ {
+ const ALbufferlistitem *BufferList{Source->queue};
+ while(BufferList && BufferList != Current)
+ {
+ readPos += uint64_t{BufferList->mSampleLen} << 32;
+ BufferList = BufferList->mNext.load(std::memory_order_relaxed);
+ }
+ readPos = minu64(readPos, 0x7fffffffffffffff_u64);
+ }
+
+ return static_cast<int64_t>(readPos);
+}
+
+/* GetSourceSecOffset
+ *
+ * Gets the current read offset for the given Source, in seconds. The offset is
+ * relative to the start of the queue (not the start of the current buffer).
+ */
+ALdouble GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime)
+{
+ ALCdevice *device{context->mDevice.get()};
+ const ALbufferlistitem *Current;
+ uint64_t readPos;
+ ALuint refcount;
+ ALvoice *voice;
+
+ do {
+ Current = nullptr;
+ readPos = 0;
+ while(((refcount=device->MixCount.load(std::memory_order_acquire))&1))
+ std::this_thread::yield();
+ *clocktime = GetDeviceClockTime(device);
+
+ voice = GetSourceVoice(Source, context);
+ if(voice)
+ {
+ Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
+
+ readPos = uint64_t{voice->mPosition.load(std::memory_order_relaxed)} << FRACTIONBITS;
+ readPos |= voice->mPositionFrac.load(std::memory_order_relaxed);
+ }
+ std::atomic_thread_fence(std::memory_order_acquire);
+ } while(refcount != device->MixCount.load(std::memory_order_relaxed));
+
+ ALdouble offset{0.0};
+ if(voice)
+ {
+ const ALbufferlistitem *BufferList{Source->queue};
+ const ALbuffer *BufferFmt{nullptr};
+ while(BufferList && BufferList != Current)
+ {
+ if(!BufferFmt) BufferFmt = BufferList->mBuffer;
+ readPos += uint64_t{BufferList->mSampleLen} << FRACTIONBITS;
+ BufferList = BufferList->mNext.load(std::memory_order_relaxed);
+ }
+
+ while(BufferList && !BufferFmt)
+ {
+ BufferFmt = BufferList->mBuffer;
+ BufferList = BufferList->mNext.load(std::memory_order_relaxed);
+ }
+ assert(BufferFmt != nullptr);
+
+ offset = static_cast<ALdouble>(readPos) / ALdouble{FRACTIONONE} / BufferFmt->Frequency;
+ }
+
+ return offset;
+}
+
+/* GetSourceOffset
+ *
+ * Gets the current read offset for the given Source, in the appropriate format
+ * (Bytes, Samples or Seconds). The offset is relative to the start of the
+ * queue (not the start of the current buffer).
+ */
+ALdouble GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context)
+{
+ ALCdevice *device{context->mDevice.get()};
+ const ALbufferlistitem *Current;
+ ALuint readPos;
+ ALuint readPosFrac;
+ ALuint refcount;
+ ALvoice *voice;
+
+ do {
+ Current = nullptr;
+ readPos = readPosFrac = 0;
+ while(((refcount=device->MixCount.load(std::memory_order_acquire))&1))
+ std::this_thread::yield();
+ voice = GetSourceVoice(Source, context);
+ if(voice)
+ {
+ Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
+
+ readPos = voice->mPosition.load(std::memory_order_relaxed);
+ readPosFrac = voice->mPositionFrac.load(std::memory_order_relaxed);
+ }
+ std::atomic_thread_fence(std::memory_order_acquire);
+ } while(refcount != device->MixCount.load(std::memory_order_relaxed));
+
+ ALdouble offset{0.0};
+ if(!voice) return offset;
+
+ const ALbufferlistitem *BufferList{Source->queue};
+ const ALbuffer *BufferFmt{nullptr};
+ ALuint totalBufferLen{0u};
+ bool readFin{false};
+
+ while(BufferList)
+ {
+ if(!BufferFmt) BufferFmt = BufferList->mBuffer;
+
+ readFin |= (BufferList == Current);
+ totalBufferLen += BufferList->mSampleLen;
+ if(!readFin) readPos += BufferList->mSampleLen;
+
+ BufferList = BufferList->mNext.load(std::memory_order_relaxed);
+ }
+ assert(BufferFmt != nullptr);
+
+ if(Source->Looping)
+ readPos %= totalBufferLen;
+ else
+ {
+ /* Wrap back to 0 */
+ if(readPos >= totalBufferLen)
+ readPos = readPosFrac = 0;
+ }
+
+ switch(name)
+ {
+ case AL_SEC_OFFSET:
+ offset = (readPos + static_cast<ALdouble>(readPosFrac)/FRACTIONONE) / BufferFmt->Frequency;
+ break;
+
+ case AL_SAMPLE_OFFSET:
+ offset = readPos + static_cast<ALdouble>(readPosFrac)/FRACTIONONE;
+ break;
+
+ case AL_BYTE_OFFSET:
+ if(BufferFmt->OriginalType == UserFmtIMA4)
+ {
+ ALuint FrameBlockSize{BufferFmt->OriginalAlign};
+ ALuint align{(BufferFmt->OriginalAlign-1)/2 + 4};
+ ALuint BlockSize{align * ChannelsFromFmt(BufferFmt->mFmtChannels)};
+
+ /* Round down to nearest ADPCM block */
+ offset = static_cast<ALdouble>(readPos / FrameBlockSize * BlockSize);
+ }
+ else if(BufferFmt->OriginalType == UserFmtMSADPCM)
+ {
+ ALuint FrameBlockSize{BufferFmt->OriginalAlign};
+ ALuint align{(FrameBlockSize-2)/2 + 7};
+ ALuint BlockSize{align * ChannelsFromFmt(BufferFmt->mFmtChannels)};
+
+ /* Round down to nearest ADPCM block */
+ offset = static_cast<ALdouble>(readPos / FrameBlockSize * BlockSize);
+ }
+ else
+ {
+ const ALuint FrameSize{FrameSizeFromFmt(BufferFmt->mFmtChannels, BufferFmt->mFmtType)};
+ offset = static_cast<ALdouble>(readPos * FrameSize);
+ }
+ break;
+ }
+
+ return offset;
+}
+
+
+struct VoicePos {
+ ALuint pos, frac;
+ ALbufferlistitem *bufferitem;
+};
+
+/**
+ * GetSampleOffset
+ *
+ * Retrieves the voice position, fixed-point fraction, and bufferlist item
+ * using the source's stored offset and offset type. If the source has no
+ * stored offset, or the offset is out of range, returns an empty optional.
+ */
+al::optional<VoicePos> GetSampleOffset(ALsource *Source)
+{
+ al::optional<VoicePos> ret;
+
+ /* Find the first valid Buffer in the Queue */
+ const ALbuffer *BufferFmt{nullptr};
+ ALbufferlistitem *BufferList{Source->queue};
+ while(BufferList)
+ {
+ if((BufferFmt=BufferList->mBuffer) != nullptr) break;
+ BufferList = BufferList->mNext.load(std::memory_order_relaxed);
+ }
+ if(!BufferList)
+ {
+ Source->OffsetType = AL_NONE;
+ Source->Offset = 0.0;
+ return ret;
+ }
+
+ /* Get sample frame offset */
+ ALuint offset{0u}, frac{0u};
+ ALdouble dbloff, dblfrac;
+ switch(Source->OffsetType)
+ {
+ case AL_BYTE_OFFSET:
+ /* Determine the ByteOffset (and ensure it is block aligned) */
+ offset = static_cast<ALuint>(Source->Offset);
+ if(BufferFmt->OriginalType == UserFmtIMA4)
+ {
+ const ALuint align{(BufferFmt->OriginalAlign-1)/2 + 4};
+ offset /= align * ChannelsFromFmt(BufferFmt->mFmtChannels);
+ offset *= BufferFmt->OriginalAlign;
+ }
+ else if(BufferFmt->OriginalType == UserFmtMSADPCM)
+ {
+ const ALuint align{(BufferFmt->OriginalAlign-2)/2 + 7};
+ offset /= align * ChannelsFromFmt(BufferFmt->mFmtChannels);
+ offset *= BufferFmt->OriginalAlign;
+ }
+ else
+ offset /= FrameSizeFromFmt(BufferFmt->mFmtChannels, BufferFmt->mFmtType);
+ frac = 0;
+ break;
+
+ case AL_SAMPLE_OFFSET:
+ dblfrac = std::modf(Source->Offset, &dbloff);
+ offset = static_cast<ALuint>(mind(dbloff, std::numeric_limits<ALuint>::max()));
+ frac = static_cast<ALuint>(mind(dblfrac*FRACTIONONE, FRACTIONONE-1.0));
+ break;
+
+ case AL_SEC_OFFSET:
+ dblfrac = std::modf(Source->Offset*BufferFmt->Frequency, &dbloff);
+ offset = static_cast<ALuint>(mind(dbloff, std::numeric_limits<ALuint>::max()));
+ frac = static_cast<ALuint>(mind(dblfrac*FRACTIONONE, FRACTIONONE-1.0));
+ break;
+ }
+ Source->OffsetType = AL_NONE;
+ Source->Offset = 0.0;
+
+ /* Find the bufferlist item this offset belongs to. */
+ ALuint totalBufferLen{0u};
+ while(BufferList && totalBufferLen <= offset)
+ {
+ if(BufferList->mSampleLen > offset-totalBufferLen)
+ {
+ /* Offset is in this buffer */
+ ret = {offset-totalBufferLen, frac, BufferList};
+ return ret;
+ }
+ totalBufferLen += BufferList->mSampleLen;
+
+ BufferList = BufferList->mNext.load(std::memory_order_relaxed);
+ }
+
+ /* Offset is out of range of the queue */
+ return ret;
+}
+
+
+/**
+ * Returns if the last known state for the source was playing or paused. Does
+ * not sync with the mixer voice.
+ */
+inline bool IsPlayingOrPaused(ALsource *source)
+{ return source->state == AL_PLAYING || source->state == AL_PAUSED; }
+
+/**
+ * Returns an updated source state using the matching voice's status (or lack
+ * thereof).
+ */
+inline ALenum GetSourceState(ALsource *source, ALvoice *voice)
+{
+ if(!voice && source->state == AL_PLAYING)
+ source->state = AL_STOPPED;
+ return source->state;
+}
+
+/**
+ * Returns if the source should specify an update, given the context's
+ * deferring state and the source's last known state.
+ */
+inline bool SourceShouldUpdate(ALsource *source, ALCcontext *context)
+{
+ return !context->mDeferUpdates.load(std::memory_order_acquire) &&
+ IsPlayingOrPaused(source);
+}
+
+
+bool EnsureSources(ALCcontext *context, size_t needed)
+{
+ size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(),
+ size_t{0},
+ [](size_t cur, const SourceSubList &sublist) noexcept -> size_t
+ { return cur + static_cast<ALuint>(POPCNT64(sublist.FreeMask)); }
+ )};
+
+ while(needed > count)
+ {
+ if UNLIKELY(context->mSourceList.size() >= 1<<25)
+ return false;
+
+ context->mSourceList.emplace_back();
+ auto sublist = context->mSourceList.end() - 1;
+ sublist->FreeMask = ~0_u64;
+ sublist->Sources = static_cast<ALsource*>(al_calloc(alignof(ALsource), sizeof(ALsource)*64));
+ if UNLIKELY(!sublist->Sources)
+ {
+ context->mSourceList.pop_back();
+ return false;
+ }
+ count += 64;
+ }
+ return true;
+}
+
+ALsource *AllocSource(ALCcontext *context, ALuint num_sends)
+{
+ auto sublist = std::find_if(context->mSourceList.begin(), context->mSourceList.end(),
+ [](const SourceSubList &entry) noexcept -> bool
+ { return entry.FreeMask != 0; }
+ );
+ auto lidx = static_cast<ALuint>(std::distance(context->mSourceList.begin(), sublist));
+ auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
+
+ ALsource *source{::new (sublist->Sources + slidx) ALsource{num_sends}};
+
+ /* Add 1 to avoid source ID 0. */
+ source->id = ((lidx<<6) | slidx) + 1;
+
+ context->mNumSources += 1;
+ sublist->FreeMask &= ~(1_u64 << slidx);
+
+ return source;
+}
+
+void FreeSource(ALCcontext *context, ALsource *source)
+{
+ const ALuint id{source->id - 1};
+ const size_t lidx{id >> 6};
+ const ALuint slidx{id & 0x3f};
+
+ if(IsPlayingOrPaused(source))
+ {
+ ALCdevice *device{context->mDevice.get()};
+ BackendLockGuard _{*device->Backend};
+ if(ALvoice *voice{GetSourceVoice(source, context)})
+ {
+ voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
+ voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
+ voice->mSourceID.store(0u, std::memory_order_relaxed);
+ std::atomic_thread_fence(std::memory_order_release);
+ /* Don't set the voice to stopping if it was already stopped or
+ * stopping.
+ */
+ ALvoice::State oldvstate{ALvoice::Playing};
+ voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping,
+ std::memory_order_acq_rel, std::memory_order_acquire);
+ }
+ }
+
+ al::destroy_at(source);
+
+ context->mSourceList[lidx].FreeMask |= 1_u64 << slidx;
+ context->mNumSources--;
+}
+
+
+inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept
+{
+ const size_t lidx{(id-1) >> 6};
+ const ALuint slidx{(id-1) & 0x3f};
+
+ if UNLIKELY(lidx >= context->mSourceList.size())
+ return nullptr;
+ SourceSubList &sublist{context->mSourceList[lidx]};
+ if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
+ return nullptr;
+ return sublist.Sources + slidx;
+}
+
+inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept
+{
+ const size_t lidx{(id-1) >> 6};
+ const ALuint slidx{(id-1) & 0x3f};
+
+ if UNLIKELY(lidx >= device->BufferList.size())
+ return nullptr;
+ BufferSubList &sublist = device->BufferList[lidx];
+ if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
+ return nullptr;
+ return sublist.Buffers + slidx;
+}
+
+inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) noexcept
+{
+ const size_t lidx{(id-1) >> 6};
+ const ALuint slidx{(id-1) & 0x3f};
+
+ if UNLIKELY(lidx >= device->FilterList.size())
+ return nullptr;
+ FilterSubList &sublist = device->FilterList[lidx];
+ if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
+ return nullptr;
+ return sublist.Filters + slidx;
+}
+
+inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept
+{
+ const size_t lidx{(id-1) >> 6};
+ const ALuint slidx{(id-1) & 0x3f};
+
+ if UNLIKELY(lidx >= context->mEffectSlotList.size())
+ return nullptr;
+ EffectSlotSubList &sublist{context->mEffectSlotList[lidx]};
+ if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
+ return nullptr;
+ return sublist.EffectSlots + slidx;
+}
+
+
+enum SourceProp : ALenum {
+ srcPitch = AL_PITCH,
+ srcGain = AL_GAIN,
+ srcMinGain = AL_MIN_GAIN,
+ srcMaxGain = AL_MAX_GAIN,
+ srcMaxDistance = AL_MAX_DISTANCE,
+ srcRolloffFactor = AL_ROLLOFF_FACTOR,
+ srcDopplerFactor = AL_DOPPLER_FACTOR,
+ srcConeOuterGain = AL_CONE_OUTER_GAIN,
+ srcSecOffset = AL_SEC_OFFSET,
+ srcSampleOffset = AL_SAMPLE_OFFSET,
+ srcByteOffset = AL_BYTE_OFFSET,
+ srcConeInnerAngle = AL_CONE_INNER_ANGLE,
+ srcConeOuterAngle = AL_CONE_OUTER_ANGLE,
+ srcRefDistance = AL_REFERENCE_DISTANCE,
+
+ srcPosition = AL_POSITION,
+ srcVelocity = AL_VELOCITY,
+ srcDirection = AL_DIRECTION,
+
+ srcSourceRelative = AL_SOURCE_RELATIVE,
+ srcLooping = AL_LOOPING,
+ srcBuffer = AL_BUFFER,
+ srcSourceState = AL_SOURCE_STATE,
+ srcBuffersQueued = AL_BUFFERS_QUEUED,
+ srcBuffersProcessed = AL_BUFFERS_PROCESSED,
+ srcSourceType = AL_SOURCE_TYPE,
+
+ /* ALC_EXT_EFX */
+ srcConeOuterGainHF = AL_CONE_OUTER_GAINHF,
+ srcAirAbsorptionFactor = AL_AIR_ABSORPTION_FACTOR,
+ srcRoomRolloffFactor = AL_ROOM_ROLLOFF_FACTOR,
+ srcDirectFilterGainHFAuto = AL_DIRECT_FILTER_GAINHF_AUTO,
+ srcAuxSendFilterGainAuto = AL_AUXILIARY_SEND_FILTER_GAIN_AUTO,
+ srcAuxSendFilterGainHFAuto = AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO,
+ srcDirectFilter = AL_DIRECT_FILTER,
+ srcAuxSendFilter = AL_AUXILIARY_SEND_FILTER,
+
+ /* AL_SOFT_direct_channels */
+ srcDirectChannelsSOFT = AL_DIRECT_CHANNELS_SOFT,
+
+ /* AL_EXT_source_distance_model */
+ srcDistanceModel = AL_DISTANCE_MODEL,
+
+ /* AL_SOFT_source_latency */
+ srcSampleOffsetLatencySOFT = AL_SAMPLE_OFFSET_LATENCY_SOFT,
+ srcSecOffsetLatencySOFT = AL_SEC_OFFSET_LATENCY_SOFT,
+
+ /* AL_EXT_STEREO_ANGLES */
+ srcAngles = AL_STEREO_ANGLES,
+
+ /* AL_EXT_SOURCE_RADIUS */
+ srcRadius = AL_SOURCE_RADIUS,
+
+ /* AL_EXT_BFORMAT */
+ srcOrientation = AL_ORIENTATION,
+
+ /* AL_SOFT_source_resampler */
+ srcResampler = AL_SOURCE_RESAMPLER_SOFT,
+
+ /* AL_SOFT_source_spatialize */
+ srcSpatialize = AL_SOURCE_SPATIALIZE_SOFT,
+
+ /* ALC_SOFT_device_clock */
+ srcSampleOffsetClockSOFT = AL_SAMPLE_OFFSET_CLOCK_SOFT,
+ srcSecOffsetClockSOFT = AL_SEC_OFFSET_CLOCK_SOFT,
+};
+
+
+/** Can only be called while the mixer is locked! */
+void SendStateChangeEvent(ALCcontext *context, ALuint id, ALenum state)
+{
+ ALbitfieldSOFT enabledevt{context->mEnabledEvts.load(std::memory_order_acquire)};
+ if(!(enabledevt&EventType_SourceStateChange)) return;
+
+ /* The mixer may have queued a state change that's not yet been processed,
+ * and we don't want state change messages to occur out of order, so send
+ * it through the async queue to ensure proper ordering.
+ */
+ 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);
+ context->mEventSem.post();
+}
+
+
+constexpr size_t MaxValues{6u};
+
+ALuint FloatValsByProp(ALenum prop)
+{
+ switch(static_cast<SourceProp>(prop))
+ {
+ case AL_PITCH:
+ case AL_GAIN:
+ case AL_MIN_GAIN:
+ case AL_MAX_GAIN:
+ case AL_MAX_DISTANCE:
+ case AL_ROLLOFF_FACTOR:
+ case AL_DOPPLER_FACTOR:
+ case AL_CONE_OUTER_GAIN:
+ case AL_SEC_OFFSET:
+ case AL_SAMPLE_OFFSET:
+ case AL_BYTE_OFFSET:
+ case AL_CONE_INNER_ANGLE:
+ case AL_CONE_OUTER_ANGLE:
+ case AL_REFERENCE_DISTANCE:
+ case AL_CONE_OUTER_GAINHF:
+ case AL_AIR_ABSORPTION_FACTOR:
+ case AL_ROOM_ROLLOFF_FACTOR:
+ case AL_DIRECT_FILTER_GAINHF_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
+ case AL_DIRECT_CHANNELS_SOFT:
+ case AL_DISTANCE_MODEL:
+ case AL_SOURCE_RELATIVE:
+ case AL_LOOPING:
+ case AL_SOURCE_STATE:
+ case AL_BUFFERS_QUEUED:
+ case AL_BUFFERS_PROCESSED:
+ case AL_SOURCE_TYPE:
+ case AL_SOURCE_RADIUS:
+ case AL_SOURCE_RESAMPLER_SOFT:
+ case AL_SOURCE_SPATIALIZE_SOFT:
+ return 1;
+
+ case AL_STEREO_ANGLES:
+ return 2;
+
+ case AL_POSITION:
+ case AL_VELOCITY:
+ case AL_DIRECTION:
+ return 3;
+
+ case AL_ORIENTATION:
+ return 6;
+
+ case AL_SEC_OFFSET_LATENCY_SOFT:
+ case AL_SEC_OFFSET_CLOCK_SOFT:
+ break; /* Double only */
+
+ case AL_BUFFER:
+ case AL_DIRECT_FILTER:
+ case AL_AUXILIARY_SEND_FILTER:
+ break; /* i/i64 only */
+ case AL_SAMPLE_OFFSET_LATENCY_SOFT:
+ case AL_SAMPLE_OFFSET_CLOCK_SOFT:
+ break; /* i64 only */
+ }
+ return 0;
+}
+ALuint DoubleValsByProp(ALenum prop)
+{
+ switch(static_cast<SourceProp>(prop))
+ {
+ case AL_PITCH:
+ case AL_GAIN:
+ case AL_MIN_GAIN:
+ case AL_MAX_GAIN:
+ case AL_MAX_DISTANCE:
+ case AL_ROLLOFF_FACTOR:
+ case AL_DOPPLER_FACTOR:
+ case AL_CONE_OUTER_GAIN:
+ case AL_SEC_OFFSET:
+ case AL_SAMPLE_OFFSET:
+ case AL_BYTE_OFFSET:
+ case AL_CONE_INNER_ANGLE:
+ case AL_CONE_OUTER_ANGLE:
+ case AL_REFERENCE_DISTANCE:
+ case AL_CONE_OUTER_GAINHF:
+ case AL_AIR_ABSORPTION_FACTOR:
+ case AL_ROOM_ROLLOFF_FACTOR:
+ case AL_DIRECT_FILTER_GAINHF_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
+ case AL_DIRECT_CHANNELS_SOFT:
+ case AL_DISTANCE_MODEL:
+ case AL_SOURCE_RELATIVE:
+ case AL_LOOPING:
+ case AL_SOURCE_STATE:
+ case AL_BUFFERS_QUEUED:
+ case AL_BUFFERS_PROCESSED:
+ case AL_SOURCE_TYPE:
+ case AL_SOURCE_RADIUS:
+ case AL_SOURCE_RESAMPLER_SOFT:
+ case AL_SOURCE_SPATIALIZE_SOFT:
+ return 1;
+
+ case AL_SEC_OFFSET_LATENCY_SOFT:
+ case AL_SEC_OFFSET_CLOCK_SOFT:
+ case AL_STEREO_ANGLES:
+ return 2;
+
+ case AL_POSITION:
+ case AL_VELOCITY:
+ case AL_DIRECTION:
+ return 3;
+
+ case AL_ORIENTATION:
+ return 6;
+
+ case AL_BUFFER:
+ case AL_DIRECT_FILTER:
+ case AL_AUXILIARY_SEND_FILTER:
+ break; /* i/i64 only */
+ case AL_SAMPLE_OFFSET_LATENCY_SOFT:
+ case AL_SAMPLE_OFFSET_CLOCK_SOFT:
+ break; /* i64 only */
+ }
+ return 0;
+}
+
+
+bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<const ALfloat> values);
+bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<const ALint> values);
+bool SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<const ALint64SOFT> values);
+
+#define CHECKSIZE(v, s) do { \
+ if LIKELY((v).size() == (s) || (v).size() == MaxValues) break; \
+ Context->setError(AL_INVALID_ENUM, \
+ "Property 0x%04x expects %d value(s), got %zu", prop, (s), \
+ (v).size()); \
+ return false; \
+} while(0)
+#define CHECKVAL(x) do { \
+ if LIKELY(x) break; \
+ Context->setError(AL_INVALID_VALUE, "Value out of range"); \
+ return false; \
+} while(0)
+
+bool UpdateSourceProps(ALsource *source, ALCcontext *context)
+{
+ ALvoice *voice;
+ if(SourceShouldUpdate(source, context) && (voice=GetSourceVoice(source, context)) != nullptr)
+ UpdateSourceProps(source, voice, context);
+ else
+ source->PropsClean.clear(std::memory_order_release);
+ return true;
+}
+
+bool SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<const ALfloat> values)
+{
+ ALint ival;
+
+ switch(prop)
+ {
+ case AL_SEC_OFFSET_LATENCY_SOFT:
+ case AL_SEC_OFFSET_CLOCK_SOFT:
+ /* Query only */
+ SETERR_RETURN(Context, AL_INVALID_OPERATION, false,
+ "Setting read-only source property 0x%04x", prop);
+
+ case AL_PITCH:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f);
+
+ Source->Pitch = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_CONE_INNER_ANGLE:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f && values[0] <= 360.0f);
+
+ Source->InnerAngle = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_CONE_OUTER_ANGLE:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f && values[0] <= 360.0f);
+
+ Source->OuterAngle = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_GAIN:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f);
+
+ Source->Gain = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_MAX_DISTANCE:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f);
+
+ Source->MaxDistance = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_ROLLOFF_FACTOR:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f);
+
+ Source->RolloffFactor = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_REFERENCE_DISTANCE:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f);
+
+ Source->RefDistance = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_MIN_GAIN:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f);
+
+ Source->MinGain = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_MAX_GAIN:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f);
+
+ Source->MaxGain = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_CONE_OUTER_GAIN:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f);
+
+ Source->OuterGain = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_CONE_OUTER_GAINHF:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f);
+
+ Source->OuterGainHF = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_AIR_ABSORPTION_FACTOR:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f && values[0] <= 10.0f);
+
+ Source->AirAbsorptionFactor = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_ROOM_ROLLOFF_FACTOR:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f && values[0] <= 10.0f);
+
+ Source->RoomRolloffFactor = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_DOPPLER_FACTOR:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f);
+
+ Source->DopplerFactor = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_SEC_OFFSET:
+ case AL_SAMPLE_OFFSET:
+ case AL_BYTE_OFFSET:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f);
+
+ Source->OffsetType = prop;
+ Source->Offset = values[0];
+
+ if(IsPlayingOrPaused(Source))
+ {
+ ALCdevice *device{Context->mDevice.get()};
+ BackendLockGuard _{*device->Backend};
+ /* Double-check that the source is still playing while we have the
+ * lock.
+ */
+ if(ALvoice *voice{GetSourceVoice(Source, Context)})
+ {
+ auto vpos = GetSampleOffset(Source);
+ if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid offset");
+
+ voice->mPosition.store(vpos->pos, std::memory_order_relaxed);
+ voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed);
+ voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_release);
+ }
+ }
+ return true;
+
+ case AL_SOURCE_RADIUS:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0.0f && std::isfinite(values[0]));
+
+ Source->Radius = values[0];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_STEREO_ANGLES:
+ CHECKSIZE(values, 2);
+ CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]));
+
+ Source->StereoPan[0] = values[0];
+ Source->StereoPan[1] = values[1];
+ return UpdateSourceProps(Source, Context);
+
+
+ case AL_POSITION:
+ CHECKSIZE(values, 3);
+ CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]));
+
+ Source->Position[0] = values[0];
+ Source->Position[1] = values[1];
+ Source->Position[2] = values[2];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_VELOCITY:
+ CHECKSIZE(values, 3);
+ CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]));
+
+ Source->Velocity[0] = values[0];
+ Source->Velocity[1] = values[1];
+ Source->Velocity[2] = values[2];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_DIRECTION:
+ CHECKSIZE(values, 3);
+ CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]));
+
+ Source->Direction[0] = values[0];
+ Source->Direction[1] = values[1];
+ Source->Direction[2] = values[2];
+ return UpdateSourceProps(Source, Context);
+
+ case AL_ORIENTATION:
+ CHECKSIZE(values, 6);
+ CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])
+ && std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5]));
+
+ Source->OrientAt[0] = values[0];
+ Source->OrientAt[1] = values[1];
+ Source->OrientAt[2] = values[2];
+ Source->OrientUp[0] = values[3];
+ Source->OrientUp[1] = values[4];
+ Source->OrientUp[2] = values[5];
+ return UpdateSourceProps(Source, Context);
+
+
+ case AL_SOURCE_RELATIVE:
+ case AL_LOOPING:
+ case AL_SOURCE_STATE:
+ case AL_SOURCE_TYPE:
+ case AL_DISTANCE_MODEL:
+ case AL_DIRECT_FILTER_GAINHF_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
+ case AL_DIRECT_CHANNELS_SOFT:
+ case AL_SOURCE_RESAMPLER_SOFT:
+ case AL_SOURCE_SPATIALIZE_SOFT:
+ CHECKSIZE(values, 1);
+ ival = static_cast<ALint>(values[0]);
+ return SetSourceiv(Source, Context, prop, {&ival, 1u});
+
+ case AL_BUFFERS_QUEUED:
+ case AL_BUFFERS_PROCESSED:
+ CHECKSIZE(values, 1);
+ ival = static_cast<ALint>(static_cast<ALuint>(values[0]));
+ return SetSourceiv(Source, Context, prop, {&ival, 1u});
+
+ case AL_BUFFER:
+ case AL_DIRECT_FILTER:
+ case AL_AUXILIARY_SEND_FILTER:
+ case AL_SAMPLE_OFFSET_LATENCY_SOFT:
+ case AL_SAMPLE_OFFSET_CLOCK_SOFT:
+ break;
+ }
+
+ ERR("Unexpected property: 0x%04x\n", prop);
+ Context->setError(AL_INVALID_ENUM, "Invalid source float property 0x%04x", prop);
+ return false;
+}
+
+bool SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<const ALint> values)
+{
+ ALCdevice *device{Context->mDevice.get()};
+ ALbuffer *buffer{nullptr};
+ ALfilter *filter{nullptr};
+ ALeffectslot *slot{nullptr};
+ ALbufferlistitem *oldlist{nullptr};
+ std::unique_lock<std::mutex> slotlock;
+ std::unique_lock<std::mutex> filtlock;
+ std::unique_lock<std::mutex> buflock;
+ ALfloat fvals[6];
+
+ switch(prop)
+ {
+ case AL_SOURCE_STATE:
+ case AL_SOURCE_TYPE:
+ case AL_BUFFERS_QUEUED:
+ case AL_BUFFERS_PROCESSED:
+ /* Query only */
+ SETERR_RETURN(Context, AL_INVALID_OPERATION, false,
+ "Setting read-only source property 0x%04x", prop);
+
+ case AL_SOURCE_RELATIVE:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE);
+
+ Source->HeadRelative = values[0] != AL_FALSE;
+ return UpdateSourceProps(Source, Context);
+
+ case AL_LOOPING:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE);
+
+ Source->Looping = values[0] != AL_FALSE;
+ if(IsPlayingOrPaused(Source))
+ {
+ if(ALvoice *voice{GetSourceVoice(Source, Context)})
+ {
+ if(Source->Looping)
+ voice->mLoopBuffer.store(Source->queue, std::memory_order_release);
+ else
+ voice->mLoopBuffer.store(nullptr, std::memory_order_release);
+
+ /* If the source is playing, wait for the current mix to finish
+ * to ensure it isn't currently looping back or reaching the
+ * end.
+ */
+ while((device->MixCount.load(std::memory_order_acquire)&1))
+ std::this_thread::yield();
+ }
+ }
+ return true;
+
+ case AL_BUFFER:
+ CHECKSIZE(values, 1);
+ buflock = std::unique_lock<std::mutex>{device->BufferLock};
+ if(values[0] && (buffer=LookupBuffer(device, static_cast<ALuint>(values[0]))) == nullptr)
+ SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid buffer ID %u",
+ static_cast<ALuint>(values[0]));
+
+ if(buffer && buffer->MappedAccess != 0 &&
+ !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
+ SETERR_RETURN(Context, AL_INVALID_OPERATION, false,
+ "Setting non-persistently mapped buffer %u", buffer->id);
+ else
+ {
+ ALenum state = GetSourceState(Source, GetSourceVoice(Source, Context));
+ if(state == AL_PLAYING || state == AL_PAUSED)
+ SETERR_RETURN(Context, AL_INVALID_OPERATION, false,
+ "Setting buffer on playing or paused source %u", Source->id);
+ }
+
+ oldlist = Source->queue;
+ if(buffer != nullptr)
+ {
+ /* Add the selected buffer to a one-item queue */
+ auto newlist = new ALbufferlistitem{};
+ newlist->mSampleLen = buffer->SampleLen;
+ newlist->mBuffer = buffer;
+ IncrementRef(buffer->ref);
+
+ /* Source is now Static */
+ Source->SourceType = AL_STATIC;
+ Source->queue = newlist;
+ }
+ else
+ {
+ /* Source is now Undetermined */
+ Source->SourceType = AL_UNDETERMINED;
+ Source->queue = nullptr;
+ }
+ buflock.unlock();
+
+ /* Delete all elements in the previous queue */
+ while(oldlist != nullptr)
+ {
+ std::unique_ptr<ALbufferlistitem> temp{oldlist};
+ oldlist = temp->mNext.load(std::memory_order_relaxed);
+
+ if((buffer=temp->mBuffer) != nullptr)
+ DecrementRef(buffer->ref);
+ }
+ return true;
+
+ case AL_SEC_OFFSET:
+ case AL_SAMPLE_OFFSET:
+ case AL_BYTE_OFFSET:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0);
+
+ Source->OffsetType = prop;
+ Source->Offset = values[0];
+
+ if(IsPlayingOrPaused(Source))
+ {
+ BackendLockGuard _{*device->Backend};
+ if(ALvoice *voice{GetSourceVoice(Source, Context)})
+ {
+ auto vpos = GetSampleOffset(Source);
+ if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid source offset");
+
+ voice->mPosition.store(vpos->pos, std::memory_order_relaxed);
+ voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed);
+ voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_release);
+ }
+ }
+ return true;
+
+ case AL_DIRECT_FILTER:
+ CHECKSIZE(values, 1);
+ filtlock = std::unique_lock<std::mutex>{device->FilterLock};
+ if(values[0] && (filter=LookupFilter(device, static_cast<ALuint>(values[0]))) == nullptr)
+ SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid filter ID %u",
+ static_cast<ALuint>(values[0]));
+
+ if(!filter)
+ {
+ Source->Direct.Gain = 1.0f;
+ Source->Direct.GainHF = 1.0f;
+ Source->Direct.HFReference = LOWPASSFREQREF;
+ Source->Direct.GainLF = 1.0f;
+ Source->Direct.LFReference = HIGHPASSFREQREF;
+ }
+ else
+ {
+ Source->Direct.Gain = filter->Gain;
+ Source->Direct.GainHF = filter->GainHF;
+ Source->Direct.HFReference = filter->HFReference;
+ Source->Direct.GainLF = filter->GainLF;
+ Source->Direct.LFReference = filter->LFReference;
+ }
+ filtlock.unlock();
+ return UpdateSourceProps(Source, Context);
+
+ case AL_DIRECT_FILTER_GAINHF_AUTO:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE);
+
+ Source->DryGainHFAuto = values[0] != AL_FALSE;
+ return UpdateSourceProps(Source, Context);
+
+ case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE);
+
+ Source->WetGainAuto = values[0] != AL_FALSE;
+ return UpdateSourceProps(Source, Context);
+
+ case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE);
+
+ Source->WetGainHFAuto = values[0] != AL_FALSE;
+ return UpdateSourceProps(Source, Context);
+
+ case AL_DIRECT_CHANNELS_SOFT:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE);
+
+ Source->DirectChannels = values[0] != AL_FALSE;
+ return UpdateSourceProps(Source, Context);
+
+ case AL_DISTANCE_MODEL:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] == AL_NONE ||
+ values[0] == AL_INVERSE_DISTANCE || values[0] == AL_INVERSE_DISTANCE_CLAMPED ||
+ values[0] == AL_LINEAR_DISTANCE || values[0] == AL_LINEAR_DISTANCE_CLAMPED ||
+ values[0] == AL_EXPONENT_DISTANCE || values[0] == AL_EXPONENT_DISTANCE_CLAMPED);
+
+ Source->mDistanceModel = static_cast<DistanceModel>(values[0]);
+ if(Context->mSourceDistanceModel)
+ return UpdateSourceProps(Source, Context);
+ return true;
+
+ case AL_SOURCE_RESAMPLER_SOFT:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= 0 && values[0] <= static_cast<int>(Resampler::Max));
+
+ Source->mResampler = static_cast<Resampler>(values[0]);
+ return UpdateSourceProps(Source, Context);
+
+ case AL_SOURCE_SPATIALIZE_SOFT:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] >= AL_FALSE && values[0] <= AL_AUTO_SOFT);
+
+ Source->mSpatialize = static_cast<SpatializeMode>(values[0]);
+ return UpdateSourceProps(Source, Context);
+
+
+ case AL_AUXILIARY_SEND_FILTER:
+ CHECKSIZE(values, 3);
+ slotlock = std::unique_lock<std::mutex>{Context->mEffectSlotLock};
+ if(values[0] && (slot=LookupEffectSlot(Context, static_cast<ALuint>(values[0]))) == nullptr)
+ SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid effect ID %u", values[0]);
+ if(static_cast<ALuint>(values[1]) >= device->NumAuxSends)
+ SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid send %u", values[1]);
+
+ filtlock = std::unique_lock<std::mutex>{device->FilterLock};
+ if(values[2] && (filter=LookupFilter(device, static_cast<ALuint>(values[2]))) == nullptr)
+ SETERR_RETURN(Context, AL_INVALID_VALUE, false, "Invalid filter ID %u", values[2]);
+
+ if(!filter)
+ {
+ /* Disable filter */
+ auto &send = Source->Send[static_cast<ALuint>(values[1])];
+ send.Gain = 1.0f;
+ send.GainHF = 1.0f;
+ send.HFReference = LOWPASSFREQREF;
+ send.GainLF = 1.0f;
+ send.LFReference = HIGHPASSFREQREF;
+ }
+ else
+ {
+ auto &send = Source->Send[static_cast<ALuint>(values[1])];
+ send.Gain = filter->Gain;
+ send.GainHF = filter->GainHF;
+ send.HFReference = filter->HFReference;
+ send.GainLF = filter->GainLF;
+ send.LFReference = filter->LFReference;
+ }
+ filtlock.unlock();
+
+ if(slot != Source->Send[static_cast<ALuint>(values[1])].Slot && IsPlayingOrPaused(Source))
+ {
+ /* Add refcount on the new slot, and release the previous slot */
+ if(slot) IncrementRef(slot->ref);
+ if(auto *oldslot = Source->Send[static_cast<ALuint>(values[1])].Slot)
+ DecrementRef(oldslot->ref);
+ Source->Send[static_cast<ALuint>(values[1])].Slot = slot;
+
+ /* We must force an update if the auxiliary slot changed on an
+ * active source, in case the slot is about to be deleted.
+ */
+ ALvoice *voice{GetSourceVoice(Source, Context)};
+ if(voice) UpdateSourceProps(Source, voice, Context);
+ else Source->PropsClean.clear(std::memory_order_release);
+ }
+ else
+ {
+ if(slot) IncrementRef(slot->ref);
+ if(auto *oldslot = Source->Send[static_cast<ALuint>(values[1])].Slot)
+ DecrementRef(oldslot->ref);
+ Source->Send[static_cast<ALuint>(values[1])].Slot = slot;
+ UpdateSourceProps(Source, Context);
+ }
+ return true;
+
+
+ /* 1x float */
+ case AL_CONE_INNER_ANGLE:
+ case AL_CONE_OUTER_ANGLE:
+ case AL_PITCH:
+ case AL_GAIN:
+ case AL_MIN_GAIN:
+ case AL_MAX_GAIN:
+ case AL_REFERENCE_DISTANCE:
+ case AL_ROLLOFF_FACTOR:
+ case AL_CONE_OUTER_GAIN:
+ case AL_MAX_DISTANCE:
+ case AL_DOPPLER_FACTOR:
+ case AL_CONE_OUTER_GAINHF:
+ case AL_AIR_ABSORPTION_FACTOR:
+ case AL_ROOM_ROLLOFF_FACTOR:
+ case AL_SOURCE_RADIUS:
+ CHECKSIZE(values, 1);
+ fvals[0] = static_cast<ALfloat>(values[0]);
+ return SetSourcefv(Source, Context, prop, {fvals, 1u});
+
+ /* 3x float */
+ case AL_POSITION:
+ case AL_VELOCITY:
+ case AL_DIRECTION:
+ CHECKSIZE(values, 3);
+ fvals[0] = static_cast<ALfloat>(values[0]);
+ fvals[1] = static_cast<ALfloat>(values[1]);
+ fvals[2] = static_cast<ALfloat>(values[2]);
+ return SetSourcefv(Source, Context, prop, {fvals, 3u});
+
+ /* 6x float */
+ case AL_ORIENTATION:
+ CHECKSIZE(values, 6);
+ fvals[0] = static_cast<ALfloat>(values[0]);
+ fvals[1] = static_cast<ALfloat>(values[1]);
+ fvals[2] = static_cast<ALfloat>(values[2]);
+ fvals[3] = static_cast<ALfloat>(values[3]);
+ fvals[4] = static_cast<ALfloat>(values[4]);
+ fvals[5] = static_cast<ALfloat>(values[5]);
+ return SetSourcefv(Source, Context, prop, {fvals, 6u});
+
+ case AL_SAMPLE_OFFSET_LATENCY_SOFT:
+ case AL_SEC_OFFSET_LATENCY_SOFT:
+ case AL_SEC_OFFSET_CLOCK_SOFT:
+ case AL_SAMPLE_OFFSET_CLOCK_SOFT:
+ case AL_STEREO_ANGLES:
+ break;
+ }
+
+ ERR("Unexpected property: 0x%04x\n", prop);
+ Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop);
+ return false;
+}
+
+bool SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<const ALint64SOFT> values)
+{
+ ALfloat fvals[MaxValues];
+ ALint ivals[MaxValues];
+
+ switch(prop)
+ {
+ case AL_SOURCE_TYPE:
+ case AL_BUFFERS_QUEUED:
+ case AL_BUFFERS_PROCESSED:
+ case AL_SOURCE_STATE:
+ case AL_SAMPLE_OFFSET_LATENCY_SOFT:
+ case AL_SAMPLE_OFFSET_CLOCK_SOFT:
+ /* Query only */
+ SETERR_RETURN(Context, AL_INVALID_OPERATION, false,
+ "Setting read-only source property 0x%04x", prop);
+
+ /* 1x int */
+ case AL_SOURCE_RELATIVE:
+ case AL_LOOPING:
+ case AL_SEC_OFFSET:
+ case AL_SAMPLE_OFFSET:
+ case AL_BYTE_OFFSET:
+ case AL_DIRECT_FILTER_GAINHF_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
+ case AL_DIRECT_CHANNELS_SOFT:
+ case AL_DISTANCE_MODEL:
+ case AL_SOURCE_RESAMPLER_SOFT:
+ case AL_SOURCE_SPATIALIZE_SOFT:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] <= INT_MAX && values[0] >= INT_MIN);
+
+ ivals[0] = static_cast<ALint>(values[0]);
+ return SetSourceiv(Source, Context, prop, {ivals, 1u});
+
+ /* 1x uint */
+ case AL_BUFFER:
+ case AL_DIRECT_FILTER:
+ CHECKSIZE(values, 1);
+ CHECKVAL(values[0] <= UINT_MAX && values[0] >= 0);
+
+ ivals[0] = static_cast<ALint>(values[0]);
+ return SetSourceiv(Source, Context, prop, {ivals, 1u});
+
+ /* 3x uint */
+ case AL_AUXILIARY_SEND_FILTER:
+ CHECKSIZE(values, 3);
+ CHECKVAL(values[0] <= UINT_MAX && values[0] >= 0 && values[1] <= UINT_MAX && values[1] >= 0
+ && values[2] <= UINT_MAX && values[2] >= 0);
+
+ ivals[0] = static_cast<ALint>(values[0]);
+ ivals[1] = static_cast<ALint>(values[1]);
+ ivals[2] = static_cast<ALint>(values[2]);
+ return SetSourceiv(Source, Context, prop, {ivals, 3u});
+
+ /* 1x float */
+ case AL_CONE_INNER_ANGLE:
+ case AL_CONE_OUTER_ANGLE:
+ case AL_PITCH:
+ case AL_GAIN:
+ case AL_MIN_GAIN:
+ case AL_MAX_GAIN:
+ case AL_REFERENCE_DISTANCE:
+ case AL_ROLLOFF_FACTOR:
+ case AL_CONE_OUTER_GAIN:
+ case AL_MAX_DISTANCE:
+ case AL_DOPPLER_FACTOR:
+ case AL_CONE_OUTER_GAINHF:
+ case AL_AIR_ABSORPTION_FACTOR:
+ case AL_ROOM_ROLLOFF_FACTOR:
+ case AL_SOURCE_RADIUS:
+ CHECKSIZE(values, 1);
+ fvals[0] = static_cast<ALfloat>(values[0]);
+ return SetSourcefv(Source, Context, prop, {fvals, 1u});
+
+ /* 3x float */
+ case AL_POSITION:
+ case AL_VELOCITY:
+ case AL_DIRECTION:
+ CHECKSIZE(values, 3);
+ fvals[0] = static_cast<ALfloat>(values[0]);
+ fvals[1] = static_cast<ALfloat>(values[1]);
+ fvals[2] = static_cast<ALfloat>(values[2]);
+ return SetSourcefv(Source, Context, prop, {fvals, 3u});
+
+ /* 6x float */
+ case AL_ORIENTATION:
+ CHECKSIZE(values, 6);
+ fvals[0] = static_cast<ALfloat>(values[0]);
+ fvals[1] = static_cast<ALfloat>(values[1]);
+ fvals[2] = static_cast<ALfloat>(values[2]);
+ fvals[3] = static_cast<ALfloat>(values[3]);
+ fvals[4] = static_cast<ALfloat>(values[4]);
+ fvals[5] = static_cast<ALfloat>(values[5]);
+ return SetSourcefv(Source, Context, prop, {fvals, 6u});
+
+ case AL_SEC_OFFSET_LATENCY_SOFT:
+ case AL_SEC_OFFSET_CLOCK_SOFT:
+ case AL_STEREO_ANGLES:
+ break;
+ }
+
+ ERR("Unexpected property: 0x%04x\n", prop);
+ Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop);
+ return false;
+}
+
+#undef CHECKVAL
+
+
+bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<ALdouble> values);
+bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<ALint> values);
+bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<ALint64SOFT> values);
+
+bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<ALdouble> values)
+{
+ ALCdevice *device{Context->mDevice.get()};
+ ClockLatency clocktime;
+ nanoseconds srcclock;
+ ALint ivals[MaxValues];
+ bool err;
+
+ switch(prop)
+ {
+ case AL_GAIN:
+ CHECKSIZE(values, 1);
+ values[0] = Source->Gain;
+ return true;
+
+ case AL_PITCH:
+ CHECKSIZE(values, 1);
+ values[0] = Source->Pitch;
+ return true;
+
+ case AL_MAX_DISTANCE:
+ CHECKSIZE(values, 1);
+ values[0] = Source->MaxDistance;
+ return true;
+
+ case AL_ROLLOFF_FACTOR:
+ CHECKSIZE(values, 1);
+ values[0] = Source->RolloffFactor;
+ return true;
+
+ case AL_REFERENCE_DISTANCE:
+ CHECKSIZE(values, 1);
+ values[0] = Source->RefDistance;
+ return true;
+
+ case AL_CONE_INNER_ANGLE:
+ CHECKSIZE(values, 1);
+ values[0] = Source->InnerAngle;
+ return true;
+
+ case AL_CONE_OUTER_ANGLE:
+ CHECKSIZE(values, 1);
+ values[0] = Source->OuterAngle;
+ return true;
+
+ case AL_MIN_GAIN:
+ CHECKSIZE(values, 1);
+ values[0] = Source->MinGain;
+ return true;
+
+ case AL_MAX_GAIN:
+ CHECKSIZE(values, 1);
+ values[0] = Source->MaxGain;
+ return true;
+
+ case AL_CONE_OUTER_GAIN:
+ CHECKSIZE(values, 1);
+ values[0] = Source->OuterGain;
+ return true;
+
+ case AL_SEC_OFFSET:
+ case AL_SAMPLE_OFFSET:
+ case AL_BYTE_OFFSET:
+ CHECKSIZE(values, 1);
+ values[0] = GetSourceOffset(Source, prop, Context);
+ return true;
+
+ case AL_CONE_OUTER_GAINHF:
+ CHECKSIZE(values, 1);
+ values[0] = Source->OuterGainHF;
+ return true;
+
+ case AL_AIR_ABSORPTION_FACTOR:
+ CHECKSIZE(values, 1);
+ values[0] = Source->AirAbsorptionFactor;
+ return true;
+
+ case AL_ROOM_ROLLOFF_FACTOR:
+ CHECKSIZE(values, 1);
+ values[0] = Source->RoomRolloffFactor;
+ return true;
+
+ case AL_DOPPLER_FACTOR:
+ CHECKSIZE(values, 1);
+ values[0] = Source->DopplerFactor;
+ return true;
+
+ case AL_SOURCE_RADIUS:
+ CHECKSIZE(values, 1);
+ values[0] = Source->Radius;
+ return true;
+
+ case AL_STEREO_ANGLES:
+ CHECKSIZE(values, 2);
+ values[0] = Source->StereoPan[0];
+ values[1] = Source->StereoPan[1];
+ return true;
+
+ case AL_SEC_OFFSET_LATENCY_SOFT:
+ CHECKSIZE(values, 2);
+ /* Get the source offset with the clock time first. Then get the clock
+ * time with the device latency. Order is important.
+ */
+ values[0] = GetSourceSecOffset(Source, Context, &srcclock);
+ {
+ std::lock_guard<std::mutex> _{device->StateLock};
+ clocktime = GetClockLatency(device);
+ }
+ if(srcclock == clocktime.ClockTime)
+ values[1] = static_cast<ALdouble>(clocktime.Latency.count()) / 1000000000.0;
+ else
+ {
+ /* If the clock time incremented, reduce the latency by that much
+ * since it's that much closer to the source offset it got earlier.
+ */
+ const nanoseconds diff{clocktime.ClockTime - srcclock};
+ const nanoseconds latency{clocktime.Latency - std::min(clocktime.Latency, diff)};
+ values[1] = static_cast<ALdouble>(latency.count()) / 1000000000.0;
+ }
+ return true;
+
+ case AL_SEC_OFFSET_CLOCK_SOFT:
+ CHECKSIZE(values, 2);
+ values[0] = GetSourceSecOffset(Source, Context, &srcclock);
+ values[1] = static_cast<ALdouble>(srcclock.count()) / 1000000000.0;
+ return true;
+
+ case AL_POSITION:
+ CHECKSIZE(values, 3);
+ values[0] = Source->Position[0];
+ values[1] = Source->Position[1];
+ values[2] = Source->Position[2];
+ return true;
+
+ case AL_VELOCITY:
+ CHECKSIZE(values, 3);
+ values[0] = Source->Velocity[0];
+ values[1] = Source->Velocity[1];
+ values[2] = Source->Velocity[2];
+ return true;
+
+ case AL_DIRECTION:
+ CHECKSIZE(values, 3);
+ values[0] = Source->Direction[0];
+ values[1] = Source->Direction[1];
+ values[2] = Source->Direction[2];
+ return true;
+
+ case AL_ORIENTATION:
+ CHECKSIZE(values, 6);
+ values[0] = Source->OrientAt[0];
+ values[1] = Source->OrientAt[1];
+ values[2] = Source->OrientAt[2];
+ values[3] = Source->OrientUp[0];
+ values[4] = Source->OrientUp[1];
+ values[5] = Source->OrientUp[2];
+ return true;
+
+ /* 1x int */
+ case AL_SOURCE_RELATIVE:
+ case AL_LOOPING:
+ case AL_SOURCE_STATE:
+ case AL_BUFFERS_QUEUED:
+ case AL_BUFFERS_PROCESSED:
+ case AL_SOURCE_TYPE:
+ case AL_DIRECT_FILTER_GAINHF_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
+ case AL_DIRECT_CHANNELS_SOFT:
+ case AL_DISTANCE_MODEL:
+ case AL_SOURCE_RESAMPLER_SOFT:
+ case AL_SOURCE_SPATIALIZE_SOFT:
+ CHECKSIZE(values, 1);
+ if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false)
+ values[0] = static_cast<ALdouble>(ivals[0]);
+ return err;
+
+ case AL_BUFFER:
+ case AL_DIRECT_FILTER:
+ case AL_AUXILIARY_SEND_FILTER:
+ case AL_SAMPLE_OFFSET_LATENCY_SOFT:
+ case AL_SAMPLE_OFFSET_CLOCK_SOFT:
+ break;
+ }
+
+ ERR("Unexpected property: 0x%04x\n", prop);
+ Context->setError(AL_INVALID_ENUM, "Invalid source double property 0x%04x", prop);
+ return false;
+}
+
+bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<ALint> values)
+{
+ ALdouble dvals[MaxValues];
+ bool err;
+
+ switch(prop)
+ {
+ case AL_SOURCE_RELATIVE:
+ CHECKSIZE(values, 1);
+ values[0] = Source->HeadRelative;
+ return true;
+
+ case AL_LOOPING:
+ CHECKSIZE(values, 1);
+ values[0] = Source->Looping;
+ return true;
+
+ case AL_BUFFER:
+ CHECKSIZE(values, 1);
+ {
+ ALbufferlistitem *BufferList{nullptr};
+ if(Source->SourceType == AL_STATIC) BufferList = Source->queue;
+ ALbuffer *buffer{nullptr};
+ if(BufferList) buffer = BufferList->mBuffer;
+ values[0] = buffer ? static_cast<ALint>(buffer->id) : 0;
+ }
+ return true;
+
+ case AL_SOURCE_STATE:
+ CHECKSIZE(values, 1);
+ values[0] = GetSourceState(Source, GetSourceVoice(Source, Context));
+ return true;
+
+ case AL_BUFFERS_QUEUED:
+ CHECKSIZE(values, 1);
+ if(ALbufferlistitem *BufferList{Source->queue})
+ {
+ ALsizei count{0};
+ do {
+ ++count;
+ BufferList = BufferList->mNext.load(std::memory_order_relaxed);
+ } while(BufferList != nullptr);
+ values[0] = count;
+ }
+ else
+ values[0] = 0;
+ return true;
+
+ case AL_BUFFERS_PROCESSED:
+ CHECKSIZE(values, 1);
+ if(Source->Looping || Source->SourceType != AL_STREAMING)
+ {
+ /* Buffers on a looping source are in a perpetual state of PENDING,
+ * so don't report any as PROCESSED
+ */
+ values[0] = 0;
+ }
+ else
+ {
+ const ALbufferlistitem *BufferList{Source->queue};
+ const ALbufferlistitem *Current{nullptr};
+ ALsizei played{0};
+
+ ALvoice *voice{GetSourceVoice(Source, Context)};
+ if(voice != nullptr)
+ Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
+ else if(Source->state == AL_INITIAL)
+ Current = BufferList;
+
+ while(BufferList && BufferList != Current)
+ {
+ ++played;
+ BufferList = BufferList->mNext.load(std::memory_order_relaxed);
+ }
+ values[0] = played;
+ }
+ return true;
+
+ case AL_SOURCE_TYPE:
+ CHECKSIZE(values, 1);
+ values[0] = Source->SourceType;
+ return true;
+
+ case AL_DIRECT_FILTER_GAINHF_AUTO:
+ CHECKSIZE(values, 1);
+ values[0] = Source->DryGainHFAuto;
+ return true;
+
+ case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
+ CHECKSIZE(values, 1);
+ values[0] = Source->WetGainAuto;
+ return true;
+
+ case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
+ CHECKSIZE(values, 1);
+ values[0] = Source->WetGainHFAuto;
+ return true;
+
+ case AL_DIRECT_CHANNELS_SOFT:
+ CHECKSIZE(values, 1);
+ values[0] = Source->DirectChannels;
+ return true;
+
+ case AL_DISTANCE_MODEL:
+ CHECKSIZE(values, 1);
+ values[0] = static_cast<int>(Source->mDistanceModel);
+ return true;
+
+ case AL_SOURCE_RESAMPLER_SOFT:
+ CHECKSIZE(values, 1);
+ values[0] = static_cast<int>(Source->mResampler);
+ return true;
+
+ case AL_SOURCE_SPATIALIZE_SOFT:
+ CHECKSIZE(values, 1);
+ values[0] = Source->mSpatialize;
+ return true;
+
+ /* 1x float/double */
+ case AL_CONE_INNER_ANGLE:
+ case AL_CONE_OUTER_ANGLE:
+ case AL_PITCH:
+ case AL_GAIN:
+ case AL_MIN_GAIN:
+ case AL_MAX_GAIN:
+ case AL_REFERENCE_DISTANCE:
+ case AL_ROLLOFF_FACTOR:
+ case AL_CONE_OUTER_GAIN:
+ case AL_MAX_DISTANCE:
+ case AL_SEC_OFFSET:
+ case AL_SAMPLE_OFFSET:
+ case AL_BYTE_OFFSET:
+ case AL_DOPPLER_FACTOR:
+ case AL_AIR_ABSORPTION_FACTOR:
+ case AL_ROOM_ROLLOFF_FACTOR:
+ case AL_CONE_OUTER_GAINHF:
+ case AL_SOURCE_RADIUS:
+ CHECKSIZE(values, 1);
+ if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false)
+ values[0] = static_cast<ALint>(dvals[0]);
+ return err;
+
+ /* 3x float/double */
+ case AL_POSITION:
+ case AL_VELOCITY:
+ case AL_DIRECTION:
+ CHECKSIZE(values, 3);
+ if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false)
+ {
+ values[0] = static_cast<ALint>(dvals[0]);
+ values[1] = static_cast<ALint>(dvals[1]);
+ values[2] = static_cast<ALint>(dvals[2]);
+ }
+ return err;
+
+ /* 6x float/double */
+ case AL_ORIENTATION:
+ CHECKSIZE(values, 6);
+ if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false)
+ {
+ values[0] = static_cast<ALint>(dvals[0]);
+ values[1] = static_cast<ALint>(dvals[1]);
+ values[2] = static_cast<ALint>(dvals[2]);
+ values[3] = static_cast<ALint>(dvals[3]);
+ values[4] = static_cast<ALint>(dvals[4]);
+ values[5] = static_cast<ALint>(dvals[5]);
+ }
+ return err;
+
+ case AL_SAMPLE_OFFSET_LATENCY_SOFT:
+ case AL_SAMPLE_OFFSET_CLOCK_SOFT:
+ break; /* i64 only */
+ case AL_SEC_OFFSET_LATENCY_SOFT:
+ case AL_SEC_OFFSET_CLOCK_SOFT:
+ break; /* Double only */
+ case AL_STEREO_ANGLES:
+ break; /* Float/double only */
+
+ case AL_DIRECT_FILTER:
+ case AL_AUXILIARY_SEND_FILTER:
+ break; /* ??? */
+ }
+
+ ERR("Unexpected property: 0x%04x\n", prop);
+ Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop);
+ return false;
+}
+
+bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span<ALint64SOFT> values)
+{
+ ALCdevice *device = Context->mDevice.get();
+ ClockLatency clocktime;
+ nanoseconds srcclock;
+ ALdouble dvals[MaxValues];
+ ALint ivals[MaxValues];
+ bool err;
+
+ switch(prop)
+ {
+ case AL_SAMPLE_OFFSET_LATENCY_SOFT:
+ CHECKSIZE(values, 2);
+ /* Get the source offset with the clock time first. Then get the clock
+ * time with the device latency. Order is important.
+ */
+ values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
+ {
+ std::lock_guard<std::mutex> _{device->StateLock};
+ clocktime = GetClockLatency(device);
+ }
+ if(srcclock == clocktime.ClockTime)
+ values[1] = clocktime.Latency.count();
+ else
+ {
+ /* If the clock time incremented, reduce the latency by that much
+ * since it's that much closer to the source offset it got earlier.
+ */
+ const nanoseconds diff{clocktime.ClockTime - srcclock};
+ values[1] = nanoseconds{clocktime.Latency - std::min(clocktime.Latency, diff)}.count();
+ }
+ return true;
+
+ case AL_SAMPLE_OFFSET_CLOCK_SOFT:
+ CHECKSIZE(values, 2);
+ values[0] = GetSourceSampleOffset(Source, Context, &srcclock);
+ values[1] = srcclock.count();
+ return true;
+
+ /* 1x float/double */
+ case AL_CONE_INNER_ANGLE:
+ case AL_CONE_OUTER_ANGLE:
+ case AL_PITCH:
+ case AL_GAIN:
+ case AL_MIN_GAIN:
+ case AL_MAX_GAIN:
+ case AL_REFERENCE_DISTANCE:
+ case AL_ROLLOFF_FACTOR:
+ case AL_CONE_OUTER_GAIN:
+ case AL_MAX_DISTANCE:
+ case AL_SEC_OFFSET:
+ case AL_SAMPLE_OFFSET:
+ case AL_BYTE_OFFSET:
+ case AL_DOPPLER_FACTOR:
+ case AL_AIR_ABSORPTION_FACTOR:
+ case AL_ROOM_ROLLOFF_FACTOR:
+ case AL_CONE_OUTER_GAINHF:
+ case AL_SOURCE_RADIUS:
+ CHECKSIZE(values, 1);
+ if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false)
+ values[0] = static_cast<int64_t>(dvals[0]);
+ return err;
+
+ /* 3x float/double */
+ case AL_POSITION:
+ case AL_VELOCITY:
+ case AL_DIRECTION:
+ CHECKSIZE(values, 3);
+ if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false)
+ {
+ values[0] = static_cast<int64_t>(dvals[0]);
+ values[1] = static_cast<int64_t>(dvals[1]);
+ values[2] = static_cast<int64_t>(dvals[2]);
+ }
+ return err;
+
+ /* 6x float/double */
+ case AL_ORIENTATION:
+ CHECKSIZE(values, 6);
+ if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false)
+ {
+ values[0] = static_cast<int64_t>(dvals[0]);
+ values[1] = static_cast<int64_t>(dvals[1]);
+ values[2] = static_cast<int64_t>(dvals[2]);
+ values[3] = static_cast<int64_t>(dvals[3]);
+ values[4] = static_cast<int64_t>(dvals[4]);
+ values[5] = static_cast<int64_t>(dvals[5]);
+ }
+ return err;
+
+ /* 1x int */
+ case AL_SOURCE_RELATIVE:
+ case AL_LOOPING:
+ case AL_SOURCE_STATE:
+ case AL_BUFFERS_QUEUED:
+ case AL_BUFFERS_PROCESSED:
+ case AL_SOURCE_TYPE:
+ case AL_DIRECT_FILTER_GAINHF_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO:
+ case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO:
+ case AL_DIRECT_CHANNELS_SOFT:
+ case AL_DISTANCE_MODEL:
+ case AL_SOURCE_RESAMPLER_SOFT:
+ case AL_SOURCE_SPATIALIZE_SOFT:
+ CHECKSIZE(values, 1);
+ if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false)
+ values[0] = ivals[0];
+ return err;
+
+ /* 1x uint */
+ case AL_BUFFER:
+ case AL_DIRECT_FILTER:
+ CHECKSIZE(values, 1);
+ if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false)
+ values[0] = static_cast<ALuint>(ivals[0]);
+ return err;
+
+ /* 3x uint */
+ case AL_AUXILIARY_SEND_FILTER:
+ CHECKSIZE(values, 3);
+ if((err=GetSourceiv(Source, Context, prop, {ivals, 3u})) != false)
+ {
+ values[0] = static_cast<ALuint>(ivals[0]);
+ values[1] = static_cast<ALuint>(ivals[1]);
+ values[2] = static_cast<ALuint>(ivals[2]);
+ }
+ return err;
+
+ case AL_SEC_OFFSET_LATENCY_SOFT:
+ case AL_SEC_OFFSET_CLOCK_SOFT:
+ break; /* Double only */
+ case AL_STEREO_ANGLES:
+ break; /* Float/double only */
+ }
+
+ ERR("Unexpected property: 0x%04x\n", prop);
+ Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop);
+ return false;
+}
+
+} // namespace
+
+AL_API ALvoid AL_APIENTRY alGenSources(ALsizei n, ALuint *sources)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Generating %d sources", n);
+ if UNLIKELY(n <= 0) return;
+
+ std::unique_lock<std::mutex> srclock{context->mSourceLock};
+ ALCdevice *device{context->mDevice.get()};
+ if(static_cast<ALuint>(n) > device->SourcesMax-context->mNumSources)
+ {
+ context->setError(AL_OUT_OF_MEMORY, "Exceeding %u source limit (%u + %d)",
+ device->SourcesMax, context->mNumSources, n);
+ return;
+ }
+ if(!EnsureSources(context.get(), static_cast<ALuint>(n)))
+ {
+ context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n, (n==1)?"":"s");
+ return;
+ }
+
+ if(n == 1)
+ {
+ ALsource *source{AllocSource(context.get(), device->NumAuxSends)};
+ sources[0] = source->id;
+ }
+ else
+ {
+ const ALuint num_sends{device->NumAuxSends};
+ al::vector<ALuint> ids;
+ ids.reserve(static_cast<ALuint>(n));
+ do {
+ ALsource *source{AllocSource(context.get(), num_sends)};
+ ids.emplace_back(source->id);
+ } while(--n);
+ std::copy(ids.cbegin(), ids.cend(), sources);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Deleting %d sources", n);
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+
+ /* Check that all Sources are valid */
+ auto validate_source = [&context](const ALuint sid) -> bool
+ { return LookupSource(context.get(), sid) != nullptr; };
+
+ const ALuint *sources_end = sources + n;
+ auto invsrc = std::find_if_not(sources, sources_end, validate_source);
+ if UNLIKELY(invsrc != sources_end)
+ {
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", *invsrc);
+ return;
+ }
+
+ /* All good. Delete source IDs. */
+ auto delete_source = [&context](const ALuint sid) -> void
+ {
+ ALsource *src{LookupSource(context.get(), sid)};
+ if(src) FreeSource(context.get(), src);
+ };
+ std::for_each(sources, sources_end, delete_source);
+}
+END_API_FUNC
+
+AL_API ALboolean AL_APIENTRY alIsSource(ALuint source)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if LIKELY(context)
+ {
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ if(LookupSource(context.get(), source) != nullptr)
+ return AL_TRUE;
+ }
+ return AL_FALSE;
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source = LookupSource(context.get(), source);
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else
+ SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), {&value, 1u});
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source = LookupSource(context.get(), source);
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else
+ {
+ const ALfloat fvals[3]{ value1, value2, value3 };
+ SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), fvals);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source = LookupSource(context.get(), source);
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), {values, MaxValues});
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source = LookupSource(context.get(), source);
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else
+ {
+ const ALfloat fval[1]{static_cast<ALfloat>(value)};
+ SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), fval);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source = LookupSource(context.get(), source);
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else
+ {
+ const ALfloat fvals[3]{static_cast<ALfloat>(value1), static_cast<ALfloat>(value2),
+ static_cast<ALfloat>(value3)};
+ SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), fvals);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source = LookupSource(context.get(), source);
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ {
+ const ALuint count{DoubleValsByProp(param)};
+ ALfloat fvals[MaxValues];
+ for(ALuint i{0};i < count;i++)
+ fvals[i] = static_cast<ALfloat>(values[i]);
+ SetSourcefv(Source, context.get(), static_cast<SourceProp>(param), {fvals, count});
+ }
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source = LookupSource(context.get(), source);
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else
+ SetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {&value, 1u});
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source = LookupSource(context.get(), source);
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else
+ {
+ const ALint ivals[3]{ value1, value2, value3 };
+ SetSourceiv(Source, context.get(), static_cast<SourceProp>(param), ivals);
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source = LookupSource(context.get(), source);
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ SetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {values, MaxValues});
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else
+ SetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {&value, 1u});
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else
+ {
+ const ALint64SOFT i64vals[3]{ value1, value2, value3 };
+ SetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), i64vals);
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ std::lock_guard<std::mutex> __{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ SetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {values, MaxValues});
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!value)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ {
+ ALdouble dval[1];
+ if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), dval))
+ *value = static_cast<ALfloat>(dval[0]);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!(value1 && value2 && value3))
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ {
+ ALdouble dvals[3];
+ if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), dvals))
+ {
+ *value1 = static_cast<ALfloat>(dvals[0]);
+ *value2 = static_cast<ALfloat>(dvals[1]);
+ *value3 = static_cast<ALfloat>(dvals[2]);
+ }
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ {
+ const ALuint count{FloatValsByProp(param)};
+ ALdouble dvals[MaxValues];
+ if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), {dvals, count}))
+ {
+ for(ALuint i{0};i < count;i++)
+ values[i] = static_cast<ALfloat>(dvals[i]);
+ }
+ }
+}
+END_API_FUNC
+
+
+AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!value)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), {value, 1u});
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!(value1 && value2 && value3))
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ {
+ ALdouble dvals[3];
+ if(GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), dvals))
+ {
+ *value1 = dvals[0];
+ *value2 = dvals[1];
+ *value3 = dvals[2];
+ }
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ GetSourcedv(Source, context.get(), static_cast<SourceProp>(param), {values, MaxValues});
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!value)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ GetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {value, 1u});
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!(value1 && value2 && value3))
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ {
+ ALint ivals[3];
+ if(GetSourceiv(Source, context.get(), static_cast<SourceProp>(param), ivals))
+ {
+ *value1 = ivals[0];
+ *value2 = ivals[1];
+ *value3 = ivals[2];
+ }
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ GetSourceiv(Source, context.get(), static_cast<SourceProp>(param), {values, MaxValues});
+}
+END_API_FUNC
+
+
+AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!value)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ GetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {value, 1u});
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!(value1 && value2 && value3))
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ {
+ ALint64SOFT i64vals[3];
+ if(GetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), i64vals))
+ {
+ *value1 = i64vals[0];
+ *value2 = i64vals[1];
+ *value3 = i64vals[2];
+ }
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *Source{LookupSource(context.get(), source)};
+ if UNLIKELY(!Source)
+ context->setError(AL_INVALID_NAME, "Invalid source ID %u", source);
+ else if UNLIKELY(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else
+ GetSourcei64v(Source, context.get(), static_cast<SourceProp>(param), {values, MaxValues});
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alSourcePlay(ALuint source)
+START_API_FUNC
+{ alSourcePlayv(1, &source); }
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Playing %d sources", n);
+ if UNLIKELY(n <= 0) return;
+
+ al::vector<ALsource*> extra_sources;
+ std::array<ALsource*,8> source_storage;
+ al::span<ALsource*> srchandles;
+ if LIKELY(static_cast<ALuint>(n) <= source_storage.size())
+ srchandles = {source_storage.data(), static_cast<ALuint>(n)};
+ else
+ {
+ extra_sources.resize(static_cast<ALuint>(n));
+ srchandles = {extra_sources.data(), extra_sources.size()};
+ }
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ for(auto &srchdl : srchandles)
+ {
+ srchdl = LookupSource(context.get(), *sources);
+ if(!srchdl)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources);
+ ++sources;
+ }
+
+ ALCdevice *device{context->mDevice.get()};
+ BackendLockGuard __{*device->Backend};
+ /* If the device is disconnected, go right to stopped. */
+ if UNLIKELY(!device->Connected.load(std::memory_order_acquire))
+ {
+ /* TODO: Send state change event? */
+ std::for_each(srchandles.begin(), srchandles.end(),
+ [](ALsource *source) -> void
+ {
+ source->OffsetType = AL_NONE;
+ source->Offset = 0.0;
+ source->state = AL_STOPPED;
+ }
+ );
+ return;
+ }
+
+ /* Count the number of reusable voices. */
+ auto count_free_voices = [](const ALuint count, const ALvoice &voice) noexcept -> ALuint
+ {
+ if(voice.mPlayState.load(std::memory_order_acquire) == ALvoice::Stopped
+ && voice.mSourceID.load(std::memory_order_relaxed) == 0u)
+ return count + 1;
+ return count;
+ };
+ auto free_voices = std::accumulate(context->mVoices.begin(), context->mVoices.end(),
+ ALuint{0}, count_free_voices);
+ if UNLIKELY(srchandles.size() > free_voices)
+ {
+ /* Increase the number of voices to handle the request. */
+ const size_t need_voices{srchandles.size() - free_voices};
+ context->mVoices.resize(context->mVoices.size() + need_voices);
+ }
+
+ auto start_source = [&context,device](ALsource *source) -> void
+ {
+ /* Check that there is a queue containing at least one valid, non zero
+ * length buffer.
+ */
+ ALbufferlistitem *BufferList{source->queue};
+ while(BufferList && BufferList->mSampleLen == 0)
+ BufferList = BufferList->mNext.load(std::memory_order_relaxed);
+
+ /* If there's nothing to play, go right to stopped. */
+ if UNLIKELY(!BufferList)
+ {
+ /* NOTE: A source without any playable buffers should not have an
+ * ALvoice since it shouldn't be in a playing or paused state. So
+ * there's no need to look up its voice and clear the source.
+ */
+ ALenum oldstate{GetSourceState(source, nullptr)};
+ source->OffsetType = AL_NONE;
+ source->Offset = 0.0;
+ if(oldstate != AL_STOPPED)
+ {
+ source->state = AL_STOPPED;
+ SendStateChangeEvent(context.get(), source->id, AL_STOPPED);
+ }
+ return;
+ }
+
+ ALvoice *voice{GetSourceVoice(source, context.get())};
+ switch(GetSourceState(source, voice))
+ {
+ case AL_PAUSED:
+ assert(voice != nullptr);
+ /* A source that's paused simply resumes. */
+ voice->mPlayState.store(ALvoice::Playing, std::memory_order_release);
+ source->state = AL_PLAYING;
+ SendStateChangeEvent(context.get(), source->id, AL_PLAYING);
+ return;
+
+ case AL_PLAYING:
+ assert(voice != nullptr);
+ /* A source that's already playing is restarted from the beginning.
+ * Stop the current voice and start a new one so it properly cross-
+ * fades back to the beginning.
+ */
+ voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
+ voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
+ voice->mSourceID.store(0u, std::memory_order_release);
+ voice->mPlayState.store(ALvoice::Stopping, std::memory_order_release);
+ voice = nullptr;
+ break;
+
+ default:
+ assert(voice == nullptr);
+ break;
+ }
+
+ /* Look for an unused voice to play this source with. */
+ auto find_voice = [](const ALvoice &v) noexcept -> bool
+ {
+ return v.mPlayState.load(std::memory_order_acquire) == ALvoice::Stopped
+ && v.mSourceID.load(std::memory_order_relaxed) == 0u;
+ };
+ auto voices_end = context->mVoices.data() + context->mVoices.size();
+ voice = std::find_if(context->mVoices.data(), voices_end, find_voice);
+ assert(voice != voices_end);
+
+ auto vidx = static_cast<ALuint>(std::distance(context->mVoices.data(), voice));
+ voice->mPlayState.store(ALvoice::Stopped, std::memory_order_release);
+
+ source->PropsClean.test_and_set(std::memory_order_acquire);
+ UpdateSourceProps(source, voice, context.get());
+
+ /* A source that's not playing or paused has any offset applied when it
+ * starts playing.
+ */
+ if(source->Looping)
+ voice->mLoopBuffer.store(source->queue, std::memory_order_relaxed);
+ else
+ voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
+ voice->mCurrentBuffer.store(BufferList, std::memory_order_relaxed);
+ voice->mPosition.store(0u, std::memory_order_relaxed);
+ voice->mPositionFrac.store(0, std::memory_order_relaxed);
+ bool start_fading{false};
+ if(auto vpos = GetSampleOffset(source))
+ {
+ start_fading = vpos->pos != 0 || vpos->frac != 0 || vpos->bufferitem != BufferList;
+ voice->mPosition.store(vpos->pos, std::memory_order_relaxed);
+ voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed);
+ voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed);
+ }
+
+ ALbuffer *buffer{BufferList->mBuffer};
+ voice->mFrequency = buffer->Frequency;
+ voice->mFmtChannels = buffer->mFmtChannels;
+ voice->mNumChannels = ChannelsFromFmt(buffer->mFmtChannels);
+ voice->mSampleSize = BytesFromFmt(buffer->mFmtType);
+
+ /* Clear the stepping value so the mixer knows not to mix this until
+ * the update gets applied.
+ */
+ voice->mStep = 0;
+
+ voice->mFlags = start_fading ? VOICE_IS_FADING : 0;
+ if(source->SourceType == AL_STATIC) voice->mFlags |= VOICE_IS_STATIC;
+
+ /* Don't need to set the VOICE_IS_AMBISONIC flag if the device is
+ * mixing in first order. No HF scaling is necessary to mix it.
+ */
+ if((voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtBFormat3D)
+ && device->mAmbiOrder > 1)
+ {
+ const ALuint *OrderFromChan;
+ if(voice->mFmtChannels == FmtBFormat2D)
+ {
+ static const ALuint Order2DFromChan[MAX_AMBI2D_CHANNELS]{
+ 0, 1,1, 2,2, 3,3,};
+ OrderFromChan = Order2DFromChan;
+ }
+ else
+ {
+ static const ALuint Order3DFromChan[MAX_AMBI_CHANNELS]{
+ 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3,};
+ OrderFromChan = Order3DFromChan;
+ }
+
+ BandSplitter splitter{400.0f / static_cast<float>(device->Frequency)};
+
+ const auto scales = BFormatDec::GetHFOrderScales(1, device->mAmbiOrder);
+ auto init_ambi = [scales,&OrderFromChan,&splitter](ALvoice::ChannelData &chandata) -> void
+ {
+ chandata.mPrevSamples.fill(0.0f);
+ chandata.mAmbiScale = scales[*(OrderFromChan++)];
+ chandata.mAmbiSplitter = splitter;
+ };
+ std::for_each(voice->mChans.begin(), voice->mChans.begin()+voice->mNumChannels,
+ init_ambi);
+
+ voice->mFlags |= VOICE_IS_AMBISONIC;
+ }
+ else
+ {
+ /* Clear previous samples. */
+ auto clear_prevs = [](ALvoice::ChannelData &chandata) -> void
+ { chandata.mPrevSamples.fill(0.0f); };
+ std::for_each(voice->mChans.begin(), voice->mChans.begin()+voice->mNumChannels,
+ clear_prevs);
+ }
+
+ auto clear_params = [device](ALvoice::ChannelData &chandata) -> void
+ {
+ chandata.mDryParams = DirectParams{};
+ std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{});
+ };
+ std::for_each(voice->mChans.begin(), voice->mChans.begin()+voice->mNumChannels,
+ clear_params);
+
+ if(device->AvgSpeakerDist > 0.0f)
+ {
+ const ALfloat w1{SPEEDOFSOUNDMETRESPERSEC /
+ (device->AvgSpeakerDist * static_cast<float>(device->Frequency))};
+ auto init_nfc = [w1](ALvoice::ChannelData &chandata) -> void
+ { chandata.mDryParams.NFCtrlFilter.init(w1); };
+ std::for_each(voice->mChans.begin(), voice->mChans.begin()+voice->mNumChannels,
+ init_nfc);
+ }
+
+ voice->mSourceID.store(source->id, std::memory_order_relaxed);
+ voice->mPlayState.store(ALvoice::Playing, std::memory_order_release);
+ source->VoiceIdx = vidx;
+
+ if(source->state != AL_PLAYING)
+ {
+ source->state = AL_PLAYING;
+ SendStateChangeEvent(context.get(), source->id, AL_PLAYING);
+ }
+ };
+ std::for_each(srchandles.begin(), srchandles.end(), start_source);
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alSourcePause(ALuint source)
+START_API_FUNC
+{ alSourcePausev(1, &source); }
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Pausing %d sources", n);
+ if UNLIKELY(n <= 0) return;
+
+ al::vector<ALsource*> extra_sources;
+ std::array<ALsource*,8> source_storage;
+ al::span<ALsource*> srchandles;
+ if LIKELY(static_cast<ALuint>(n) <= source_storage.size())
+ srchandles = {source_storage.data(), static_cast<ALuint>(n)};
+ else
+ {
+ extra_sources.resize(static_cast<ALuint>(n));
+ srchandles = {extra_sources.data(), extra_sources.size()};
+ }
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ for(auto &srchdl : srchandles)
+ {
+ srchdl = LookupSource(context.get(), *sources);
+ if(!srchdl)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources);
+ ++sources;
+ }
+
+ ALCdevice *device{context->mDevice.get()};
+ BackendLockGuard __{*device->Backend};
+ auto pause_source = [&context](ALsource *source) -> void
+ {
+ ALvoice *voice{GetSourceVoice(source, context.get())};
+ if(voice)
+ {
+ std::atomic_thread_fence(std::memory_order_release);
+ ALvoice::State oldvstate{ALvoice::Playing};
+ voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping,
+ std::memory_order_acq_rel, std::memory_order_acquire);
+ }
+ if(GetSourceState(source, voice) == AL_PLAYING)
+ {
+ source->state = AL_PAUSED;
+ SendStateChangeEvent(context.get(), source->id, AL_PAUSED);
+ }
+ };
+ std::for_each(srchandles.begin(), srchandles.end(), pause_source);
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alSourceStop(ALuint source)
+START_API_FUNC
+{ alSourceStopv(1, &source); }
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Stopping %d sources", n);
+ if UNLIKELY(n <= 0) return;
+
+ al::vector<ALsource*> extra_sources;
+ std::array<ALsource*,8> source_storage;
+ al::span<ALsource*> srchandles;
+ if LIKELY(static_cast<ALuint>(n) <= source_storage.size())
+ srchandles = {source_storage.data(), static_cast<ALuint>(n)};
+ else
+ {
+ extra_sources.resize(static_cast<ALuint>(n));
+ srchandles = {extra_sources.data(), extra_sources.size()};
+ }
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ for(auto &srchdl : srchandles)
+ {
+ srchdl = LookupSource(context.get(), *sources);
+ if(!srchdl)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources);
+ ++sources;
+ }
+
+ ALCdevice *device{context->mDevice.get()};
+ BackendLockGuard __{*device->Backend};
+ auto stop_source = [&context](ALsource *source) -> void
+ {
+ /* Get the source state before clearing from the voice, so we know what
+ * state the source+voice was actually in.
+ */
+ ALvoice *voice{GetSourceVoice(source, context.get())};
+ const ALenum oldstate{GetSourceState(source, voice)};
+ if(voice != nullptr)
+ {
+ voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
+ voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
+ voice->mSourceID.store(0u, std::memory_order_relaxed);
+ std::atomic_thread_fence(std::memory_order_release);
+ ALvoice::State oldvstate{ALvoice::Playing};
+ voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping,
+ std::memory_order_acq_rel, std::memory_order_acquire);
+ voice = nullptr;
+ }
+ if(oldstate != AL_INITIAL && oldstate != AL_STOPPED)
+ {
+ source->state = AL_STOPPED;
+ SendStateChangeEvent(context.get(), source->id, AL_STOPPED);
+ }
+ source->OffsetType = AL_NONE;
+ source->Offset = 0.0;
+ };
+ std::for_each(srchandles.begin(), srchandles.end(), stop_source);
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alSourceRewind(ALuint source)
+START_API_FUNC
+{ alSourceRewindv(1, &source); }
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(n < 0)
+ context->setError(AL_INVALID_VALUE, "Rewinding %d sources", n);
+ if UNLIKELY(n <= 0) return;
+
+ al::vector<ALsource*> extra_sources;
+ std::array<ALsource*,8> source_storage;
+ al::span<ALsource*> srchandles;
+ if LIKELY(static_cast<ALuint>(n) <= source_storage.size())
+ srchandles = {source_storage.data(), static_cast<ALuint>(n)};
+ else
+ {
+ extra_sources.resize(static_cast<ALuint>(n));
+ srchandles = {extra_sources.data(), extra_sources.size()};
+ }
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ for(auto &srchdl : srchandles)
+ {
+ srchdl = LookupSource(context.get(), *sources);
+ if(!srchdl)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources);
+ ++sources;
+ }
+
+ ALCdevice *device{context->mDevice.get()};
+ BackendLockGuard __{*device->Backend};
+ auto rewind_source = [&context](ALsource *source) -> void
+ {
+ ALvoice *voice{GetSourceVoice(source, context.get())};
+ if(voice != nullptr)
+ {
+ voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed);
+ voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed);
+ voice->mSourceID.store(0u, std::memory_order_relaxed);
+ std::atomic_thread_fence(std::memory_order_release);
+ ALvoice::State oldvstate{ALvoice::Playing};
+ voice->mPlayState.compare_exchange_strong(oldvstate, ALvoice::Stopping,
+ std::memory_order_acq_rel, std::memory_order_acquire);
+ voice = nullptr;
+ }
+ if(source->state != AL_INITIAL)
+ {
+ source->state = AL_INITIAL;
+ SendStateChangeEvent(context.get(), source->id, AL_INITIAL);
+ }
+ source->OffsetType = AL_NONE;
+ source->Offset = 0.0;
+ };
+ std::for_each(srchandles.begin(), srchandles.end(), rewind_source);
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alSourceQueueBuffers(ALuint src, ALsizei nb, const ALuint *buffers)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(nb < 0)
+ context->setError(AL_INVALID_VALUE, "Queueing %d buffers", nb);
+ if UNLIKELY(nb <= 0) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *source{LookupSource(context.get(),src)};
+ if UNLIKELY(!source)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", src);
+
+ /* Can't queue on a Static Source */
+ if UNLIKELY(source->SourceType == AL_STATIC)
+ SETERR_RETURN(context, AL_INVALID_OPERATION,, "Queueing onto static source %u", src);
+
+ /* Check for a valid Buffer, for its frequency and format */
+ ALCdevice *device{context->mDevice.get()};
+ ALbuffer *BufferFmt{nullptr};
+ ALbufferlistitem *BufferList{source->queue};
+ while(BufferList && !BufferFmt)
+ {
+ BufferFmt = BufferList->mBuffer;
+ BufferList = BufferList->mNext.load(std::memory_order_relaxed);
+ }
+
+ std::unique_lock<std::mutex> buflock{device->BufferLock};
+ ALbufferlistitem *BufferListStart{nullptr};
+ BufferList = nullptr;
+ for(ALsizei i{0};i < nb;i++)
+ {
+ ALbuffer *buffer{nullptr};
+ if(buffers[i] && (buffer=LookupBuffer(device, buffers[i])) == nullptr)
+ {
+ context->setError(AL_INVALID_NAME, "Queueing invalid buffer ID %u", buffers[i]);
+ goto buffer_error;
+ }
+
+ if(!BufferListStart)
+ {
+ BufferListStart = new ALbufferlistitem{};
+ BufferList = BufferListStart;
+ }
+ else
+ {
+ auto item = new ALbufferlistitem{};
+ BufferList->mNext.store(item, std::memory_order_relaxed);
+ BufferList = item;
+ }
+ BufferList->mNext.store(nullptr, std::memory_order_relaxed);
+ BufferList->mSampleLen = buffer ? buffer->SampleLen : 0;
+ BufferList->mBuffer = buffer;
+ if(!buffer) continue;
+
+ IncrementRef(buffer->ref);
+
+ if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT))
+ {
+ context->setError(AL_INVALID_OPERATION, "Queueing non-persistently mapped buffer %u",
+ buffer->id);
+ goto buffer_error;
+ }
+
+ if(BufferFmt == nullptr)
+ BufferFmt = buffer;
+ else if(BufferFmt->Frequency != buffer->Frequency ||
+ BufferFmt->mFmtChannels != buffer->mFmtChannels ||
+ BufferFmt->OriginalType != buffer->OriginalType)
+ {
+ context->setError(AL_INVALID_OPERATION, "Queueing buffer with mismatched format");
+
+ buffer_error:
+ /* A buffer failed (invalid ID or format), so unlock and release
+ * each buffer we had. */
+ while(BufferListStart)
+ {
+ std::unique_ptr<ALbufferlistitem> head{BufferListStart};
+ BufferListStart = head->mNext.load(std::memory_order_relaxed);
+ if((buffer=head->mBuffer) != nullptr) DecrementRef(buffer->ref);
+ }
+ return;
+ }
+ }
+ /* All buffers good. */
+ buflock.unlock();
+
+ /* Source is now streaming */
+ source->SourceType = AL_STREAMING;
+
+ BufferList = source->queue;
+ if(!BufferList)
+ source->queue = BufferListStart;
+ else
+ {
+ ALbufferlistitem *next;
+ while((next=BufferList->mNext.load(std::memory_order_relaxed)) != nullptr)
+ BufferList = next;
+ BufferList->mNext.store(BufferListStart, std::memory_order_release);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alSourceUnqueueBuffers(ALuint src, ALsizei nb, ALuint *buffers)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if UNLIKELY(nb < 0)
+ context->setError(AL_INVALID_VALUE, "Unqueueing %d buffers", nb);
+ if UNLIKELY(nb <= 0) return;
+
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ ALsource *source{LookupSource(context.get(),src)};
+ if UNLIKELY(!source)
+ SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", src);
+
+ if UNLIKELY(source->Looping)
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Unqueueing from looping source %u", src);
+ if UNLIKELY(source->SourceType != AL_STREAMING)
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Unqueueing from a non-streaming source %u",
+ src);
+
+ /* Make sure enough buffers have been processed to unqueue. */
+ ALbufferlistitem *BufferList{source->queue};
+ ALvoice *voice{GetSourceVoice(source, context.get())};
+ ALbufferlistitem *Current{nullptr};
+ if(voice)
+ Current = voice->mCurrentBuffer.load(std::memory_order_relaxed);
+ else if(source->state == AL_INITIAL)
+ Current = BufferList;
+ if UNLIKELY(BufferList == Current)
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Unqueueing pending buffers");
+
+ ALuint i{1u};
+ while(i < static_cast<ALuint>(nb))
+ {
+ /* If the next bufferlist to check is NULL or is the current one, it's
+ * trying to unqueue pending buffers.
+ */
+ ALbufferlistitem *next{BufferList->mNext.load(std::memory_order_relaxed)};
+ if UNLIKELY(!next || next == Current)
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Unqueueing pending buffers");
+ BufferList = next;
+
+ ++i;
+ }
+
+ do {
+ std::unique_ptr<ALbufferlistitem> head{source->queue};
+ source->queue = head->mNext.load(std::memory_order_relaxed);
+
+ if(ALbuffer *buffer{head->mBuffer})
+ {
+ *(buffers++) = buffer->id;
+ DecrementRef(buffer->ref);
+ }
+ else
+ *(buffers++) = 0;
+ } while(--nb);
+}
+END_API_FUNC
+
+
+ALsource::ALsource(ALuint num_sends)
+{
+ InnerAngle = 360.0f;
+ OuterAngle = 360.0f;
+ Pitch = 1.0f;
+ Position[0] = 0.0f;
+ Position[1] = 0.0f;
+ Position[2] = 0.0f;
+ Velocity[0] = 0.0f;
+ Velocity[1] = 0.0f;
+ Velocity[2] = 0.0f;
+ Direction[0] = 0.0f;
+ Direction[1] = 0.0f;
+ Direction[2] = 0.0f;
+ OrientAt[0] = 0.0f;
+ OrientAt[1] = 0.0f;
+ OrientAt[2] = -1.0f;
+ OrientUp[0] = 0.0f;
+ OrientUp[1] = 1.0f;
+ OrientUp[2] = 0.0f;
+ RefDistance = 1.0f;
+ MaxDistance = std::numeric_limits<float>::max();
+ RolloffFactor = 1.0f;
+ Gain = 1.0f;
+ MinGain = 0.0f;
+ MaxGain = 1.0f;
+ OuterGain = 0.0f;
+ OuterGainHF = 1.0f;
+
+ DryGainHFAuto = AL_TRUE;
+ WetGainAuto = AL_TRUE;
+ WetGainHFAuto = AL_TRUE;
+ AirAbsorptionFactor = 0.0f;
+ RoomRolloffFactor = 0.0f;
+ DopplerFactor = 1.0f;
+ HeadRelative = AL_FALSE;
+ Looping = AL_FALSE;
+ mDistanceModel = DistanceModel::Default;
+ mResampler = ResamplerDefault;
+ DirectChannels = AL_FALSE;
+ mSpatialize = SpatializeAuto;
+
+ StereoPan[0] = Deg2Rad( 30.0f);
+ StereoPan[1] = Deg2Rad(-30.0f);
+
+ Radius = 0.0f;
+
+ Direct.Gain = 1.0f;
+ Direct.GainHF = 1.0f;
+ Direct.HFReference = LOWPASSFREQREF;
+ Direct.GainLF = 1.0f;
+ Direct.LFReference = HIGHPASSFREQREF;
+ Send.resize(num_sends);
+ for(auto &send : Send)
+ {
+ send.Slot = nullptr;
+ send.Gain = 1.0f;
+ send.GainHF = 1.0f;
+ send.HFReference = LOWPASSFREQREF;
+ send.GainLF = 1.0f;
+ send.LFReference = HIGHPASSFREQREF;
+ }
+
+ PropsClean.test_and_set(std::memory_order_relaxed);
+}
+
+ALsource::~ALsource()
+{
+ ALbufferlistitem *BufferList{queue};
+ while(BufferList != nullptr)
+ {
+ std::unique_ptr<ALbufferlistitem> head{BufferList};
+ BufferList = head->mNext.load(std::memory_order_relaxed);
+ if(ALbuffer *buffer{head->mBuffer}) DecrementRef(buffer->ref);
+ }
+ queue = nullptr;
+
+ std::for_each(Send.begin(), Send.end(),
+ [](ALsource::SendData &send) -> void
+ {
+ if(send.Slot)
+ DecrementRef(send.Slot->ref);
+ send.Slot = nullptr;
+ }
+ );
+}
+
+void UpdateAllSourceProps(ALCcontext *context)
+{
+ std::lock_guard<std::mutex> _{context->mSourceLock};
+ std::for_each(context->mVoices.begin(), context->mVoices.end(),
+ [context](ALvoice &voice) -> void
+ {
+ ALuint sid{voice.mSourceID.load(std::memory_order_acquire)};
+ ALsource *source = sid ? LookupSource(context, sid) : nullptr;
+ if(source && !source->PropsClean.test_and_set(std::memory_order_acq_rel))
+ UpdateSourceProps(source, &voice, context);
+ }
+ );
+}
+
+SourceSubList::~SourceSubList()
+{
+ uint64_t usemask{~FreeMask};
+ while(usemask)
+ {
+ ALsizei idx{CTZ64(usemask)};
+ al::destroy_at(Sources+idx);
+ usemask &= ~(1_u64 << idx);
+ }
+ FreeMask = ~usemask;
+ al_free(Sources);
+ Sources = nullptr;
+}
diff --git a/al/source.h b/al/source.h
new file mode 100644
index 00000000..7ca889d7
--- /dev/null
+++ b/al/source.h
@@ -0,0 +1,129 @@
+#ifndef AL_SOURCE_H
+#define AL_SOURCE_H
+
+#include <array>
+#include <atomic>
+#include <cstddef>
+#include <iterator>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+
+#include "alcontext.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alu.h"
+#include "vector.h"
+
+struct ALbuffer;
+struct ALeffectslot;
+
+
+#define DEFAULT_SENDS 2
+
+#define INVALID_VOICE_IDX static_cast<ALuint>(-1)
+
+struct ALbufferlistitem {
+ std::atomic<ALbufferlistitem*> mNext{nullptr};
+ ALuint mSampleLen{0u};
+ ALbuffer *mBuffer{nullptr};
+
+ DEF_NEWDEL(ALbufferlistitem)
+};
+
+
+struct ALsource {
+ /** Source properties. */
+ ALfloat Pitch;
+ ALfloat Gain;
+ ALfloat OuterGain;
+ ALfloat MinGain;
+ ALfloat MaxGain;
+ ALfloat InnerAngle;
+ ALfloat OuterAngle;
+ ALfloat RefDistance;
+ ALfloat MaxDistance;
+ ALfloat RolloffFactor;
+ std::array<ALfloat,3> Position;
+ std::array<ALfloat,3> Velocity;
+ std::array<ALfloat,3> Direction;
+ std::array<ALfloat,3> OrientAt;
+ std::array<ALfloat,3> OrientUp;
+ bool HeadRelative;
+ bool Looping;
+ DistanceModel mDistanceModel;
+ Resampler mResampler;
+ bool DirectChannels;
+ SpatializeMode mSpatialize;
+
+ bool DryGainHFAuto;
+ bool WetGainAuto;
+ bool WetGainHFAuto;
+ ALfloat OuterGainHF;
+
+ ALfloat AirAbsorptionFactor;
+ ALfloat RoomRolloffFactor;
+ ALfloat DopplerFactor;
+
+ /* NOTE: Stereo pan angles are specified in radians, counter-clockwise
+ * rather than clockwise.
+ */
+ std::array<ALfloat,2> StereoPan;
+
+ ALfloat Radius;
+
+ /** Direct filter and auxiliary send info. */
+ struct {
+ ALfloat Gain;
+ ALfloat GainHF;
+ ALfloat HFReference;
+ ALfloat GainLF;
+ ALfloat LFReference;
+ } Direct;
+ struct SendData {
+ ALeffectslot *Slot;
+ ALfloat Gain;
+ ALfloat GainHF;
+ ALfloat HFReference;
+ ALfloat GainLF;
+ ALfloat LFReference;
+ };
+ al::vector<SendData> Send;
+
+ /**
+ * Last user-specified offset, and the offset type (bytes, samples, or
+ * seconds).
+ */
+ ALdouble Offset{0.0};
+ ALenum OffsetType{AL_NONE};
+
+ /** Source type (static, streaming, or undetermined) */
+ ALenum SourceType{AL_UNDETERMINED};
+
+ /** Source state (initial, playing, paused, or stopped) */
+ ALenum state{AL_INITIAL};
+
+ /** Source Buffer Queue head. */
+ ALbufferlistitem *queue{nullptr};
+
+ std::atomic_flag PropsClean;
+
+ /* Index into the context's Voices array. Lazily updated, only checked and
+ * reset when looking up the voice.
+ */
+ ALuint VoiceIdx{INVALID_VOICE_IDX};
+
+ /** Self ID */
+ ALuint id{0};
+
+
+ ALsource(ALuint num_sends);
+ ~ALsource();
+
+ ALsource(const ALsource&) = delete;
+ ALsource& operator=(const ALsource&) = delete;
+};
+
+void UpdateAllSourceProps(ALCcontext *context);
+
+#endif
diff --git a/al/state.cpp b/al/state.cpp
new file mode 100644
index 00000000..25a0efd1
--- /dev/null
+++ b/al/state.cpp
@@ -0,0 +1,880 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 1999-2000 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 <atomic>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <mutex>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#include "alcontext.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "alu.h"
+#include "atomic.h"
+#include "event.h"
+#include "inprogext.h"
+#include "opthelpers.h"
+#include "strutils.h"
+
+
+namespace {
+
+constexpr ALchar alVendor[] = "OpenAL Community";
+constexpr ALchar alVersion[] = "1.1 ALSOFT " ALSOFT_VERSION;
+constexpr ALchar alRenderer[] = "OpenAL Soft";
+
+// Error Messages
+constexpr ALchar alNoError[] = "No Error";
+constexpr ALchar alErrInvalidName[] = "Invalid Name";
+constexpr ALchar alErrInvalidEnum[] = "Invalid Enum";
+constexpr ALchar alErrInvalidValue[] = "Invalid Value";
+constexpr ALchar alErrInvalidOp[] = "Invalid Operation";
+constexpr ALchar alErrOutOfMemory[] = "Out of Memory";
+
+/* Resampler strings */
+template<Resampler rtype> struct ResamplerName { };
+template<> struct ResamplerName<Resampler::Point>
+{ static constexpr const ALchar *Get() noexcept { return "Nearest"; } };
+template<> struct ResamplerName<Resampler::Linear>
+{ static constexpr const ALchar *Get() noexcept { return "Linear"; } };
+template<> struct ResamplerName<Resampler::Cubic>
+{ static constexpr const ALchar *Get() noexcept { return "Cubic"; } };
+template<> struct ResamplerName<Resampler::FastBSinc12>
+{ static constexpr const ALchar *Get() noexcept { return "11th order Sinc (fast)"; } };
+template<> struct ResamplerName<Resampler::BSinc12>
+{ static constexpr const ALchar *Get() noexcept { return "11th order Sinc"; } };
+template<> struct ResamplerName<Resampler::FastBSinc24>
+{ static constexpr const ALchar *Get() noexcept { return "23rd order Sinc (fast)"; } };
+template<> struct ResamplerName<Resampler::BSinc24>
+{ static constexpr const ALchar *Get() noexcept { return "23rd order Sinc"; } };
+
+const ALchar *GetResamplerName(const Resampler rtype)
+{
+#define HANDLE_RESAMPLER(r) case r: return ResamplerName<r>::Get()
+ switch(rtype)
+ {
+ HANDLE_RESAMPLER(Resampler::Point);
+ HANDLE_RESAMPLER(Resampler::Linear);
+ HANDLE_RESAMPLER(Resampler::Cubic);
+ HANDLE_RESAMPLER(Resampler::FastBSinc12);
+ HANDLE_RESAMPLER(Resampler::BSinc12);
+ HANDLE_RESAMPLER(Resampler::FastBSinc24);
+ HANDLE_RESAMPLER(Resampler::BSinc24);
+ }
+#undef HANDLE_RESAMPLER
+ /* Should never get here. */
+ throw std::runtime_error{"Unexpected resampler index"};
+}
+
+} // namespace
+
+/* WARNING: Non-standard export! Not part of any extension, or exposed in the
+ * alcFunctions list.
+ */
+extern "C" AL_API const ALchar* AL_APIENTRY alsoft_get_version(void)
+START_API_FUNC
+{
+ static const auto spoof = al::getenv("ALSOFT_SPOOF_VERSION");
+ if(spoof) return spoof->c_str();
+ return ALSOFT_VERSION;
+}
+END_API_FUNC
+
+#define DO_UPDATEPROPS() do { \
+ if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
+ UpdateContextProps(context.get()); \
+ else \
+ context->mPropsClean.clear(std::memory_order_release); \
+} while(0)
+
+
+AL_API ALvoid AL_APIENTRY alEnable(ALenum capability)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ switch(capability)
+ {
+ case AL_SOURCE_DISTANCE_MODEL:
+ context->mSourceDistanceModel = AL_TRUE;
+ DO_UPDATEPROPS();
+ break;
+
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alDisable(ALenum capability)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ switch(capability)
+ {
+ case AL_SOURCE_DISTANCE_MODEL:
+ context->mSourceDistanceModel = AL_FALSE;
+ DO_UPDATEPROPS();
+ break;
+
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability);
+ }
+}
+END_API_FUNC
+
+AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return AL_FALSE;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ ALboolean value{AL_FALSE};
+ switch(capability)
+ {
+ case AL_SOURCE_DISTANCE_MODEL:
+ value = context->mSourceDistanceModel;
+ break;
+
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability);
+ }
+
+ return value;
+}
+END_API_FUNC
+
+AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum pname)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return AL_FALSE;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ ALboolean value{AL_FALSE};
+ switch(pname)
+ {
+ case AL_DOPPLER_FACTOR:
+ if(context->mDopplerFactor != 0.0f)
+ value = AL_TRUE;
+ break;
+
+ case AL_DOPPLER_VELOCITY:
+ if(context->mDopplerVelocity != 0.0f)
+ value = AL_TRUE;
+ break;
+
+ case AL_DISTANCE_MODEL:
+ if(context->mDistanceModel == DistanceModel::Default)
+ value = AL_TRUE;
+ break;
+
+ case AL_SPEED_OF_SOUND:
+ if(context->mSpeedOfSound != 0.0f)
+ value = AL_TRUE;
+ break;
+
+ case AL_DEFERRED_UPDATES_SOFT:
+ if(context->mDeferUpdates.load(std::memory_order_acquire))
+ value = AL_TRUE;
+ break;
+
+ case AL_GAIN_LIMIT_SOFT:
+ if(GAIN_MIX_MAX/context->mGainBoost != 0.0f)
+ value = AL_TRUE;
+ break;
+
+ case AL_NUM_RESAMPLERS_SOFT:
+ /* Always non-0. */
+ value = AL_TRUE;
+ break;
+
+ case AL_DEFAULT_RESAMPLER_SOFT:
+ value = static_cast<int>(ResamplerDefault) ? AL_TRUE : AL_FALSE;
+ break;
+
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid boolean property 0x%04x", pname);
+ }
+
+ return value;
+}
+END_API_FUNC
+
+AL_API ALdouble AL_APIENTRY alGetDouble(ALenum pname)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return 0.0;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ ALdouble value{0.0};
+ switch(pname)
+ {
+ case AL_DOPPLER_FACTOR:
+ value = context->mDopplerFactor;
+ break;
+
+ case AL_DOPPLER_VELOCITY:
+ value = context->mDopplerVelocity;
+ break;
+
+ case AL_DISTANCE_MODEL:
+ value = static_cast<ALdouble>(context->mDistanceModel);
+ break;
+
+ case AL_SPEED_OF_SOUND:
+ value = context->mSpeedOfSound;
+ break;
+
+ case AL_DEFERRED_UPDATES_SOFT:
+ if(context->mDeferUpdates.load(std::memory_order_acquire))
+ value = static_cast<ALdouble>(AL_TRUE);
+ break;
+
+ case AL_GAIN_LIMIT_SOFT:
+ value = ALdouble{GAIN_MIX_MAX}/context->mGainBoost;
+ break;
+
+ case AL_NUM_RESAMPLERS_SOFT:
+ value = static_cast<ALdouble>(Resampler::Max) + 1.0;
+ break;
+
+ case AL_DEFAULT_RESAMPLER_SOFT:
+ value = static_cast<ALdouble>(ResamplerDefault);
+ break;
+
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid double property 0x%04x", pname);
+ }
+
+ return value;
+}
+END_API_FUNC
+
+AL_API ALfloat AL_APIENTRY alGetFloat(ALenum pname)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return 0.0f;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ ALfloat value{0.0f};
+ switch(pname)
+ {
+ case AL_DOPPLER_FACTOR:
+ value = context->mDopplerFactor;
+ break;
+
+ case AL_DOPPLER_VELOCITY:
+ value = context->mDopplerVelocity;
+ break;
+
+ case AL_DISTANCE_MODEL:
+ value = static_cast<ALfloat>(context->mDistanceModel);
+ break;
+
+ case AL_SPEED_OF_SOUND:
+ value = context->mSpeedOfSound;
+ break;
+
+ case AL_DEFERRED_UPDATES_SOFT:
+ if(context->mDeferUpdates.load(std::memory_order_acquire))
+ value = static_cast<ALfloat>(AL_TRUE);
+ break;
+
+ case AL_GAIN_LIMIT_SOFT:
+ value = GAIN_MIX_MAX/context->mGainBoost;
+ break;
+
+ case AL_NUM_RESAMPLERS_SOFT:
+ value = static_cast<ALfloat>(Resampler::Max) + 1.0f;
+ break;
+
+ case AL_DEFAULT_RESAMPLER_SOFT:
+ value = static_cast<ALfloat>(ResamplerDefault);
+ break;
+
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid float property 0x%04x", pname);
+ }
+
+ return value;
+}
+END_API_FUNC
+
+AL_API ALint AL_APIENTRY alGetInteger(ALenum pname)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return 0;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ ALint value{0};
+ switch(pname)
+ {
+ case AL_DOPPLER_FACTOR:
+ value = static_cast<ALint>(context->mDopplerFactor);
+ break;
+
+ case AL_DOPPLER_VELOCITY:
+ value = static_cast<ALint>(context->mDopplerVelocity);
+ break;
+
+ case AL_DISTANCE_MODEL:
+ value = static_cast<ALint>(context->mDistanceModel);
+ break;
+
+ case AL_SPEED_OF_SOUND:
+ value = static_cast<ALint>(context->mSpeedOfSound);
+ break;
+
+ case AL_DEFERRED_UPDATES_SOFT:
+ if(context->mDeferUpdates.load(std::memory_order_acquire))
+ value = AL_TRUE;
+ break;
+
+ case AL_GAIN_LIMIT_SOFT:
+ value = static_cast<ALint>(GAIN_MIX_MAX/context->mGainBoost);
+ break;
+
+ case AL_NUM_RESAMPLERS_SOFT:
+ value = static_cast<int>(Resampler::Max) + 1;
+ break;
+
+ case AL_DEFAULT_RESAMPLER_SOFT:
+ value = static_cast<int>(ResamplerDefault);
+ break;
+
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname);
+ }
+
+ return value;
+}
+END_API_FUNC
+
+extern "C" AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return 0_i64;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ ALint64SOFT value{0};
+ switch(pname)
+ {
+ case AL_DOPPLER_FACTOR:
+ value = static_cast<ALint64SOFT>(context->mDopplerFactor);
+ break;
+
+ case AL_DOPPLER_VELOCITY:
+ value = static_cast<ALint64SOFT>(context->mDopplerVelocity);
+ break;
+
+ case AL_DISTANCE_MODEL:
+ value = static_cast<ALint64SOFT>(context->mDistanceModel);
+ break;
+
+ case AL_SPEED_OF_SOUND:
+ value = static_cast<ALint64SOFT>(context->mSpeedOfSound);
+ break;
+
+ case AL_DEFERRED_UPDATES_SOFT:
+ if(context->mDeferUpdates.load(std::memory_order_acquire))
+ value = AL_TRUE;
+ break;
+
+ case AL_GAIN_LIMIT_SOFT:
+ value = static_cast<ALint64SOFT>(GAIN_MIX_MAX/context->mGainBoost);
+ break;
+
+ case AL_NUM_RESAMPLERS_SOFT:
+ value = static_cast<ALint64SOFT>(Resampler::Max) + 1;
+ break;
+
+ case AL_DEFAULT_RESAMPLER_SOFT:
+ value = static_cast<ALint64SOFT>(ResamplerDefault);
+ break;
+
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid integer64 property 0x%04x", pname);
+ }
+
+ return value;
+}
+END_API_FUNC
+
+AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return nullptr;
+
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ void *value{nullptr};
+ switch(pname)
+ {
+ case AL_EVENT_CALLBACK_FUNCTION_SOFT:
+ value = reinterpret_cast<void*>(context->mEventCb);
+ break;
+
+ case AL_EVENT_CALLBACK_USER_PARAM_SOFT:
+ value = context->mEventParam;
+ break;
+
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid pointer property 0x%04x", pname);
+ }
+
+ return value;
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetBooleanv(ALenum pname, ALboolean *values)
+START_API_FUNC
+{
+ if(values)
+ {
+ switch(pname)
+ {
+ case AL_DOPPLER_FACTOR:
+ case AL_DOPPLER_VELOCITY:
+ case AL_DISTANCE_MODEL:
+ case AL_SPEED_OF_SOUND:
+ case AL_DEFERRED_UPDATES_SOFT:
+ case AL_GAIN_LIMIT_SOFT:
+ case AL_NUM_RESAMPLERS_SOFT:
+ case AL_DEFAULT_RESAMPLER_SOFT:
+ values[0] = alGetBoolean(pname);
+ return;
+ }
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(pname)
+ {
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid boolean-vector property 0x%04x", pname);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetDoublev(ALenum pname, ALdouble *values)
+START_API_FUNC
+{
+ if(values)
+ {
+ switch(pname)
+ {
+ case AL_DOPPLER_FACTOR:
+ case AL_DOPPLER_VELOCITY:
+ case AL_DISTANCE_MODEL:
+ case AL_SPEED_OF_SOUND:
+ case AL_DEFERRED_UPDATES_SOFT:
+ case AL_GAIN_LIMIT_SOFT:
+ case AL_NUM_RESAMPLERS_SOFT:
+ case AL_DEFAULT_RESAMPLER_SOFT:
+ values[0] = alGetDouble(pname);
+ return;
+ }
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(pname)
+ {
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid double-vector property 0x%04x", pname);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetFloatv(ALenum pname, ALfloat *values)
+START_API_FUNC
+{
+ if(values)
+ {
+ switch(pname)
+ {
+ case AL_DOPPLER_FACTOR:
+ case AL_DOPPLER_VELOCITY:
+ case AL_DISTANCE_MODEL:
+ case AL_SPEED_OF_SOUND:
+ case AL_DEFERRED_UPDATES_SOFT:
+ case AL_GAIN_LIMIT_SOFT:
+ case AL_NUM_RESAMPLERS_SOFT:
+ case AL_DEFAULT_RESAMPLER_SOFT:
+ values[0] = alGetFloat(pname);
+ return;
+ }
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(pname)
+ {
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid float-vector property 0x%04x", pname);
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alGetIntegerv(ALenum pname, ALint *values)
+START_API_FUNC
+{
+ if(values)
+ {
+ switch(pname)
+ {
+ case AL_DOPPLER_FACTOR:
+ case AL_DOPPLER_VELOCITY:
+ case AL_DISTANCE_MODEL:
+ case AL_SPEED_OF_SOUND:
+ case AL_DEFERRED_UPDATES_SOFT:
+ case AL_GAIN_LIMIT_SOFT:
+ case AL_NUM_RESAMPLERS_SOFT:
+ case AL_DEFAULT_RESAMPLER_SOFT:
+ values[0] = alGetInteger(pname);
+ return;
+ }
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(pname)
+ {
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid integer-vector property 0x%04x", pname);
+ }
+}
+END_API_FUNC
+
+extern "C" AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values)
+START_API_FUNC
+{
+ if(values)
+ {
+ switch(pname)
+ {
+ case AL_DOPPLER_FACTOR:
+ case AL_DOPPLER_VELOCITY:
+ case AL_DISTANCE_MODEL:
+ case AL_SPEED_OF_SOUND:
+ case AL_DEFERRED_UPDATES_SOFT:
+ case AL_GAIN_LIMIT_SOFT:
+ case AL_NUM_RESAMPLERS_SOFT:
+ case AL_DEFAULT_RESAMPLER_SOFT:
+ values[0] = alGetInteger64SOFT(pname);
+ return;
+ }
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(pname)
+ {
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid integer64-vector property 0x%04x", pname);
+ }
+}
+END_API_FUNC
+
+AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values)
+START_API_FUNC
+{
+ if(values)
+ {
+ switch(pname)
+ {
+ case AL_EVENT_CALLBACK_FUNCTION_SOFT:
+ case AL_EVENT_CALLBACK_USER_PARAM_SOFT:
+ values[0] = alGetPointerSOFT(pname);
+ return;
+ }
+ }
+
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if(!values)
+ context->setError(AL_INVALID_VALUE, "NULL pointer");
+ else switch(pname)
+ {
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid pointer-vector property 0x%04x", pname);
+ }
+}
+END_API_FUNC
+
+AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return nullptr;
+
+ const ALchar *value{nullptr};
+ switch(pname)
+ {
+ case AL_VENDOR:
+ value = alVendor;
+ break;
+
+ case AL_VERSION:
+ value = alVersion;
+ break;
+
+ case AL_RENDERER:
+ value = alRenderer;
+ break;
+
+ case AL_EXTENSIONS:
+ value = context->mExtensionList;
+ break;
+
+ case AL_NO_ERROR:
+ value = alNoError;
+ break;
+
+ case AL_INVALID_NAME:
+ value = alErrInvalidName;
+ break;
+
+ case AL_INVALID_ENUM:
+ value = alErrInvalidEnum;
+ break;
+
+ case AL_INVALID_VALUE:
+ value = alErrInvalidValue;
+ break;
+
+ case AL_INVALID_OPERATION:
+ value = alErrInvalidOp;
+ break;
+
+ case AL_OUT_OF_MEMORY:
+ value = alErrOutOfMemory;
+ break;
+
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname);
+ }
+ return value;
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alDopplerFactor(ALfloat value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if(!(value >= 0.0f && std::isfinite(value)))
+ context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value);
+ else
+ {
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ context->mDopplerFactor = value;
+ DO_UPDATEPROPS();
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alDopplerVelocity(ALfloat value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if((context->mEnabledEvts.load(std::memory_order_relaxed)&EventType_Deprecated))
+ {
+ std::lock_guard<std::mutex> _{context->mEventCbLock};
+ ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
+ if((enabledevts&EventType_Deprecated) && context->mEventCb)
+ {
+ static const char msg[] =
+ "alDopplerVelocity is deprecated in AL1.1, use alSpeedOfSound";
+ const ALsizei msglen{sizeof(msg)-1};
+ (*context->mEventCb)(AL_EVENT_TYPE_DEPRECATED_SOFT, 0, 0, msglen, msg,
+ context->mEventParam);
+ }
+ }
+
+ if(!(value >= 0.0f && std::isfinite(value)))
+ context->setError(AL_INVALID_VALUE, "Doppler velocity %f out of range", value);
+ else
+ {
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ context->mDopplerVelocity = value;
+ DO_UPDATEPROPS();
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alSpeedOfSound(ALfloat value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if(!(value > 0.0f && std::isfinite(value)))
+ context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value);
+ else
+ {
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ context->mSpeedOfSound = value;
+ DO_UPDATEPROPS();
+ }
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alDistanceModel(ALenum value)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ if(!(value == AL_INVERSE_DISTANCE || value == AL_INVERSE_DISTANCE_CLAMPED ||
+ value == AL_LINEAR_DISTANCE || value == AL_LINEAR_DISTANCE_CLAMPED ||
+ value == AL_EXPONENT_DISTANCE || value == AL_EXPONENT_DISTANCE_CLAMPED ||
+ value == AL_NONE))
+ context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value);
+ else
+ {
+ std::lock_guard<std::mutex> _{context->mPropLock};
+ context->mDistanceModel = static_cast<DistanceModel>(value);
+ if(!context->mSourceDistanceModel)
+ DO_UPDATEPROPS();
+ }
+}
+END_API_FUNC
+
+
+AL_API ALvoid AL_APIENTRY alDeferUpdatesSOFT(void)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ context->deferUpdates();
+}
+END_API_FUNC
+
+AL_API ALvoid AL_APIENTRY alProcessUpdatesSOFT(void)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return;
+
+ context->processUpdates();
+}
+END_API_FUNC
+
+
+AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index)
+START_API_FUNC
+{
+ ContextRef context{GetContextRef()};
+ if UNLIKELY(!context) return nullptr;
+
+ const ALchar *value{nullptr};
+ switch(pname)
+ {
+ case AL_RESAMPLER_NAME_SOFT:
+ if(index < 0 || index > static_cast<ALint>(Resampler::Max))
+ context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index);
+ else
+ value = GetResamplerName(static_cast<Resampler>(index));
+ break;
+
+ default:
+ context->setError(AL_INVALID_VALUE, "Invalid string indexed property");
+ }
+ return value;
+}
+END_API_FUNC
+
+
+void UpdateContextProps(ALCcontext *context)
+{
+ /* Get an unused proprty container, or allocate a new one as needed. */
+ ALcontextProps *props{context->mFreeContextProps.load(std::memory_order_acquire)};
+ if(!props)
+ props = new ALcontextProps{};
+ else
+ {
+ ALcontextProps *next;
+ do {
+ next = props->next.load(std::memory_order_relaxed);
+ } while(context->mFreeContextProps.compare_exchange_weak(props, next,
+ std::memory_order_seq_cst, std::memory_order_acquire) == 0);
+ }
+
+ /* Copy in current property values. */
+ props->DopplerFactor = context->mDopplerFactor;
+ props->DopplerVelocity = context->mDopplerVelocity;
+ props->SpeedOfSound = context->mSpeedOfSound;
+
+ props->SourceDistanceModel = context->mSourceDistanceModel;
+ props->mDistanceModel = context->mDistanceModel;
+
+ /* Set the new container for updating internal parameters. */
+ props = context->mUpdate.exchange(props, std::memory_order_acq_rel);
+ if(props)
+ {
+ /* If there was an unused update container, put it back in the
+ * freelist.
+ */
+ AtomicReplaceHead(context->mFreeContextProps, props);
+ }
+}
diff --git a/alc/alc.cpp b/alc/alc.cpp
new file mode 100644
index 00000000..bee42fc5
--- /dev/null
+++ b/alc/alc.cpp
@@ -0,0 +1,4195 @@
+/**
+ * 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 <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/effect.h"
+#include "al/event.h"
+#include "al/filter.h"
+#include "al/listener.h"
+#include "al/source.h"
+#include "alcmain.h"
+#include "albyte.h"
+#include "alconfig.h"
+#include "alcontext.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alspan.h"
+#include "alstring.h"
+#include "alu.h"
+#include "ambidefs.h"
+#include "atomic.h"
+#include "bformatdec.h"
+#include "bs2b.h"
+#include "compat.h"
+#include "cpu_caps.h"
+#include "devformat.h"
+#include "effects/base.h"
+#include "filters/nfc.h"
+#include "filters/splitter.h"
+#include "fpu_modes.h"
+#include "hrtf.h"
+#include "inprogext.h"
+#include "intrusive_ptr.h"
+#include "logging.h"
+#include "mastering.h"
+#include "opthelpers.h"
+#include "pragmadefs.h"
+#include "ringbuffer.h"
+#include "strutils.h"
+#include "threads.h"
+#include "uhjfilter.h"
+#include "vecmat.h"
+#include "vector.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_SOLARIS
+#include "backends/solaris.h"
+#endif
+#ifdef HAVE_SNDIO
+#include "backends/sndio.h"
+#endif
+#ifdef HAVE_OSS
+#include "backends/oss.h"
+#endif
+#ifdef HAVE_QSA
+#include "backends/qsa.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;
+
+
+/************************************************
+ * 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_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_QSA
+ { "qsa", QSABackendFactory::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
+};
+auto BackendListEnd = std::end(BackendList);
+
+BackendFactory *PlaybackFactory{};
+BackendFactory *CaptureFactory{};
+
+
+/************************************************
+ * Functions, enums, and errors
+ ************************************************/
+#define DECL(x) { #x, reinterpret_cast<void*>(x) }
+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) }
+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_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
+
+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_block_alignment "
+ "AL_SOFT_deferred_updates "
+ "AL_SOFT_direct_channels "
+ "AL_SOFTX_effect_chain "
+ "AL_SOFTX_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 */
+class ThreadCtx {
+ ALCcontext *ctx{nullptr};
+
+public:
+ ~ThreadCtx()
+ {
+ if(ctx)
+ {
+ const bool result{ctx->releaseIfNoDelete()};
+ ERR("Context %p current for thread being destroyed%s!\n",
+ decltype(std::declval<void*>()){ctx}, result ? "" : ", leak detected");
+ }
+ }
+
+ ALCcontext *get() const noexcept { return ctx; }
+ void set(ALCcontext *ctx_) noexcept { ctx = ctx_; }
+};
+thread_local ThreadCtx LocalContext;
+/* 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 ALuint 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";
+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_output_limiter "
+ "ALC_SOFT_pause_device";
+constexpr ALCint alcMajorVersion = 1;
+constexpr ALCint alcMinorVersion = 1;
+
+constexpr ALCint alcEFXMajorVersion = 1;
+constexpr ALCint alcEFXMinorVersion = 0;
+
+
+/* To avoid extraneous allocations, a 0-sized FlexArray<ALCcontext*> is defined
+ * globally as a sharable object. MSVC warns that a zero-sized array will have
+ * zero objects here, so silence that.
+ */
+DIAGNOSTIC_PUSH
+msc_pragma(warning(disable : 4815))
+al::FlexArray<ALCcontext*> EmptyContextArray{0u};
+DIAGNOSTIC_POP
+
+
+using DeviceRef = al::intrusive_ptr<ALCdevice>;
+
+
+/************************************************
+ * Device lists
+ ************************************************/
+al::vector<DeviceRef> DeviceList;
+al::vector<ContextRef> 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 >= NoLog && lvl <= LogRef)
+ gLogLevel = static_cast<LogLevel>(lvl);
+ }
+
+ if(auto logfile = al::getenv("ALSOFT_LOGFILE"))
+ {
+#ifdef _WIN32
+ std::wstring wname{utf8_to_wstr(logfile->c_str())};
+ FILE *logf{_wfopen(wname.c_str(), L"wt")};
+#else
+ FILE *logf{fopen(logfile->c_str(), "wt")};
+#endif
+ if(logf) gLogFile = logf;
+ else ERR("Failed to open log file '%s'\n", logfile->c_str());
+ }
+
+ TRACE("Initializing library v%s-%s %s\n", ALSOFT_VERSION, ALSOFT_GIT_COMMIT_HASH,
+ ALSOFT_GIT_BRANCH);
+ {
+ al::string names;
+ if(std::begin(BackendList) == BackendListEnd)
+ names += "(none)";
+ else
+ {
+ const al::span<const BackendInfo> infos{std::begin(BackendList), BackendListEnd};
+ 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++);
+ }
+ }
+ FillCPUCaps(capfilter);
+
+#ifdef _WIN32
+#define DEF_MIXER_PRIO 1
+#else
+#define DEF_MIXER_PRIO 0
+#endif
+ RTPrioLevel = ConfigValueInt(nullptr, nullptr, "rt-prio").value_or(DEF_MIXER_PRIO);
+#undef DEF_MIXER_PRIO
+
+ 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 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) -> bool
+ {
+ if(PlaybackFactory && CaptureFactory)
+ return true;
+
+ BackendFactory &factory = backend.getFactory();
+ if(!factory.init())
+ {
+ WARN("Failed to initialize backend \"%s\"\n", backend.name);
+ return true;
+ }
+
+ 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);
+ }
+ return false;
+ };
+ BackendListEnd = std::remove_if(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] = AL_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};
+ alcAllDevicesList.clear();
+ if(PlaybackFactory)
+ PlaybackFactory->probe(DevProbe::Playback, &alcAllDevicesList);
+}
+void ProbeCaptureDeviceList()
+{
+ DO_INITCONFIG();
+
+ std::lock_guard<std::recursive_mutex> _{ListLock};
+ alcCaptureDeviceList.clear();
+ if(CaptureFactory)
+ CaptureFactory->probe(DevProbe::Capture, &alcCaptureDeviceList);
+}
+
+} // namespace
+
+/* Mixing thread piority level */
+ALint RTPrioLevel;
+
+FILE *gLogFile{stderr};
+#ifdef _DEBUG
+LogLevel gLogLevel{LogWarning};
+#else
+LogLevel gLogLevel{LogError};
+#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
+ ************************************************/
+const ALCchar *DevFmtTypeString(DevFmtType type) noexcept
+{
+ 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(DevFmtChannels chans) noexcept
+{
+ 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)";
+}
+
+ALuint BytesFromDevFmt(DevFmtType type) noexcept
+{
+ 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;
+}
+ALuint ChannelsFromDevFmt(DevFmtChannels chans, ALuint ambiorder) noexcept
+{
+ 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+1) * (ambiorder+1);
+ }
+ return 0;
+}
+
+struct DevFmtPair { DevFmtChannels chans; DevFmtType type; };
+static 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;
+}
+
+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)
+{
+ device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
+
+ switch(device->FmtChans)
+ {
+ case DevFmtMono:
+ device->RealOut.ChannelIndex[FrontCenter] = 0;
+ break;
+ case DevFmtStereo:
+ device->RealOut.ChannelIndex[FrontLeft] = 0;
+ device->RealOut.ChannelIndex[FrontRight] = 1;
+ break;
+ case DevFmtQuad:
+ device->RealOut.ChannelIndex[FrontLeft] = 0;
+ device->RealOut.ChannelIndex[FrontRight] = 1;
+ device->RealOut.ChannelIndex[BackLeft] = 2;
+ device->RealOut.ChannelIndex[BackRight] = 3;
+ break;
+ case DevFmtX51:
+ device->RealOut.ChannelIndex[FrontLeft] = 0;
+ device->RealOut.ChannelIndex[FrontRight] = 1;
+ device->RealOut.ChannelIndex[FrontCenter] = 2;
+ device->RealOut.ChannelIndex[LFE] = 3;
+ device->RealOut.ChannelIndex[SideLeft] = 4;
+ device->RealOut.ChannelIndex[SideRight] = 5;
+ break;
+ case DevFmtX51Rear:
+ device->RealOut.ChannelIndex[FrontLeft] = 0;
+ device->RealOut.ChannelIndex[FrontRight] = 1;
+ device->RealOut.ChannelIndex[FrontCenter] = 2;
+ device->RealOut.ChannelIndex[LFE] = 3;
+ device->RealOut.ChannelIndex[BackLeft] = 4;
+ device->RealOut.ChannelIndex[BackRight] = 5;
+ break;
+ case DevFmtX61:
+ device->RealOut.ChannelIndex[FrontLeft] = 0;
+ device->RealOut.ChannelIndex[FrontRight] = 1;
+ device->RealOut.ChannelIndex[FrontCenter] = 2;
+ device->RealOut.ChannelIndex[LFE] = 3;
+ device->RealOut.ChannelIndex[BackCenter] = 4;
+ device->RealOut.ChannelIndex[SideLeft] = 5;
+ device->RealOut.ChannelIndex[SideRight] = 6;
+ break;
+ case DevFmtX71:
+ device->RealOut.ChannelIndex[FrontLeft] = 0;
+ device->RealOut.ChannelIndex[FrontRight] = 1;
+ device->RealOut.ChannelIndex[FrontCenter] = 2;
+ device->RealOut.ChannelIndex[LFE] = 3;
+ device->RealOut.ChannelIndex[BackLeft] = 4;
+ device->RealOut.ChannelIndex[BackRight] = 5;
+ device->RealOut.ChannelIndex[SideLeft] = 6;
+ device->RealOut.ChannelIndex[SideRight] = 7;
+ break;
+ case DevFmtAmbi3D:
+ device->RealOut.ChannelIndex[Aux0] = 0;
+ if(device->mAmbiOrder > 0)
+ {
+ device->RealOut.ChannelIndex[Aux1] = 1;
+ device->RealOut.ChannelIndex[Aux2] = 2;
+ device->RealOut.ChannelIndex[Aux3] = 3;
+ }
+ if(device->mAmbiOrder > 1)
+ {
+ device->RealOut.ChannelIndex[Aux4] = 4;
+ device->RealOut.ChannelIndex[Aux5] = 5;
+ device->RealOut.ChannelIndex[Aux6] = 6;
+ device->RealOut.ChannelIndex[Aux7] = 7;
+ device->RealOut.ChannelIndex[Aux8] = 8;
+ }
+ if(device->mAmbiOrder > 2)
+ {
+ device->RealOut.ChannelIndex[Aux9] = 9;
+ device->RealOut.ChannelIndex[Aux10] = 10;
+ device->RealOut.ChannelIndex[Aux11] = 11;
+ device->RealOut.ChannelIndex[Aux12] = 12;
+ device->RealOut.ChannelIndex[Aux13] = 13;
+ device->RealOut.ChannelIndex[Aux14] = 14;
+ device->RealOut.ChannelIndex[Aux15] = 15;
+ }
+ break;
+ }
+}
+
+/* SetDefaultChannelOrder
+ *
+ * Sets the default channel order used by most non-WaveFormatEx-based APIs.
+ */
+void SetDefaultChannelOrder(ALCdevice *device)
+{
+ device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
+
+ switch(device->FmtChans)
+ {
+ case DevFmtX51Rear:
+ device->RealOut.ChannelIndex[FrontLeft] = 0;
+ device->RealOut.ChannelIndex[FrontRight] = 1;
+ device->RealOut.ChannelIndex[BackLeft] = 2;
+ device->RealOut.ChannelIndex[BackRight] = 3;
+ device->RealOut.ChannelIndex[FrontCenter] = 4;
+ device->RealOut.ChannelIndex[LFE] = 5;
+ return;
+ case DevFmtX71:
+ device->RealOut.ChannelIndex[FrontLeft] = 0;
+ device->RealOut.ChannelIndex[FrontRight] = 1;
+ device->RealOut.ChannelIndex[BackLeft] = 2;
+ device->RealOut.ChannelIndex[BackRight] = 3;
+ device->RealOut.ChannelIndex[FrontCenter] = 4;
+ device->RealOut.ChannelIndex[LFE] = 5;
+ device->RealOut.ChannelIndex[SideLeft] = 6;
+ device->RealOut.ChannelIndex[SideRight] = 7;
+ return;
+
+ /* Same as WFX order */
+ case DevFmtMono:
+ case DevFmtStereo:
+ case DevFmtQuad:
+ case DevFmtX51:
+ case DevFmtX61:
+ case DevFmtAmbi3D:
+ SetDefaultWFXChannelOrder(device);
+ break;
+ }
+}
+
+
+void ALCcontext::processUpdates()
+{
+ std::lock_guard<std::mutex> _{mPropLock};
+ if(mDeferUpdates.exchange(false))
+ {
+ /* 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)
+ std::this_thread::yield();
+
+ 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);
+ }
+}
+
+
+/* alcSetError
+ *
+ * Stores the latest ALC device error
+ */
+static void alcSetError(ALCdevice *device, ALCenum errorCode)
+{
+ WARN("Error generated on device %p, code 0x%04x\n", decltype(std::declval<void*>()){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 ALfloat threshold)
+{
+ return CompressorInit(static_cast<ALuint>(device->RealOut.Buffer.size()),
+ static_cast<float>(device->Frequency), AL_TRUE, AL_TRUE, AL_TRUE, AL_TRUE, AL_TRUE, 0.001f,
+ 0.002f, 0.0f, 0.0f, threshold, INFINITY, 0.0f, 0.020f, 0.200f);
+}
+
+/* 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 += nanoseconds{seconds{device->SamplesDone}} / 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)
+{
+ HrtfRequestMode hrtf_userreq{Hrtf_Default};
+ HrtfRequestMode hrtf_appreq{Hrtf_Default};
+ ALCenum gainLimiter{device->LimiterState};
+ const ALCuint old_sends{device->NumAuxSends};
+ ALCuint new_sends{device->NumAuxSends};
+ DevFmtChannels oldChans;
+ DevFmtType oldType;
+ ALboolean update_failed;
+ ALCsizei hrtf_id{-1};
+ ALCuint oldFreq;
+
+ if((!attrList || !attrList[0]) && device->Type == Loopback)
+ {
+ WARN("Missing attributes for loopback device\n");
+ return ALC_INVALID_VALUE;
+ }
+
+ // Check for attributes
+ if(attrList && attrList[0])
+ {
+ ALCenum alayout{AL_NONE};
+ ALCenum ascale{AL_NONE};
+ ALCenum schans{AL_NONE};
+ ALCenum stype{AL_NONE};
+ ALCsizei attrIdx{0};
+ ALCuint aorder{0};
+ ALCuint freq{0u};
+
+ ALuint numMono{device->NumMonoSources};
+ ALuint numStereo{device->NumStereoSources};
+ ALuint numSends{old_sends};
+
+#define TRACE_ATTR(a, v) TRACE("%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);
+ break;
+
+ case ALC_FORMAT_TYPE_SOFT:
+ stype = attrList[attrIdx + 1];
+ TRACE_ATTR(ALC_FORMAT_TYPE_SOFT, stype);
+ break;
+
+ case ALC_FREQUENCY:
+ freq = static_cast<ALuint>(attrList[attrIdx + 1]);
+ TRACE_ATTR(ALC_FREQUENCY, freq);
+ break;
+
+ case ALC_AMBISONIC_LAYOUT_SOFT:
+ alayout = attrList[attrIdx + 1];
+ TRACE_ATTR(ALC_AMBISONIC_LAYOUT_SOFT, alayout);
+ break;
+
+ case ALC_AMBISONIC_SCALING_SOFT:
+ ascale = attrList[attrIdx + 1];
+ TRACE_ATTR(ALC_AMBISONIC_SCALING_SOFT, ascale);
+ break;
+
+ case ALC_AMBISONIC_ORDER_SOFT:
+ aorder = static_cast<ALuint>(attrList[attrIdx + 1]);
+ TRACE_ATTR(ALC_AMBISONIC_ORDER_SOFT, aorder);
+ break;
+
+ case ALC_MONO_SOURCES:
+ numMono = static_cast<ALuint>(attrList[attrIdx + 1]);
+ TRACE_ATTR(ALC_MONO_SOURCES, numMono);
+ if(numMono > INT_MAX) numMono = 0;
+ break;
+
+ case ALC_STEREO_SOURCES:
+ numStereo = static_cast<ALuint>(attrList[attrIdx + 1]);
+ TRACE_ATTR(ALC_STEREO_SOURCES, numStereo);
+ if(numStereo > INT_MAX) numStereo = 0;
+ break;
+
+ case ALC_MAX_AUXILIARY_SENDS:
+ numSends = static_cast<ALuint>(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 == Loopback};
+ if(loopback)
+ {
+ if(!schans || !stype || !freq)
+ {
+ WARN("Missing format for loopback device\n");
+ return ALC_INVALID_VALUE;
+ }
+ if(!IsValidALCChannels(schans) || !IsValidALCType(stype) || freq < MIN_OUTPUT_RATE)
+ return ALC_INVALID_VALUE;
+ if(schans == ALC_BFORMAT3D_SOFT)
+ {
+ if(!alayout || !ascale || !aorder)
+ {
+ WARN("Missing ambisonic info for loopback device\n");
+ return ALC_INVALID_VALUE;
+ }
+ if(!IsValidAmbiLayout(alayout) || !IsValidAmbiScaling(ascale))
+ return ALC_INVALID_VALUE;
+ if(aorder < 1 || aorder > MAX_AMBI_ORDER)
+ return ALC_INVALID_VALUE;
+ if((alayout == ALC_FUMA_SOFT || ascale == ALC_FUMA_SOFT) && 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.get<DeviceRunning>())
+ device->Backend->stop();
+ device->Flags.unset<DeviceRunning>();
+
+ UpdateClockBase(device);
+
+ const char *devname{nullptr};
+ if(!loopback)
+ {
+ 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.unset<FrequencyRequest>();
+ else
+ {
+ freq = maxu(freq, MIN_OUTPUT_RATE);
+
+ device->UpdateSize = (device->UpdateSize*freq + device->Frequency/2) /
+ device->Frequency;
+ device->BufferSize = (device->BufferSize*freq + device->Frequency/2) /
+ device->Frequency;
+
+ 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);
+ }
+ else
+ {
+ device->Frequency = freq;
+ device->FmtChans = static_cast<DevFmtChannels>(schans);
+ device->FmtType = static_cast<DevFmtType>(stype);
+ if(schans == ALC_BFORMAT3D_SOFT)
+ {
+ device->mAmbiOrder = aorder;
+ device->mAmbiLayout = static_cast<AmbiLayout>(alayout);
+ device->mAmbiScale = static_cast<AmbiNorm>(ascale);
+ }
+ }
+
+ 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<ALuint>(clampi(*sendsopt, 0, MAX_SENDS)));
+ else
+ new_sends = numSends;
+ }
+
+ if(device->Flags.get<DeviceRunning>())
+ return ALC_NO_ERROR;
+
+ device->AvgSpeakerDist = 0.0f;
+ device->Uhj_Encoder = nullptr;
+ device->AmbiDecoder = nullptr;
+ device->Bs2b = nullptr;
+ device->PostProcess = nullptr;
+
+ device->Stablizer = nullptr;
+ device->Limiter = nullptr;
+ device->ChannelDelay.clear();
+
+ 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.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 != 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))
+ {
+ HrtfEntry *hrtf{nullptr};
+ if(device->HrtfList.empty())
+ device->HrtfList = EnumerateHrtf(device->DeviceName.c_str());
+ if(!device->HrtfList.empty())
+ {
+ if(hrtf_id >= 0 && static_cast<ALuint>(hrtf_id) < device->HrtfList.size())
+ hrtf = GetLoadedHrtf(device->HrtfList[static_cast<ALuint>(hrtf_id)].hrtf);
+ else
+ hrtf = GetLoadedHrtf(device->HrtfList.front().hrtf);
+ }
+
+ if(hrtf)
+ {
+ device->FmtChans = DevFmtStereo;
+ device->Frequency = hrtf->sampleRate;
+ device->Flags.set<ChannelsRequest, FrequencyRequest>();
+ if(HrtfEntry *oldhrtf{device->mHrtf})
+ oldhrtf->DecRef();
+ device->mHrtf = 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 / %u buffer\n",
+ device->Flags.get<ChannelsRequest>()?"*":"", DevFmtChannelsString(device->FmtChans),
+ device->Flags.get<SampleTypeRequest>()?"*":"", DevFmtTypeString(device->FmtType),
+ device->Flags.get<FrequencyRequest>()?"*":"", device->Frequency,
+ device->UpdateSize, device->BufferSize);
+
+ try {
+ if(device->Backend->reset() == false)
+ return ALC_INVALID_DEVICE;
+ }
+ catch(std::exception &e) {
+ ERR("Device reset failed: %s\n", e.what());
+ return ALC_INVALID_DEVICE;
+ }
+
+ if(device->FmtChans != oldChans && device->Flags.get<ChannelsRequest>())
+ {
+ ERR("Failed to set %s, got %s instead\n", DevFmtChannelsString(oldChans),
+ DevFmtChannelsString(device->FmtChans));
+ device->Flags.unset<ChannelsRequest>();
+ }
+ if(device->FmtType != oldType && device->Flags.get<SampleTypeRequest>())
+ {
+ ERR("Failed to set %s, got %s instead\n", DevFmtTypeString(oldType),
+ DevFmtTypeString(device->FmtType));
+ device->Flags.unset<SampleTypeRequest>();
+ }
+ if(device->Frequency != oldFreq && device->Flags.get<FrequencyRequest>())
+ {
+ WARN("Failed to set %uhz, got %uhz instead\n", oldFreq, device->Frequency);
+ device->Flags.unset<FrequencyRequest>();
+ }
+
+ TRACE("Post-reset: %s, %s, %uhz, %u / %u buffer\n",
+ DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType),
+ device->Frequency, device->UpdateSize, device->BufferSize);
+
+ 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);
+
+ /* Enable the stablizer only for formats that have front-left, front-right,
+ * and front-center outputs.
+ */
+ switch(device->FmtChans)
+ {
+ case DevFmtX51:
+ case DevFmtX51Rear:
+ case DevFmtX61:
+ case DevFmtX71:
+ if(GetConfigValueBool(device->DeviceName.c_str(), nullptr, "front-stablizer", 0))
+ {
+ auto stablizer = al::make_unique<FrontStablizer>();
+ /* Initialize band-splitting filters for the front-left and front-
+ * right channels, with a crossover at 5khz (could be higher).
+ */
+ const ALfloat scale{5000.0f / static_cast<ALfloat>(device->Frequency)};
+
+ stablizer->LFilter.init(scale);
+ stablizer->RFilter = stablizer->LFilter;
+
+ device->Stablizer = std::move(stablizer);
+ /* NOTE: Don't know why this has to be "copied" into a local static
+ * constexpr variable to avoid a reference on
+ * FrontStablizer::DelayLength...
+ */
+ constexpr size_t StablizerDelay{FrontStablizer::DelayLength};
+ device->FixedLatency += nanoseconds{seconds{StablizerDelay}} / device->Frequency;
+ }
+ break;
+ case DevFmtMono:
+ case DevFmtStereo:
+ case DevFmtQuad:
+ case DevFmtAmbi3D:
+ break;
+ }
+ TRACE("Front stablizer %s\n", device->Stablizer ? "enabled" : "disabled");
+
+ if(GetConfigValueBool(device->DeviceName.c_str(), nullptr, "dither", 1))
+ {
+ ALint 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<ALfloat>(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
+ {
+ ALfloat 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);
+ /* Convert the lookahead from samples to nanosamples to nanoseconds. */
+ device->FixedLatency += nanoseconds{seconds{limiter->getLookAhead()}} / device->Frequency;
+ device->Limiter = std::move(limiter);
+ TRACE("Output limiter enabled, %.4fdB limit\n", thrshld_dB);
+ }
+
+ TRACE("Fixed device latency: %" PRId64 "ns\n", int64_t{device->FixedLatency.count()});
+
+ /* Need to delay returning failure until replacement Send arrays have been
+ * allocated with the appropriate size.
+ */
+ update_failed = AL_FALSE;
+ FPUCtl mixer_mode{};
+ for(ALCcontext *context : *device->mContexts.load())
+ {
+ if(context->mDefaultSlot)
+ {
+ ALeffectslot *slot = context->mDefaultSlot.get();
+ aluInitEffectPanning(slot, device);
+
+ EffectState *state{slot->Effect.State};
+ state->mOutTarget = device->Dry.Buffer;
+ if(state->deviceUpdate(device) == AL_FALSE)
+ update_failed = AL_TRUE;
+ else
+ UpdateEffectSlotProps(slot, context);
+ }
+
+ std::unique_lock<std::mutex> proplock{context->mPropLock};
+ std::unique_lock<std::mutex> slotlock{context->mEffectSlotLock};
+ for(auto &sublist : context->mEffectSlotList)
+ {
+ uint64_t usemask = ~sublist.FreeMask;
+ while(usemask)
+ {
+ ALsizei idx = CTZ64(usemask);
+ ALeffectslot *slot = sublist.EffectSlots + idx;
+
+ usemask &= ~(1_u64 << idx);
+
+ aluInitEffectPanning(slot, device);
+
+ EffectState *state{slot->Effect.State};
+ state->mOutTarget = device->Dry.Buffer;
+ if(state->deviceUpdate(device) == AL_FALSE)
+ update_failed = AL_TRUE;
+ else
+ UpdateEffectSlotProps(slot, context);
+ }
+ }
+ slotlock.unlock();
+
+ std::unique_lock<std::mutex> srclock{context->mSourceLock};
+ for(auto &sublist : context->mSourceList)
+ {
+ uint64_t usemask = ~sublist.FreeMask;
+ while(usemask)
+ {
+ ALsizei idx = CTZ64(usemask);
+ ALsource *source = sublist.Sources + idx;
+
+ usemask &= ~(1_u64 << idx);
+
+ if(old_sends != device->NumAuxSends)
+ {
+ if(source->Send.size() > device->NumAuxSends)
+ {
+ auto clear_send = [](ALsource::SendData &send) -> void
+ {
+ if(send.Slot)
+ DecrementRef(send.Slot->ref);
+ send.Slot = nullptr;
+ };
+ auto send_begin = source->Send.begin() +
+ static_cast<ptrdiff_t>(device->NumAuxSends);
+ std::for_each(send_begin, source->Send.end(), clear_send);
+ }
+
+ source->Send.resize(device->NumAuxSends,
+ {nullptr, 1.0f, 1.0f, LOWPASSFREQREF, 1.0f, HIGHPASSFREQREF});
+ source->Send.shrink_to_fit();
+ }
+
+ 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.
+ */
+ ALvoiceProps *vprops{context->mFreeVoiceProps.exchange(nullptr, std::memory_order_acq_rel)};
+ while(vprops)
+ {
+ ALvoiceProps *next = vprops->next.load(std::memory_order_relaxed);
+ delete vprops;
+ vprops = next;
+ }
+
+ if(device->NumAuxSends < old_sends)
+ {
+ const ALuint num_sends{device->NumAuxSends};
+ /* Clear extraneous property set sends. */
+ auto clear_sends = [num_sends](ALvoice &voice) -> void
+ {
+ std::fill(std::begin(voice.mProps.Send)+num_sends, std::end(voice.mProps.Send),
+ ALvoiceProps::SendData{});
+
+ std::fill(voice.mSend.begin()+num_sends, voice.mSend.end(), ALvoice::TargetData{});
+ auto clear_chan_sends = [num_sends](ALvoice::ChannelData &chandata) -> void
+ {
+ std::fill(chandata.mWetParams.begin()+num_sends, chandata.mWetParams.end(),
+ SendParams{});
+ };
+ std::for_each(voice.mChans.begin(), voice.mChans.end(), clear_chan_sends);
+ };
+ std::for_each(context->mVoices.begin(), context->mVoices.end(), clear_sends);
+ }
+ auto reset_voice = [device](ALvoice &voice) -> void
+ {
+ delete voice.mUpdate.exchange(nullptr, std::memory_order_acq_rel);
+
+ /* Force the voice to stopped if it was stopping. */
+ ALvoice::State vstate{ALvoice::Stopping};
+ voice.mPlayState.compare_exchange_strong(vstate, ALvoice::Stopped,
+ std::memory_order_acquire, std::memory_order_acquire);
+ if(voice.mSourceID.load(std::memory_order_relaxed) == 0u)
+ return;
+
+ if(device->AvgSpeakerDist > 0.0f)
+ {
+ /* Reinitialize the NFC filters for new parameters. */
+ const ALfloat w1{SPEEDOFSOUNDMETRESPERSEC /
+ (device->AvgSpeakerDist * static_cast<float>(device->Frequency))};
+ auto init_nfc = [w1](ALvoice::ChannelData &chandata) -> void
+ { chandata.mDryParams.NFCtrlFilter.init(w1); };
+ std::for_each(voice.mChans.begin(), voice.mChans.begin()+voice.mNumChannels,
+ init_nfc);
+ }
+ };
+ std::for_each(context->mVoices.begin(), context->mVoices.end(), reset_voice);
+ 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(update_failed)
+ return ALC_INVALID_DEVICE;
+
+ if(!device->Flags.get<DevicePaused>())
+ {
+ try {
+ auto backend = device->Backend.get();
+ if(!backend->start())
+ throw al::backend_exception{ALC_INVALID_DEVICE, "Backend error"};
+ device->Flags.set<DeviceRunning>();
+ }
+ catch(al::backend_exception& e) {
+ WARN("Failed to start playback: %s\n", e.what());
+ return ALC_INVALID_DEVICE;
+ }
+ }
+
+ return ALC_NO_ERROR;
+}
+
+
+ALCdevice::ALCdevice(DeviceType type) : Type{type}, mContexts{&EmptyContextArray}
+{
+}
+
+/* ALCdevice::~ALCdevice
+ *
+ * Frees the device structure, and destroys any objects the app failed to
+ * delete. Called once there's no more references on the device.
+ */
+ALCdevice::~ALCdevice()
+{
+ TRACE("Freeing device %p\n", decltype(std::declval<void*>()){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<ALuint>(POPCNT64(~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<ALuint>(POPCNT64(~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<ALuint>(POPCNT64(~sublist.FreeMask)); }
+ );
+ if(count > 0)
+ WARN("%zu Filter%s not deleted\n", count, (count==1)?"":"s");
+
+ if(mHrtf)
+ mHrtf->DecRef();
+ mHrtf = nullptr;
+
+ auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed);
+ if(oldarray != &EmptyContextArray) delete oldarray;
+}
+
+
+/* VerifyDevice
+ *
+ * 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.cbegin(), DeviceList.cend(), device);
+ if(iter != DeviceList.cend() && *iter == device)
+ return *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", decltype(std::declval<void*>()){this});
+
+ size_t count{0};
+ ALcontextProps *cprops{mUpdate.exchange(nullptr, std::memory_order_relaxed)};
+ if(cprops)
+ {
+ ++count;
+ delete cprops;
+ }
+ cprops = mFreeContextProps.exchange(nullptr, std::memory_order_acquire);
+ while(cprops)
+ {
+ ALcontextProps *next{cprops->next.load(std::memory_order_relaxed)};
+ delete cprops;
+ cprops = next;
+ ++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<ALuint>(POPCNT64(~sublist.FreeMask)); }
+ );
+ if(count > 0)
+ WARN("%zu Source%s not deleted\n", count, (count==1)?"":"s");
+ mSourceList.clear();
+ mNumSources = 0;
+
+ count = 0;
+ ALeffectslotProps *eprops{mFreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)};
+ while(eprops)
+ {
+ ALeffectslotProps *next{eprops->next.load(std::memory_order_relaxed)};
+ if(eprops->State) eprops->State->release();
+ delete eprops;
+ eprops = next;
+ ++count;
+ }
+ TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s");
+
+ delete mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed);
+ 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<ALuint>(POPCNT64(~sublist.FreeMask)); }
+ );
+ if(count > 0)
+ WARN("%zu AuxiliaryEffectSlot%s not deleted\n", count, (count==1)?"":"s");
+ mEffectSlotList.clear();
+ mNumEffectSlots = 0;
+
+ count = 0;
+ ALvoiceProps *vprops{mFreeVoiceProps.exchange(nullptr, std::memory_order_acquire)};
+ while(vprops)
+ {
+ ALvoiceProps *next{vprops->next.load(std::memory_order_relaxed)};
+ delete vprops;
+ vprops = next;
+ ++count;
+ }
+ TRACE("Freed %zu voice property object%s\n", count, (count==1)?"":"s");
+
+ mVoices.clear();
+
+ count = 0;
+ ALlistenerProps *lprops{mListener.Params.Update.exchange(nullptr, std::memory_order_relaxed)};
+ if(lprops)
+ {
+ ++count;
+ delete lprops;
+ }
+ lprops = mFreeListenerProps.exchange(nullptr, std::memory_order_acquire);
+ while(lprops)
+ {
+ ALlistenerProps *next{lprops->next.load(std::memory_order_relaxed)};
+ delete lprops;
+ lprops = next;
+ ++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 == Playback)
+ {
+ mDefaultSlot = std::unique_ptr<ALeffectslot>{new ALeffectslot{}};
+ if(InitEffectSlot(mDefaultSlot.get()) == AL_NO_ERROR)
+ aluInitEffectPanning(mDefaultSlot.get(), mDevice.get());
+ else
+ {
+ mDefaultSlot = nullptr;
+ ERR("Failed to initialize the default effect slot\n");
+ }
+ }
+
+ ALeffectslotArray *auxslots;
+ if(!mDefaultSlot)
+ auxslots = ALeffectslot::CreatePtrArray(0);
+ else
+ {
+ auxslots = ALeffectslot::CreatePtrArray(1);
+ (*auxslots)[0] = mDefaultSlot.get();
+ }
+ mActiveAuxSlots.store(auxslots, std::memory_order_relaxed);
+
+ mExtensionList = alExtList;
+
+
+ mListener.Params.Matrix = alu::Matrix::Identity();
+ mListener.Params.Velocity = alu::Vector{};
+ mListener.Params.Gain = mListener.Gain;
+ mListener.Params.MetersPerUnit = mListener.mMetersPerUnit;
+ mListener.Params.DopplerFactor = mDopplerFactor;
+ mListener.Params.SpeedOfSound = mSpeedOfSound * mDopplerVelocity;
+ mListener.Params.SourceDistanceModel = mSourceDistanceModel;
+ mListener.Params.mDistanceModel = mDistanceModel;
+
+
+ mAsyncEvents = CreateRingBuffer(511, sizeof(AsyncEvent), false);
+ StartEventThrd(this);
+
+
+ mVoices.reserve(256);
+ mVoices.resize(64);
+}
+
+bool ALCcontext::deinit()
+{
+ if(LocalContext.get() == this)
+ {
+ WARN("%p released while current on thread\n", decltype(std::declval<void*>()){this});
+ LocalContext.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)
+ {
+ while((mDevice->MixCount.load(std::memory_order_acquire)&1))
+ std::this_thread::yield();
+ delete oldarray;
+ }
+
+ ret = !newarray->empty();
+ }
+ else
+ ret = !oldarray->empty();
+
+ StopEventThrd(this);
+
+ return ret;
+}
+
+
+/* VerifyContext
+ *
+ * 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.cbegin(), ContextList.cend(), context);
+ if(iter != ContextList.cend() && *iter == context)
+ return *iter;
+ return nullptr;
+}
+
+/* GetContextRef
+ *
+ * Returns a new reference to the currently active context for this thread.
+ */
+ContextRef GetContextRef(void)
+{
+ ALCcontext *context{LocalContext.get()};
+ 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
+ ************************************************/
+
+/* alcGetError
+ *
+ * Return last ALC generated error code for the given device
+ */
+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
+
+
+/* alcSuspendContext
+ *
+ * Suspends updates for the given context
+ */
+ALC_API ALCvoid 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
+
+/* alcProcessContext
+ *
+ * Resumes processing updates for the given context
+ */
+ALC_API ALCvoid 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
+
+
+/* alcGetString
+ *
+ * Returns information about the device, and error strings
+ */
+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 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 size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span<ALCint> 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;
+ }
+ return 0;
+ }
+
+ if(device->Type == 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 != 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++] = static_cast<ALCint>(device->mAmbiLayout);
+
+ values[i++] = ALC_AMBISONIC_SCALING_SOFT;
+ values[i++] = static_cast<ALCint>(device->mAmbiScale);
+
+ values[i++] = ALC_AMBISONIC_ORDER_SOFT;
+ values[i++] = static_cast<ALCint>(device->mAmbiOrder);
+ }
+
+ 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++] = 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<ALCint>(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++] = MAX_AMBI_ORDER;
+
+ 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 == 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 == 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] = static_cast<ALCint>(device->mAmbiLayout);
+ return 1;
+
+ case ALC_AMBISONIC_SCALING_SOFT:
+ if(device->Type != Loopback || device->FmtChans != DevFmtAmbi3D)
+ {
+ alcSetError(device, ALC_INVALID_DEVICE);
+ return 0;
+ }
+ values[0] = static_cast<ALCint>(device->mAmbiScale);
+ return 1;
+
+ case ALC_AMBISONIC_ORDER_SOFT:
+ if(device->Type != 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<ALCint>(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<ALCint>(minz(device->HrtfList.size(),
+ std::numeric_limits<ALCint>::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] = MAX_AMBI_ORDER;
+ return 1;
+
+ default:
+ alcSetError(device, ALC_INVALID_ENUM);
+ }
+ 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)
+START_API_FUNC
+{
+ DeviceRef dev{VerifyDevice(device)};
+ if(size <= 0 || values == nullptr)
+ alcSetError(dev.get(), ALC_INVALID_VALUE);
+ else
+ GetIntegerv(dev.get(), param, {values, values+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);
+ else if(!dev || dev->Type == Capture)
+ {
+ auto ivals = al::vector<ALCint>(static_cast<ALuint>(size));
+ size_t got{GetIntegerv(dev.get(), pname, {ivals.data(), ivals.size()})};
+ 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 != 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++] = static_cast<ALCint64SOFT>(dev->mAmbiLayout);
+
+ values[i++] = ALC_AMBISONIC_SCALING_SOFT;
+ values[i++] = static_cast<ALCint64SOFT>(dev->mAmbiScale);
+
+ values[i++] = ALC_AMBISONIC_ORDER_SOFT;
+ values[i++] = dev->mAmbiOrder;
+ }
+
+ values[i++] = ALC_FORMAT_CHANNELS_SOFT;
+ values[i++] = dev->FmtChans;
+
+ values[i++] = ALC_FORMAT_TYPE_SOFT;
+ values[i++] = 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};
+ nanoseconds basecount;
+ ALuint samplecount;
+ ALuint refcount;
+ do {
+ while(((refcount=ReadRef(dev->MixCount))&1) != 0)
+ std::this_thread::yield();
+ 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<ALCint>(static_cast<ALuint>(size));
+ size_t got{GetIntegerv(dev.get(), pname, {ivals.data(), ivals.size()})};
+ std::copy_n(ivals.begin(), got, values);
+ break;
+ }
+}
+END_API_FUNC
+
+
+/* alcIsExtensionPresent
+ *
+ * Determines if there is support for a particular extension
+ */
+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
+
+
+/* alcGetProcAddress
+ *
+ * Retrieves the function address for a particular extension function
+ */
+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
+
+
+/* alcGetEnumValue
+ *
+ * Get the value for a particular ALC enumeration name
+ */
+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
+
+
+/* alcCreateContext
+ *
+ * Create and attach a context to the given device.
+ */
+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 == 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);
+ if(err == ALC_INVALID_DEVICE)
+ aluHandleDisconnect(dev.get(), "Device update failure");
+ return nullptr;
+ }
+
+ ContextRef context{new ALCcontext{dev}};
+ context->init();
+
+ if(auto volopt = ConfigValueFloat(dev->DeviceName.c_str(), nullptr, "volume-adjust"))
+ {
+ const ALfloat valf{*volopt};
+ if(!std::isfinite(valf))
+ ERR("volume-adjust must be finite: %f\n", valf);
+ else
+ {
+ const ALfloat 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)
+ {
+ while((dev->MixCount.load(std::memory_order_acquire)&1))
+ std::this_thread::yield();
+ 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);
+ }
+
+ if(context->mDefaultSlot)
+ {
+ if(InitializeEffect(context.get(), context->mDefaultSlot.get(), &DefaultEffect) == AL_NO_ERROR)
+ UpdateEffectSlotProps(context->mDefaultSlot.get(), context.get());
+ else
+ ERR("Failed to initialize the default effect\n");
+ }
+
+ TRACE("Created context %p\n", decltype(std::declval<void*>()){context.get()});
+ return context.get();
+}
+END_API_FUNC
+
+/* alcDestroyContext
+ *
+ * Remove a context from its device
+ */
+ALC_API ALCvoid 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 an extra reference to this context so it remains valid until the
+ * ListLock is released.
+ */
+ ContextRef ctx{std::move(*iter)};
+ ContextList.erase(iter);
+
+ ALCdevice *Device{ctx->mDevice.get()};
+
+ std::lock_guard<std::mutex> _{Device->StateLock};
+ if(!ctx->deinit() && Device->Flags.get<DeviceRunning>())
+ {
+ Device->Backend->stop();
+ Device->Flags.unset<DeviceRunning>();
+ }
+}
+END_API_FUNC
+
+
+/* alcGetCurrentContext
+ *
+ * Returns the currently active context on the calling thread
+ */
+ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void)
+START_API_FUNC
+{
+ ALCcontext *Context{LocalContext.get()};
+ if(!Context) Context = GlobalContext.load();
+ return Context;
+}
+END_API_FUNC
+
+/* alcGetThreadContext
+ *
+ * Returns the currently active thread-local context
+ */
+ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void)
+START_API_FUNC
+{ return LocalContext.get(); }
+END_API_FUNC
+
+/* 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)
+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.get()};
+ if(ctx) LocalContext.set(nullptr);
+ /* Reset (decrement) the previous thread-local reference. */
+
+ return ALC_TRUE;
+}
+END_API_FUNC
+
+/* alcSetThreadContext
+ *
+ * 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.get()};
+ LocalContext.set(ctx.release());
+
+ return ALC_TRUE;
+}
+END_API_FUNC
+
+
+/* alcGetContextsDevice
+ *
+ * Returns the device that a particular context is attached to
+ */
+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
+
+
+/* alcOpenDevice
+ *
+ * Opens the named device.
+ */
+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{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);
+ 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());
+ return nullptr;
+ }
+
+ deviceName = device->DeviceName.c_str();
+ if(auto chanopt = ConfigValueStr(deviceName, nullptr, "channels"))
+ {
+ static const struct ChannelMap {
+ const char name[16];
+ DevFmtChannels chans;
+ ALuint 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(ALuint freq{ConfigValueUInt(deviceName, nullptr, "frequency").value_or(0)})
+ {
+ if(freq < MIN_OUTPUT_RATE)
+ {
+ ERR("%uhz request clamped to %uhz minimum\n", freq, MIN_OUTPUT_RATE);
+ freq = MIN_OUTPUT_RATE;
+ }
+ device->UpdateSize = (device->UpdateSize*freq + device->Frequency/2) / device->Frequency;
+ device->BufferSize = (device->BufferSize*freq + device->Frequency/2) / device->Frequency;
+ 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 srcsopt = ConfigValueUInt(deviceName, nullptr, "sources"))
+ {
+ if(*srcsopt > 0) device->SourcesMax = *srcsopt;
+ }
+
+ if(auto slotsopt = ConfigValueUInt(deviceName, nullptr, "slots"))
+ {
+ if(*slotsopt > 0)
+ device->AuxiliaryEffectSlotMax = minu(*slotsopt, INT_MAX);
+ }
+
+ if(auto sendsopt = ConfigValueInt(deviceName, nullptr, "sends"))
+ device->NumAuxSends = clampu(DEFAULT_SENDS, 0,
+ static_cast<ALuint>(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 = AmbiLayout::FuMa;
+ device->mAmbiScale = AmbiNorm::FuMa;
+ }
+ }
+ else if(al::strcasecmp(fmt, "ambix") == 0 || al::strcasecmp(fmt, "acn+sn3d") == 0)
+ {
+ device->mAmbiLayout = AmbiLayout::ACN;
+ device->mAmbiScale = AmbiNorm::SN3D;
+ }
+ else if(al::strcasecmp(fmt, "acn+n3d") == 0)
+ {
+ device->mAmbiLayout = AmbiLayout::ACN;
+ device->mAmbiScale = AmbiNorm::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);
+ }
+
+ TRACE("Created device %p, \"%s\"\n", decltype(std::declval<void*>()){device.get()},
+ device->DeviceName.c_str());
+ return device.get();
+}
+END_API_FUNC
+
+/* alcCloseDevice
+ *
+ * Closes the given device.
+ */
+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 == Capture)
+ {
+ alcSetError(iter->get(), ALC_INVALID_DEVICE);
+ return ALC_FALSE;
+ }
+
+ /* Erase the device, and any remaining contexts left on it, from their
+ * respective lists.
+ */
+ DeviceRef dev{std::move(*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(std::move(*ctxiter));
+ ContextList.erase(ctxiter);
+ }
+ }
+ listlock.unlock();
+
+ for(ContextRef &context : orphanctxs)
+ {
+ WARN("Releasing orphaned context %p\n", decltype(std::declval<void*>()){context.get()});
+ context->deinit();
+ }
+ orphanctxs.clear();
+
+ if(dev->Flags.get<DeviceRunning>())
+ dev->Backend->stop();
+ dev->Flags.unset<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{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, ChannelsRequest, SampleTypeRequest>();
+
+ device->UpdateSize = static_cast<ALuint>(samples);
+ device->BufferSize = static_cast<ALuint>(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);
+ 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());
+ return nullptr;
+ }
+
+ {
+ std::lock_guard<std::recursive_mutex> _{ListLock};
+ auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get());
+ DeviceList.emplace(iter, device);
+ }
+
+ TRACE("Created capture device %p, \"%s\"\n", decltype(std::declval<void*>()){device.get()},
+ device->DeviceName.c_str());
+ return device.get();
+}
+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 != Capture)
+ {
+ alcSetError(iter->get(), ALC_INVALID_DEVICE);
+ return ALC_FALSE;
+ }
+
+ DeviceRef dev{std::move(*iter)};
+ DeviceList.erase(iter);
+ listlock.unlock();
+
+ std::lock_guard<std::mutex> _{dev->StateLock};
+ if(dev->Flags.get<DeviceRunning>())
+ dev->Backend->stop();
+ dev->Flags.unset<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 != 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.get<DeviceRunning>())
+ {
+ try {
+ auto backend = dev->Backend.get();
+ if(!backend->start())
+ throw al::backend_exception{ALC_INVALID_DEVICE, "Device start failure"};
+ dev->Flags.set<DeviceRunning>();
+ }
+ catch(al::backend_exception& e) {
+ aluHandleDisconnect(dev.get(), "%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 != Capture)
+ alcSetError(dev.get(), ALC_INVALID_DEVICE);
+ else
+ {
+ std::lock_guard<std::mutex> _{dev->StateLock};
+ if(dev->Flags.get<DeviceRunning>())
+ dev->Backend->stop();
+ dev->Flags.unset<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 != 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<ALCuint>(samples);
+ if(usamples > backend->availableSamples())
+ {
+ alcSetError(dev.get(), ALC_INVALID_VALUE);
+ return;
+ }
+
+ auto *bbuffer = static_cast<al::byte*>(buffer);
+ if(ALCenum err{backend->captureSamples(bbuffer, usamples)})
+ alcSetError(dev.get(), err);
+}
+END_API_FUNC
+
+
+/************************************************
+ * ALC loopback functions
+ ************************************************/
+
+/* alcLoopbackOpenDeviceSOFT
+ *
+ * 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{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 srcsopt = ConfigValueUInt(nullptr, nullptr, "sources"))
+ {
+ if(*srcsopt > 0) device->SourcesMax = *srcsopt;
+ }
+
+ if(auto slotsopt = ConfigValueUInt(nullptr, nullptr, "slots"))
+ {
+ if(*slotsopt > 0)
+ device->AuxiliaryEffectSlotMax = minu(*slotsopt, INT_MAX);
+ }
+
+ if(auto sendsopt = ConfigValueInt(nullptr, nullptr, "sends"))
+ device->NumAuxSends = clampu(DEFAULT_SENDS, 0,
+ static_cast<ALuint>(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());
+ return nullptr;
+ }
+
+ {
+ std::lock_guard<std::recursive_mutex> _{ListLock};
+ auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get());
+ DeviceList.emplace(iter, device);
+ }
+
+ TRACE("Created loopback device %p\n", decltype(std::declval<void*>()){device.get()});
+ return device.get();
+}
+END_API_FUNC
+
+/* 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)
+START_API_FUNC
+{
+ DeviceRef dev{VerifyDevice(device)};
+ if(!dev || dev->Type != Loopback)
+ alcSetError(dev.get(), ALC_INVALID_DEVICE);
+ else if(freq <= 0)
+ alcSetError(dev.get(), ALC_INVALID_VALUE);
+ else
+ {
+ if(IsValidALCType(type) && IsValidALCChannels(channels) && freq >= MIN_OUTPUT_RATE)
+ return ALC_TRUE;
+ }
+
+ return ALC_FALSE;
+}
+END_API_FUNC
+
+/* 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)
+START_API_FUNC
+{
+ DeviceRef dev{VerifyDevice(device)};
+ if(!dev || dev->Type != Loopback)
+ alcSetError(dev.get(), ALC_INVALID_DEVICE);
+ else if(samples < 0 || (samples > 0 && buffer == nullptr))
+ alcSetError(dev.get(), ALC_INVALID_VALUE);
+ else
+ {
+ BackendLockGuard _{*dev->Backend};
+ aluMixData(dev.get(), buffer, static_cast<ALuint>(samples));
+ }
+}
+END_API_FUNC
+
+
+/************************************************
+ * ALC DSP pause/resume functions
+ ************************************************/
+
+/* alcDevicePauseSOFT
+ *
+ * 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 != Playback)
+ alcSetError(dev.get(), ALC_INVALID_DEVICE);
+ else
+ {
+ std::lock_guard<std::mutex> _{dev->StateLock};
+ if(dev->Flags.get<DeviceRunning>())
+ dev->Backend->stop();
+ dev->Flags.unset<DeviceRunning>();
+ dev->Flags.set<DevicePaused>();
+ }
+}
+END_API_FUNC
+
+/* alcDeviceResumeSOFT
+ *
+ * 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 != Playback)
+ {
+ alcSetError(dev.get(), ALC_INVALID_DEVICE);
+ return;
+ }
+
+ std::lock_guard<std::mutex> _{dev->StateLock};
+ if(!dev->Flags.get<DevicePaused>())
+ return;
+ dev->Flags.unset<DevicePaused>();
+ if(dev->mContexts.load()->empty())
+ return;
+
+ try {
+ auto backend = dev->Backend.get();
+ if(!backend->start())
+ throw al::backend_exception{ALC_INVALID_DEVICE, "Device start failure"};
+ dev->Flags.set<DeviceRunning>();
+ }
+ catch(al::backend_exception& e) {
+ aluHandleDisconnect(dev.get(), "%s", e.what());
+ alcSetError(dev.get(), ALC_INVALID_DEVICE);
+ }
+}
+END_API_FUNC
+
+
+/************************************************
+ * 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)
+START_API_FUNC
+{
+ DeviceRef dev{VerifyDevice(device)};
+ if(!dev || dev->Type == Capture)
+ alcSetError(dev.get(), ALC_INVALID_DEVICE);
+ else switch(paramName)
+ {
+ case ALC_HRTF_SPECIFIER_SOFT:
+ if(index >= 0 && static_cast<size_t>(index) < dev->HrtfList.size())
+ return dev->HrtfList[static_cast<ALuint>(index)].name.c_str();
+ alcSetError(dev.get(), ALC_INVALID_VALUE);
+ break;
+
+ default:
+ alcSetError(dev.get(), ALC_INVALID_ENUM);
+ break;
+ }
+
+ return nullptr;
+}
+END_API_FUNC
+
+/* alcResetDeviceSOFT
+ *
+ * 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 == 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.get<DeviceRunning>())
+ dev->Backend->stop();
+ dev->Flags.unset<DeviceRunning>();
+ device->Connected.store(true);
+
+ ALCenum err{UpdateDeviceParams(dev.get(), attribs)};
+ if LIKELY(err == ALC_NO_ERROR) return ALC_TRUE;
+
+ alcSetError(dev.get(), err);
+ if(err == ALC_INVALID_DEVICE)
+ aluHandleDisconnect(dev.get(), "Device start failure");
+ return ALC_FALSE;
+}
+END_API_FUNC
diff --git a/alc/alcmain.h b/alc/alcmain.h
new file mode 100644
index 00000000..30c5b835
--- /dev/null
+++ b/alc/alcmain.h
@@ -0,0 +1,390 @@
+#ifndef ALC_MAIN_H
+#define ALC_MAIN_H
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <chrono>
+#include <cstdint>
+#include <cstddef>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <utility>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#include "albyte.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "ambidefs.h"
+#include "atomic.h"
+#include "devformat.h"
+#include "filters/splitter.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;
+
+
+#define MIN_OUTPUT_RATE 8000
+#define DEFAULT_OUTPUT_RATE 44100
+#define DEFAULT_UPDATE_SIZE 882 /* 20ms */
+#define DEFAULT_NUM_UPDATES 3
+
+
+enum DeviceType {
+ Playback,
+ Capture,
+ Loopback
+};
+
+
+enum RenderMode {
+ NormalRender,
+ StereoPair,
+ HrtfRender
+};
+
+
+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
+
+class DistanceComp {
+public:
+ struct DistData {
+ ALfloat Gain{1.0f};
+ ALuint Length{0u}; /* Valid range is [0...MAX_DELAY_LENGTH). */
+ ALfloat *Buffer{nullptr};
+ };
+
+private:
+ std::array<DistData,MAX_OUTPUT_CHANNELS> mChannels;
+ al::vector<ALfloat,16> mSamples;
+
+public:
+ void setSampleCount(size_t new_size) { mSamples.resize(new_size); }
+ void clear() noexcept
+ {
+ for(auto &chan : mChannels)
+ {
+ chan.Gain = 1.0f;
+ chan.Length = 0;
+ chan.Buffer = nullptr;
+ }
+ using SampleVecT = decltype(mSamples);
+ SampleVecT{}.swap(mSamples);
+ }
+
+ ALfloat *getSamples() noexcept { return mSamples.data(); }
+
+ al::span<DistData,MAX_OUTPUT_CHANNELS> as_span() { return mChannels; }
+};
+
+struct BFChannelConfig {
+ ALfloat Scale;
+ ALuint Index;
+};
+
+/* Size for temporary storage of buffer data, in ALfloats. Larger values need
+ * more memory, while smaller values may need more iterations. The value needs
+ * to be a sensible size, however, as it constrains the max stepping value used
+ * for mixing, as well as the maximum number of samples per mixing iteration.
+ */
+#define BUFFERSIZE 1024
+
+using FloatBufferLine = std::array<float,BUFFERSIZE>;
+
+/* Maximum number of samples to pad on the ends of a buffer for resampling.
+ * Note that the padding is symmetric (half at the beginning and half at the
+ * end)!
+ */
+#define MAX_RESAMPLER_PADDING 48
+
+
+struct FrontStablizer {
+ static constexpr size_t DelayLength{256u};
+
+ alignas(16) float DelayBuf[MAX_OUTPUT_CHANNELS][DelayLength];
+
+ BandSplitter LFilter, RFilter;
+ alignas(16) float LSplit[2][BUFFERSIZE];
+ alignas(16) float RSplit[2][BUFFERSIZE];
+
+ alignas(16) float TempBuf[BUFFERSIZE + DelayLength];
+
+ DEF_NEWDEL(FrontStablizer)
+};
+
+
+struct MixParams {
+ /* Coefficient channel mapping for mixing to the buffer. */
+ std::array<BFChannelConfig,MAX_OUTPUT_CHANNELS> AmbiMap{};
+
+ al::span<FloatBufferLine> Buffer;
+};
+
+struct RealMixParams {
+ std::array<ALuint,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{};
+
+ ALuint Frequency{};
+ ALuint UpdateSize{};
+ ALuint BufferSize{};
+
+ DevFmtChannels FmtChans{};
+ DevFmtType FmtType{};
+ ALboolean IsHeadphones{AL_FALSE};
+ ALuint mAmbiOrder{0};
+ /* For DevFmtAmbi* output only, specifies the channel order and
+ * normalization.
+ */
+ AmbiLayout mAmbiLayout{AmbiLayout::Default};
+ AmbiNorm mAmbiScale{AmbiNorm::Default};
+
+ ALCenum LimiterState{ALC_DONT_CARE_SOFT};
+
+ std::string DeviceName;
+
+ // Device flags
+ al::bitfield<DeviceFlagsCount> Flags{};
+
+ std::string HrtfName;
+ al::vector<EnumeratedHrtf> HrtfList;
+ ALCenum HrtfStatus{ALC_FALSE};
+
+ std::atomic<ALCenum> LastError{ALC_NO_ERROR};
+
+ // Maximum number of sources that can be created
+ ALuint SourcesMax{};
+ // Maximum number of slots that can be created
+ ALuint AuxiliaryEffectSlotMax{};
+
+ ALCuint NumMonoSources{};
+ ALCuint NumStereoSources{};
+ ALCuint NumAuxSends{};
+
+ // 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;
+
+ /* Rendering mode. */
+ RenderMode mRenderMode{NormalRender};
+
+ /* The average speaker distance as determined by the ambdec configuration,
+ * HRTF data set, or the NFC-HOA reference delay. Only used for NFC.
+ */
+ ALfloat AvgSpeakerDist{0.0f};
+
+ ALuint SamplesDone{0u};
+ std::chrono::nanoseconds ClockBase{0};
+ std::chrono::nanoseconds FixedLatency{0};
+
+ /* Temp storage used for mixer processing. */
+ alignas(16) ALfloat SourceData[BUFFERSIZE + MAX_RESAMPLER_PADDING];
+ alignas(16) ALfloat ResampledData[BUFFERSIZE];
+ alignas(16) ALfloat FilteredData[BUFFERSIZE];
+ union {
+ alignas(16) ALfloat HrtfSourceData[BUFFERSIZE + HRTF_HISTORY_LENGTH];
+ alignas(16) ALfloat NfcSampleData[BUFFERSIZE];
+ };
+
+ /* Persistent storage for HRTF mixing. */
+ alignas(16) float2 HrtfAccumData[BUFFERSIZE + HRIR_LENGTH];
+
+ /* 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;
+ ALuint NumChannelsPerOrder[MAX_AMBI_ORDER+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;
+ HrtfEntry *mHrtf{nullptr};
+
+ /* 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<FrontStablizer> Stablizer;
+
+ std::unique_ptr<Compressor> Limiter;
+
+ /* Delay buffers used to compensate for speaker distances. */
+ DistanceComp ChannelDelay;
+
+ /* Dithering control. */
+ ALfloat DitherDepth{0.0f};
+ ALuint 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;
+
+
+ ALCdevice(DeviceType type);
+ ALCdevice(const ALCdevice&) = delete;
+ ALCdevice& operator=(const ALCdevice&) = delete;
+ ~ALCdevice();
+
+ ALuint bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); }
+ ALuint channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); }
+ ALuint frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); }
+
+ void ProcessHrtf(const size_t SamplesToDo);
+ void ProcessAmbiDec(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); }
+
+ 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 ALint RTPrioLevel;
+void SetRTPriority(void);
+
+void SetDefaultChannelOrder(ALCdevice *device);
+void SetDefaultWFXChannelOrder(ALCdevice *device);
+
+const ALCchar *DevFmtTypeString(DevFmtType type) noexcept;
+const ALCchar *DevFmtChannelsString(DevFmtChannels chans) noexcept;
+
+/**
+ * GetChannelIdxByName
+ *
+ * Returns the index for the given channel name (e.g. FrontCenter), or
+ * INVALID_CHANNEL_INDEX if it doesn't exist.
+ */
+inline ALuint 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
diff --git a/alc/alconfig.cpp b/alc/alconfig.cpp
new file mode 100644
index 00000000..ede39156
--- /dev/null
+++ b/alc/alconfig.cpp
@@ -0,0 +1,549 @@
+/**
+ * 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 "alconfig.h"
+
+#include <cstdlib>
+#include <cctype>
+#include <cstring>
+#ifdef _WIN32_IE
+#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 "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 = buffer.find('#');
+ if(cmtpos != std::string::npos)
+ buffer.resize(cmtpos);
+ while(!buffer.empty() && std::isspace(buffer.back()))
+ buffer.pop_back();
+ if(buffer.empty()) continue;
+
+ const char *line{&buffer[0]};
+ char key[256]{};
+ char value[256]{};
+ if(std::sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 ||
+ std::sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 ||
+ std::sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2)
+ {
+ /* sscanf doesn't handle '' or "" as empty values, so clip it
+ * manually. */
+ if(std::strcmp(value, "\"\"") == 0 || std::strcmp(value, "''") == 0)
+ value[0] = 0;
+ }
+ else if(std::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;
+ }
+
+ std::string fullKey;
+ if(!curSection.empty())
+ {
+ fullKey += curSection;
+ fullKey += '/';
+ }
+ fullKey += key;
+ while(!fullKey.empty() && std::isspace(fullKey.back()))
+ fullKey.pop_back();
+
+ TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value);
+
+ /* Check if we already have this option set */
+ auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(),
+ [&fullKey](const ConfigEntry &entry) -> bool
+ { return entry.key == fullKey; }
+ );
+ if(ent != ConfOpts.end())
+ {
+ if(value[0])
+ ent->value = expdup(value);
+ else
+ ConfOpts.erase(ent);
+ }
+ else if(value[0])
+ ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value)});
+ }
+ 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);
+}
diff --git a/alc/alconfig.h b/alc/alconfig.h
new file mode 100644
index 00000000..ffc7adad
--- /dev/null
+++ b/alc/alconfig.h
@@ -0,0 +1,20 @@
+#ifndef ALCONFIG_H
+#define ALCONFIG_H
+
+#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);
+
+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 */
diff --git a/alc/alcontext.h b/alc/alcontext.h
new file mode 100644
index 00000000..ba3942f5
--- /dev/null
+++ b/alc/alcontext.h
@@ -0,0 +1,196 @@
+#ifndef ALCONTEXT_H
+#define ALCONTEXT_H
+
+#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 "logging.h"
+#include "threads.h"
+#include "vector.h"
+#include "voice.h"
+
+struct ALeffectslot;
+struct ALeffectslotProps;
+struct ALsource;
+struct RingBuffer;
+
+
+enum class DistanceModel {
+ InverseClamped = AL_INVERSE_DISTANCE_CLAMPED,
+ LinearClamped = AL_LINEAR_DISTANCE_CLAMPED,
+ ExponentClamped = AL_EXPONENT_DISTANCE_CLAMPED,
+ Inverse = AL_INVERSE_DISTANCE,
+ Linear = AL_LINEAR_DISTANCE,
+ Exponent = AL_EXPONENT_DISTANCE,
+ Disable = AL_NONE,
+
+ Default = InverseClamped
+};
+
+
+struct ALcontextProps {
+ ALfloat DopplerFactor;
+ ALfloat DopplerVelocity;
+ ALfloat SpeedOfSound;
+ ALboolean SourceDistanceModel;
+ DistanceModel mDistanceModel;
+
+ std::atomic<ALcontextProps*> next;
+
+ DEF_NEWDEL(ALcontextProps)
+};
+
+
+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> {
+ al::vector<SourceSubList> mSourceList;
+ ALuint mNumSources{0};
+ std::mutex mSourceLock;
+
+ al::vector<EffectSlotSubList> mEffectSlotList;
+ ALuint mNumEffectSlots{0u};
+ std::mutex mEffectSlotLock;
+
+ std::atomic<ALenum> mLastError{AL_NO_ERROR};
+
+ DistanceModel mDistanceModel{DistanceModel::Default};
+ ALboolean mSourceDistanceModel{AL_FALSE};
+
+ ALfloat mDopplerFactor{1.0f};
+ ALfloat mDopplerVelocity{1.0f};
+ ALfloat mSpeedOfSound{SPEEDOFSOUNDMETRESPERSEC};
+
+ std::atomic_flag mPropsClean;
+ std::atomic<bool> mDeferUpdates{false};
+
+ std::mutex mPropLock;
+
+ /* 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};
+
+ ALfloat mGainBoost{1.0f};
+
+ std::atomic<ALcontextProps*> mUpdate{nullptr};
+
+ /* Linked lists of unused property containers, free to use for future
+ * updates.
+ */
+ std::atomic<ALcontextProps*> mFreeContextProps{nullptr};
+ std::atomic<ALlistenerProps*> mFreeListenerProps{nullptr};
+ std::atomic<ALvoiceProps*> mFreeVoiceProps{nullptr};
+ std::atomic<ALeffectslotProps*> mFreeEffectslotProps{nullptr};
+
+ al::vector<ALvoice> mVoices;
+
+ using ALeffectslotArray = al::FlexArray<ALeffectslot*>;
+ std::atomic<ALeffectslotArray*> mActiveAuxSlots{nullptr};
+
+ std::thread mEventThread;
+ al::semaphore mEventSem;
+ std::unique_ptr<RingBuffer> mAsyncEvents;
+ std::atomic<ALbitfieldSOFT> mEnabledEvts{0u};
+ std::mutex mEventCbLock;
+ ALEVENTPROCSOFT mEventCb{};
+ void *mEventParam{nullptr};
+
+ /* Default effect slot */
+ std::unique_ptr<ALeffectslot> mDefaultSlot;
+
+ const al::intrusive_ptr<ALCdevice> mDevice;
+ const ALCchar *mExtensionList{nullptr};
+
+ ALlistener mListener{};
+
+
+ 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.store(true); }
+
+ /** Resumes update processing after being deferred. */
+ void processUpdates();
+
+ void setError(ALenum errorCode, const char *msg, ...) DECL_FORMAT(printf, 3, 4);
+
+ 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 */
diff --git a/alc/alu.cpp b/alc/alu.cpp
new file mode 100644
index 00000000..9bf052e1
--- /dev/null
+++ b/alc/alu.cpp
@@ -0,0 +1,1842 @@
+/**
+ * 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 <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 "al/auxeffectslot.h"
+#include "al/buffer.h"
+#include "al/effect.h"
+#include "al/event.h"
+#include "al/listener.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "alstring.h"
+#include "ambidefs.h"
+#include "atomic.h"
+#include "bformatdec.h"
+#include "bs2b.h"
+#include "cpu_caps.h"
+#include "devformat.h"
+#include "effects/base.h"
+#include "filters/biquad.h"
+#include "filters/nfc.h"
+#include "filters/splitter.h"
+#include "fpu_modes.h"
+#include "hrtf.h"
+#include "inprogext.h"
+#include "mastering.h"
+#include "math_defs.h"
+#include "mixer/defs.h"
+#include "opthelpers.h"
+#include "ringbuffer.h"
+#include "strutils.h"
+#include "threads.h"
+#include "uhjfilter.h"
+#include "vecmat.h"
+#include "voice.h"
+
+#include "bsinc_inc.h"
+
+
+static_assert(!(MAX_RESAMPLER_PADDING&1) && MAX_RESAMPLER_PADDING >= bsinc24.m[0],
+ "MAX_RESAMPLER_PADDING is not a multiple of two, or is too small");
+
+
+namespace {
+
+using namespace std::placeholders;
+
+ALfloat InitConeScale()
+{
+ ALfloat 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;
+}
+
+ALfloat InitZScale()
+{
+ ALfloat 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 ALfloat ConeScale{InitConeScale()};
+
+/* Localized Z scalar for mono sources */
+const ALfloat ZScale{InitZScale()};
+
+MixerFunc MixSamples{Mix_<CTag>};
+RowMixerFunc MixRowSamples{MixRow_<CTag>};
+
+namespace {
+
+struct ChanMap {
+ Channel channel;
+ ALfloat angle;
+ ALfloat elevation;
+};
+
+HrtfDirectMixerFunc MixDirectHrtf = MixDirectHrtf_<CTag>;
+
+inline MixerFunc SelectMixer()
+{
+#ifdef HAVE_NEON
+ if((CPUCapFlags&CPU_CAP_NEON))
+ return Mix_<NEONTag>;
+#endif
+#ifdef HAVE_SSE
+ if((CPUCapFlags&CPU_CAP_SSE))
+ return Mix_<SSETag>;
+#endif
+ return Mix_<CTag>;
+}
+
+inline RowMixerFunc SelectRowMixer()
+{
+#ifdef HAVE_NEON
+ if((CPUCapFlags&CPU_CAP_NEON))
+ return MixRow_<NEONTag>;
+#endif
+#ifdef HAVE_SSE
+ if((CPUCapFlags&CPU_CAP_SSE))
+ return MixRow_<SSETag>;
+#endif
+ return MixRow_<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 ALuint increment, BsincState *state, const BSincTable *table)
+{
+ size_t si{BSINC_SCALE_COUNT - 1};
+ float sf{0.0f};
+
+ if(increment > FRACTIONONE)
+ {
+ sf = FRACTIONONE / static_cast<float>(increment);
+ sf = maxf(0.0f, (BSINC_SCALE_COUNT-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, ALuint 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 <= FRACTIONONE)
+ {
+ /* 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)
+{
+ MixSamples = SelectMixer();
+ MixRowSamples = SelectRowMixer();
+ MixDirectHrtf = SelectHrtfMixer();
+}
+
+
+ResamplerFunc PrepareResampler(Resampler resampler, ALuint 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 ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
+ const ALuint ridx{RealOut.ChannelIndex[FrontRight]};
+
+ MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData,
+ mHrtfState.get(), SamplesToDo);
+}
+
+void ALCdevice::ProcessAmbiDec(const size_t SamplesToDo)
+{
+ AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo);
+}
+
+void ALCdevice::ProcessUhj(const size_t SamplesToDo)
+{
+ /* UHJ is stereo output only. */
+ const ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
+ const ALuint 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 ALuint lidx{RealOut.ChannelIndex[FrontLeft]};
+ const ALuint 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 ALuint dither_rng(ALuint *seed) noexcept
+{
+ *seed = (*seed * 96314165) + 907633515;
+ return *seed;
+}
+
+
+inline alu::Vector aluCrossproduct(const alu::Vector &in1, const alu::Vector &in2)
+{
+ return alu::Vector{
+ in1[1]*in2[2] - in1[2]*in2[1],
+ in1[2]*in2[0] - in1[0]*in2[2],
+ in1[0]*in2[1] - in1[1]*in2[0],
+ 0.0f
+ };
+}
+
+inline ALfloat aluDotproduct(const alu::Vector &vec1, const alu::Vector &vec2)
+{
+ return vec1[0]*vec2[0] + vec1[1]*vec2[1] + vec1[2]*vec2[2];
+}
+
+
+alu::Vector operator*(const alu::Matrix &mtx, const alu::Vector &vec) noexcept
+{
+ return alu::Vector{
+ vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
+ vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
+ vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
+ vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]
+ };
+}
+
+
+bool CalcContextParams(ALCcontext *Context)
+{
+ ALcontextProps *props{Context->mUpdate.exchange(nullptr, std::memory_order_acq_rel)};
+ if(!props) return false;
+
+ ALlistener &Listener = Context->mListener;
+ Listener.Params.DopplerFactor = props->DopplerFactor;
+ Listener.Params.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
+
+ Listener.Params.SourceDistanceModel = props->SourceDistanceModel;
+ Listener.Params.mDistanceModel = props->mDistanceModel;
+
+ AtomicReplaceHead(Context->mFreeContextProps, props);
+ return true;
+}
+
+bool CalcListenerParams(ALCcontext *Context)
+{
+ ALlistener &Listener = Context->mListener;
+
+ ALlistenerProps *props{Listener.Params.Update.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{aluCrossproduct(N, V)};
+ U.normalize();
+
+ Listener.Params.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,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+
+ const alu::Vector P{Listener.Params.Matrix *
+ alu::Vector{props->Position[0], props->Position[1], props->Position[2], 1.0f}};
+ Listener.Params.Matrix.setRow(3, -P[0], -P[1], -P[2], 1.0f);
+
+ const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f};
+ Listener.Params.Velocity = Listener.Params.Matrix * vel;
+
+ Listener.Params.Gain = props->Gain * Context->mGainBoost;
+ Listener.Params.MetersPerUnit = props->MetersPerUnit;
+
+ AtomicReplaceHead(Context->mFreeListenerProps, props);
+ return true;
+}
+
+bool CalcEffectSlotParams(ALeffectslot *slot, ALCcontext *context)
+{
+ ALeffectslotProps *props{slot->Params.Update.exchange(nullptr, std::memory_order_acq_rel)};
+ if(!props) return false;
+
+ slot->Params.Gain = props->Gain;
+ slot->Params.AuxSendAuto = props->AuxSendAuto;
+ slot->Params.Target = props->Target;
+ slot->Params.EffectType = props->Type;
+ slot->Params.mEffectProps = 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;
+ }
+
+ EffectState *state{props->State};
+ props->State = nullptr;
+ EffectState *oldstate{slot->Params.mEffectState};
+ slot->Params.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);
+ context->mEventSem.post();
+ }
+ 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 = oldstate;
+ }
+ }
+
+ AtomicReplaceHead(context->mFreeEffectslotProps, props);
+
+ EffectTarget output;
+ if(ALeffectslot *target{slot->Params.Target})
+ output = EffectTarget{&target->Wet, nullptr};
+ else
+ {
+ ALCdevice *device{context->mDevice.get()};
+ output = EffectTarget{&device->Dry, &device->RealOut};
+ }
+ state->update(context, slot, &slot->Params.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 ALfloat 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;
+}
+
+void CalcPanningAndFilters(ALvoice *voice, const ALfloat xpos, const ALfloat ypos,
+ const ALfloat zpos, const ALfloat Distance, const ALfloat Spread, const ALfloat DryGain,
+ const ALfloat DryGainHF, const ALfloat DryGainLF, const ALfloat (&WetGain)[MAX_SENDS],
+ const ALfloat (&WetGainLF)[MAX_SENDS], const ALfloat (&WetGainHF)[MAX_SENDS],
+ ALeffectslot *(&SendSlots)[MAX_SENDS], const ALvoicePropsBase *props,
+ const ALlistener &Listener, 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<ALfloat>(Device->Frequency);
+ const ALuint NumSends{Device->NumAuxSends};
+
+ bool DirectChannels{props->DirectChannels != AL_FALSE};
+ const ChanMap *chans{nullptr};
+ ALuint num_channels{0};
+ bool isbformat{false};
+ ALfloat downmix_gain{1.0f};
+ switch(voice->mFmtChannels)
+ {
+ 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;
+ }
+ ASSUME(num_channels > 0);
+
+ std::for_each(voice->mChans.begin(), voice->mChans.begin()+num_channels,
+ [NumSends](ALvoice::ChannelData &chandata) -> void
+ {
+ 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); });
+ });
+
+ voice->mFlags &= ~(VOICE_HAS_HRTF | VOICE_HAS_NFC);
+ if(isbformat)
+ {
+ /* Special handling for B-Format sources. */
+
+ if(Distance > std::numeric_limits<float>::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.
+ */
+
+ if(Device->AvgSpeakerDist > 0.0f)
+ {
+ /* Clamp the distance for really close sources, to prevent
+ * excessive bass.
+ */
+ const ALfloat mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
+ const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)};
+
+ /* Only need to adjust the first channel of a B-Format source. */
+ voice->mChans[0].mDryParams.NFCtrlFilter.adjust(w0);
+
+ voice->mFlags |= VOICE_HAS_NFC;
+ }
+
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ if(Device->mRenderMode != StereoPair)
+ CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs);
+ else
+ {
+ /* Clamp Y, in case rounding errors caused it to end up outside
+ * of -1...+1.
+ */
+ const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
+ /* Negate Z for right-handed coords with -Z in front. */
+ const ALfloat 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.
+ */
+ CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread, coeffs);
+ }
+
+ /* NOTE: W needs to be scaled due to FuMa normalization. */
+ const ALfloat &scale0 = AmbiScale::FromFuMa[0];
+ ComputePanGains(&Device->Dry, coeffs, DryGain*scale0,
+ voice->mChans[0].mDryParams.Gains.Target);
+ for(ALuint i{0};i < NumSends;i++)
+ {
+ if(const ALeffectslot *Slot{SendSlots[i]})
+ ComputePanGains(&Slot->Wet, coeffs, WetGain[i]*scale0,
+ voice->mChans[0].mWetParams[i].Gains.Target);
+ }
+ }
+ else
+ {
+ 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.
+ */
+ voice->mChans[0].mDryParams.NFCtrlFilter.adjust(0.0f);
+
+ voice->mFlags |= VOICE_HAS_NFC;
+ }
+
+ /* 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 = Listener.Params.Matrix * N;
+ V = Listener.Params.Matrix * V;
+ }
+ /* Build and normalize right-vector */
+ alu::Vector U{aluCrossproduct(N, V)};
+ U.normalize();
+
+ /* 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.
+ */
+ const ALfloat &wscale = AmbiScale::FromFuMa[0];
+ const ALfloat &yscale = AmbiScale::FromFuMa[1];
+ const ALfloat &zscale = AmbiScale::FromFuMa[2];
+ const ALfloat &xscale = AmbiScale::FromFuMa[3];
+ const ALfloat matrix[4][MAX_AMBI_CHANNELS]{
+ // ACN0 ACN1 ACN2 ACN3
+ { wscale, 0.0f, 0.0f, 0.0f }, // FuMa W
+ { 0.0f, -N[0]*xscale, N[1]*xscale, -N[2]*xscale }, // FuMa X
+ { 0.0f, U[0]*yscale, -U[1]*yscale, U[2]*yscale }, // FuMa Y
+ { 0.0f, -V[0]*zscale, V[1]*zscale, -V[2]*zscale } // FuMa Z
+ };
+
+ for(ALuint c{0};c < num_channels;c++)
+ {
+ ComputePanGains(&Device->Dry, matrix[c], DryGain,
+ voice->mChans[c].mDryParams.Gains.Target);
+
+ for(ALuint i{0};i < NumSends;i++)
+ {
+ if(const ALeffectslot *Slot{SendSlots[i]})
+ ComputePanGains(&Slot->Wet, matrix[c], WetGain[i],
+ voice->mChans[c].mWetParams[i].Gains.Target);
+ }
+ }
+ }
+ }
+ else if(DirectChannels)
+ {
+ /* 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(ALuint c{0};c < num_channels;c++)
+ {
+ const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
+ if(idx != INVALID_CHANNEL_INDEX)
+ voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain;
+ }
+
+ /* Auxiliary sends still use normal channel panning since they mix to
+ * B-Format, which can't channel-match.
+ */
+ for(ALuint c{0};c < num_channels;c++)
+ {
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f, coeffs);
+
+ for(ALuint i{0};i < NumSends;i++)
+ {
+ if(const ALeffectslot *Slot{SendSlots[i]})
+ ComputePanGains(&Slot->Wet, coeffs, WetGain[i],
+ voice->mChans[c].mWetParams[i].Gains.Target);
+ }
+ }
+ }
+ else if(Device->mRenderMode == HrtfRender)
+ {
+ /* 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 ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
+ const ALfloat az{std::atan2(xpos, -zpos)};
+
+ /* Get the HRIR coefficients and delays just once, for the given
+ * source direction.
+ */
+ GetHrtfCoeffs(Device->mHrtf, 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 * downmix_gain;
+
+ /* Remaining channels use the same results as the first. */
+ for(ALuint 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.
+ */
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs);
+
+ for(ALuint c{0};c < num_channels;c++)
+ {
+ /* Skip LFE */
+ if(chans[c].channel == LFE)
+ continue;
+ for(ALuint i{0};i < NumSends;i++)
+ {
+ if(const ALeffectslot *Slot{SendSlots[i]})
+ ComputePanGains(&Slot->Wet, coeffs, WetGain[i] * 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(ALuint 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, 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;
+
+ /* Normal panning for auxiliary sends. */
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ CalcAngleCoeffs(chans[c].angle, chans[c].elevation, Spread, coeffs);
+
+ for(ALuint i{0};i < NumSends;i++)
+ {
+ if(const ALeffectslot *Slot{SendSlots[i]})
+ ComputePanGains(&Slot->Wet, coeffs, WetGain[i],
+ voice->mChans[c].mWetParams[i].Gains.Target);
+ }
+ }
+ }
+
+ voice->mFlags |= VOICE_HAS_HRTF;
+ }
+ 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 ALfloat mdist{maxf(Distance, Device->AvgSpeakerDist/4.0f)};
+ const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (mdist * Frequency)};
+
+ /* Adjust NFC filters. */
+ for(ALuint c{0};c < num_channels;c++)
+ voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
+
+ voice->mFlags |= VOICE_HAS_NFC;
+ }
+
+ /* Calculate the directional coefficients once, which apply to all
+ * input channels.
+ */
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ if(Device->mRenderMode != StereoPair)
+ CalcDirectionCoeffs({xpos, ypos, zpos}, Spread, coeffs);
+ else
+ {
+ const ALfloat ev{std::asin(clampf(ypos, -1.0f, 1.0f))};
+ const ALfloat az{std::atan2(xpos, -zpos)};
+ CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread, coeffs);
+ }
+
+ for(ALuint c{0};c < num_channels;c++)
+ {
+ /* Special-case LFE */
+ if(chans[c].channel == LFE)
+ {
+ if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
+ {
+ const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
+ if(idx != INVALID_CHANNEL_INDEX)
+ voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain;
+ }
+ continue;
+ }
+
+ ComputePanGains(&Device->Dry, coeffs, DryGain * downmix_gain,
+ voice->mChans[c].mDryParams.Gains.Target);
+ for(ALuint i{0};i < NumSends;i++)
+ {
+ if(const ALeffectslot *Slot{SendSlots[i]})
+ ComputePanGains(&Slot->Wet, coeffs, WetGain[i] * downmix_gain,
+ voice->mChans[c].mWetParams[i].Gains.Target);
+ }
+ }
+ }
+ else
+ {
+ 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.
+ */
+ const ALfloat w0{SPEEDOFSOUNDMETRESPERSEC / (Device->AvgSpeakerDist * Frequency)};
+
+ for(ALuint c{0};c < num_channels;c++)
+ voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0);
+
+ voice->mFlags |= VOICE_HAS_NFC;
+ }
+
+ for(ALuint c{0};c < num_channels;c++)
+ {
+ /* Special-case LFE */
+ if(chans[c].channel == LFE)
+ {
+ if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
+ {
+ const ALuint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)};
+ if(idx != INVALID_CHANNEL_INDEX)
+ voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain;
+ }
+ continue;
+ }
+
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ CalcAngleCoeffs(
+ (Device->mRenderMode==StereoPair) ? ScaleAzimuthFront(chans[c].angle, 3.0f)
+ : chans[c].angle,
+ chans[c].elevation, Spread, coeffs
+ );
+
+ ComputePanGains(&Device->Dry, coeffs, DryGain,
+ voice->mChans[c].mDryParams.Gains.Target);
+ for(ALuint i{0};i < NumSends;i++)
+ {
+ if(const ALeffectslot *Slot{SendSlots[i]})
+ ComputePanGains(&Slot->Wet, coeffs, WetGain[i],
+ voice->mChans[c].mWetParams[i].Gains.Target);
+ }
+ }
+ }
+ }
+
+ {
+ const ALfloat hfScale{props->Direct.HFReference / Frequency};
+ const ALfloat lfScale{props->Direct.LFReference / Frequency};
+ const ALfloat gainHF{maxf(DryGainHF, 0.001f)}; /* Limit -60dB */
+ const ALfloat gainLF{maxf(DryGainLF, 0.001f)};
+
+ voice->mDirect.FilterType = AF_None;
+ if(gainHF != 1.0f) voice->mDirect.FilterType |= AF_LowPass;
+ if(gainLF != 1.0f) voice->mDirect.FilterType |= AF_HighPass;
+ auto &lowpass = voice->mChans[0].mDryParams.LowPass;
+ auto &highpass = voice->mChans[0].mDryParams.HighPass;
+ lowpass.setParams(BiquadType::HighShelf, gainHF, hfScale,
+ lowpass.rcpQFromSlope(gainHF, 1.0f));
+ highpass.setParams(BiquadType::LowShelf, gainLF, lfScale,
+ highpass.rcpQFromSlope(gainLF, 1.0f));
+ for(ALuint c{1};c < num_channels;c++)
+ {
+ voice->mChans[c].mDryParams.LowPass.copyParamsFrom(lowpass);
+ voice->mChans[c].mDryParams.HighPass.copyParamsFrom(highpass);
+ }
+ }
+ for(ALuint i{0};i < NumSends;i++)
+ {
+ const ALfloat hfScale{props->Send[i].HFReference / Frequency};
+ const ALfloat lfScale{props->Send[i].LFReference / Frequency};
+ const ALfloat gainHF{maxf(WetGainHF[i], 0.001f)};
+ const ALfloat gainLF{maxf(WetGainLF[i], 0.001f)};
+
+ voice->mSend[i].FilterType = AF_None;
+ if(gainHF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass;
+ if(gainLF != 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.setParams(BiquadType::HighShelf, gainHF, hfScale,
+ lowpass.rcpQFromSlope(gainHF, 1.0f));
+ highpass.setParams(BiquadType::LowShelf, gainLF, lfScale,
+ highpass.rcpQFromSlope(gainLF, 1.0f));
+ for(ALuint c{1};c < num_channels;c++)
+ {
+ voice->mChans[c].mWetParams[i].LowPass.copyParamsFrom(lowpass);
+ voice->mChans[c].mWetParams[i].HighPass.copyParamsFrom(highpass);
+ }
+ }
+}
+
+void CalcNonAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext)
+{
+ const ALCdevice *Device{ALContext->mDevice.get()};
+ ALeffectslot *SendSlots[MAX_SENDS];
+
+ voice->mDirect.Buffer = Device->Dry.Buffer;
+ for(ALuint i{0};i < Device->NumAuxSends;i++)
+ {
+ SendSlots[i] = props->Send[i].Slot;
+ if(!SendSlots[i] && i == 0)
+ SendSlots[i] = ALContext->mDefaultSlot.get();
+ if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
+ {
+ 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<ALfloat>(voice->mFrequency) /
+ static_cast<ALfloat>(Device->Frequency) * props->Pitch;
+ if(Pitch > float{MAX_PITCH})
+ voice->mStep = MAX_PITCH<<FRACTIONBITS;
+ else
+ voice->mStep = maxu(fastf2u(Pitch * FRACTIONONE), 1);
+ voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
+
+ /* Calculate gains */
+ const ALlistener &Listener = ALContext->mListener;
+ ALfloat DryGain{clampf(props->Gain, props->MinGain, props->MaxGain)};
+ DryGain *= props->Direct.Gain * Listener.Params.Gain;
+ DryGain = minf(DryGain, GAIN_MIX_MAX);
+ ALfloat DryGainHF{props->Direct.GainHF};
+ ALfloat DryGainLF{props->Direct.GainLF};
+ ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
+ for(ALuint 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, -1.0f, 0.0f, 0.0f, DryGain, DryGainHF, DryGainLF,
+ WetGain, WetGainLF, WetGainHF, SendSlots, props, Listener, Device);
+}
+
+void CalcAttnSourceParams(ALvoice *voice, const ALvoicePropsBase *props, const ALCcontext *ALContext)
+{
+ const ALCdevice *Device{ALContext->mDevice.get()};
+ const ALuint NumSends{Device->NumAuxSends};
+ const ALlistener &Listener = ALContext->mListener;
+
+ /* Set mixing buffers and get send parameters. */
+ voice->mDirect.Buffer = Device->Dry.Buffer;
+ ALeffectslot *SendSlots[MAX_SENDS];
+ ALfloat RoomRolloff[MAX_SENDS];
+ ALfloat DecayDistance[MAX_SENDS];
+ ALfloat DecayLFDistance[MAX_SENDS];
+ ALfloat DecayHFDistance[MAX_SENDS];
+ for(ALuint i{0};i < NumSends;i++)
+ {
+ SendSlots[i] = props->Send[i].Slot;
+ if(!SendSlots[i] && i == 0)
+ SendSlots[i] = ALContext->mDefaultSlot.get();
+ if(!SendSlots[i] || SendSlots[i]->Params.EffectType == AL_EFFECT_NULL)
+ {
+ SendSlots[i] = nullptr;
+ 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 * SPEEDOFSOUNDMETRESPERSEC;
+ 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{std::log10(REVERB_DECAY_GAIN) / std::log10(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->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 == AL_FALSE)
+ {
+ /* Transform source vectors */
+ Position = Listener.Params.Matrix * Position;
+ Velocity = Listener.Params.Matrix * Velocity;
+ Direction = Listener.Params.Matrix * Direction;
+ }
+ else
+ {
+ /* Offset the source velocity to be relative of the listener velocity */
+ Velocity += Listener.Params.Velocity;
+ }
+
+ const bool directional{Direction.normalize() > 0.0f};
+ alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f};
+ const ALfloat Distance{ToSource.normalize()};
+
+ /* Initial source gain */
+ ALfloat DryGain{props->Gain};
+ ALfloat DryGainHF{1.0f};
+ ALfloat DryGainLF{1.0f};
+ ALfloat WetGain[MAX_SENDS], WetGainHF[MAX_SENDS], WetGainLF[MAX_SENDS];
+ for(ALuint i{0};i < NumSends;i++)
+ {
+ WetGain[i] = props->Gain;
+ WetGainHF[i] = 1.0f;
+ WetGainLF[i] = 1.0f;
+ }
+
+ /* Calculate distance attenuation */
+ ALfloat ClampedDist{Distance};
+
+ switch(Listener.Params.SourceDistanceModel ?
+ props->mDistanceModel : Listener.Params.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
+ {
+ ALfloat dist = lerp(props->RefDistance, ClampedDist, props->RolloffFactor);
+ if(dist > 0.0f) DryGain *= props->RefDistance / dist;
+ for(ALuint i{0};i < NumSends;i++)
+ {
+ dist = lerp(props->RefDistance, ClampedDist, RoomRolloff[i]);
+ if(dist > 0.0f) WetGain[i] *= 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
+ {
+ ALfloat attn = props->RolloffFactor * (ClampedDist-props->RefDistance) /
+ (props->MaxDistance-props->RefDistance);
+ DryGain *= maxf(1.0f - attn, 0.0f);
+ for(ALuint 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 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
+ {
+ DryGain *= std::pow(ClampedDist/props->RefDistance, -props->RolloffFactor);
+ for(ALuint i{0};i < NumSends;i++)
+ WetGain[i] *= std::pow(ClampedDist/props->RefDistance, -RoomRolloff[i]);
+ }
+ break;
+
+ case DistanceModel::Disable:
+ ClampedDist = props->RefDistance;
+ break;
+ }
+
+ /* Calculate directional soundcones */
+ if(directional && props->InnerAngle < 360.0f)
+ {
+ const ALfloat Angle{Rad2Deg(std::acos(-aluDotproduct(Direction, ToSource)) *
+ ConeScale * 2.0f)};
+
+ ALfloat ConeVolume, ConeHF;
+ 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)
+ std::transform(std::begin(WetGain), std::begin(WetGain)+NumSends, std::begin(WetGain),
+ [ConeVolume](ALfloat gain) noexcept -> ALfloat { return gain * ConeVolume; }
+ );
+ if(props->WetGainHFAuto)
+ std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
+ std::begin(WetGainHF),
+ [ConeHF](ALfloat gain) noexcept -> ALfloat { return gain * 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(ALuint 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{std::pow(AIRABSORBGAINHF, meters_base * props->AirAbsorptionFactor)};
+ DryGainHF *= hfattn;
+ std::transform(std::begin(WetGainHF), std::begin(WetGainHF)+NumSends,
+ std::begin(WetGainHF),
+ [hfattn](ALfloat gain) noexcept -> ALfloat { return gain * 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(ALuint i{0};i < NumSends;i++)
+ {
+ if(!(DecayDistance[i] > 0.0f))
+ continue;
+
+ const ALfloat gain{std::pow(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)
+ {
+ ALfloat gainhf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayHFDistance[i])};
+ WetGainHF[i] *= minf(gainhf / gain, 1.0f);
+ ALfloat gainlf{std::pow(REVERB_DECAY_GAIN, meters_base/DecayLFDistance[i])};
+ WetGainLF[i] *= minf(gainlf / gain, 1.0f);
+ }
+ }
+ }
+ }
+
+
+ /* Initial source pitch */
+ ALfloat Pitch{props->Pitch};
+
+ /* Calculate velocity-based doppler effect */
+ ALfloat DopplerFactor{props->DopplerFactor * Listener.Params.DopplerFactor};
+ if(DopplerFactor > 0.0f)
+ {
+ const alu::Vector &lvelocity = Listener.Params.Velocity;
+ ALfloat vss{aluDotproduct(Velocity, ToSource) * -DopplerFactor};
+ ALfloat vls{aluDotproduct(lvelocity, ToSource) * -DopplerFactor};
+
+ const ALfloat SpeedOfSound{Listener.Params.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<ALfloat>(voice->mFrequency)/static_cast<ALfloat>(Device->Frequency);
+ if(Pitch > float{MAX_PITCH})
+ voice->mStep = MAX_PITCH<<FRACTIONBITS;
+ else
+ voice->mStep = maxu(fastf2u(Pitch * FRACTIONONE), 1);
+ voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
+
+ ALfloat 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*Listener.Params.MetersPerUnit, spread, DryGain, DryGainHF, DryGainLF, WetGain,
+ WetGainLF, WetGainHF, SendSlots, props, Listener, Device);
+}
+
+void CalcSourceParams(ALvoice *voice, ALCcontext *context, bool force)
+{
+ ALvoiceProps *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.mSpatializeMode == SpatializeAuto && voice->mFmtChannels == FmtMono) ||
+ voice->mProps.mSpatializeMode == SpatializeOn)
+ CalcAttnSourceParams(voice, &voice->mProps, context);
+ else
+ CalcNonAttnSourceParams(voice, &voice->mProps, context);
+}
+
+
+void ProcessParamUpdates(ALCcontext *ctx, const ALeffectslotArray &slots,
+ const al::span<ALvoice> voices)
+{
+ IncrementRef(ctx->mUpdateCount);
+ if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire))
+ {
+ bool force{CalcContextParams(ctx)};
+ force |= CalcListenerParams(ctx);
+ force = std::accumulate(slots.begin(), slots.end(), force,
+ [ctx](const bool f, ALeffectslot *slot) -> bool
+ { return CalcEffectSlotParams(slot, ctx) | f; }
+ );
+
+ auto calc_params = [ctx,force](ALvoice &voice) -> void
+ {
+ if(voice.mSourceID.load(std::memory_order_acquire) != 0)
+ CalcSourceParams(&voice, ctx, force);
+ };
+ std::for_each(voices.begin(), voices.end(), calc_params);
+ }
+ IncrementRef(ctx->mUpdateCount);
+}
+
+void ProcessContext(ALCcontext *ctx, const ALuint SamplesToDo)
+{
+ ASSUME(SamplesToDo > 0);
+
+ const ALeffectslotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire);
+ const al::span<ALvoice> voices{ctx->mVoices.data(), ctx->mVoices.size()};
+
+ /* Process pending propery updates for objects on the context. */
+ ProcessParamUpdates(ctx, auxslots, voices);
+
+ /* Clear auxiliary effect slot mixing buffers. */
+ std::for_each(auxslots.begin(), auxslots.end(),
+ [SamplesToDo](ALeffectslot *slot) -> void
+ {
+ for(auto &buffer : slot->MixBuffer)
+ std::fill_n(buffer.begin(), SamplesToDo, 0.0f);
+ }
+ );
+
+ /* Process voices that have a playing source. */
+ std::for_each(voices.begin(), voices.end(),
+ [SamplesToDo,ctx](ALvoice &voice) -> void
+ {
+ const ALvoice::State vstate{voice.mPlayState.load(std::memory_order_acquire)};
+ if(vstate != ALvoice::Stopped) voice.mix(vstate, ctx, SamplesToDo);
+ }
+ );
+
+ /* Process effects. */
+ if(auxslots.empty()) return;
+ auto slots = auxslots.data();
+ auto slots_end = slots + auxslots.size();
+
+ /* First sort the slots into scratch storage, so that effects come before
+ * their effect target (or their targets' target).
+ */
+ auto sorted_slots = const_cast<ALeffectslot**>(slots_end);
+ auto sorted_slots_end = sorted_slots;
+ auto in_chain = [](const ALeffectslot *slot1, const ALeffectslot *slot2) noexcept -> bool
+ {
+ while((slot1=slot1->Params.Target) != nullptr) {
+ if(slot1 == slot2) return true;
+ }
+ return false;
+ };
+
+ *sorted_slots_end = *slots;
+ ++sorted_slots_end;
+ while(++slots != slots_end)
+ {
+ /* If this effect slot targets an effect slot already in the list (i.e.
+ * slots outputs to something in sorted_slots), directly or indirectly,
+ * insert it prior to that element.
+ */
+ auto checker = sorted_slots;
+ do {
+ if(in_chain(*slots, *checker)) break;
+ } while(++checker != sorted_slots_end);
+
+ checker = std::move_backward(checker, sorted_slots_end, sorted_slots_end+1);
+ *--checker = *slots;
+ ++sorted_slots_end;
+ }
+
+ std::for_each(sorted_slots, sorted_slots_end,
+ [SamplesToDo](const ALeffectslot *slot) -> void
+ {
+ EffectState *state{slot->Params.mEffectState};
+ state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget);
+ }
+ );
+}
+
+
+void ApplyStablizer(FrontStablizer *Stablizer, const al::span<FloatBufferLine> Buffer,
+ const ALuint lidx, const ALuint ridx, const ALuint cidx, const ALuint SamplesToDo)
+{
+ ASSUME(SamplesToDo > 0);
+
+ /* Apply a delay to all channels, except the front-left and front-right, so
+ * they maintain correct timing.
+ */
+ const size_t NumChannels{Buffer.size()};
+ for(size_t i{0u};i < NumChannels;i++)
+ {
+ if(i == lidx || i == ridx)
+ continue;
+
+ auto &DelayBuf = Stablizer->DelayBuf[i];
+ auto buffer_end = Buffer[i].begin() + SamplesToDo;
+ if LIKELY(SamplesToDo >= ALuint{FrontStablizer::DelayLength})
+ {
+ auto delay_end = std::rotate(Buffer[i].begin(),
+ buffer_end - FrontStablizer::DelayLength, buffer_end);
+ std::swap_ranges(Buffer[i].begin(), delay_end, std::begin(DelayBuf));
+ }
+ else
+ {
+ auto delay_start = std::swap_ranges(Buffer[i].begin(), buffer_end,
+ std::begin(DelayBuf));
+ std::rotate(std::begin(DelayBuf), delay_start, std::end(DelayBuf));
+ }
+ }
+
+ ALfloat (&lsplit)[2][BUFFERSIZE] = Stablizer->LSplit;
+ ALfloat (&rsplit)[2][BUFFERSIZE] = Stablizer->RSplit;
+ auto &tmpbuf = Stablizer->TempBuf;
+
+ /* This applies the band-splitter, preserving phase at the cost of some
+ * delay. The shorter the delay, the more error seeps into the result.
+ */
+ auto apply_splitter = [&tmpbuf,SamplesToDo](const FloatBufferLine &InBuf,
+ ALfloat (&DelayBuf)[FrontStablizer::DelayLength], BandSplitter &Filter,
+ ALfloat (&splitbuf)[2][BUFFERSIZE]) -> void
+ {
+ /* Combine the delayed samples and the input samples into the temp
+ * buffer, in reverse. Then copy the final samples back into the delay
+ * buffer for next time. Note that the delay buffer's samples are
+ * stored backwards here.
+ */
+ auto tmpbuf_end = std::begin(tmpbuf) + SamplesToDo;
+ std::copy_n(std::begin(DelayBuf), FrontStablizer::DelayLength, tmpbuf_end);
+ std::reverse_copy(InBuf.begin(), InBuf.begin()+SamplesToDo, std::begin(tmpbuf));
+ std::copy_n(std::begin(tmpbuf), FrontStablizer::DelayLength, std::begin(DelayBuf));
+
+ /* Apply an all-pass on the reversed signal, then reverse the samples
+ * to get the forward signal with a reversed phase shift.
+ */
+ Filter.applyAllpass(tmpbuf, SamplesToDo+FrontStablizer::DelayLength);
+ std::reverse(std::begin(tmpbuf), tmpbuf_end+FrontStablizer::DelayLength);
+
+ /* Now apply the band-splitter, combining its phase shift with the
+ * reversed phase shift, restoring the original phase on the split
+ * signal.
+ */
+ Filter.process(splitbuf[1], splitbuf[0], tmpbuf, SamplesToDo);
+ };
+ apply_splitter(Buffer[lidx], Stablizer->DelayBuf[lidx], Stablizer->LFilter, lsplit);
+ apply_splitter(Buffer[ridx], Stablizer->DelayBuf[ridx], Stablizer->RFilter, rsplit);
+
+ for(ALuint i{0};i < SamplesToDo;i++)
+ {
+ ALfloat lfsum{lsplit[0][i] + rsplit[0][i]};
+ ALfloat hfsum{lsplit[1][i] + rsplit[1][i]};
+ ALfloat 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.
+ */
+ ALfloat m{lfsum*std::cos(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) +
+ hfsum*std::cos(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
+ ALfloat c{lfsum*std::sin(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f)) +
+ hfsum*std::sin(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
+
+ /* 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;
+ }
+}
+
+void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const ALuint SamplesToDo,
+ const DistanceComp::DistData *distcomp)
+{
+ ASSUME(SamplesToDo > 0);
+
+ for(auto &chanbuffer : Samples)
+ {
+ const ALfloat gain{distcomp->Gain};
+ const ALuint base{distcomp->Length};
+ ALfloat *distbuf{al::assume_aligned<16>(distcomp->Buffer)};
+ ++distcomp;
+
+ if(base < 1)
+ continue;
+
+ ALfloat *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, ALuint *dither_seed,
+ const ALfloat quant_scale, const ALuint SamplesToDo)
+{
+ /* 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 ALfloat invscale{1.0f / quant_scale};
+ ALuint seed{*dither_seed};
+ auto dither_channel = [&seed,invscale,quant_scale,SamplesToDo](FloatBufferLine &input) -> void
+ {
+ ASSUME(SamplesToDo > 0);
+ auto dither_sample = [&seed,invscale,quant_scale](const ALfloat sample) noexcept -> ALfloat
+ {
+ ALfloat val{sample * quant_scale};
+ ALuint rng0{dither_rng(&seed)};
+ ALuint rng1{dither_rng(&seed)};
+ val += static_cast<ALfloat>(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX));
+ return fast_roundf(val) * invscale;
+ };
+ std::transform(input.begin(), input.begin()+SamplesToDo, input.begin(), dither_sample);
+ };
+ std::for_each(Samples.begin(), Samples.end(), dither_channel);
+ *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 ALuint SamplesToDo)
+{
+ using SampleType = typename DevFmtTypeTraits<T>::Type;
+
+ const size_t numchans{InBuffer.size()};
+ ASSUME(numchans > 0);
+
+ SampleType *outbase = static_cast<SampleType*>(OutBuffer) + Offset*numchans;
+ auto conv_channel = [&outbase,SamplesToDo,numchans](const FloatBufferLine &inbuf) -> void
+ {
+ ASSUME(SamplesToDo > 0);
+ SampleType *out{outbase++};
+ auto conv_sample = [numchans,&out](const float s) noexcept -> void
+ {
+ *out = SampleConv<SampleType>(s);
+ out += numchans;
+ };
+ std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample);
+ };
+ std::for_each(InBuffer.cbegin(), InBuffer.cend(), conv_channel);
+}
+
+} // namespace
+
+void aluMixData(ALCdevice *device, ALvoid *OutBuffer, const ALuint NumSamples)
+{
+ FPUCtl mixer_mode{};
+ for(ALuint SamplesDone{0u};SamplesDone < NumSamples;)
+ {
+ const ALuint SamplesToDo{minu(NumSamples-SamplesDone, BUFFERSIZE)};
+
+ /* Clear main mixing buffers. */
+ std::for_each(device->MixBuffer.begin(), device->MixBuffer.end(),
+ [SamplesToDo](std::array<ALfloat,BUFFERSIZE> &buffer) -> void
+ { std::fill_n(buffer.begin(), SamplesToDo, 0.0f); }
+ );
+
+ /* Increment the mix count at the start (lsb should now be 1). */
+ IncrementRef(device->MixCount);
+
+ /* For each context on this device, process and mix its sources and
+ * effects.
+ */
+ for(ALCcontext *ctx : *device->mContexts.load(std::memory_order_acquire))
+ ProcessContext(ctx, 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.
+ */
+ device->SamplesDone += SamplesToDo;
+ device->ClockBase += std::chrono::seconds{device->SamplesDone / device->Frequency};
+ device->SamplesDone %= device->Frequency;
+
+ /* Increment the mix count at the end (lsb should now be 0). */
+ IncrementRef(device->MixCount);
+
+ /* Apply any needed post-process for finalizing the Dry mix to the
+ * RealOut (Ambisonic decode, UHJ encode, etc).
+ */
+ device->postProcess(SamplesToDo);
+
+ const al::span<FloatBufferLine> RealOut{device->RealOut.Buffer};
+
+ /* Apply front image stablization for surround sound, if applicable. */
+ if(device->Stablizer)
+ {
+ const ALuint lidx{GetChannelIdxByName(device->RealOut, FrontLeft)};
+ const ALuint ridx{GetChannelIdxByName(device->RealOut, FrontRight)};
+ const ALuint cidx{GetChannelIdxByName(device->RealOut, FrontCenter)};
+
+ ApplyStablizer(device->Stablizer.get(), RealOut, lidx, ridx, cidx, SamplesToDo);
+ }
+
+ /* Apply compression, limiting sample amplitude if needed or desired. */
+ if(Compressor *comp{device->Limiter.get()})
+ comp->process(SamplesToDo, RealOut.data());
+
+ /* Apply delays and attenuation for mismatched speaker distances. */
+ ApplyDistanceComp(RealOut, SamplesToDo, device->ChannelDelay.as_span().cbegin());
+
+ /* Apply dithering. The compressor should have left enough headroom for
+ * the dither noise to not saturate.
+ */
+ if(device->DitherDepth > 0.0f)
+ ApplyDither(RealOut, &device->DitherSeed, device->DitherDepth, SamplesToDo);
+
+ if LIKELY(OutBuffer)
+ {
+ /* Finally, interleave and convert samples, writing to the device's
+ * output buffer.
+ */
+ switch(device->FmtType)
+ {
+#define HANDLE_WRITE(T) case T: \
+ Write<T>(RealOut, OutBuffer, SamplesDone, SamplesToDo); break;
+ HANDLE_WRITE(DevFmtByte)
+ HANDLE_WRITE(DevFmtUByte)
+ HANDLE_WRITE(DevFmtShort)
+ HANDLE_WRITE(DevFmtUShort)
+ HANDLE_WRITE(DevFmtInt)
+ HANDLE_WRITE(DevFmtUInt)
+ HANDLE_WRITE(DevFmtFloat)
+#undef HANDLE_WRITE
+ }
+ }
+
+ SamplesDone += SamplesToDo;
+ }
+}
+
+
+void aluHandleDisconnect(ALCdevice *device, const char *msg, ...)
+{
+ if(!device->Connected.exchange(false, std::memory_order_acq_rel))
+ return;
+
+ AsyncEvent evt{EventType_Disconnected};
+ evt.u.user.type = AL_EVENT_TYPE_DISCONNECTED_SOFT;
+ evt.u.user.id = 0;
+ evt.u.user.param = 0;
+
+ va_list args;
+ va_start(args, msg);
+ int msglen{vsnprintf(evt.u.user.msg, sizeof(evt.u.user.msg), msg, args)};
+ va_end(args);
+
+ if(msglen < 0 || static_cast<size_t>(msglen) >= sizeof(evt.u.user.msg))
+ evt.u.user.msg[sizeof(evt.u.user.msg)-1] = 0;
+
+ IncrementRef(device->MixCount);
+ for(ALCcontext *ctx : *device->mContexts.load())
+ {
+ const ALbitfieldSOFT 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 stop_voice = [](ALvoice &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(ALvoice::Stopped, std::memory_order_release);
+ };
+ std::for_each(ctx->mVoices.begin(), ctx->mVoices.end(), stop_voice);
+ }
+ IncrementRef(device->MixCount);
+}
diff --git a/alc/alu.h b/alc/alu.h
new file mode 100644
index 00000000..b3cfd840
--- /dev/null
+++ b/alc/alu.h
@@ -0,0 +1,158 @@
+#ifndef ALU_H
+#define ALU_H
+
+#include <array>
+#include <cmath>
+#include <cstddef>
+
+#include "AL/al.h"
+
+#include "alcmain.h"
+#include "alspan.h"
+#include "logging.h"
+
+struct ALbufferlistitem;
+struct ALeffectslot;
+
+
+#define MAX_PITCH 255
+#define MAX_SENDS 16
+
+
+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);
+using RowMixerFunc = void(*)(const al::span<float> OutBuffer, const al::span<const float> Gains,
+ const float *InSamples, const size_t InStride);
+using HrtfDirectMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
+ const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State,
+ const size_t BufferSize);
+
+extern MixerFunc MixSamples;
+extern RowMixerFunc MixRowSamples;
+
+
+#define GAIN_MIX_MAX (1000.0f) /* +60dB */
+
+#define GAIN_SILENCE_THRESHOLD (0.00001f) /* -100dB */
+
+#define SPEEDOFSOUNDMETRESPERSEC (343.3f)
+#define AIRABSORBGAINHF (0.99426f) /* -0.05dB */
+
+/* Target gain for the reverb decay feedback reaching the decay time. */
+#define REVERB_DECAY_GAIN (0.001f) /* -60 dB */
+
+#define FRACTIONBITS (12)
+#define FRACTIONONE (1<<FRACTIONBITS)
+#define FRACTIONMASK (FRACTIONONE-1)
+
+
+inline ALfloat lerp(ALfloat val1, ALfloat val2, ALfloat mu) noexcept
+{ return val1 + (val2-val1)*mu; }
+inline ALfloat cubic(ALfloat val1, ALfloat val2, ALfloat val3, ALfloat val4, ALfloat mu) noexcept
+{
+ ALfloat mu2 = mu*mu, mu3 = mu2*mu;
+ ALfloat a0 = -0.5f*mu3 + mu2 + -0.5f*mu;
+ ALfloat a1 = 1.5f*mu3 + -2.5f*mu2 + 1.0f;
+ ALfloat a2 = -1.5f*mu3 + 2.0f*mu2 + 0.5f*mu;
+ ALfloat a3 = 0.5f*mu3 + -0.5f*mu2;
+ return val1*a0 + val2*a1 + val3*a2 + val4*a3;
+}
+
+
+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, ALint hrtf_id, HrtfRequestMode hrtf_appreq, HrtfRequestMode hrtf_userreq);
+
+void aluInitEffectPanning(ALeffectslot *slot, ALCdevice *device);
+
+/**
+ * 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.
+ */
+void CalcAmbiCoeffs(const float y, const float z, const float x, const float spread,
+ const al::span<float,MAX_AMBI_CHANNELS> coeffs);
+
+/**
+ * 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 void CalcDirectionCoeffs(const float (&dir)[3], const float spread,
+ const al::span<float,MAX_AMBI_CHANNELS> coeffs)
+{
+ /* Convert from OpenAL coords to Ambisonics. */
+ CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread, coeffs);
+}
+
+/**
+ * CalcAngleCoeffs
+ *
+ * Calculates ambisonic coefficients based on azimuth and elevation. The
+ * azimuth and elevation parameters are in radians, going right and up
+ * respectively.
+ */
+inline void CalcAngleCoeffs(const float azimuth, const float elevation, const float spread,
+ const al::span<float,MAX_AMBI_CHANNELS> coeffs)
+{
+ const float x{-std::sin(azimuth) * std::cos(elevation)};
+ const float y{ std::sin(elevation)};
+ const float z{ std::cos(azimuth) * std::cos(elevation)};
+
+ CalcAmbiCoeffs(x, y, z, spread, coeffs);
+}
+
+
+/**
+ * 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);
+
+
+inline std::array<ALfloat,MAX_AMBI_CHANNELS> GetAmbiIdentityRow(size_t i) noexcept
+{
+ std::array<ALfloat,MAX_AMBI_CHANNELS> ret{};
+ ret[i] = 1.0f;
+ return ret;
+}
+
+
+void aluMixData(ALCdevice *device, ALvoid *OutBuffer, const ALuint NumSamples);
+/* Caller must lock the device state, and the mixer must not be running. */
+void aluHandleDisconnect(ALCdevice *device, const char *msg, ...) DECL_FORMAT(printf, 2, 3);
+
+extern const ALfloat ConeScale;
+extern const ALfloat ZScale;
+
+#endif
diff --git a/alc/ambdec.cpp b/alc/ambdec.cpp
new file mode 100644
index 00000000..adf116fe
--- /dev/null
+++ b/alc/ambdec.cpp
@@ -0,0 +1,434 @@
+
+#include "config.h"
+
+#include "ambdec.h"
+
+#include <algorithm>
+#include <cctype>
+#include <cstddef>
+#include <iterator>
+#include <sstream>
+#include <string>
+
+#include "alfstream.h"
+#include "logging.h"
+
+
+namespace {
+
+template<typename T, std::size_t N>
+constexpr inline std::size_t size(const T(&)[N]) noexcept
+{ return N; }
+
+int readline(std::istream &f, std::string &output)
+{
+ while(f.good() && f.peek() == '\n')
+ f.ignore();
+
+ return std::getline(f, output) && !output.empty();
+}
+
+bool read_clipped_line(std::istream &f, std::string &buffer)
+{
+ while(readline(f, buffer))
+ {
+ std::size_t pos{0};
+ while(pos < buffer.length() && std::isspace(buffer[pos]))
+ pos++;
+ buffer.erase(0, pos);
+
+ std::size_t cmtpos{buffer.find_first_of('#')};
+ if(cmtpos < buffer.length())
+ buffer.resize(cmtpos);
+ while(!buffer.empty() && std::isspace(buffer.back()))
+ buffer.pop_back();
+
+ if(!buffer.empty())
+ return true;
+ }
+ return false;
+}
+
+
+std::string read_word(std::istream &f)
+{
+ std::string ret;
+ f >> ret;
+ return ret;
+}
+
+bool is_at_end(const std::string &buffer, std::size_t endpos)
+{
+ while(endpos < buffer.length() && std::isspace(buffer[endpos]))
+ ++endpos;
+ return !(endpos < buffer.length());
+}
+
+
+bool load_ambdec_speakers(al::vector<AmbDecConf::SpeakerConf> &spkrs, const std::size_t num_speakers, std::istream &f, std::string &buffer)
+{
+ while(spkrs.size() < num_speakers)
+ {
+ std::istringstream istr{buffer};
+
+ std::string cmd{read_word(istr)};
+ if(cmd.empty())
+ {
+ if(!read_clipped_line(f, buffer))
+ {
+ ERR("Unexpected end of file\n");
+ return false;
+ }
+ continue;
+ }
+
+ if(cmd == "add_spkr")
+ {
+ spkrs.emplace_back();
+ AmbDecConf::SpeakerConf &spkr = spkrs.back();
+ const size_t spkr_num{spkrs.size()};
+
+ istr >> spkr.Name;
+ if(istr.fail()) WARN("Name not specified for speaker %zu\n", spkr_num);
+ istr >> spkr.Distance;
+ if(istr.fail()) WARN("Distance not specified for speaker %zu\n", spkr_num);
+ istr >> spkr.Azimuth;
+ if(istr.fail()) WARN("Azimuth not specified for speaker %zu\n", spkr_num);
+ istr >> spkr.Elevation;
+ if(istr.fail()) WARN("Elevation not specified for speaker %zu\n", spkr_num);
+ istr >> spkr.Connection;
+ if(istr.fail()) TRACE("Connection not specified for speaker %zu\n", spkr_num);
+ }
+ else
+ {
+ ERR("Unexpected speakers command: %s\n", cmd.c_str());
+ return false;
+ }
+
+ istr.clear();
+ const auto endpos = static_cast<std::size_t>(istr.tellg());
+ if(!is_at_end(buffer, endpos))
+ {
+ ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
+ return false;
+ }
+ buffer.clear();
+ }
+
+ return true;
+}
+
+bool load_ambdec_matrix(float (&gains)[MAX_AMBI_ORDER+1], al::vector<AmbDecConf::CoeffArray> &matrix, const std::size_t maxrow, std::istream &f, std::string &buffer)
+{
+ bool gotgains{false};
+ std::size_t cur{0u};
+ while(cur < maxrow)
+ {
+ std::istringstream istr{buffer};
+
+ std::string cmd{read_word(istr)};
+ if(cmd.empty())
+ {
+ if(!read_clipped_line(f, buffer))
+ {
+ ERR("Unexpected end of file\n");
+ return false;
+ }
+ continue;
+ }
+
+ if(cmd == "order_gain")
+ {
+ std::size_t curgain{0u};
+ float value;
+ while(istr.good())
+ {
+ istr >> value;
+ if(istr.fail()) break;
+ if(!istr.eof() && !std::isspace(istr.peek()))
+ {
+ ERR("Extra junk on gain %zu: %s\n", curgain+1,
+ buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
+ return false;
+ }
+ if(curgain < size(gains))
+ gains[curgain++] = value;
+ }
+ std::fill(std::begin(gains)+curgain, std::end(gains), 0.0f);
+ gotgains = true;
+ }
+ else if(cmd == "add_row")
+ {
+ matrix.emplace_back();
+ AmbDecConf::CoeffArray &mtxrow = matrix.back();
+ std::size_t curidx{0u};
+ float value{};
+ while(istr.good())
+ {
+ istr >> value;
+ if(istr.fail()) break;
+ if(!istr.eof() && !std::isspace(istr.peek()))
+ {
+ ERR("Extra junk on matrix element %zux%zu: %s\n", curidx,
+ matrix.size(), buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
+ matrix.pop_back();
+ return false;
+ }
+ if(curidx < mtxrow.size())
+ mtxrow[curidx++] = value;
+ }
+ std::fill(mtxrow.begin()+curidx, mtxrow.end(), 0.0f);
+ cur++;
+ }
+ else
+ {
+ ERR("Unexpected matrix command: %s\n", cmd.c_str());
+ return false;
+ }
+
+ istr.clear();
+ const auto endpos = static_cast<std::size_t>(istr.tellg());
+ if(!is_at_end(buffer, endpos))
+ {
+ ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
+ return false;
+ }
+ buffer.clear();
+ }
+
+ if(!gotgains)
+ {
+ ERR("Matrix order_gain not specified\n");
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+int AmbDecConf::load(const char *fname) noexcept
+{
+ al::ifstream f{fname};
+ if(!f.is_open())
+ {
+ ERR("Failed to open: %s\n", fname);
+ return 0;
+ }
+
+ std::size_t num_speakers{0u};
+ std::string buffer;
+ while(read_clipped_line(f, buffer))
+ {
+ std::istringstream istr{buffer};
+
+ std::string command{read_word(istr)};
+ if(command.empty())
+ {
+ ERR("Malformed line: %s\n", buffer.c_str());
+ return 0;
+ }
+
+ if(command == "/description")
+ istr >> Description;
+ else if(command == "/version")
+ {
+ istr >> Version;
+ if(!istr.eof() && !std::isspace(istr.peek()))
+ {
+ ERR("Extra junk after version: %s\n",
+ buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
+ return 0;
+ }
+ if(Version != 3)
+ {
+ ERR("Unsupported version: %u\n", Version);
+ return 0;
+ }
+ }
+ else if(command == "/dec/chan_mask")
+ {
+ istr >> std::hex >> ChanMask >> std::dec;
+ if(!istr.eof() && !std::isspace(istr.peek()))
+ {
+ ERR("Extra junk after mask: %s\n",
+ buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
+ return 0;
+ }
+ }
+ else if(command == "/dec/freq_bands")
+ {
+ istr >> FreqBands;
+ if(!istr.eof() && !std::isspace(istr.peek()))
+ {
+ ERR("Extra junk after freq_bands: %s\n",
+ buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
+ return 0;
+ }
+ if(FreqBands != 1 && FreqBands != 2)
+ {
+ ERR("Invalid freq_bands value: %u\n", FreqBands);
+ return 0;
+ }
+ }
+ else if(command == "/dec/speakers")
+ {
+ istr >> num_speakers;
+ if(!istr.eof() && !std::isspace(istr.peek()))
+ {
+ ERR("Extra junk after speakers: %s\n",
+ buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
+ return 0;
+ }
+ Speakers.reserve(num_speakers);
+ LFMatrix.reserve(num_speakers);
+ HFMatrix.reserve(num_speakers);
+ }
+ else if(command == "/dec/coeff_scale")
+ {
+ std::string scale = read_word(istr);
+ if(scale == "n3d") CoeffScale = AmbDecScale::N3D;
+ else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D;
+ else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa;
+ else
+ {
+ ERR("Unsupported coeff scale: %s\n", scale.c_str());
+ return 0;
+ }
+ }
+ else if(command == "/opt/xover_freq")
+ {
+ istr >> XOverFreq;
+ if(!istr.eof() && !std::isspace(istr.peek()))
+ {
+ ERR("Extra junk after xover_freq: %s\n",
+ buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
+ return 0;
+ }
+ }
+ else if(command == "/opt/xover_ratio")
+ {
+ istr >> XOverRatio;
+ if(!istr.eof() && !std::isspace(istr.peek()))
+ {
+ ERR("Extra junk after xover_ratio: %s\n",
+ buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
+ return 0;
+ }
+ }
+ else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp" ||
+ command == "/opt/delay_comp" || command == "/opt/level_comp")
+ {
+ /* Unused */
+ read_word(istr);
+ }
+ else if(command == "/speakers/{")
+ {
+ const auto endpos = static_cast<std::size_t>(istr.tellg());
+ if(!is_at_end(buffer, endpos))
+ {
+ ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
+ return 0;
+ }
+ buffer.clear();
+
+ if(!load_ambdec_speakers(Speakers, num_speakers, f, buffer))
+ return 0;
+
+ if(!read_clipped_line(f, buffer))
+ {
+ ERR("Unexpected end of file\n");
+ return 0;
+ }
+ std::istringstream istr2{buffer};
+ std::string endmark{read_word(istr2)};
+ if(endmark != "/}")
+ {
+ ERR("Expected /} after speaker definitions, got %s\n", endmark.c_str());
+ return 0;
+ }
+ istr.swap(istr2);
+ }
+ else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{")
+ {
+ const auto endpos = static_cast<std::size_t>(istr.tellg());
+ if(!is_at_end(buffer, endpos))
+ {
+ ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
+ return 0;
+ }
+ buffer.clear();
+
+ if(FreqBands == 1)
+ {
+ if(command != "/matrix/{")
+ {
+ ERR("Unexpected \"%s\" type for a single-band decoder\n", command.c_str());
+ return 0;
+ }
+ if(!load_ambdec_matrix(HFOrderGain, HFMatrix, num_speakers, f, buffer))
+ return 0;
+ }
+ else
+ {
+ if(command == "/lfmatrix/{")
+ {
+ if(!load_ambdec_matrix(LFOrderGain, LFMatrix, num_speakers, f, buffer))
+ return 0;
+ }
+ else if(command == "/hfmatrix/{")
+ {
+ if(!load_ambdec_matrix(HFOrderGain, HFMatrix, num_speakers, f, buffer))
+ return 0;
+ }
+ else
+ {
+ ERR("Unexpected \"%s\" type for a dual-band decoder\n", command.c_str());
+ return 0;
+ }
+ }
+
+ if(!read_clipped_line(f, buffer))
+ {
+ ERR("Unexpected end of file\n");
+ return 0;
+ }
+ std::istringstream istr2{buffer};
+ std::string endmark{read_word(istr2)};
+ if(endmark != "/}")
+ {
+ ERR("Expected /} after matrix definitions, got %s\n", endmark.c_str());
+ return 0;
+ }
+ istr.swap(istr2);
+ }
+ else if(command == "/end")
+ {
+ const auto endpos = static_cast<std::size_t>(istr.tellg());
+ if(!is_at_end(buffer, endpos))
+ {
+ ERR("Unexpected junk on end: %s\n", buffer.c_str()+endpos);
+ return 0;
+ }
+
+ return 1;
+ }
+ else
+ {
+ ERR("Unexpected command: %s\n", command.c_str());
+ return 0;
+ }
+
+ istr.clear();
+ const auto endpos = static_cast<std::size_t>(istr.tellg());
+ if(!is_at_end(buffer, endpos))
+ {
+ ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
+ return 0;
+ }
+ buffer.clear();
+ }
+ ERR("Unexpected end of file\n");
+
+ return 0;
+}
diff --git a/alc/ambdec.h b/alc/ambdec.h
new file mode 100644
index 00000000..ff7b71ee
--- /dev/null
+++ b/alc/ambdec.h
@@ -0,0 +1,48 @@
+#ifndef AMBDEC_H
+#define AMBDEC_H
+
+#include <array>
+#include <string>
+
+#include "ambidefs.h"
+#include "vector.h"
+
+/* Helpers to read .ambdec configuration files. */
+
+enum class AmbDecScale {
+ N3D,
+ SN3D,
+ FuMa,
+};
+struct AmbDecConf {
+ std::string Description;
+ int Version{0}; /* Must be 3 */
+
+ unsigned int ChanMask{0u};
+ unsigned int FreqBands{0u}; /* Must be 1 or 2 */
+ AmbDecScale CoeffScale{};
+
+ float XOverFreq{0.0f};
+ float XOverRatio{0.0f};
+
+ struct SpeakerConf {
+ std::string Name;
+ float Distance{0.0f};
+ float Azimuth{0.0f};
+ float Elevation{0.0f};
+ std::string Connection;
+ };
+ al::vector<SpeakerConf> Speakers;
+
+ using CoeffArray = std::array<float,MAX_AMBI_CHANNELS>;
+ /* Unused when FreqBands == 1 */
+ float LFOrderGain[MAX_AMBI_ORDER+1]{};
+ al::vector<CoeffArray> LFMatrix;
+
+ float HFOrderGain[MAX_AMBI_ORDER+1]{};
+ al::vector<CoeffArray> HFMatrix;
+
+ int load(const char *fname) noexcept;
+};
+
+#endif /* AMBDEC_H */
diff --git a/alc/ambidefs.h b/alc/ambidefs.h
new file mode 100644
index 00000000..9bc3e969
--- /dev/null
+++ b/alc/ambidefs.h
@@ -0,0 +1,120 @@
+#ifndef AMBIDEFS_H
+#define AMBIDEFS_H
+
+#include <array>
+#include <cstdint>
+
+/* The maximum number of Ambisonics channels. For a given order (o), the size
+ * needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second-
+ * order has 9, third-order has 16, and fourth-order has 25.
+ */
+#define MAX_AMBI_ORDER 3
+constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept
+{ return (order+1) * (order+1); }
+#define MAX_AMBI_CHANNELS AmbiChannelsFromOrder(MAX_AMBI_ORDER)
+
+/* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up
+ * to 4th order, which is the highest order a 32-bit mask value can specify (a
+ * 64-bit mask could handle up to 7th order).
+ */
+#define AMBI_0ORDER_MASK 0x00000001
+#define AMBI_1ORDER_MASK 0x0000000f
+#define AMBI_2ORDER_MASK 0x000001ff
+#define AMBI_3ORDER_MASK 0x0000ffff
+#define AMBI_4ORDER_MASK 0x01ffffff
+
+/* A bitmask of ambisonic channels with height information. If none of these
+ * channels are used/needed, there's no height (e.g. with most surround sound
+ * speaker setups). This is ACN ordering, with bit 0 being ACN 0, etc.
+ */
+#define AMBI_PERIPHONIC_MASK (0xfe7ce4)
+
+/* The maximum number of ambisonic channels for 2D (non-periphonic)
+ * representation. This is 2 per each order above zero-order, plus 1 for zero-
+ * order. Or simply, o*2 + 1.
+ */
+constexpr inline size_t Ambi2DChannelsFromOrder(size_t order) noexcept
+{ return order*2 + 1; }
+#define MAX_AMBI2D_CHANNELS Ambi2DChannelsFromOrder(MAX_AMBI_ORDER)
+
+
+/* NOTE: These are scale factors as applied to Ambisonics content. Decoder
+ * coefficients should be divided by these values to get proper scalings.
+ */
+struct AmbiScale {
+ static constexpr std::array<float,MAX_AMBI_CHANNELS> FromN3D{{
+ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f
+ }};
+ static constexpr std::array<float,MAX_AMBI_CHANNELS> FromSN3D{{
+ 1.000000000f, /* ACN 0, sqrt(1) */
+ 1.732050808f, /* ACN 1, sqrt(3) */
+ 1.732050808f, /* ACN 2, sqrt(3) */
+ 1.732050808f, /* ACN 3, sqrt(3) */
+ 2.236067978f, /* ACN 4, sqrt(5) */
+ 2.236067978f, /* ACN 5, sqrt(5) */
+ 2.236067978f, /* ACN 6, sqrt(5) */
+ 2.236067978f, /* ACN 7, sqrt(5) */
+ 2.236067978f, /* ACN 8, sqrt(5) */
+ 2.645751311f, /* ACN 9, sqrt(7) */
+ 2.645751311f, /* ACN 10, sqrt(7) */
+ 2.645751311f, /* ACN 11, sqrt(7) */
+ 2.645751311f, /* ACN 12, sqrt(7) */
+ 2.645751311f, /* ACN 13, sqrt(7) */
+ 2.645751311f, /* ACN 14, sqrt(7) */
+ 2.645751311f, /* ACN 15, sqrt(7) */
+ }};
+ static constexpr std::array<float,MAX_AMBI_CHANNELS> FromFuMa{{
+ 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) */
+ }};
+};
+
+struct AmbiIndex {
+ static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> FromFuMa{{
+ 0, /* W */
+ 3, /* X */
+ 1, /* Y */
+ 2, /* Z */
+ 6, /* R */
+ 7, /* S */
+ 5, /* T */
+ 8, /* U */
+ 4, /* V */
+ 12, /* K */
+ 13, /* L */
+ 11, /* M */
+ 14, /* N */
+ 10, /* O */
+ 15, /* P */
+ 9, /* Q */
+ }};
+ static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> FromACN{{
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15
+ }};
+
+ static constexpr std::array<uint8_t,MAX_AMBI2D_CHANNELS> From2D{{
+ 0, 1,3, 4,8, 9,15
+ }};
+ static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> From3D{{
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15
+ }};
+};
+
+#endif /* AMBIDEFS_H */
diff --git a/alc/backends/alsa.cpp b/alc/backends/alsa.cpp
new file mode 100644
index 00000000..7dc3c3c4
--- /dev/null
+++ b/alc/backends/alsa.cpp
@@ -0,0 +1,1265 @@
+/**
+ * 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 "AL/al.h"
+
+#include "albyte.h"
+#include "alcmain.h"
+#include "alconfig.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alu.h"
+#include "dynload.h"
+#include "logging.h"
+#include "ringbuffer.h"
+#include "threads.h"
+#include "vector.h"
+
+#include <alsa/asoundlib.h>
+
+
+namespace {
+
+constexpr 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) 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<ALuint>(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 ALCchar *name) override;
+ bool reset() override;
+ bool start() override;
+ void stop() override;
+
+ ClockLatency getClockLatency() override;
+
+ snd_pcm_t *mPcmHandle{nullptr};
+
+ 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 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));
+ aluHandleDisconnect(mDevice, "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<AlsaPlayback> _{*this};
+ 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)};
+ aluMixData(mDevice, WritePtr, static_cast<ALuint>(frames));
+
+ snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)};
+ if(commitres < 0 || (static_cast<snd_pcm_uframes_t>(commitres)-frames) != 0)
+ {
+ 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 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));
+ aluHandleDisconnect(mDevice, "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;
+ }
+
+ std::lock_guard<AlsaPlayback> _{*this};
+ al::byte *WritePtr{mBuffer.data()};
+ avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast<ssize_t>(mBuffer.size()));
+ aluMixData(mDevice, WritePtr, static_cast<ALuint>(avail));
+ 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 ALCchar *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{ALC_INVALID_VALUE, "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{ALC_OUT_OF_MEMORY, "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)};
+ ALuint periodLen{static_cast<ALuint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)};
+ ALuint bufferLen{static_cast<ALuint>(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)};
+ ALuint rate{mDevice->Frequency};
+
+ int err{};
+ HwParamsPtr hp{CreateHwParams()};
+#define CHECK(x) do { \
+ if((err=(x)) < 0) \
+ throw al::backend_exception{ALC_INVALID_VALUE, #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.get<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<ALuint>(bufferSizeInFrames);
+ mDevice->UpdateSize = static_cast<ALuint>(periodSizeInFrames);
+ mDevice->Frequency = rate;
+
+ SetDefaultChannelOrder(mDevice);
+
+ return true;
+}
+
+bool AlsaPlayback::start()
+{
+ int err{};
+ snd_pcm_access_t access{};
+ HwParamsPtr hp{CreateHwParams()};
+#define CHECK(x) do { \
+ if((err=(x)) < 0) \
+ throw al::backend_exception{ALC_INVALID_VALUE, #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));
+#undef CHECK
+ 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
+ {
+ err = snd_pcm_prepare(mPcmHandle);
+ if(err < 0)
+ {
+ ERR("snd_pcm_prepare(data->mPcmHandle) failed: %s\n", snd_strerror(err));
+ return false;
+ }
+ thread_func = &AlsaPlayback::mixerProc;
+ }
+
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(thread_func), this};
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Could not create playback thread: %s\n", e.what());
+ }
+ mBuffer.clear();
+ return false;
+}
+
+void AlsaPlayback::stop()
+{
+ if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+ return;
+ mThread.join();
+
+ mBuffer.clear();
+}
+
+ClockLatency AlsaPlayback::getClockLatency()
+{
+ ClockLatency ret;
+
+ std::lock_guard<AlsaPlayback> _{*this};
+ 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 ALCchar *name) override;
+ bool start() override;
+ void stop() override;
+ ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
+ ALCuint 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 ALCchar *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{ALC_INVALID_VALUE, "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{ALC_OUT_OF_MEMORY, "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{ALC_INVALID_VALUE, #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 = CreateRingBuffer(mDevice->BufferSize, mDevice->frameSizeFromFmt(), false);
+
+ mDevice->DeviceName = name;
+}
+
+
+bool AlsaCapture::start()
+{
+ int err{snd_pcm_prepare(mPcmHandle)};
+ if(err < 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "snd_pcm_prepare failed: %s",
+ snd_strerror(err)};
+
+ err = snd_pcm_start(mPcmHandle);
+ if(err < 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "snd_pcm_start failed: %s",
+ snd_strerror(err)};
+
+ mDoCapture = true;
+ return 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.
+ */
+ ALCuint 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;
+}
+
+ALCenum AlsaCapture::captureSamples(al::byte *buffer, ALCuint samples)
+{
+ if(mRing)
+ {
+ mRing->read(buffer, samples);
+ return ALC_NO_ERROR;
+ }
+
+ 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);
+ aluHandleDisconnect(mDevice, "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<ALCuint>(amt);
+ }
+ if(samples > 0)
+ std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples),
+ al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0));
+
+ return ALC_NO_ERROR;
+}
+
+ALCuint 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);
+ aluHandleDisconnect(mDevice, "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<ALCuint>(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);
+ aluHandleDisconnect(mDevice, "Capture recovery failure: %s", err);
+ break;
+ }
+ avail = amt;
+ continue;
+ }
+
+ mRing->writeAdvance(static_cast<snd_pcm_uframes_t>(amt));
+ avail -= amt;
+ }
+
+ return static_cast<ALCuint>(mRing->readSpace());
+}
+
+ClockLatency AlsaCapture::getClockLatency()
+{
+ ClockLatency ret;
+
+ std::lock_guard<AlsaCapture> _{*this};
+ 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 ALC_FALSE;
+ }
+
+ error = ALC_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); }
+
+void AlsaBackendFactory::probe(DevProbe 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 DevProbe::Playback:
+ PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK);
+ std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+ break;
+
+ case DevProbe::Capture:
+ CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE);
+ std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/alsa.h b/alc/backends/alsa.h
new file mode 100644
index 00000000..fb9de006
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_ALSA_H */
diff --git a/alc/backends/base.cpp b/alc/backends/base.cpp
new file mode 100644
index 00000000..25531cf5
--- /dev/null
+++ b/alc/backends/base.cpp
@@ -0,0 +1,62 @@
+
+#include "config.h"
+
+#include "base.h"
+
+#include <atomic>
+#include <thread>
+
+#include "AL/al.h"
+
+#include "alcmain.h"
+#include "alexcpt.h"
+#include "alnumeric.h"
+#include "atomic.h"
+
+
+ClockLatency GetClockLatency(ALCdevice *device)
+{
+ BackendBase *backend{device->Backend.get()};
+ ClockLatency ret{backend->getClockLatency()};
+ ret.Latency += device->FixedLatency;
+ return ret;
+}
+
+
+/* BackendBase method implementations. */
+BackendBase::BackendBase(ALCdevice *device) noexcept : mDevice{device}
+{ }
+
+BackendBase::~BackendBase() = default;
+
+bool BackendBase::reset()
+{ throw al::backend_exception{ALC_INVALID_DEVICE, "Invalid BackendBase call"}; }
+
+ALCenum BackendBase::captureSamples(al::byte*, ALCuint)
+{ return ALC_INVALID_DEVICE; }
+
+ALCuint BackendBase::availableSamples()
+{ return 0; }
+
+ClockLatency BackendBase::getClockLatency()
+{
+ ClockLatency ret;
+
+ ALuint refcount;
+ do {
+ while(((refcount=ReadRef(mDevice->MixCount))&1) != 0)
+ std::this_thread::yield();
+ 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;
+}
diff --git a/alc/backends/base.h b/alc/backends/base.h
new file mode 100644
index 00000000..d4856818
--- /dev/null
+++ b/alc/backends/base.h
@@ -0,0 +1,84 @@
+#ifndef ALC_BACKENDS_BASE_H
+#define ALC_BACKENDS_BASE_H
+
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <string>
+
+#include "AL/alc.h"
+
+#include "alcmain.h"
+#include "albyte.h"
+
+
+struct ClockLatency {
+ std::chrono::nanoseconds ClockTime;
+ std::chrono::nanoseconds Latency;
+};
+
+/* 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;
+
+ auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
+ return device->ClockBase + ns;
+}
+
+ClockLatency GetClockLatency(ALCdevice *device);
+
+struct BackendBase {
+ virtual void open(const ALCchar *name) = 0;
+
+ virtual bool reset();
+ virtual bool start() = 0;
+ virtual void stop() = 0;
+
+ virtual ALCenum captureSamples(al::byte *buffer, ALCuint samples);
+ virtual ALCuint availableSamples();
+
+ virtual ClockLatency getClockLatency();
+
+ virtual void lock() { mMutex.lock(); }
+ virtual void unlock() { mMutex.unlock(); }
+
+ ALCdevice *mDevice;
+
+ std::recursive_mutex mMutex;
+
+ BackendBase(ALCdevice *device) noexcept;
+ virtual ~BackendBase();
+};
+using BackendPtr = std::unique_ptr<BackendBase>;
+using BackendUniqueLock = std::unique_lock<BackendBase>;
+using BackendLockGuard = std::lock_guard<BackendBase>;
+
+enum class BackendType {
+ Playback,
+ Capture
+};
+
+enum class DevProbe {
+ Playback,
+ Capture
+};
+
+
+struct BackendFactory {
+ virtual bool init() = 0;
+
+ virtual bool querySupport(BackendType type) = 0;
+
+ virtual void probe(DevProbe type, std::string *outnames) = 0;
+
+ virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0;
+
+protected:
+ virtual ~BackendFactory() = default;
+};
+
+#endif /* ALC_BACKENDS_BASE_H */
diff --git a/alc/backends/coreaudio.cpp b/alc/backends/coreaudio.cpp
new file mode 100644
index 00000000..7c18287b
--- /dev/null
+++ b/alc/backends/coreaudio.cpp
@@ -0,0 +1,659 @@
+/**
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alcmain.h"
+#include "alexcpt.h"
+#include "alu.h"
+#include "ringbuffer.h"
+#include "converter.h"
+#include "backends/base.h"
+
+#include <unistd.h>
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioToolbox.h>
+
+
+namespace {
+
+static const ALCchar 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 ALCchar *name) override;
+ bool reset() override;
+ bool start() override;
+ void stop() override;
+
+ AudioUnit mAudioUnit{};
+
+ ALuint 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
+{
+ std::lock_guard<CoreAudioPlayback> _{*this};
+ aluMixData(mDevice, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize/mFrameSize);
+ return noErr;
+}
+
+
+void CoreAudioPlayback::open(const ALCchar *name)
+{
+ if(!name)
+ name = ca_device;
+ else if(strcmp(name, ca_device) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "Could not find audio component"};
+
+ OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
+ if(err != noErr)
+ throw al::backend_exception{ALC_INVALID_VALUE, "Could not create component instance: %u",
+ err};
+
+ /* init and start the default audio unit... */
+ err = AudioUnitInitialize(mAudioUnit);
+ if(err != noErr)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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<ALuint>(uint64_t{mDevice->BufferSize} *
+ streamFormat.mSampleRate / mDevice->Frequency);
+ mDevice->Frequency = static_cast<ALuint>(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(mDevice);
+
+ /* 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;
+}
+
+bool CoreAudioPlayback::start()
+{
+ OSStatus err{AudioOutputUnitStart(mAudioUnit)};
+ if(err != noErr)
+ {
+ ERR("AudioOutputUnitStart failed\n");
+ return false;
+ }
+ return true;
+}
+
+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 ALCchar *name) override;
+ bool start() override;
+ void stop() override;
+ ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
+ ALCuint availableSamples() override;
+
+ AudioUnit mAudioUnit{0};
+
+ ALuint 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<ALuint>(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 ALCchar *name)
+{
+ AudioStreamBasicDescription requestedFormat; // The application requested format
+ AudioStreamBasicDescription hardwareFormat; // The hardware format
+ AudioStreamBasicDescription outputFormat; // The AudioUnit output format
+ AURenderCallbackStruct input;
+ AudioComponentDescription desc;
+ UInt32 outputFrameCount;
+ UInt32 propertySize;
+#if !TARGET_OS_IOS
+ AudioObjectPropertyAddress propertyAddress;
+#endif
+ UInt32 enableIO;
+ AudioComponent comp;
+ OSStatus err;
+
+ if(!name)
+ name = ca_device;
+ else if(strcmp(name, ca_device) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "Could not find audio component"};
+
+ // Open the component
+ err = AudioComponentInstanceNew(comp, &mAudioUnit);
+ if(err != noErr)
+ throw al::backend_exception{ALC_INVALID_VALUE, "Could not create component instance: %u",
+ err};
+
+ // Turn off AudioUnit output
+ enableIO = 0;
+ err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint));
+ if(err != noErr)
+ throw al::backend_exception{ALC_INVALID_VALUE,
+ "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(ALuint));
+ if(err != noErr)
+ throw al::backend_exception{ALC_INVALID_VALUE,
+ "Could not enable audio unit input property: %u", err};
+
+#if !TARGET_OS_IOS
+ {
+ // Get the default input device
+ AudioDeviceID inputDevice = kAudioDeviceUnknown;
+
+ propertySize = sizeof(AudioDeviceID);
+ propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+ propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+
+ err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
+ &propertySize, &inputDevice);
+ if(err != noErr)
+ throw al::backend_exception{ALC_INVALID_VALUE, "Could not get input device: %u", err};
+ if(inputDevice == kAudioDeviceUnknown)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "Could not set capture callback: %u", err};
+
+ // Initialize the device
+ err = AudioUnitInitialize(mAudioUnit);
+ if(err != noErr)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "Could not get input format: %u", err};
+
+ // Set up the requested format description
+ switch(mDevice->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:
+ throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not suppoted",
+ DevFmtTypeString(mDevice->FmtType)};
+ }
+
+ 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{ALC_INVALID_VALUE, "%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{ALC_INVALID_VALUE, "Could not set input format: %u", err};
+
+ // Set the AudioUnit output format frame count
+ uint64_t FrameCount64{mDevice->UpdateSize};
+ FrameCount64 = static_cast<uint64_t>(FrameCount64*outputFormat.mSampleRate + mDevice->Frequency-1) /
+ mDevice->Frequency;
+ FrameCount64 += MAX_RESAMPLER_PADDING;
+ if(FrameCount64 > std::numeric_limits<uint32_t>::max()/2)
+ throw al::backend_exception{ALC_INVALID_VALUE,
+ "Calculated frame count is too large: %" PRIu64, FrameCount64};
+
+ outputFrameCount = static_cast<uint32_t>(FrameCount64);
+ err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount));
+ if(err != noErr)
+ throw al::backend_exception{ALC_INVALID_VALUE, "Failed to set capture frame count: %u",
+ err};
+
+ // Set up sample converter if needed
+ if(outputFormat.mSampleRate != mDevice->Frequency)
+ mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
+ mFormat.mChannelsPerFrame, static_cast<ALuint>(hardwareFormat.mSampleRate),
+ mDevice->Frequency, Resampler::FastBSinc24);
+
+ mRing = CreateRingBuffer(outputFrameCount, mFrameSize, false);
+
+ mDevice->DeviceName = name;
+}
+
+
+bool CoreAudioCapture::start()
+{
+ OSStatus err{AudioOutputUnitStart(mAudioUnit)};
+ if(err != noErr)
+ {
+ ERR("AudioOutputUnitStart failed\n");
+ return false;
+ }
+ return true;
+}
+
+void CoreAudioCapture::stop()
+{
+ OSStatus err{AudioOutputUnitStop(mAudioUnit)};
+ if(err != noErr)
+ ERR("AudioOutputUnitStop failed\n");
+}
+
+ALCenum CoreAudioCapture::captureSamples(al::byte *buffer, ALCuint samples)
+{
+ if(!mConverter)
+ {
+ mRing->read(buffer, samples);
+ return ALC_NO_ERROR;
+ }
+
+ auto rec_vec = mRing->getReadVector();
+ const void *src0{rec_vec.first.buf};
+ auto src0len = static_cast<ALuint>(rec_vec.first.len);
+ ALuint 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<ALuint>(rec_vec.second.len);
+ got += mConverter->convert(&src1, &src1len, buffer+got, samples-got);
+ total_read += rec_vec.second.len - src1len;
+ }
+
+ mRing->readAdvance(total_read);
+ return ALC_NO_ERROR;
+}
+
+ALCuint CoreAudioCapture::availableSamples()
+{
+ if(!mConverter) return static_cast<ALCuint>(mRing->readSpace());
+ return mConverter->availableOut(static_cast<ALCuint>(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; }
+
+void CoreAudioBackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ switch(type)
+ {
+ case DevProbe::Playback:
+ case DevProbe::Capture:
+ /* Includes null char. */
+ outnames->append(ca_device, sizeof(ca_device));
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/coreaudio.h b/alc/backends/coreaudio.h
new file mode 100644
index 00000000..37b9ebe5
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_COREAUDIO_H */
diff --git a/alc/backends/dsound.cpp b/alc/backends/dsound.cpp
new file mode 100644
index 00000000..c04ba9e4
--- /dev/null
+++ b/alc/backends/dsound.cpp
@@ -0,0 +1,916 @@
+/**
+ * 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 "alexcpt.h"
+#include "alu.h"
+#include "ringbuffer.h"
+#include "compat.h"
+#include "dynload.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 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;
+ bool 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);
+ aluHandleDisconnect(mDevice, "Failure retrieving playback buffer info: 0x%lx", err);
+ return 1;
+ }
+
+ ALuint 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);
+ aluHandleDisconnect(mDevice, "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))
+ {
+ std::unique_lock<DSoundPlayback> dlock{*this};
+ aluMixData(mDevice, WritePtr1, WriteCnt1/FrameSize);
+ if(WriteCnt2 > 0)
+ aluMixData(mDevice, WritePtr2, WriteCnt2/FrameSize);
+ dlock.unlock();
+
+ mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
+ }
+ else
+ {
+ ERR("Buffer lock error: %#lx\n", err);
+ std::lock_guard<DSoundPlayback> _{*this};
+ aluHandleDisconnect(mDevice, "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 ALCchar *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())
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "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.get<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.get<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 = SPEAKER_FRONT_CENTER;
+ break;
+ case DevFmtAmbi3D:
+ mDevice->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 = 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))
+ {
+ ALuint 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))
+ {
+ auto Notifies = static_cast<IDirectSoundNotify*>(ptr);
+ mNotifies = Notifies;
+
+ ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
+ assert(num_updates <= MAX_UPDATES);
+
+ std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
+ for(ALuint i{0};i < num_updates;++i)
+ {
+ nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
+ nots[i].hEventNotify = mNotifyEvent;
+ }
+ if(Notifies->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);
+ SetDefaultWFXChannelOrder(mDevice);
+
+ return true;
+}
+
+bool DSoundPlayback::start()
+{
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Failed to start mixing thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ return false;
+}
+
+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 ALCchar *name) override;
+ bool start() override;
+ void stop() override;
+ ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
+ ALCuint 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 ALCchar *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())
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "%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 = 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(mDevice->FmtChans));
+ throw al::backend_exception{ALC_INVALID_VALUE, "%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);
+ }
+
+ ALuint 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 = CreateRingBuffer(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{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
+ }
+
+ mBufferBytes = DSCBDescription.dwBufferBytes;
+ SetDefaultWFXChannelOrder(mDevice);
+
+ mDevice->DeviceName = name;
+}
+
+bool DSoundCapture::start()
+{
+ HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
+ if(FAILED(hr))
+ {
+ ERR("start failed: 0x%08lx\n", hr);
+ aluHandleDisconnect(mDevice, "Failure starting capture: 0x%lx", hr);
+ return false;
+ }
+ return true;
+}
+
+void DSoundCapture::stop()
+{
+ HRESULT hr{mDSCbuffer->Stop()};
+ if(FAILED(hr))
+ {
+ ERR("stop failed: 0x%08lx\n", hr);
+ aluHandleDisconnect(mDevice, "Failure stopping capture: 0x%lx", hr);
+ }
+}
+
+ALCenum DSoundCapture::captureSamples(al::byte *buffer, ALCuint samples)
+{
+ mRing->read(buffer, samples);
+ return ALC_NO_ERROR;
+}
+
+ALCuint DSoundCapture::availableSamples()
+{
+ if(!mDevice->Connected.load(std::memory_order_acquire))
+ return static_cast<ALCuint>(mRing->readSpace());
+
+ ALuint FrameSize{mDevice->frameSizeFromFmt()};
+ DWORD BufferBytes{mBufferBytes};
+ DWORD LastCursor{mCursor};
+
+ DWORD ReadCursor{};
+ void *ReadPtr1{}, *ReadPtr2{};
+ DWORD ReadCnt1{}, ReadCnt2{};
+ HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
+ if(SUCCEEDED(hr))
+ {
+ DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes};
+ if(!NumBytes) return static_cast<ALCubyte>(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 = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes;
+ }
+
+ if(FAILED(hr))
+ {
+ ERR("update failed: 0x%08lx\n", hr);
+ aluHandleDisconnect(mDevice, "Failure retrieving capture data: 0x%lx", hr);
+ }
+
+ return static_cast<ALCuint>(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); }
+
+void DSoundBackendFactory::probe(DevProbe 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 DevProbe::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 DevProbe::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();
+}
+
+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;
+}
diff --git a/alc/backends/dsound.h b/alc/backends/dsound.h
new file mode 100644
index 00000000..6bef0bfc
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_DSOUND_H */
diff --git a/alc/backends/jack.cpp b/alc/backends/jack.cpp
new file mode 100644
index 00000000..c7bf8469
--- /dev/null
+++ b/alc/backends/jack.cpp
@@ -0,0 +1,544 @@
+/**
+ * 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 <memory.h>
+
+#include <thread>
+#include <functional>
+
+#include "alcmain.h"
+#include "alu.h"
+#include "alconfig.h"
+#include "alexcpt.h"
+#include "dynload.h"
+#include "ringbuffer.h"
+#include "threads.h"
+
+#include <jack/jack.h>
+#include <jack/ringbuffer.h>
+
+
+namespace {
+
+constexpr 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);
+
+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;
+
+ALCboolean jack_load()
+{
+ ALCboolean error = ALC_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 ALC_FALSE;
+ }
+
+ error = ALC_FALSE;
+#define LOAD_FUNC(f) do { \
+ p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
+ if(p##f == nullptr) { \
+ error = ALC_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 JackPlayback final : public BackendBase {
+ JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ ~JackPlayback() override;
+
+ int bufferSizeNotify(jack_nframes_t numframes) noexcept;
+ static int bufferSizeNotifyC(jack_nframes_t numframes, void *arg) noexcept
+ { return static_cast<JackPlayback*>(arg)->bufferSizeNotify(numframes); }
+
+ int process(jack_nframes_t numframes) noexcept;
+ static int processC(jack_nframes_t numframes, void *arg) noexcept
+ { return static_cast<JackPlayback*>(arg)->process(numframes); }
+
+ int mixerProc();
+
+ void open(const ALCchar *name) override;
+ bool reset() override;
+ bool start() override;
+ void stop() override;
+ ClockLatency getClockLatency() override;
+
+ jack_client_t *mClient{nullptr};
+ jack_port_t *mPort[MAX_OUTPUT_CHANNELS]{};
+
+ RingBufferPtr mRing;
+ al::semaphore mSem;
+
+ std::atomic<bool> mKillNow{true};
+ std::thread mThread;
+
+ DEF_NEWDEL(JackPlayback)
+};
+
+JackPlayback::~JackPlayback()
+{
+ if(!mClient)
+ return;
+
+ std::for_each(std::begin(mPort), std::end(mPort),
+ [this](jack_port_t *port) -> void
+ { if(port) jack_port_unregister(mClient, port); }
+ );
+ std::fill(std::begin(mPort), std::end(mPort), nullptr);
+ jack_client_close(mClient);
+ mClient = nullptr;
+}
+
+
+int JackPlayback::bufferSizeNotify(jack_nframes_t numframes) noexcept
+{
+ std::lock_guard<std::mutex> _{mDevice->StateLock};
+ mDevice->UpdateSize = numframes;
+ mDevice->BufferSize = numframes*2;
+
+ const char *devname{mDevice->DeviceName.c_str()};
+ ALuint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
+ bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
+ mDevice->BufferSize = bufsize + mDevice->UpdateSize;
+
+ TRACE("%u / %u buffer\n", mDevice->UpdateSize, mDevice->BufferSize);
+
+ mRing = nullptr;
+ mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true);
+
+ return 0;
+}
+
+
+int JackPlayback::process(jack_nframes_t numframes) noexcept
+{
+ jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS];
+ ALsizei numchans{0};
+ for(auto port : mPort)
+ {
+ if(!port) break;
+ out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
+ }
+
+ auto data = mRing->getReadVector();
+ jack_nframes_t todo{minu(numframes, static_cast<ALuint>(data.first.len))};
+ std::transform(out, out+numchans, out,
+ [&data,numchans,todo](ALfloat *outbuf) -> ALfloat*
+ {
+ const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.first.buf);
+ std::generate_n(outbuf, todo,
+ [&in,numchans]() noexcept -> ALfloat
+ {
+ ALfloat ret{*in};
+ in += numchans;
+ return ret;
+ }
+ );
+ data.first.buf += sizeof(ALfloat);
+ return outbuf + todo;
+ }
+ );
+ jack_nframes_t total{todo};
+
+ todo = minu(numframes-total, static_cast<ALuint>(data.second.len));
+ if(todo > 0)
+ {
+ std::transform(out, out+numchans, out,
+ [&data,numchans,todo](ALfloat *outbuf) -> ALfloat*
+ {
+ const ALfloat *RESTRICT in = reinterpret_cast<ALfloat*>(data.second.buf);
+ std::generate_n(outbuf, todo,
+ [&in,numchans]() noexcept -> ALfloat
+ {
+ ALfloat ret{*in};
+ in += numchans;
+ return ret;
+ }
+ );
+ data.second.buf += sizeof(ALfloat);
+ return outbuf + todo;
+ }
+ );
+ total += todo;
+ }
+
+ mRing->readAdvance(total);
+ mSem.post();
+
+ if(numframes > total)
+ {
+ todo = numframes-total;
+ std::transform(out, out+numchans, out,
+ [todo](ALfloat *outbuf) -> ALfloat*
+ {
+ std::fill_n(outbuf, todo, 0.0f);
+ return outbuf + todo;
+ }
+ );
+ }
+
+ return 0;
+}
+
+int JackPlayback::mixerProc()
+{
+ SetRTPriority();
+ althrd_setname(MIXER_THREAD_NAME);
+
+ std::unique_lock<JackPlayback> dlock{*this};
+ while(!mKillNow.load(std::memory_order_acquire) &&
+ mDevice->Connected.load(std::memory_order_acquire))
+ {
+ if(mRing->writeSpace() < mDevice->UpdateSize)
+ {
+ dlock.unlock();
+ mSem.wait();
+ dlock.lock();
+ continue;
+ }
+
+ auto data = mRing->getWriteVector();
+ auto todo = static_cast<ALuint>(data.first.len + data.second.len);
+ todo -= todo%mDevice->UpdateSize;
+
+ ALuint len1{minu(static_cast<ALuint>(data.first.len), todo)};
+ ALuint len2{minu(static_cast<ALuint>(data.second.len), todo-len1)};
+
+ aluMixData(mDevice, data.first.buf, len1);
+ if(len2 > 0)
+ aluMixData(mDevice, data.second.buf, len2);
+ mRing->writeAdvance(todo);
+ }
+
+ return 0;
+}
+
+
+void JackPlayback::open(const ALCchar *name)
+{
+ if(!name)
+ name = jackDevice;
+ else if(strcmp(name, jackDevice) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+
+ 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{ALC_INVALID_VALUE, "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);
+ jack_set_buffer_size_callback(mClient, &JackPlayback::bufferSizeNotifyC, this);
+
+ mDevice->DeviceName = name;
+}
+
+bool JackPlayback::reset()
+{
+ std::for_each(std::begin(mPort), std::end(mPort),
+ [this](jack_port_t *port) -> void
+ { if(port) jack_port_unregister(mClient, port); }
+ );
+ std::fill(std::begin(mPort), std::end(mPort), 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()};
+ ALuint 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 = std::begin(mPort) + mDevice->channelsFromFmt();
+ auto bad_port = std::find_if_not(std::begin(mPort), ports_end,
+ [this](jack_port_t *&port) -> bool
+ {
+ std::string name{"channel_" + std::to_string(&port - mPort + 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 == std::begin(mPort)) return false;
+
+ if(bad_port == std::begin(mPort)+1)
+ mDevice->FmtChans = DevFmtMono;
+ else
+ {
+ ports_end = mPort+2;
+ while(bad_port != ports_end)
+ {
+ jack_port_unregister(mClient, *(--bad_port));
+ *bad_port = nullptr;
+ }
+ mDevice->FmtChans = DevFmtStereo;
+ }
+ }
+
+ mRing = nullptr;
+ mRing = CreateRingBuffer(bufsize, mDevice->frameSizeFromFmt(), true);
+
+ SetDefaultChannelOrder(mDevice);
+
+ return true;
+}
+
+bool JackPlayback::start()
+{
+ if(jack_activate(mClient))
+ {
+ ERR("Failed to activate client\n");
+ return false;
+ }
+
+ const char **ports{jack_get_ports(mClient, nullptr, nullptr,
+ JackPortIsPhysical|JackPortIsInput)};
+ if(ports == nullptr)
+ {
+ ERR("No physical playback ports found\n");
+ jack_deactivate(mClient);
+ return false;
+ }
+ std::mismatch(std::begin(mPort), std::end(mPort), ports,
+ [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;
+ }
+ );
+ jack_free(ports);
+
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Could not create playback thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ jack_deactivate(mClient);
+ return false;
+}
+
+void JackPlayback::stop()
+{
+ if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+ return;
+
+ mSem.post();
+ mThread.join();
+
+ jack_deactivate(mClient);
+}
+
+
+ClockLatency JackPlayback::getClockLatency()
+{
+ ClockLatency ret;
+
+ std::lock_guard<JackPlayback> _{*this};
+ 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); }
+
+void JackBackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ switch(type)
+ {
+ case DevProbe::Playback:
+ /* Includes null char. */
+ outnames->append(jackDevice, sizeof(jackDevice));
+ break;
+
+ case DevProbe::Capture:
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/jack.h b/alc/backends/jack.h
new file mode 100644
index 00000000..10beebfb
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_JACK_H */
diff --git a/alc/backends/loopback.cpp b/alc/backends/loopback.cpp
new file mode 100644
index 00000000..511061f3
--- /dev/null
+++ b/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;
+ bool start() override;
+ void stop() override;
+
+ DEF_NEWDEL(LoopbackBackend)
+};
+
+
+void LoopbackBackend::open(const ALCchar *name)
+{
+ mDevice->DeviceName = name;
+}
+
+bool LoopbackBackend::reset()
+{
+ SetDefaultWFXChannelOrder(mDevice);
+ return true;
+}
+
+bool LoopbackBackend::start()
+{ return true; }
+
+void LoopbackBackend::stop()
+{ }
+
+} // namespace
+
+
+bool LoopbackBackendFactory::init()
+{ return true; }
+
+bool LoopbackBackendFactory::querySupport(BackendType)
+{ return true; }
+
+void LoopbackBackendFactory::probe(DevProbe, std::string*)
+{ }
+
+BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType)
+{ return BackendPtr{new LoopbackBackend{device}}; }
+
+BackendFactory &LoopbackBackendFactory::getFactory()
+{
+ static LoopbackBackendFactory factory{};
+ return factory;
+}
diff --git a/alc/backends/loopback.h b/alc/backends/loopback.h
new file mode 100644
index 00000000..09c085b8
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_LOOPBACK_H */
diff --git a/alc/backends/null.cpp b/alc/backends/null.cpp
new file mode 100644
index 00000000..bc2a0c9c
--- /dev/null
+++ b/alc/backends/null.cpp
@@ -0,0 +1,182 @@
+/**
+ * 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 "alexcpt.h"
+#include "almalloc.h"
+#include "alu.h"
+#include "logging.h"
+#include "threads.h"
+
+
+namespace {
+
+using std::chrono::seconds;
+using std::chrono::milliseconds;
+using std::chrono::nanoseconds;
+
+constexpr ALCchar nullDevice[] = "No Output";
+
+
+struct NullBackend final : public BackendBase {
+ NullBackend(ALCdevice *device) noexcept : BackendBase{device} { }
+
+ int mixerProc();
+
+ void open(const ALCchar *name) override;
+ bool reset() override;
+ bool 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)
+ {
+ std::lock_guard<NullBackend> _{*this};
+ aluMixData(mDevice, nullptr, mDevice->UpdateSize);
+ 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 ALCchar *name)
+{
+ if(!name)
+ name = nullDevice;
+ else if(strcmp(name, nullDevice) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+
+ mDevice->DeviceName = name;
+}
+
+bool NullBackend::reset()
+{
+ SetDefaultWFXChannelOrder(mDevice);
+ return true;
+}
+
+bool NullBackend::start()
+{
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this};
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Failed to start mixing thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ return false;
+}
+
+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); }
+
+void NullBackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ switch(type)
+ {
+ case DevProbe::Playback:
+ /* Includes null char. */
+ outnames->append(nullDevice, sizeof(nullDevice));
+ break;
+ case DevProbe::Capture:
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/null.h b/alc/backends/null.h
new file mode 100644
index 00000000..f19d5b4d
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_NULL_H */
diff --git a/alc/backends/opensl.cpp b/alc/backends/opensl.cpp
new file mode 100644
index 00000000..a1fdccc7
--- /dev/null
+++ b/alc/backends/opensl.cpp
@@ -0,0 +1,964 @@
+/*
+ * 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 "alcmain.h"
+#include "alexcpt.h"
+#include "alu.h"
+#include "compat.h"
+#include "endiantest.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 ALCchar opensl_device[] = "OpenSL";
+
+
+SLuint32 GetChannelMask(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_ANDROID_DATAFORMAT_PCM_EX
+SLuint32 GetTypeRepresentation(DevFmtType type)
+{
+ 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
+
+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 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 ALCchar *name) override;
+ bool reset() override;
+ bool 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;
+
+ ALuint 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");
+ }
+
+ std::unique_lock<OpenSLPlayback> dlock{*this};
+ if(SL_RESULT_SUCCESS != result)
+ aluHandleDisconnect(mDevice, "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)
+ {
+ aluHandleDisconnect(mDevice, "Failed to start platback: 0x%08x", result);
+ break;
+ }
+
+ if(mRing->writeSpace() == 0)
+ {
+ dlock.unlock();
+ mSem.wait();
+ dlock.lock();
+ continue;
+ }
+ }
+
+ auto data = mRing->getWriteVector();
+ aluMixData(mDevice, data.first.buf,
+ static_cast<ALuint>(data.first.len*mDevice->UpdateSize));
+ if(data.second.len > 0)
+ aluMixData(mDevice, data.second.buf,
+ static_cast<ALuint>(data.second.len*mDevice->UpdateSize));
+
+ size_t todo{data.first.len + data.second.len};
+ mRing->writeAdvance(todo);
+
+ 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)
+ {
+ aluHandleDisconnect(mDevice, "Failed to queue audio: 0x%08x", result);
+ break;
+ }
+
+ data.first.len--;
+ data.first.buf += mDevice->UpdateSize*mFrameSize;
+ }
+ }
+
+ return 0;
+}
+
+
+void OpenSLPlayback::open(const ALCchar *name)
+{
+ if(!name)
+ name = opensl_device;
+ else if(strcmp(name, opensl_device) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE,
+ "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(mDevice);
+ 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 = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
+ 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 = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
+ SL_BYTEORDER_BIGENDIAN;
+
+ 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 ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
+ mRing = CreateRingBuffer(num_updates, mFrameSize*mDevice->UpdateSize, true);
+ }
+
+ if(SL_RESULT_SUCCESS != result)
+ {
+ if(mBufferQueueObj)
+ VCALL0(mBufferQueueObj,Destroy)();
+ mBufferQueueObj = nullptr;
+
+ return false;
+ }
+
+ return true;
+}
+
+bool OpenSLPlayback::start()
+{
+ mRing->reset();
+
+ SLAndroidSimpleBufferQueueItf bufferQueue;
+ SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &bufferQueue)};
+ PRINTERR(result, "bufferQueue->GetInterface");
+ if(SL_RESULT_SUCCESS != result)
+ return false;
+
+ result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
+ PRINTERR(result, "bufferQueue->RegisterCallback");
+ if(SL_RESULT_SUCCESS != result) return false;
+
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this);
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Could not create playback thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ return false;
+}
+
+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<OpenSLPlayback> _{*this};
+ 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 ALCchar *name) override;
+ bool start() override;
+ void stop() override;
+ ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
+ ALCuint availableSamples() override;
+
+ /* engine interfaces */
+ SLObjectItf mEngineObj{nullptr};
+ SLEngineItf mEngine;
+
+ /* recording interfaces */
+ SLObjectItf mRecordObj{nullptr};
+
+ RingBufferPtr mRing{nullptr};
+ ALCuint mSplOffset{0u};
+
+ ALuint 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 ALCchar* name)
+{
+ if(!name)
+ name = opensl_device;
+ else if(strcmp(name, opensl_device) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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 */
+ ALuint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
+ /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
+ ALuint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
+ mDevice->Frequency/100*5)};
+ ALuint num_updates{(length+update_len-1) / update_len};
+
+ mRing = CreateRingBuffer(num_updates, update_len*mFrameSize, false);
+
+ mDevice->UpdateSize = update_len;
+ mDevice->BufferSize = static_cast<ALuint>(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 = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
+ SL_BYTEORDER_BIGENDIAN;
+ 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 = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
+ SL_BYTEORDER_BIGENDIAN;
+
+ 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 ALuint 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{ALC_INVALID_VALUE,
+ "Failed to initialize OpenSL device: 0x%08x", result};
+ }
+
+ mDevice->DeviceName = name;
+}
+
+bool 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)
+ {
+ aluHandleDisconnect(mDevice, "Failed to start capture: 0x%08x", result);
+ return false;
+ }
+
+ return true;
+}
+
+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");
+ }
+}
+
+ALCenum OpenSLCapture::captureSamples(al::byte *buffer, ALCuint samples)
+{
+ 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)
+ {
+ aluHandleDisconnect(mDevice, "Failed to get capture buffer queue: 0x%08x", result);
+ bufferQueue = nullptr;
+ }
+ }
+
+ const ALuint update_size{mDevice->UpdateSize};
+ const ALuint chunk_size{update_size * mFrameSize};
+
+ /* Read the desired samples from the ring buffer then advance its read
+ * pointer.
+ */
+ auto data = mRing->getReadVector();
+ for(ALCuint i{0};i < samples;)
+ {
+ const ALCuint rem{minu(samples - i, update_size - mSplOffset)};
+ std::copy_n(data.first.buf + mSplOffset*mFrameSize, rem*mFrameSize, buffer + i*mFrameSize);
+
+ mSplOffset += rem;
+ if(mSplOffset == update_size)
+ {
+ /* Finished a chunk, reset the offset and advance the read pointer. */
+ mSplOffset = 0;
+ mRing->readAdvance(1);
+
+ if LIKELY(bufferQueue)
+ {
+ const SLresult result{VCALL(bufferQueue,Enqueue)(data.first.buf, chunk_size)};
+ PRINTERR(result, "bufferQueue->Enqueue");
+ if UNLIKELY(SL_RESULT_SUCCESS != result)
+ {
+ aluHandleDisconnect(mDevice, "Failed to update capture buffer: 0x%08x",
+ result);
+ bufferQueue = nullptr;
+ }
+ }
+
+ data.first.len--;
+ if(!data.first.len)
+ data.first = data.second;
+ else
+ data.first.buf += chunk_size;
+ }
+
+ i += rem;
+ }
+
+ return ALC_NO_ERROR;
+}
+
+ALCuint OpenSLCapture::availableSamples()
+{ return static_cast<ALuint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); }
+
+} // namespace
+
+bool OSLBackendFactory::init() { return true; }
+
+bool OSLBackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback || type == BackendType::Capture); }
+
+void OSLBackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ switch(type)
+ {
+ case DevProbe::Playback:
+ case DevProbe::Capture:
+ /* Includes null char. */
+ outnames->append(opensl_device, sizeof(opensl_device));
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/opensl.h b/alc/backends/opensl.h
new file mode 100644
index 00000000..809aa339
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_OSL_H */
diff --git a/alc/backends/oss.cpp b/alc/backends/oss.cpp
new file mode 100644
index 00000000..59cc44e4
--- /dev/null
+++ b/alc/backends/oss.cpp
@@ -0,0 +1,712 @@
+/**
+ * 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 "AL/al.h"
+
+#include "alcmain.h"
+#include "alconfig.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alu.h"
+#include "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;
+};
+
+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;
+
+
+#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, const char *handle, size_t hlen, const char *path, size_t plen)
+{
+#ifdef ALC_OSS_DEVNODE_TRUC
+ for(size_t 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;
+ }
+ }
+#endif
+ if(handle[0] == '\0')
+ {
+ handle = path;
+ hlen = plen;
+ }
+
+ std::string basename{handle, hlen};
+ basename.erase(std::find(basename.begin(), basename.end(), '\0'), basename.end());
+ std::string devname{path, plen};
+ devname.erase(std::find(devname.begin(), devname.end(), '\0'), devname.end());
+
+ auto iter = std::find_if(list->cbegin(), list->cend(),
+ [&devname](const DevMap &entry) -> bool
+ { return entry.device_name == devname; }
+ );
+ if(iter != list->cend())
+ return;
+
+ int count{1};
+ std::string newname{basename};
+ while(checkName(PlaybackDevices, 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;
+
+ const char *handle;
+ size_t len;
+ 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;
+ }
+
+ ALCossListAppend(devlist, handle, len, ai.devnode,
+ strnlen(ai.devnode, sizeof(ai.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
+
+ALCuint log2i(ALCuint x)
+{
+ ALCuint 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 ALCchar *name) override;
+ bool reset() override;
+ bool start() override;
+ void stop() override;
+
+ int mFd{-1};
+
+ al::vector<ALubyte> 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 ALuint frame_size{mDevice->frameSizeFromFmt()};
+
+ std::unique_lock<OSSPlayback> dlock{*this};
+ while(!mKillNow.load(std::memory_order_acquire) &&
+ mDevice->Connected.load(std::memory_order_acquire))
+ {
+ pollfd pollitem{};
+ pollitem.fd = mFd;
+ pollitem.events = POLLOUT;
+
+ dlock.unlock();
+ int pret{poll(&pollitem, 1, 1000)};
+ dlock.lock();
+ if(pret < 0)
+ {
+ if(errno == EINTR || errno == EAGAIN)
+ continue;
+ ERR("poll failed: %s\n", strerror(errno));
+ aluHandleDisconnect(mDevice, "Failed waiting for playback buffer: %s", strerror(errno));
+ break;
+ }
+ else if(pret == 0)
+ {
+ WARN("poll timeout\n");
+ continue;
+ }
+
+ ALubyte *write_ptr{mMixData.data()};
+ size_t to_write{mMixData.size()};
+ aluMixData(mDevice, write_ptr, static_cast<ALuint>(to_write/frame_size));
+ 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));
+ aluHandleDisconnect(mDevice, "Failed writing playback samples: %s",
+ strerror(errno));
+ break;
+ }
+
+ to_write -= static_cast<size_t>(wrote);
+ write_ptr += wrote;
+ }
+ }
+
+ return 0;
+}
+
+
+void OSSPlayback::open(const ALCchar *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{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ devname = iter->device_name.c_str();
+ }
+
+ mFd = ::open(devname, O_WRONLY);
+ if(mFd == -1)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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;
+ }
+
+ ALuint periods{mDevice->BufferSize / mDevice->UpdateSize};
+ ALuint numChannels{mDevice->channelsFromFmt()};
+ ALuint ossSpeed{mDevice->Frequency};
+ ALuint frameSize{numChannels * mDevice->bytesFromFmt()};
+ /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
+ ALuint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
+ ALuint 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<ALuint>(info.fragsize) / frameSize;
+ mDevice->BufferSize = static_cast<ALuint>(info.fragments) * mDevice->UpdateSize;
+
+ SetDefaultChannelOrder(mDevice);
+
+ mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
+
+ return true;
+}
+
+bool OSSPlayback::start()
+{
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this};
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Could not create playback thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ return false;
+}
+
+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 ALCchar *name) override;
+ bool start() override;
+ void stop() override;
+ ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
+ ALCuint 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 ALuint 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));
+ aluHandleDisconnect(mDevice, "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));
+ aluHandleDisconnect(mDevice, "Failed reading capture samples: %s",
+ strerror(errno));
+ break;
+ }
+ mRing->writeAdvance(static_cast<ALuint>(amt)/frame_size);
+ }
+ }
+
+ return 0;
+}
+
+
+void OSScapture::open(const ALCchar *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{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ devname = iter->device_name.c_str();
+ }
+
+ mFd = ::open(devname, O_RDONLY);
+ if(mFd == -1)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "%s capture samples not supported",
+ DevFmtTypeString(mDevice->FmtType)};
+ }
+
+ ALuint periods{4};
+ ALuint numChannels{mDevice->channelsFromFmt()};
+ ALuint frameSize{numChannels * mDevice->bytesFromFmt()};
+ ALuint ossSpeed{mDevice->Frequency};
+ /* according to the OSS spec, 16 bytes are the minimum */
+ ALuint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
+ ALuint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
+
+ audio_buf_info info{};
+#define CHECKERR(func) if((func) < 0) { \
+ throw al::backend_exception{ALC_INVALID_VALUE, #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{ALC_INVALID_VALUE,
+ "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{ALC_INVALID_VALUE,
+ "Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType),
+ ossFormat};
+
+ mRing = CreateRingBuffer(mDevice->BufferSize, frameSize, false);
+
+ mDevice->DeviceName = name;
+}
+
+bool OSScapture::start()
+{
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this};
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Could not create record thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ return false;
+}
+
+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));
+}
+
+ALCenum OSScapture::captureSamples(al::byte *buffer, ALCuint samples)
+{
+ mRing->read(buffer, samples);
+ return ALC_NO_ERROR;
+}
+
+ALCuint OSScapture::availableSamples()
+{ return static_cast<ALCuint>(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); }
+
+void OSSBackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ auto add_device = [outnames](const DevMap &entry) -> void
+ {
+#ifdef HAVE_STAT
+ struct stat buf;
+ if(stat(entry.device_name.c_str(), &buf) == 0)
+#endif
+ {
+ /* Includes null char. */
+ outnames->append(entry.name.c_str(), entry.name.length()+1);
+ }
+ };
+
+ switch(type)
+ {
+ case DevProbe::Playback:
+ PlaybackDevices.clear();
+ ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT);
+ std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+ break;
+
+ case DevProbe::Capture:
+ CaptureDevices.clear();
+ ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT);
+ std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/oss.h b/alc/backends/oss.h
new file mode 100644
index 00000000..9e63d7b6
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_OSS_H */
diff --git a/alc/backends/portaudio.cpp b/alc/backends/portaudio.cpp
new file mode 100644
index 00000000..1e3d0ce8
--- /dev/null
+++ b/alc/backends/portaudio.cpp
@@ -0,0 +1,447 @@
+/**
+ * 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 "alexcpt.h"
+#include "alu.h"
+#include "alconfig.h"
+#include "dynload.h"
+#include "ringbuffer.h"
+
+#include <portaudio.h>
+
+
+namespace {
+
+constexpr ALCchar 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 ALCchar *name) override;
+ bool reset() override;
+ bool start() override;
+ void stop() override;
+
+ PaStream *mStream{nullptr};
+ PaStreamParameters mParams{};
+ ALuint 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
+{
+ std::lock_guard<PortPlayback> _{*this};
+ aluMixData(mDevice, outputBuffer, static_cast<ALuint>(framesPerBuffer));
+ return 0;
+}
+
+
+void PortPlayback::open(const ALCchar *name)
+{
+ if(!name)
+ name = pa_device;
+ else if(strcmp(name, pa_device) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "Failed to open stream: %s",
+ Pa_GetErrorText(err)};
+ }
+
+ mDevice->DeviceName = name;
+}
+
+bool PortPlayback::reset()
+{
+ const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
+ mDevice->Frequency = static_cast<ALuint>(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(mDevice);
+
+ return true;
+}
+
+bool PortPlayback::start()
+{
+ PaError err{Pa_StartStream(mStream)};
+ if(err != paNoError)
+ {
+ ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err));
+ return false;
+ }
+ return true;
+}
+
+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 ALCchar *name) override;
+ bool start() override;
+ void stop() override;
+ ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
+ ALCuint 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 ALCchar *name)
+{
+ if(!name)
+ name = pa_device;
+ else if(strcmp(name, pa_device) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+
+ ALuint samples{mDevice->BufferSize};
+ samples = maxu(samples, 100 * mDevice->Frequency / 1000);
+ ALuint frame_size{mDevice->frameSizeFromFmt()};
+
+ mRing = CreateRingBuffer(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{ALC_INVALID_VALUE, "%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{ALC_INVALID_VALUE, "Failed to open stream: %s",
+ Pa_GetErrorText(err)};
+
+ mDevice->DeviceName = name;
+}
+
+
+bool PortCapture::start()
+{
+ PaError err{Pa_StartStream(mStream)};
+ if(err != paNoError)
+ {
+ ERR("Error starting stream: %s\n", Pa_GetErrorText(err));
+ return false;
+ }
+ return true;
+}
+
+void PortCapture::stop()
+{
+ PaError err{Pa_StopStream(mStream)};
+ if(err != paNoError)
+ ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
+}
+
+
+ALCuint PortCapture::availableSamples()
+{ return static_cast<ALCuint>(mRing->readSpace()); }
+
+ALCenum PortCapture::captureSamples(al::byte *buffer, ALCuint samples)
+{
+ mRing->read(buffer, samples);
+ return ALC_NO_ERROR;
+}
+
+} // 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); }
+
+void PortBackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ switch(type)
+ {
+ case DevProbe::Playback:
+ case DevProbe::Capture:
+ /* Includes null char. */
+ outnames->append(pa_device, sizeof(pa_device));
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/portaudio.h b/alc/backends/portaudio.h
new file mode 100644
index 00000000..082e9020
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_PORTAUDIO_H */
diff --git a/alc/backends/pulseaudio.cpp b/alc/backends/pulseaudio.cpp
new file mode 100644
index 00000000..4e46460b
--- /dev/null
+++ b/alc/backends/pulseaudio.cpp
@@ -0,0 +1,1507 @@
+/**
+ * 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 "alexcpt.h"
+#include "compat.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_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_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
+ }
+};
+
+size_t ChannelFromPulse(pa_channel_position_t chan)
+{
+ switch(chan)
+ {
+ case PA_CHANNEL_POSITION_INVALID: break;
+ case PA_CHANNEL_POSITION_MONO: return FrontCenter;
+ case PA_CHANNEL_POSITION_FRONT_LEFT: return FrontLeft;
+ case PA_CHANNEL_POSITION_FRONT_RIGHT: return FrontRight;
+ case PA_CHANNEL_POSITION_FRONT_CENTER: return FrontCenter;
+ case PA_CHANNEL_POSITION_REAR_CENTER: return BackCenter;
+ case PA_CHANNEL_POSITION_REAR_LEFT: return BackLeft;
+ case PA_CHANNEL_POSITION_REAR_RIGHT: return BackRight;
+ case PA_CHANNEL_POSITION_LFE: return 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 SideLeft;
+ case PA_CHANNEL_POSITION_SIDE_RIGHT: return SideRight;
+ case PA_CHANNEL_POSITION_AUX0: return Aux0;
+ case PA_CHANNEL_POSITION_AUX1: return Aux1;
+ case PA_CHANNEL_POSITION_AUX2: return Aux2;
+ case PA_CHANNEL_POSITION_AUX3: return Aux3;
+ case PA_CHANNEL_POSITION_AUX4: return Aux4;
+ case PA_CHANNEL_POSITION_AUX5: return Aux5;
+ case PA_CHANNEL_POSITION_AUX6: return Aux6;
+ case PA_CHANNEL_POSITION_AUX7: return Aux7;
+ case PA_CHANNEL_POSITION_AUX8: return Aux8;
+ case PA_CHANNEL_POSITION_AUX9: return Aux9;
+ case PA_CHANNEL_POSITION_AUX10: return Aux10;
+ case PA_CHANNEL_POSITION_AUX11: return Aux11;
+ case PA_CHANNEL_POSITION_AUX12: return Aux12;
+ case PA_CHANNEL_POSITION_AUX13: return Aux13;
+ case PA_CHANNEL_POSITION_AUX14: return Aux14;
+ case PA_CHANNEL_POSITION_AUX15: return Aux15;
+ 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: break;
+ case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return UpperFrontLeft;
+ case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return UpperFrontRight;
+ case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: break;
+ case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return UpperBackLeft;
+ case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return UpperBackRight;
+ case PA_CHANNEL_POSITION_TOP_REAR_CENTER: break;
+ case PA_CHANNEL_POSITION_MAX: break;
+ }
+ throw al::backend_exception{ALC_INVALID_VALUE, "Unexpected channel enum %d", chan};
+}
+
+void SetChannelOrderFromMap(ALCdevice *device, const pa_channel_map &chanmap)
+{
+ device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
+ for(ALuint i{0};i < chanmap.channels;++i)
+ device->RealOut.ChannelIndex[ChannelFromPulse(chanmap.map[i])] = 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;
+}
+
+
+/* Global flags and properties */
+pa_context_flags_t pulse_ctx_flags;
+
+int pulse_poll_func(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;
+}
+
+class PulseMainloop {
+ std::thread mThread;
+ std::mutex mMutex;
+ std::condition_variable mCondVar;
+ pa_mainloop *mMainloop{nullptr};
+
+public:
+ ~PulseMainloop()
+ {
+ if(mThread.joinable())
+ {
+ pa_mainloop_quit(mMainloop, 0);
+ mThread.join();
+ }
+ }
+
+ int mainloop_thread()
+ {
+ SetRTPriority();
+
+ std::unique_lock<std::mutex> plock{mMutex};
+ mMainloop = pa_mainloop_new();
+
+ pa_mainloop_set_poll_func(mMainloop, pulse_poll_func, &plock);
+ mCondVar.notify_all();
+
+ int ret{};
+ pa_mainloop_run(mMainloop, &ret);
+
+ pa_mainloop_free(mMainloop);
+ mMainloop = nullptr;
+
+ return ret;
+ }
+
+ void doLock() { mMutex.lock(); }
+ void doUnlock() { mMutex.unlock(); }
+ std::unique_lock<std::mutex> getLock() { 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)
+ {
+ while(pa_operation_get_state(op) == PA_OPERATION_RUNNING)
+ mCondVar.wait(plock);
+ 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);
+};
+
+
+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_thread), this};
+ while(!mMainloop) mCondVar.wait(plock);
+ }
+
+ pa_context *context{pa_context_new(pa_mainloop_get_api(mMainloop), name)};
+ if(!context) throw al::backend_exception{ALC_OUT_OF_MEMORY, "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{ALC_INVALID_VALUE, "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{ALC_OUT_OF_MEMORY, "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{ALC_INVALID_VALUE, "%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{ALC_INVALID_VALUE, "%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);
+}
+
+
+/* Used for initial connection test and enumeration. */
+PulseMainloop gGlobalMainloop;
+
+
+struct DevMap {
+ std::string name;
+ std::string device_name;
+};
+
+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();
+}
+
+al::vector<DevMap> PlaybackDevices;
+al::vector<DevMap> CaptureDevices;
+
+
+void device_sink_callback(pa_context*, const pa_sink_info *info, int eol, void *pdata) noexcept
+{
+ if(eol)
+ {
+ static_cast<PulseMainloop*>(pdata)->getCondVar().notify_all();
+ return;
+ }
+
+ /* Skip this device is if it's already in the list. */
+ if(std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
+ [info](const DevMap &entry) -> bool
+ { return entry.device_name == info->name; }
+ ) != 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());
+}
+
+void probePlaybackDevices(PulseMainloop &mainloop)
+{
+ pa_context *context{};
+ pa_stream *stream{};
+
+ PlaybackDevices.clear();
+ try {
+ auto plock = mainloop.getLock();
+
+ context = mainloop.connectContext(plock);
+
+ constexpr pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE |
+ PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE | PA_STREAM_START_CORKED};
+
+ pa_sample_spec spec{};
+ spec.format = PA_SAMPLE_S16NE;
+ spec.rate = 44100;
+ spec.channels = 2;
+
+ stream = mainloop.connectStream(nullptr, plock, context, flags, nullptr, &spec, nullptr,
+ BackendType::Playback);
+ pa_operation *op{pa_context_get_sink_info_by_name(context,
+ pa_stream_get_device_name(stream), device_sink_callback, &mainloop)};
+ mainloop.waitForOperation(op, plock);
+
+ pa_stream_disconnect(stream);
+ pa_stream_unref(stream);
+ stream = nullptr;
+
+ op = pa_context_get_sink_info_list(context, device_sink_callback, &mainloop);
+ mainloop.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) mainloop.close(context, stream);
+ }
+}
+
+
+void device_source_callback(pa_context*, const pa_source_info *info, int eol, void *pdata) noexcept
+{
+ if(eol)
+ {
+ static_cast<PulseMainloop*>(pdata)->getCondVar().notify_all();
+ return;
+ }
+
+ /* Skip this device is if it's already in the list. */
+ if(std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
+ [info](const DevMap &entry) -> bool
+ { return entry.device_name == info->name; }
+ ) != 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());
+}
+
+void probeCaptureDevices(PulseMainloop &mainloop)
+{
+ pa_context *context{};
+ pa_stream *stream{};
+
+ CaptureDevices.clear();
+ try {
+ auto plock = mainloop.getLock();
+
+ context = mainloop.connectContext(plock);
+
+ constexpr pa_stream_flags_t flags{PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE |
+ PA_STREAM_FIX_CHANNELS | PA_STREAM_DONT_MOVE | PA_STREAM_START_CORKED};
+
+ pa_sample_spec spec{};
+ spec.format = PA_SAMPLE_S16NE;
+ spec.rate = 44100;
+ spec.channels = 1;
+
+ stream = mainloop.connectStream(nullptr, plock, context, flags, nullptr, &spec, nullptr,
+ BackendType::Capture);
+ pa_operation *op{pa_context_get_source_info_by_name(context,
+ pa_stream_get_device_name(stream), device_source_callback, &mainloop)};
+ mainloop.waitForOperation(op, plock);
+
+ pa_stream_disconnect(stream);
+ pa_stream_unref(stream);
+ stream = nullptr;
+
+ op = pa_context_get_source_info_list(context, device_source_callback, &mainloop);
+ mainloop.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) mainloop.close(context, stream);
+ }
+}
+
+
+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 ALCchar *name) override;
+ bool reset() override;
+ bool start() override;
+ void stop() override;
+ ClockLatency getClockLatency() override;
+ void lock() override { mMainloop.doLock(); }
+ void unlock() override { mMainloop.doUnlock(); }
+
+ PulseMainloop mMainloop;
+
+ std::string mDeviceName;
+
+ pa_buffer_attr mAttr;
+ pa_sample_spec mSpec;
+
+ pa_stream *mStream{nullptr};
+ pa_context *mContext{nullptr};
+
+ ALuint 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");
+ aluHandleDisconnect(mDevice, "Playback stream failure");
+ }
+ mMainloop.getCondVar().notify_all();
+}
+
+void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept
+{
+ void *buf{pa_xmalloc(nbytes)};
+ aluMixData(mDevice, buf, static_cast<ALuint>(nbytes/mFrameSize));
+
+ int ret{pa_stream_write(stream, buf, nbytes, pa_xfree, 0, PA_SEEK_RELATIVE)};
+ if UNLIKELY(ret != PA_OK)
+ ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret));
+}
+
+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.get<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 ALCchar *name)
+{
+ const char *pulse_name{nullptr};
+ const char *dev_name{nullptr};
+
+ if(name)
+ {
+ if(PlaybackDevices.empty())
+ probePlaybackDevices(mMainloop);
+
+ 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{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ pulse_name = iter->device_name.c_str();
+ dev_name = iter->name.c_str();
+ }
+
+ auto plock = mMainloop.getLock();
+
+ 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<ALuint>(pa_frame_size(pa_stream_get_sample_spec(mStream)));
+
+ mDeviceName = pa_stream_get_device_name(mStream);
+ if(!dev_name)
+ {
+ pa_operation *op{pa_context_get_sink_info_by_name(mContext, mDeviceName.c_str(),
+ &PulsePlayback::sinkNameCallbackC, this)};
+ mMainloop.waitForOperation(op, plock);
+ }
+ else
+ mDevice->DeviceName = dev_name;
+}
+
+bool PulsePlayback::reset()
+{
+ auto plock = mMainloop.getLock();
+
+ 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, mDeviceName.c_str(),
+ &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.get<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{ALC_INVALID_VALUE, "Invalid sample spec"};
+
+ const ALuint frame_size{static_cast<ALuint>(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(mDeviceName.c_str(), 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<ALuint>(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 ALuint perlen{static_cast<ALuint>(clampd(scale*mDevice->UpdateSize + 0.5, 64.0,
+ 8192.0))};
+ const ALuint buflen{static_cast<ALuint>(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;
+}
+
+bool PulsePlayback::start()
+{
+ auto plock = mMainloop.getLock();
+
+ pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this);
+ pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC,
+ &mMainloop)};
+ mMainloop.waitForOperation(op, plock);
+
+ return true;
+}
+
+void PulsePlayback::stop()
+{
+ auto plock = mMainloop.getLock();
+
+ 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 _ = mMainloop.getLock();
+ ret.ClockTime = GetDeviceClockTime(mDevice);
+ err = pa_stream_get_latency(mStream, &latency, &neg);
+ }
+
+ 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 = 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 ALCchar *name) override;
+ bool start() override;
+ void stop() override;
+ ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
+ ALCuint availableSamples() override;
+ ClockLatency getClockLatency() override;
+ void lock() override { mMainloop.doLock(); }
+ void unlock() override { mMainloop.doUnlock(); }
+
+ PulseMainloop mMainloop;
+
+ std::string mDeviceName;
+
+ ALCuint 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");
+ aluHandleDisconnect(mDevice, "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 ALCchar *name)
+{
+ const char *pulse_name{nullptr};
+ if(name)
+ {
+ if(CaptureDevices.empty())
+ probeCaptureDevices(mMainloop);
+
+ 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{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+ pulse_name = iter->device_name.c_str();
+ mDevice->DeviceName = iter->name;
+ }
+
+ auto plock = mMainloop.getLock();
+
+ 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{ALC_INVALID_VALUE, "%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{ALC_INVALID_VALUE, "%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{ALC_INVALID_VALUE, "Invalid sample format"};
+
+ const ALuint frame_size{static_cast<ALuint>(pa_frame_size(&mSpec))};
+ const ALuint 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 = pa_stream_get_device_name(mStream);
+ if(mDevice->DeviceName.empty())
+ {
+ pa_operation *op{pa_context_get_source_info_by_name(mContext, mDeviceName.c_str(),
+ &PulseCapture::sourceNameCallbackC, this)};
+ mMainloop.waitForOperation(op, plock);
+ }
+}
+
+bool PulseCapture::start()
+{
+ auto plock = mMainloop.getLock();
+ pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC,
+ &mMainloop)};
+ mMainloop.waitForOperation(op, plock);
+ return true;
+}
+
+void PulseCapture::stop()
+{
+ auto plock = mMainloop.getLock();
+ pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC,
+ &mMainloop)};
+ mMainloop.waitForOperation(op, plock);
+}
+
+ALCenum PulseCapture::captureSamples(al::byte *buffer, ALCuint 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<ALCuint>(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.getLock();
+ 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))
+ {
+ aluHandleDisconnect(mDevice, "Bad capture state: %u", state);
+ break;
+ }
+ const void *capbuf;
+ size_t caplen;
+ if UNLIKELY(pa_stream_peek(mStream, &capbuf, &caplen) < 0)
+ {
+ aluHandleDisconnect(mDevice, "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);
+
+ return ALC_NO_ERROR;
+}
+
+ALCuint PulseCapture::availableSamples()
+{
+ size_t readable{mCapBuffer.size()};
+
+ if(mDevice->Connected.load(std::memory_order_acquire))
+ {
+ auto _ = mMainloop.getLock();
+ 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);
+ aluHandleDisconnect(mDevice, "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<ALCuint>::max());
+ mLastReadable = std::max(mLastReadable, static_cast<ALCuint>(readable));
+ return mLastReadable / static_cast<ALCuint>(pa_frame_size(&mSpec));
+}
+
+
+ClockLatency PulseCapture::getClockLatency()
+{
+ ClockLatency ret;
+ pa_usec_t latency;
+ int neg, err;
+
+ {
+ auto _ = mMainloop.getLock();
+ 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.getLock();
+ 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; }
+
+void PulseBackendFactory::probe(DevProbe 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 DevProbe::Playback:
+ probePlaybackDevices(gGlobalMainloop);
+ std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+ break;
+
+ case DevProbe::Capture:
+ probeCaptureDevices(gGlobalMainloop);
+ std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/pulseaudio.h b/alc/backends/pulseaudio.h
new file mode 100644
index 00000000..40f3e305
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_PULSEAUDIO_H */
diff --git a/Alc/backends/qsa.c b/alc/backends/qsa.cpp
index 81645096..5ed65798 100644
--- a/Alc/backends/qsa.c
+++ b/alc/backends/qsa.cpp
@@ -20,49 +20,56 @@
#include "config.h"
+#include "backends/qsa.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 <poll.h>
-#include "alMain.h"
+#include <thread>
+#include <memory>
+#include <algorithm>
+
+#include "alcmain.h"
+#include "alexcpt.h"
#include "alu.h"
#include "threads.h"
-#include "backends/base.h"
+#include <sys/asoundlib.h>
+#include <sys/neutrino.h>
-typedef struct {
- snd_pcm_t* pcmHandle;
- int audio_fd;
+namespace {
- snd_pcm_channel_setup_t csetup;
- snd_pcm_channel_params_t cparams;
+struct qsa_data {
+ snd_pcm_t* pcmHandle{nullptr};
+ int audio_fd{-1};
- ALvoid* buffer;
- ALsizei size;
+ snd_pcm_channel_setup_t csetup{};
+ snd_pcm_channel_params_t cparams{};
- ATOMIC(ALenum) killNow;
- althrd_t thread;
-} qsa_data;
+ ALvoid* buffer{nullptr};
+ ALsizei size{0};
-typedef struct {
+ std::atomic<ALenum> mKillNow{AL_TRUE};
+ std::thread mThread;
+};
+
+struct DevMap {
ALCchar* name;
int card;
int dev;
-} DevMap;
-TYPEDEF_VECTOR(DevMap, vector_DevMap)
+};
-static vector_DevMap DeviceNameMap;
-static vector_DevMap CaptureNameMap;
+al::vector<DevMap> DeviceNameMap;
+al::vector<DevMap> CaptureNameMap;
-static const ALCchar qsaDevice[] = "QSA Default";
+constexpr ALCchar qsaDevice[] = "QSA Default";
-static const struct {
+constexpr struct {
int32_t format;
} formatlist[] = {
{SND_PCM_SFMT_FLOAT_LE},
@@ -75,7 +82,7 @@ static const struct {
{0},
};
-static const struct {
+constexpr struct {
int32_t rate;
} ratelist[] = {
{192000},
@@ -94,7 +101,7 @@ static const struct {
{0},
};
-static const struct {
+constexpr struct {
int32_t channels;
} channellist[] = {
{8},
@@ -106,28 +113,29 @@ static const struct {
{0},
};
-static void deviceList(int type, vector_DevMap *devmap)
+void deviceList(int type, al::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;
+ snd_ctl_hw_info info;
max_cards = snd_cards();
if(max_cards < 0)
return;
-#define FREE_NAME(iter) free((iter)->name)
- VECTOR_FOR_EACH(DevMap, *devmap, FREE_NAME);
-#undef FREE_NAME
- VECTOR_RESIZE(*devmap, 0, max_cards+1);
+ std::for_each(devmap->begin(), devmap->end(),
+ [](const DevMap &entry) -> void
+ { free(entry.name); }
+ );
+ devmap->clear();
entry.name = strdup(qsaDevice);
entry.card = 0;
entry.dev = 0;
- VECTOR_PUSH_BACK(*devmap, entry);
+ devmap->push_back(entry);
for(card = 0;card < max_cards;card++)
{
@@ -153,7 +161,7 @@ static void deviceList(int type, vector_DevMap *devmap)
entry.card = card;
entry.dev = dev;
- VECTOR_PUSH_BACK(*devmap, entry);
+ devmap->push_back(entry);
TRACE("Got device \"%s\", card %d, dev %d\n", name, card, dev);
}
}
@@ -163,79 +171,71 @@ static void deviceList(int type, vector_DevMap *devmap)
/* 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);
+struct PlaybackWrapper final : public BackendBase {
+ PlaybackWrapper(ALCdevice *device) noexcept : BackendBase{device} { }
+ ~PlaybackWrapper() override;
+
+ void open(const ALCchar *name) override;
+ bool reset() override;
+ bool start() override;
+ void stop() override;
+
+ std::unique_ptr<qsa_data> mExtraData;
+
+ DEF_NEWDEL(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;
+ PlaybackWrapper *self = static_cast<PlaybackWrapper*>(ptr);
+ ALCdevice *device = self->mDevice;
+ qsa_data *data = self->mExtraData.get();
snd_pcm_channel_status_t status;
- struct sched_param param;
- struct timeval timeout;
+ sched_param param;
char* write_ptr;
- fd_set wfds;
ALint len;
int sret;
SetRTPriority();
- althrd_setname(althrd_current(), MIXER_THREAD_NAME);
+ althrd_setname(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
- );
+ const ALint frame_size = device->frameSizeFromFmt();
- V0(device->Backend,lock)();
- while(!ATOMIC_LOAD(&data->killNow, almemory_order_acquire))
+ std::unique_lock<PlaybackWrapper> dlock{*self};
+ while(!data->mKillNow.load(std::memory_order_acquire))
{
- FD_ZERO(&wfds);
- FD_SET(data->audio_fd, &wfds);
- timeout.tv_sec=2;
- timeout.tv_usec=0;
+ pollfd pollitem{};
+ pollitem.fd = data->audio_fd;
+ pollitem.events = POLLOUT;
/* 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)();
+ dlock.unlock();
+ sret = poll(&pollitem, 1, 2000);
+ dlock.lock();
if(sret == -1)
{
- ERR("select error: %s\n", strerror(errno));
+ if(errno == EINTR || errno == EAGAIN)
+ continue;
+ ERR("poll error: %s\n", strerror(errno));
aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno));
break;
}
if(sret == 0)
{
- ERR("select timeout\n");
+ ERR("poll timeout\n");
continue;
}
len = data->size;
- write_ptr = data->buffer;
+ write_ptr = static_cast<char*>(data->buffer);
aluMixData(device, write_ptr, len/frame_size);
- while(len>0 && !ATOMIC_LOAD(&data->killNow, almemory_order_acquire))
+ while(len>0 && !data->mKillNow.load(std::memory_order_acquire))
{
int wrote = snd_pcm_plugin_write(data->pcmHandle, write_ptr, len);
if(wrote <= 0)
@@ -266,7 +266,6 @@ FORCE_ALIGN static int qsa_proc_playback(void *ptr)
}
}
}
- V0(device->Backend,unlock)();
return 0;
}
@@ -277,15 +276,12 @@ FORCE_ALIGN static int qsa_proc_playback(void *ptr)
static ALCenum qsa_open_playback(PlaybackWrapper *self, const ALCchar* deviceName)
{
- ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
- qsa_data *data;
+ ALCdevice *device = self->mDevice;
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);
+ std::unique_ptr<qsa_data> data{new qsa_data{}};
+ data->mKillNow.store(AL_TRUE, std::memory_order_relaxed);
if(!deviceName)
deviceName = qsaDevice;
@@ -294,46 +290,38 @@ static ALCenum qsa_open_playback(PlaybackWrapper *self, const ALCchar* deviceNam
status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_PLAYBACK);
else
{
- const DevMap *iter;
-
- if(VECTOR_SIZE(DeviceNameMap) == 0)
+ if(DeviceNameMap.empty())
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);
+ auto iter = std::find_if(DeviceNameMap.begin(), DeviceNameMap.end(),
+ [deviceName](const DevMap &entry) -> bool
+ { return entry.name && strcmp(deviceName, entry.name) == 0; }
+ );
+ if(iter == DeviceNameMap.cend())
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;
+ device->DeviceName = deviceName;
+ self->mExtraData = std::move(data);
return ALC_NO_ERROR;
}
static void qsa_close_playback(PlaybackWrapper *self)
{
- qsa_data *data = self->ExtraData;
+ qsa_data *data = self->mExtraData.get();
if (data->buffer!=NULL)
{
@@ -342,15 +330,14 @@ static void qsa_close_playback(PlaybackWrapper *self)
}
snd_pcm_close(data->pcmHandle);
- free(data);
- self->ExtraData = NULL;
+ self->mExtraData = nullptr;
}
static ALCboolean qsa_reset_playback(PlaybackWrapper *self)
{
- ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
- qsa_data *data = self->ExtraData;
+ ALCdevice *device = self->mDevice;
+ qsa_data *data = self->mExtraData.get();
int32_t format=-1;
switch(device->FmtType)
@@ -391,14 +378,13 @@ static ALCboolean qsa_reset_playback(PlaybackWrapper *self)
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.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt();
+ data->cparams.buf.block.frags_max=device->BufferSize / device->UpdateSize;
+ data->cparams.buf.block.frags_min=data->cparams.buf.block.frags_max;
data->cparams.format.interleave=1;
data->cparams.format.rate=device->Frequency;
- data->cparams.format.voices=ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
+ data->cparams.format.voices=device->channelsFromFmt();
data->cparams.format.format=format;
if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0)
@@ -581,8 +567,7 @@ static ALCboolean qsa_reset_playback(PlaybackWrapper *self)
SetDefaultChannelOrder(device);
- device->UpdateSize=data->csetup.buf.block.frag_size/
- FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
+ device->UpdateSize=data->csetup.buf.block.frag_size / device->frameSizeFromFmt();
device->NumUpdates=data->csetup.buf.block.frags;
data->size=data->csetup.buf.block.frag_size;
@@ -597,101 +582,84 @@ static ALCboolean qsa_reset_playback(PlaybackWrapper *self)
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;
+ qsa_data *data = self->mExtraData.get();
- return ALC_TRUE;
+ try {
+ data->mKillNow.store(AL_FALSE, std::memory_order_release);
+ data->mThread = std::thread(qsa_proc_playback, self);
+ return ALC_TRUE;
+ }
+ catch(std::exception& e) {
+ ERR("Could not create playback thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ return ALC_FALSE;
}
static void qsa_stop_playback(PlaybackWrapper *self)
{
- qsa_data *data = self->ExtraData;
- int res;
+ qsa_data *data = self->mExtraData.get();
- if(ATOMIC_EXCHANGE(&data->killNow, AL_TRUE, almemory_order_acq_rel))
+ if(data->mKillNow.exchange(AL_TRUE, std::memory_order_acq_rel) || !data->mThread.joinable())
return;
- althrd_join(data->thread, &res);
+ data->mThread.join();
}
-static void PlaybackWrapper_Construct(PlaybackWrapper *self, ALCdevice *device)
+PlaybackWrapper::~PlaybackWrapper()
{
- ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
- SET_VTABLE2(PlaybackWrapper, ALCbackend, self);
-
- self->ExtraData = NULL;
+ if(mExtraData)
+ qsa_close_playback(this);
}
-static void PlaybackWrapper_Destruct(PlaybackWrapper *self)
+void PlaybackWrapper::open(const ALCchar *name)
{
- if(self->ExtraData)
- qsa_close_playback(self);
-
- ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
+ if(auto err = qsa_open_playback(this, name))
+ throw al::backend_exception{ALC_INVALID_VALUE, "%d", err};
}
-static ALCenum PlaybackWrapper_open(PlaybackWrapper *self, const ALCchar *name)
+bool PlaybackWrapper::reset()
{
- return qsa_open_playback(self, name);
+ if(!qsa_reset_playback(this))
+ throw al::backend_exception{ALC_INVALID_VALUE, ""};
+ return true;
}
-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);
-}
+bool PlaybackWrapper::start()
+{ return qsa_start_playback(this); }
+void PlaybackWrapper::stop()
+{ qsa_stop_playback(this); }
/***********/
/* 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);
+struct CaptureWrapper final : public BackendBase {
+ CaptureWrapper(ALCdevice *device) noexcept : BackendBase{device} { }
+ ~CaptureWrapper() override;
+ void open(const ALCchar *name) override;
+ bool start() override;
+ void stop() override;
+ ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
+ ALCuint availableSamples() override;
+
+ std::unique_ptr<qsa_data> mExtraData;
+
+ DEF_NEWDEL(CaptureWrapper)
+};
static ALCenum qsa_open_capture(CaptureWrapper *self, const ALCchar *deviceName)
{
- ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
- qsa_data *data;
+ ALCdevice *device = self->mDevice;
int card, dev;
int format=-1;
int status;
- data=(qsa_data*)calloc(1, sizeof(qsa_data));
- if (data==NULL)
- {
- return ALC_OUT_OF_MEMORY;
- }
+ std::unique_ptr<qsa_data> data{new qsa_data{}};
if(!deviceName)
deviceName = qsaDevice;
@@ -700,39 +668,30 @@ static ALCenum qsa_open_capture(CaptureWrapper *self, const ALCchar *deviceName)
status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_CAPTURE);
else
{
- const DevMap *iter;
-
- if(VECTOR_SIZE(CaptureNameMap) == 0)
+ if(CaptureNameMap.empty())
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);
+ auto iter = std::find_if(CaptureNameMap.cbegin(), CaptureNameMap.cend(),
+ [deviceName](const DevMap &entry) -> bool
+ { return entry.name && strcmp(deviceName, entry.name) == 0; }
+ );
+ if(iter == CaptureNameMap.cend())
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;
+ device->DeviceName = deviceName;
switch (device->FmtType)
{
@@ -771,41 +730,40 @@ static ALCenum qsa_open_capture(CaptureWrapper *self, const ALCchar *deviceName)
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.frag_size=device->UpdateSize * device->frameSizeFromFmt();
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.voices=device->channelsFromFmt();
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;
}
+ self->mExtraData = std::move(data);
+
return ALC_NO_ERROR;
}
static void qsa_close_capture(CaptureWrapper *self)
{
- qsa_data *data = self->ExtraData;
+ qsa_data *data = self->mExtraData.get();
- if (data->pcmHandle!=NULL)
+ if (data->pcmHandle!=nullptr)
snd_pcm_close(data->pcmHandle);
+ data->pcmHandle = nullptr;
- free(data);
- self->ExtraData = NULL;
+ self->mExtraData = nullptr;
}
static void qsa_start_capture(CaptureWrapper *self)
{
- qsa_data *data = self->ExtraData;
+ qsa_data *data = self->mExtraData.get();
int rstatus;
if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
@@ -827,16 +785,16 @@ static void qsa_start_capture(CaptureWrapper *self)
static void qsa_stop_capture(CaptureWrapper *self)
{
- qsa_data *data = self->ExtraData;
+ qsa_data *data = self->mExtraData.get();
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;
+ ALCdevice *device = self->mDevice;
+ qsa_data *data = self->mExtraData.get();
snd_pcm_channel_status_t status;
- ALint frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
+ ALint frame_size = device->frameSizeFromFmt();
ALint free_size;
int rstatus;
@@ -865,30 +823,27 @@ static ALCuint qsa_available_samples(CaptureWrapper *self)
static ALCenum qsa_capture_samples(CaptureWrapper *self, ALCvoid *buffer, ALCuint samples)
{
- ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
- qsa_data *data = self->ExtraData;
+ ALCdevice *device = self->mDevice;
+ qsa_data *data = self->mExtraData.get();
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 frame_size=device->frameSizeFromFmt();
ALint len=samples*frame_size;
int rstatus;
- read_ptr=buffer;
+ read_ptr = static_cast<char*>(buffer);
while (len>0)
{
- FD_ZERO(&rfds);
- FD_SET(data->audio_fd, &rfds);
- timeout.tv_sec=2;
- timeout.tv_usec=0;
+ pollfd pollitem{};
+ pollitem.fd = data->audio_fd;
+ pollitem.events = POLLOUT;
/* Select also works like time slice to OS */
bytes_read=0;
- selectret=select(data->audio_fd+1, &rfds, NULL, NULL, &timeout);
+ selectret = poll(&pollitem, 1, 2000);
switch (selectret)
{
case -1:
@@ -897,11 +852,7 @@ static ALCenum qsa_capture_samples(CaptureWrapper *self, ALCvoid *buffer, ALCuin
case 0:
break;
default:
- if (FD_ISSET(data->audio_fd, &rfds))
- {
- bytes_read=snd_pcm_plugin_read(data->pcmHandle, read_ptr, len);
- break;
- }
+ bytes_read=snd_pcm_plugin_read(data->pcmHandle, read_ptr, len);
break;
}
@@ -941,128 +892,72 @@ static ALCenum qsa_capture_samples(CaptureWrapper *self, ALCvoid *buffer, ALCuin
}
-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)
+CaptureWrapper::~CaptureWrapper()
{
- qsa_start_capture(self);
- return ALC_TRUE;
+ if(mExtraData)
+ qsa_close_capture(this);
}
-static void CaptureWrapper_stop(CaptureWrapper *self)
+void CaptureWrapper::open(const ALCchar *name)
{
- qsa_stop_capture(self);
+ if(auto err = qsa_open_capture(this, name))
+ throw al::backend_exception{ALC_INVALID_VALUE, "%d", err};
}
-static ALCenum CaptureWrapper_captureSamples(CaptureWrapper *self, void *buffer, ALCuint samples)
-{
- return qsa_capture_samples(self, buffer, samples);
-}
+bool CaptureWrapper::start()
+{ qsa_start_capture(this); return true; }
-static ALCuint CaptureWrapper_availableSamples(CaptureWrapper *self)
-{
- return qsa_available_samples(self);
-}
+void CaptureWrapper::stop()
+{ qsa_stop_capture(this); }
+ALCenum CaptureWrapper::captureSamples(al::byte *buffer, ALCuint samples)
+{ return qsa_capture_samples(this, buffer, samples); }
-typedef struct ALCqsaBackendFactory {
- DERIVE_FROM_TYPE(ALCbackendFactory);
-} ALCqsaBackendFactory;
-#define ALCQSABACKENDFACTORY_INITIALIZER { { GET_VTABLE2(ALCqsaBackendFactory, ALCbackendFactory) } }
+ALCuint CaptureWrapper::availableSamples()
+{ return qsa_available_samples(this); }
-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, al_string *outnames);
-static ALCbackend* ALCqsaBackendFactory_createBackend(ALCqsaBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type);
-DEFINE_ALCBACKENDFACTORY_VTABLE(ALCqsaBackendFactory);
+} // namespace
-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);
+bool QSABackendFactory::init()
+{ return true; }
- VECTOR_FOR_EACH(DevMap, CaptureNameMap, FREE_NAME);
- VECTOR_DEINIT(CaptureNameMap);
-#undef FREE_NAME
-}
+bool QSABackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback || type == BackendType::Capture); }
-static ALCboolean ALCqsaBackendFactory_querySupport(ALCqsaBackendFactory* UNUSED(self), ALCbackend_Type type)
+void QSABackendFactory::probe(DevProbe type, std::string *outnames)
{
- if(type == ALCbackend_Playback || type == ALCbackend_Capture)
- return ALC_TRUE;
- return ALC_FALSE;
-}
+ auto add_device = [outnames](const DevMap &entry) -> void
+ {
+ const char *n = entry.name;
+ if(n && n[0])
+ outnames->append(n, strlen(n)+1);
+ };
-static void ALCqsaBackendFactory_probe(ALCqsaBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
-{
switch (type)
{
-#define APPEND_OUTNAME(e) do { \
- const char *n_ = (e)->name; \
- if(n_ && n_[0]) \
- alstr_append_range(outnames, n_, n_+strlen(n_)+1); \
-} while(0)
- case ALL_DEVICE_PROBE:
+ case DevProbe::Playback:
deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap);
- VECTOR_FOR_EACH(const DevMap, DeviceNameMap, APPEND_OUTNAME);
+ std::for_each(DeviceNameMap.cbegin(), DeviceNameMap.cend(), add_device);
break;
-
- case CAPTURE_DEVICE_PROBE:
+ case DevProbe::Capture:
deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap);
- VECTOR_FOR_EACH(const DevMap, CaptureNameMap, APPEND_OUTNAME);
+ std::for_each(CaptureNameMap.cbegin(), CaptureNameMap.cend(), add_device);
break;
-#undef APPEND_OUTNAME
}
}
-static ALCbackend* ALCqsaBackendFactory_createBackend(ALCqsaBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
+BackendPtr QSABackendFactory::createBackend(ALCdevice *device, BackendType 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;
+ if(type == BackendType::Playback)
+ return BackendPtr{new PlaybackWrapper{device}};
+ if(type == BackendType::Capture)
+ return BackendPtr{new CaptureWrapper{device}};
+ return nullptr;
}
-ALCbackendFactory *ALCqsaBackendFactory_getFactory(void)
+BackendFactory &QSABackendFactory::getFactory()
{
- static ALCqsaBackendFactory factory = ALCQSABACKENDFACTORY_INITIALIZER;
- return STATIC_CAST(ALCbackendFactory, &factory);
+ static QSABackendFactory factory{};
+ return factory;
}
diff --git a/alc/backends/qsa.h b/alc/backends/qsa.h
new file mode 100644
index 00000000..da548bba
--- /dev/null
+++ b/alc/backends/qsa.h
@@ -0,0 +1,19 @@
+#ifndef BACKENDS_QSA_H
+#define BACKENDS_QSA_H
+
+#include "backends/base.h"
+
+struct QSABackendFactory final : public BackendFactory {
+public:
+ bool init() override;
+
+ bool querySupport(BackendType type) override;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_QSA_H */
diff --git a/alc/backends/sdl2.cpp b/alc/backends/sdl2.cpp
new file mode 100644
index 00000000..25b5d4d9
--- /dev/null
+++ b/alc/backends/sdl2.cpp
@@ -0,0 +1,227 @@
+/**
+ * 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 "AL/al.h"
+
+#include "alcmain.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "alu.h"
+#include "logging.h"
+
+#include <SDL2/SDL.h>
+
+
+namespace {
+
+#ifdef _WIN32
+#define DEVNAME_PREFIX "OpenAL Soft on "
+#else
+#define DEVNAME_PREFIX ""
+#endif
+
+constexpr ALCchar 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 ALCchar *name) override;
+ bool reset() override;
+ bool start() override;
+ void stop() override;
+ void lock() override;
+ void unlock() override;
+
+ SDL_AudioDeviceID mDeviceID{0u};
+ ALuint mFrameSize{0};
+
+ ALuint mFrequency{0u};
+ DevFmtChannels mFmtChans{};
+ DevFmtType mFmtType{};
+ ALuint 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);
+ aluMixData(mDevice, stream, ulen / mFrameSize);
+}
+
+void Sdl2Backend::open(const ALCchar *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{ALC_INVALID_VALUE, "%s", SDL_GetError()};
+
+ mDevice->Frequency = static_cast<ALuint>(have.freq);
+
+ if(have.channels == 1)
+ mDevice->FmtChans = DevFmtMono;
+ else if(have.channels == 2)
+ mDevice->FmtChans = DevFmtStereo;
+ else
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "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(mDevice);
+ return true;
+}
+
+bool Sdl2Backend::start()
+{
+ SDL_PauseAudioDevice(mDeviceID, 0);
+ return true;
+}
+
+void Sdl2Backend::stop()
+{ SDL_PauseAudioDevice(mDeviceID, 1); }
+
+void Sdl2Backend::lock()
+{ SDL_LockAudioDevice(mDeviceID); }
+
+void Sdl2Backend::unlock()
+{ SDL_UnlockAudioDevice(mDeviceID); }
+
+} // 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; }
+
+void SDL2BackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ if(type != DevProbe::Playback)
+ return;
+
+ 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);
+ }
+}
+
+BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+ if(type == BackendType::Playback)
+ return BackendPtr{new Sdl2Backend{device}};
+ return nullptr;
+}
diff --git a/alc/backends/sdl2.h b/alc/backends/sdl2.h
new file mode 100644
index 00000000..041d47ee
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_SDL2_H */
diff --git a/alc/backends/sndio.cpp b/alc/backends/sndio.cpp
new file mode 100644
index 00000000..7799316f
--- /dev/null
+++ b/alc/backends/sndio.cpp
@@ -0,0 +1,476 @@
+/**
+ * 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 "alexcpt.h"
+#include "alu.h"
+#include "threads.h"
+#include "vector.h"
+#include "ringbuffer.h"
+
+#include <sndio.h>
+
+
+namespace {
+
+static const ALCchar sndio_device[] = "SndIO Default";
+
+
+struct SndioPlayback final : public BackendBase {
+ SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ ~SndioPlayback() override;
+
+ int mixerProc();
+
+ void open(const ALCchar *name) override;
+ bool reset() override;
+ bool start() override;
+ void stop() override;
+
+ sio_hdl *mSndHandle{nullptr};
+
+ al::vector<ALubyte> mBuffer;
+
+ std::atomic<bool> mKillNow{true};
+ std::thread mThread;
+
+ DEF_NEWDEL(SndioPlayback)
+};
+
+SndioPlayback::~SndioPlayback()
+{
+ if(mSndHandle)
+ sio_close(mSndHandle);
+ mSndHandle = nullptr;
+}
+
+int SndioPlayback::mixerProc()
+{
+ SetRTPriority();
+ althrd_setname(MIXER_THREAD_NAME);
+
+ const ALuint frameSize{mDevice->frameSizeFromFmt()};
+
+ while(!mKillNow.load(std::memory_order_acquire) &&
+ mDevice->Connected.load(std::memory_order_acquire))
+ {
+ ALubyte *WritePtr{mBuffer.data()};
+ size_t len{mBuffer.size()};
+
+ {
+ std::lock_guard<SndioPlayback> _{*this};
+ aluMixData(mDevice, WritePtr, static_cast<ALuint>(len/frameSize));
+ }
+ 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");
+ aluHandleDisconnect(mDevice, "Failed to write playback samples");
+ break;
+ }
+
+ len -= wrote;
+ WritePtr += wrote;
+ }
+ }
+
+ return 0;
+}
+
+
+void SndioPlayback::open(const ALCchar *name)
+{
+ if(!name)
+ name = sndio_device;
+ else if(strcmp(name, sndio_device) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+
+ mSndHandle = sio_open(nullptr, SIO_PLAY, 0);
+ if(mSndHandle == nullptr)
+ throw al::backend_exception{ALC_INVALID_VALUE, "Could not open backend device"};
+
+ mDevice->DeviceName = name;
+}
+
+bool SndioPlayback::reset()
+{
+ sio_par par;
+ sio_initpar(&par);
+
+ par.rate = mDevice->Frequency;
+ par.pchan = ((mDevice->FmtChans != DevFmtMono) ? 2 : 1);
+
+ 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 true;
+ }
+
+ mDevice->Frequency = par.rate;
+ mDevice->FmtChans = ((par.pchan==1) ? DevFmtMono : 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);
+
+ mDevice->UpdateSize = par.round;
+ mDevice->BufferSize = par.bufsz + par.round;
+
+ mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
+ std::fill(mBuffer.begin(), mBuffer.end(), 0);
+
+ return true;
+}
+
+bool SndioPlayback::start()
+{
+ if(!sio_start(mSndHandle))
+ {
+ ERR("Error starting playback\n");
+ return false;
+ }
+
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this};
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Could not create playback thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ sio_stop(mSndHandle);
+ return false;
+}
+
+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 ALCchar *name) override;
+ bool start() override;
+ void stop() override;
+ ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
+ ALCuint 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 ALuint 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)
+ {
+ aluHandleDisconnect(mDevice, "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 ALCchar *name)
+{
+ if(!name)
+ name = sndio_device;
+ else if(strcmp(name, sndio_device) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+
+ mSndHandle = sio_open(nullptr, SIO_REC, 0);
+ if(mSndHandle == nullptr)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "%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{ALC_INVALID_VALUE, "Failed to set device praameters"};
+
+ if(par.bits != par.bps*8)
+ throw al::backend_exception{ALC_INVALID_VALUE,
+ "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{ALC_INVALID_VALUE,
+ "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 = CreateRingBuffer(mDevice->BufferSize, par.bps*par.rchan, false);
+
+ SetDefaultChannelOrder(mDevice);
+
+ mDevice->DeviceName = name;
+}
+
+bool SndioCapture::start()
+{
+ if(!sio_start(mSndHandle))
+ {
+ ERR("Error starting playback\n");
+ return false;
+ }
+
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this};
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Could not create record thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ sio_stop(mSndHandle);
+ return false;
+}
+
+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");
+}
+
+ALCenum SndioCapture::captureSamples(al::byte *buffer, ALCuint samples)
+{
+ mRing->read(buffer, samples);
+ return ALC_NO_ERROR;
+}
+
+ALCuint SndioCapture::availableSamples()
+{ return static_cast<ALCuint>(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); }
+
+void SndIOBackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ switch(type)
+ {
+ case DevProbe::Playback:
+ case DevProbe::Capture:
+ /* Includes null char. */
+ outnames->append(sndio_device, sizeof(sndio_device));
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/sndio.h b/alc/backends/sndio.h
new file mode 100644
index 00000000..1ed63d5e
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_SNDIO_H */
diff --git a/alc/backends/solaris.cpp b/alc/backends/solaris.cpp
new file mode 100644
index 00000000..7cc2606e
--- /dev/null
+++ b/alc/backends/solaris.cpp
@@ -0,0 +1,299 @@
+/**
+ * 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 "alexcpt.h"
+#include "alu.h"
+#include "alconfig.h"
+#include "threads.h"
+#include "vector.h"
+#include "compat.h"
+
+#include <sys/audioio.h>
+
+
+namespace {
+
+constexpr ALCchar 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 ALCchar *name) override;
+ bool reset() override;
+ bool start() override;
+ void stop() override;
+
+ int mFd{-1};
+
+ al::vector<ALubyte> 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 ALuint frame_size{mDevice->frameSizeFromFmt()};
+
+ std::unique_lock<SolarisBackend> dlock{*this};
+ while(!mKillNow.load(std::memory_order_acquire) &&
+ mDevice->Connected.load(std::memory_order_acquire))
+ {
+ pollfd pollitem{};
+ pollitem.fd = mFd;
+ pollitem.events = POLLOUT;
+
+ dlock.unlock();
+ int pret{poll(&pollitem, 1, 1000)};
+ dlock.lock();
+ if(pret < 0)
+ {
+ if(errno == EINTR || errno == EAGAIN)
+ continue;
+ ERR("poll failed: %s\n", strerror(errno));
+ aluHandleDisconnect(mDevice, "Failed to wait for playback buffer: %s",
+ strerror(errno));
+ break;
+ }
+ else if(pret == 0)
+ {
+ WARN("poll timeout\n");
+ continue;
+ }
+
+ ALubyte *write_ptr{mBuffer.data()};
+ size_t to_write{mBuffer.size()};
+ aluMixData(mDevice, write_ptr, to_write/frame_size);
+ 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));
+ aluHandleDisconnect(mDevice, "Failed to write playback samples: %s",
+ strerror(errno));
+ break;
+ }
+
+ to_write -= wrote;
+ write_ptr += wrote;
+ }
+ }
+
+ return 0;
+}
+
+
+void SolarisBackend::open(const ALCchar *name)
+{
+ if(!name)
+ name = solaris_device;
+ else if(strcmp(name, solaris_device) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
+
+ mFd = ::open(solaris_driver.c_str(), O_WRONLY);
+ if(mFd == -1)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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;
+ ALuint 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;
+ }
+
+ ALuint 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(mDevice);
+
+ mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
+ std::fill(mBuffer.begin(), mBuffer.end(), 0);
+
+ return true;
+}
+
+bool SolarisBackend::start()
+{
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this};
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Could not create playback thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ return false;
+}
+
+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; }
+
+void SolarisBackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ switch(type)
+ {
+ case DevProbe::Playback:
+ {
+#ifdef HAVE_STAT
+ struct stat buf;
+ if(stat(solaris_driver.c_str(), &buf) == 0)
+#endif
+ outnames->append(solaris_device, sizeof(solaris_device));
+ }
+ break;
+
+ case DevProbe::Capture:
+ break;
+ }
+}
+
+BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+ if(type == BackendType::Playback)
+ return BackendPtr{new SolarisBackend{device}};
+ return nullptr;
+}
diff --git a/alc/backends/solaris.h b/alc/backends/solaris.h
new file mode 100644
index 00000000..98b10593
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_SOLARIS_H */
diff --git a/alc/backends/wasapi.cpp b/alc/backends/wasapi.cpp
new file mode 100644
index 00000000..37a547af
--- /dev/null
+++ b/alc/backends/wasapi.cpp
@@ -0,0 +1,1750 @@
+/**
+ * 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 <deque>
+#include <mutex>
+#include <atomic>
+#include <thread>
+#include <vector>
+#include <string>
+#include <future>
+#include <algorithm>
+#include <functional>
+#include <condition_variable>
+
+#include "alcmain.h"
+#include "alexcpt.h"
+#include "alu.h"
+#include "ringbuffer.h"
+#include "compat.h"
+#include "converter.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 {
+
+inline constexpr REFERENCE_TIME operator "" _reftime(unsigned long long int n) noexcept
+{ return 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)
+#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 10000000_reftime
+
+#define DEVNAME_HEAD "OpenAL Soft on "
+
+
+/* Scales the given value using 64-bit integer math, ceiling the result. */
+inline int64_t ScaleCeil(int64_t val, int64_t new_scale, int64_t old_scale)
+{
+ return (val*new_scale + old_scale-1) / old_scale;
+}
+
+
+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)
+{
+ std::string name{DEVNAME_HEAD};
+ std::string guid;
+
+ IPropertyStore *ps;
+ HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps);
+ if(FAILED(hr))
+ {
+ WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
+ return { name+"Unknown Device Name", "Unknown Device GUID" };
+ }
+
+ 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 += "Unknown Device Name";
+ }
+ else if(pvprop->vt == VT_LPWSTR)
+ name += wstr_to_utf8(pvprop->pwszVal);
+ else
+ {
+ WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt);
+ name += "Unknown Device Name";
+ }
+
+ 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 = "Unknown Device GUID";
+ }
+ else if(pvprop->vt == VT_LPWSTR)
+ guid = wstr_to_utf8(pvprop->pwszVal);
+ else
+ {
+ WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt);
+ guid = "Unknown Device GUID";
+ }
+
+ ps->Release();
+
+ return {name, guid};
+}
+
+void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor)
+{
+ IPropertyStore *ps;
+ HRESULT hr = device->OpenPropertyStore(STGM_READ, &ps);
+ 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);
+
+ ps->Release();
+}
+
+
+void add_device(IMMDevice *device, const WCHAR *devid, al::vector<DevMap> &list)
+{
+ std::string basename, guidstr;
+ std::tie(basename, guidstr) = get_device_name_and_guid(device);
+
+ int count{1};
+ std::string newname{basename};
+ while(checkName(list, newname))
+ {
+ newname = basename;
+ newname += " #";
+ newname += std::to_string(++count);
+ }
+ list.emplace_back(std::move(newname), std::move(guidstr), 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;
+
+ HRESULT hr = device->GetId(&devid);
+ if(FAILED(hr))
+ {
+ ERR("Failed to get device id: %lx\n", hr);
+ return nullptr;
+ }
+
+ return devid;
+}
+
+HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector<DevMap> &list)
+{
+ IMMDeviceCollection *coll;
+ HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, &coll)};
+ if(FAILED(hr))
+ {
+ ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr);
+ return hr;
+ }
+
+ IMMDevice *defdev{nullptr};
+ WCHAR *defdevid{nullptr};
+ UINT count{0};
+ hr = coll->GetCount(&count);
+ if(SUCCEEDED(hr) && count > 0)
+ {
+ list.clear();
+ list.reserve(count);
+
+ hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, &defdev);
+ }
+ if(SUCCEEDED(hr) && defdev != nullptr)
+ {
+ defdevid = get_device_id(defdev);
+ if(defdevid)
+ add_device(defdev, defdevid, list);
+ }
+
+ for(UINT i{0};i < count;++i)
+ {
+ IMMDevice *device;
+ hr = coll->Item(i, &device);
+ if(FAILED(hr)) continue;
+
+ WCHAR *devid{get_device_id(device)};
+ if(devid)
+ {
+ if(!defdevid || wcscmp(devid, defdevid) != 0)
+ add_device(device, devid, list);
+ CoTaskMemFree(devid);
+ }
+ device->Release();
+ }
+
+ if(defdev) defdev->Release();
+ if(defdevid) CoTaskMemFree(defdevid);
+ coll->Release();
+
+ return S_OK;
+}
+
+
+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)
+ {
+ 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; }
+ };
+
+ 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};
+ while(mMsgQueue.empty())
+ mMsgQueueCond.wait(lock);
+ 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;
+ }
+ auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr);
+ Enumerator->Release();
+ Enumerator = nullptr;
+ CoUninitialize();
+
+ TRACE("Message thread initialization complete\n");
+ promise->set_value(S_OK);
+ promise = nullptr;
+
+ TRACE("Starting message loop\n");
+ ALuint 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<int>(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
+ {
+ Enumerator = static_cast<IMMDeviceEnumerator*>(ptr);
+
+ if(msg.mType == MsgType::EnumeratePlayback)
+ hr = probe_devices(Enumerator, eRender, PlaybackDevices);
+ else if(msg.mType == MsgType::EnumerateCapture)
+ hr = probe_devices(Enumerator, eCapture, CaptureDevices);
+ msg.mPromise.set_value(hr);
+
+ Enumerator->Release();
+ Enumerator = nullptr;
+ }
+
+ if(--deviceCount == 0 && SUCCEEDED(cohr))
+ CoUninitialize();
+ continue;
+
+ default:
+ ERR("Unexpected message: %u\n", static_cast<unsigned int>(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 ALCchar *name) override;
+ HRESULT openProxy() override;
+ void closeProxy() override;
+
+ bool reset() override;
+ HRESULT resetProxy() override;
+ bool start() override;
+ HRESULT startProxy() override;
+ void stop() override;
+ void stopProxy() override;
+
+ ClockLatency getClockLatency() override;
+
+ std::wstring mDevId;
+
+ IMMDevice *mMMDev{nullptr};
+ IAudioClient *mClient{nullptr};
+ IAudioRenderClient *mRender{nullptr};
+ HANDLE mNotifyEvent{nullptr};
+
+ std::atomic<UINT32> mPadding{0u};
+
+ std::atomic<bool> mKillNow{true};
+ std::thread mThread;
+
+ DEF_NEWDEL(WasapiPlayback)
+};
+
+WasapiPlayback::~WasapiPlayback()
+{
+ pushMessage(MsgType::CloseDevice).wait();
+
+ 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);
+ aluHandleDisconnect(mDevice, "COM init failed: 0x%08lx", hr);
+ return 1;
+ }
+
+ SetRTPriority();
+ althrd_setname(MIXER_THREAD_NAME);
+
+ const ALuint 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);
+ aluHandleDisconnect(mDevice, "Failed to retrieve buffer padding: 0x%08lx", hr);
+ break;
+ }
+ mPadding.store(written, std::memory_order_relaxed);
+
+ ALuint 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::unique_lock<WasapiPlayback> dlock{*this};
+ aluMixData(mDevice, buffer, len);
+ mPadding.store(written + len, std::memory_order_relaxed);
+ dlock.unlock();
+ hr = mRender->ReleaseBuffer(len, 0);
+ }
+ if(FAILED(hr))
+ {
+ ERR("Failed to buffer data: 0x%08lx\n", hr);
+ aluHandleDisconnect(mDevice, "Failed to send playback samples: 0x%08lx", hr);
+ break;
+ }
+ }
+ mPadding.store(0u, std::memory_order_release);
+
+ CoUninitialize();
+ return 0;
+}
+
+
+void WasapiPlayback::open(const ALCchar *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())
+ {
+ 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();
+
+ if(FAILED(hr))
+ {
+ if(mNotifyEvent != nullptr)
+ CloseHandle(mNotifyEvent);
+ mNotifyEvent = nullptr;
+
+ mDevId.clear();
+
+ throw al::backend_exception{ALC_INVALID_VALUE, "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))
+ {
+ auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr);
+ if(mDevId.empty())
+ hr = Enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &mMMDev);
+ else
+ hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev);
+ Enumerator->Release();
+ }
+ if(SUCCEEDED(hr))
+ hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr);
+ if(SUCCEEDED(hr))
+ {
+ mClient = static_cast<IAudioClient*>(ptr);
+ if(mDevice->DeviceName.empty())
+ mDevice->DeviceName = get_device_name_and_guid(mMMDev).first;
+ }
+
+ if(FAILED(hr))
+ {
+ if(mMMDev)
+ mMMDev->Release();
+ mMMDev = nullptr;
+ }
+
+ return hr;
+}
+
+void WasapiPlayback::closeProxy()
+{
+ if(mClient)
+ mClient->Release();
+ mClient = nullptr;
+
+ if(mMMDev)
+ mMMDev->Release();
+ mMMDev = nullptr;
+}
+
+
+bool WasapiPlayback::reset()
+{
+ HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
+ if(FAILED(hr))
+ throw al::backend_exception{ALC_INVALID_VALUE, "0x%08lx", hr};
+ return true;
+}
+
+HRESULT WasapiPlayback::resetProxy()
+{
+ if(mClient)
+ mClient->Release();
+ 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 = 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 REFERENCE_TIME per_time{mDevice->UpdateSize * REFTIME_PER_SEC / mDevice->Frequency};
+ const REFERENCE_TIME buf_time{mDevice->BufferSize * REFTIME_PER_SEC / mDevice->Frequency};
+
+ if(!mDevice->Flags.get<FrequencyRequest>())
+ mDevice->Frequency = OutputType.Format.nSamplesPerSec;
+ if(!mDevice->Flags.get<ChannelsRequest>())
+ {
+ if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO)
+ mDevice->FmtChans = DevFmtMono;
+ else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO)
+ mDevice->FmtChans = DevFmtStereo;
+ else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD)
+ mDevice->FmtChans = DevFmtQuad;
+ else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1)
+ mDevice->FmtChans = DevFmtX51;
+ else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR)
+ mDevice->FmtChans = DevFmtX51Rear;
+ else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1)
+ mDevice->FmtChans = DevFmtX61;
+ else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE))
+ mDevice->FmtChans = DevFmtX71;
+ else
+ ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask);
+ }
+
+ 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;
+ if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO)
+ mDevice->FmtChans = DevFmtMono;
+ else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO)
+ mDevice->FmtChans = DevFmtStereo;
+ else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD)
+ mDevice->FmtChans = DevFmtQuad;
+ else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1)
+ mDevice->FmtChans = DevFmtX51;
+ else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR)
+ mDevice->FmtChans = DevFmtX51Rear;
+ else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1)
+ mDevice->FmtChans = DevFmtX61;
+ else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE))
+ mDevice->FmtChans = DevFmtX71;
+ 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\n");
+ 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;
+ }
+
+ EndpointFormFactor formfactor = UnknownFormFactor;
+ get_device_formfactor(mMMDev, &formfactor);
+ mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo &&
+ (formfactor == Headphones || formfactor == Headset));
+
+ SetDefaultWFXChannelOrder(mDevice);
+
+ hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time,
+ 0, &OutputType.Format, nullptr);
+ if(FAILED(hr))
+ {
+ ERR("Failed to initialize audio client: 0x%08lx\n", hr);
+ return hr;
+ }
+
+ UINT32 buffer_len{}, min_len{};
+ REFERENCE_TIME min_per{};
+ hr = mClient->GetDevicePeriod(&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);
+ min_len = static_cast<UINT32>(ScaleCeil(min_per, mDevice->Frequency, REFTIME_PER_SEC));
+ min_len = minu(min_len, buffer_len/2);
+
+ mDevice->UpdateSize = min_len;
+ 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;
+}
+
+
+bool WasapiPlayback::start()
+{
+ HRESULT hr{pushMessage(MsgType::StartDevice).get()};
+ return SUCCEEDED(hr) ? true : false;
+}
+
+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 = static_cast<IAudioRenderClient*>(ptr);
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this};
+ }
+ catch(...) {
+ mRender->Release();
+ 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->Release();
+ mRender = nullptr;
+ mClient->Stop();
+}
+
+
+ClockLatency WasapiPlayback::getClockLatency()
+{
+ ClockLatency ret;
+
+ std::lock_guard<WasapiPlayback> _{*this};
+ 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 ALCchar *name) override;
+ HRESULT openProxy() override;
+ void closeProxy() override;
+
+ HRESULT resetProxy() override;
+ bool start() override;
+ HRESULT startProxy() override;
+ void stop() override;
+ void stopProxy() override;
+
+ ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
+ ALCuint availableSamples() override;
+
+ std::wstring mDevId;
+
+ IMMDevice *mMMDev{nullptr};
+ IAudioClient *mClient{nullptr};
+ 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()
+{
+ pushMessage(MsgType::CloseDevice).wait();
+
+ 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);
+ aluHandleDisconnect(mDevice, "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 ALvoid *srcdata{rdata};
+ ALuint srcframes{numsamples};
+
+ dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf,
+ static_cast<ALuint>(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<ALuint>(minz(data.second.len, INT_MAX)));
+ }
+ }
+ else
+ {
+ const auto framesize = static_cast<ALuint>(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))
+ {
+ aluHandleDisconnect(mDevice, "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 ALCchar *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())
+ {
+ 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();
+
+ if(FAILED(hr))
+ {
+ if(mNotifyEvent != nullptr)
+ CloseHandle(mNotifyEvent);
+ mNotifyEvent = nullptr;
+
+ mDevId.clear();
+
+ throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
+ }
+
+ hr = pushMessage(MsgType::ResetDevice).get();
+ if(FAILED(hr))
+ {
+ if(hr == E_OUTOFMEMORY)
+ throw al::backend_exception{ALC_OUT_OF_MEMORY, "Out of memory"};
+ throw al::backend_exception{ALC_INVALID_VALUE, "Device reset failed"};
+ }
+}
+
+HRESULT WasapiCapture::openProxy()
+{
+ void *ptr;
+ HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IMMDeviceEnumerator, &ptr)};
+ if(SUCCEEDED(hr))
+ {
+ auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr);
+ if(mDevId.empty())
+ hr = Enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, &mMMDev);
+ else
+ hr = Enumerator->GetDevice(mDevId.c_str(), &mMMDev);
+ Enumerator->Release();
+ }
+ if(SUCCEEDED(hr))
+ hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr);
+ if(SUCCEEDED(hr))
+ {
+ mClient = static_cast<IAudioClient*>(ptr);
+ if(mDevice->DeviceName.empty())
+ mDevice->DeviceName = get_device_name_and_guid(mMMDev).first;
+ }
+
+ if(FAILED(hr))
+ {
+ if(mMMDev)
+ mMMDev->Release();
+ mMMDev = nullptr;
+ }
+
+ return hr;
+}
+
+void WasapiCapture::closeProxy()
+{
+ if(mClient)
+ mClient->Release();
+ mClient = nullptr;
+
+ if(mMMDev)
+ mMMDev->Release();
+ mMMDev = nullptr;
+}
+
+HRESULT WasapiCapture::resetProxy()
+{
+ if(mClient)
+ mClient->Release();
+ 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 = static_cast<IAudioClient*>(ptr);
+
+ // Make sure buffer is at least 100ms in size
+ REFERENCE_TIME buf_time{mDevice->BufferSize * REFTIME_PER_SEC / mDevice->Frequency};
+ buf_time = maxu64(buf_time, REFTIME_PER_SEC/10);
+
+ WAVEFORMATEXTENSIBLE OutputType{};
+ OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ switch(mDevice->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(mDevice->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 = mDevice->Frequency;
+
+ OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
+ OutputType.Format.wBitsPerSample / 8);
+ OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
+ OutputType.Format.nBlockAlign;
+ OutputType.Format.cbSize = sizeof(OutputType) - sizeof(OutputType.Format);
+
+ TraceFormat("Requesting capture format", &OutputType.Format);
+ WAVEFORMATEX *wfx;
+ hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.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(!(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(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
+ mDevice->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 = nullptr;
+ }
+
+ DevFmtType srcType;
+ 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(mDevice->FmtChans == DevFmtMono && OutputType.Format.nChannels == 2)
+ {
+ mChannelConv = ChannelConverter{srcType, DevFmtStereo, mDevice->FmtChans};
+ 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(mDevice->FmtChans == DevFmtStereo && OutputType.Format.nChannels == 1)
+ {
+ mChannelConv = ChannelConverter{srcType, DevFmtMono, mDevice->FmtChans};
+ TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
+ srcType = DevFmtFloat;
+ }
+
+ if(mDevice->Frequency != OutputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
+ {
+ mSampleConv = CreateSampleConverter(srcType, mDevice->FmtType, mDevice->channelsFromFmt(),
+ OutputType.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), OutputType.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), OutputType.Format.nSamplesPerSec);
+ }
+
+ hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time,
+ 0, &OutputType.Format, nullptr);
+ if(FAILED(hr))
+ {
+ ERR("Failed to initialize audio client: 0x%08lx\n", hr);
+ return hr;
+ }
+
+ UINT32 buffer_len{};
+ REFERENCE_TIME min_per{};
+ hr = mClient->GetDevicePeriod(&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 = static_cast<ALuint>(ScaleCeil(min_per, mDevice->Frequency,
+ REFTIME_PER_SEC));
+ mDevice->BufferSize = buffer_len;
+
+ buffer_len = maxu(mDevice->BufferSize, buffer_len);
+ mRing = CreateRingBuffer(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;
+}
+
+
+bool WasapiCapture::start()
+{
+ HRESULT hr{pushMessage(MsgType::StartDevice).get()};
+ return SUCCEEDED(hr) ? true : false;
+}
+
+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 = static_cast<IAudioCaptureClient*>(ptr);
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this};
+ }
+ catch(...) {
+ mCapture->Release();
+ 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->Release();
+ mCapture = nullptr;
+ mClient->Stop();
+ mClient->Reset();
+}
+
+
+ALCuint WasapiCapture::availableSamples()
+{ return static_cast<ALCuint>(mRing->readSpace()); }
+
+ALCenum WasapiCapture::captureSamples(al::byte *buffer, ALCuint samples)
+{
+ mRing->read(buffer, samples);
+ return ALC_NO_ERROR;
+}
+
+} // 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) ? ALC_TRUE : ALC_FALSE;
+}
+
+bool WasapiBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback || type == BackendType::Capture; }
+
+void WasapiBackendFactory::probe(DevProbe 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);
+ };
+ HRESULT hr{};
+ switch(type)
+ {
+ case DevProbe::Playback:
+ hr = WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).get();
+ if(SUCCEEDED(hr))
+ std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+ break;
+
+ case DevProbe::Capture:
+ hr = WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).get();
+ if(SUCCEEDED(hr))
+ std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/wasapi.h b/alc/backends/wasapi.h
new file mode 100644
index 00000000..067dd259
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_WASAPI_H */
diff --git a/alc/backends/wave.cpp b/alc/backends/wave.cpp
new file mode 100644
index 00000000..7bcc3436
--- /dev/null
+++ b/alc/backends/wave.cpp
@@ -0,0 +1,402 @@
+/**
+ * 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 "AL/al.h"
+
+#include "albyte.h"
+#include "alcmain.h"
+#include "alconfig.h"
+#include "alexcpt.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alu.h"
+#include "compat.h"
+#include "endiantest.h"
+#include "logging.h"
+#include "strutils.h"
+#include "threads.h"
+#include "vector.h"
+
+
+namespace {
+
+using std::chrono::seconds;
+using std::chrono::milliseconds;
+using std::chrono::nanoseconds;
+
+constexpr ALCchar waveDevice[] = "Wave File Writer";
+
+constexpr ALubyte SUBTYPE_PCM[]{
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
+ 0x00, 0x38, 0x9b, 0x71
+};
+constexpr ALubyte SUBTYPE_FLOAT[]{
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
+ 0x00, 0x38, 0x9b, 0x71
+};
+
+constexpr ALubyte SUBTYPE_BFORMAT_PCM[]{
+ 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
+ 0xca, 0x00, 0x00, 0x00
+};
+
+constexpr ALubyte SUBTYPE_BFORMAT_FLOAT[]{
+ 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
+ 0xca, 0x00, 0x00, 0x00
+};
+
+void fwrite16le(ALushort val, FILE *f)
+{
+ ALubyte data[2]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff) };
+ fwrite(data, 1, 2, f);
+}
+
+void fwrite32le(ALuint val, FILE *f)
+{
+ ALubyte data[4]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff),
+ static_cast<ALubyte>((val>>16)&0xff), static_cast<ALubyte>((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 ALCchar *name) override;
+ bool reset() override;
+ bool 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 ALuint 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)
+ {
+ {
+ std::lock_guard<WaveBackend> _{*this};
+ aluMixData(mDevice, mBuffer.data(), mDevice->UpdateSize);
+ }
+ done += mDevice->UpdateSize;
+
+ if(!IS_LITTLE_ENDIAN)
+ {
+ const ALuint bytesize{mDevice->bytesFromFmt()};
+
+ if(bytesize == 2)
+ {
+ ALushort *samples = reinterpret_cast<ALushort*>(mBuffer.data());
+ const size_t len{mBuffer.size() / 2};
+ for(size_t i{0};i < len;i++)
+ {
+ const ALushort samp{samples[i]};
+ samples[i] = static_cast<ALushort>((samp>>8) | (samp<<8));
+ }
+ }
+ else if(bytesize == 4)
+ {
+ ALuint *samples = reinterpret_cast<ALuint*>(mBuffer.data());
+ const size_t len{mBuffer.size() / 4};
+ for(size_t i{0};i < len;i++)
+ {
+ const ALuint samp{samples[i]};
+ samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) |
+ ((samp<<8)&0x00ff0000) | (samp<<24);
+ }
+ }
+ }
+
+ size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
+ (void)fs;
+ if(ferror(mFile))
+ {
+ ERR("Error writing to file\n");
+ aluHandleDisconnect(mDevice, "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};
+ start += s;
+ done -= mDevice->Frequency*s.count();
+ }
+ }
+
+ return 0;
+}
+
+void WaveBackend::open(const ALCchar *name)
+{
+ const char *fname{GetConfigValue(nullptr, "wave", "file", "")};
+ if(!fname[0]) throw al::backend_exception{ALC_INVALID_VALUE, "No wave output filename"};
+
+ if(!name)
+ name = waveDevice;
+ else if(strcmp(name, waveDevice) != 0)
+ throw al::backend_exception{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "Could not open file '%s': %s", fname,
+ strerror(errno)};
+
+ mDevice->DeviceName = name;
+}
+
+bool WaveBackend::reset()
+{
+ ALuint channels=0, bytes=0, chanmask=0;
+ int isbformat = 0;
+ 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 = AmbiLayout::FuMa;
+ mDevice->mAmbiScale = AmbiNorm::FuMa;
+ isbformat = 1;
+ 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<ALushort>(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<ALushort>(channels * bytes), mFile);
+ // 16-bit val, bits per sample
+ fwrite16le(static_cast<ALushort>(bytes * 8), mFile);
+ // 16-bit val, extra byte count
+ fwrite16le(22, mFile);
+ // 16-bit val, valid bits per sample
+ fwrite16le(static_cast<ALushort>(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(mDevice);
+
+ const ALuint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize};
+ mBuffer.resize(bufsize);
+
+ return true;
+}
+
+bool WaveBackend::start()
+{
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this};
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Failed to start mixing thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ return false;
+}
+
+void WaveBackend::stop()
+{
+ if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+ return;
+ mThread.join();
+
+ long size{ftell(mFile)};
+ if(size > 0)
+ {
+ long dataLen{size - mDataStart};
+ if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
+ fwrite32le(static_cast<ALuint>(dataLen), mFile); // 'data' header len
+ if(fseek(mFile, 4, SEEK_SET) == 0)
+ fwrite32le(static_cast<ALuint>(size-8), mFile); // 'WAVE' header len
+ }
+}
+
+} // namespace
+
+
+bool WaveBackendFactory::init()
+{ return true; }
+
+bool WaveBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback; }
+
+void WaveBackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ switch(type)
+ {
+ case DevProbe::Playback:
+ /* Includes null char. */
+ outnames->append(waveDevice, sizeof(waveDevice));
+ break;
+ case DevProbe::Capture:
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/wave.h b/alc/backends/wave.h
new file mode 100644
index 00000000..b9b62d7f
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_WAVE_H */
diff --git a/alc/backends/winmm.cpp b/alc/backends/winmm.cpp
new file mode 100644
index 00000000..649bb345
--- /dev/null
+++ b/alc/backends/winmm.cpp
@@ -0,0 +1,634 @@
+/**
+ * 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 <array>
+#include <atomic>
+#include <thread>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <functional>
+
+#include "alcmain.h"
+#include "alexcpt.h"
+#include "alu.h"
+#include "ringbuffer.h"
+#include "strutils.h"
+#include "threads.h"
+#include "compat.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();
+
+ ALuint numdevs{waveOutGetNumDevs()};
+ PlaybackDevices.reserve(numdevs);
+ for(ALuint 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();
+
+ ALuint numdevs{waveInGetNumDevs()};
+ CaptureDevices.reserve(numdevs);
+ for(ALuint 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 ALCchar *name) override;
+ bool reset() override;
+ bool start() override;
+ void stop() override;
+
+ std::atomic<ALuint> mWritable{0u};
+ al::semaphore mSem;
+ ALuint 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);
+
+ std::unique_lock<WinMMPlayback> dlock{*this};
+ while(!mKillNow.load(std::memory_order_acquire) &&
+ mDevice->Connected.load(std::memory_order_acquire))
+ {
+ ALsizei todo = mWritable.load(std::memory_order_acquire);
+ if(todo < 1)
+ {
+ dlock.unlock();
+ mSem.wait();
+ dlock.lock();
+ continue;
+ }
+
+ size_t widx{mIdx};
+ do {
+ WAVEHDR &waveHdr = mWaveBuffer[widx];
+ widx = (widx+1) % mWaveBuffer.size();
+
+ aluMixData(mDevice, waveHdr.lpData, mDevice->UpdateSize);
+ mWritable.fetch_sub(1, std::memory_order_acq_rel);
+ waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
+ } while(--todo);
+ mIdx = static_cast<ALuint>(widx);
+ }
+
+ return 0;
+}
+
+
+void WinMMPlayback::open(const ALCchar *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{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "waveOutOpen failed: %u", res};
+ }
+
+ mDevice->DeviceName = PlaybackDevices[DeviceID];
+}
+
+bool WinMMPlayback::reset()
+{
+ mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} *
+ mFormat.nSamplesPerSec / mDevice->Frequency);
+ mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3;
+ 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;
+ }
+
+ if(mFormat.nChannels == 2)
+ mDevice->FmtChans = DevFmtStereo;
+ else if(mFormat.nChannels == 1)
+ mDevice->FmtChans = DevFmtMono;
+ else
+ {
+ ERR("Unhandled channel count: %d\n", mFormat.nChannels);
+ return false;
+ }
+ SetDefaultWFXChannelOrder(mDevice);
+
+ ALuint 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;
+}
+
+bool WinMMPlayback::start()
+{
+ try {
+ std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
+ [this](WAVEHDR &waveHdr) -> void
+ { waveOutPrepareHeader(mOutHdl, &waveHdr, static_cast<UINT>(sizeof(WAVEHDR))); }
+ );
+ mWritable.store(static_cast<ALuint>(mWaveBuffer.size()), std::memory_order_release);
+
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this};
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Failed to start mixing thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ return false;
+}
+
+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 ALCchar *name) override;
+ bool start() override;
+ void stop() override;
+ ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
+ ALCuint availableSamples() override;
+
+ std::atomic<ALuint> mReadable{0u};
+ al::semaphore mSem;
+ ALuint 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);
+
+ std::unique_lock<WinMMCapture> dlock{*this};
+ while(!mKillNow.load(std::memory_order_acquire) &&
+ mDevice->Connected.load(std::memory_order_acquire))
+ {
+ ALuint todo{mReadable.load(std::memory_order_acquire)};
+ if(todo < 1)
+ {
+ dlock.unlock();
+ mSem.wait();
+ dlock.lock();
+ 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<ALuint>(widx);
+ }
+
+ return 0;
+}
+
+
+void WinMMCapture::open(const ALCchar *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{ALC_INVALID_VALUE, "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{ALC_INVALID_VALUE, "%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{ALC_INVALID_VALUE, "%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{ALC_INVALID_VALUE, "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
+ ALuint CapturedDataSize{mDevice->BufferSize};
+ CapturedDataSize = static_cast<ALuint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
+
+ mRing = CreateRingBuffer(CapturedDataSize, mFormat.nBlockAlign, false);
+
+ al_free(mWaveBuffer[0].lpData);
+ mWaveBuffer[0] = WAVEHDR{};
+ mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize*4));
+ 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];
+}
+
+bool 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);
+ return true;
+ }
+ catch(std::exception& e) {
+ ERR("Failed to start mixing thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ return false;
+}
+
+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;
+}
+
+ALCenum WinMMCapture::captureSamples(al::byte *buffer, ALCuint samples)
+{
+ mRing->read(buffer, samples);
+ return ALC_NO_ERROR;
+}
+
+ALCuint WinMMCapture::availableSamples()
+{ return static_cast<ALCuint>(mRing->readSpace()); }
+
+} // namespace
+
+
+bool WinMMBackendFactory::init()
+{ return true; }
+
+bool WinMMBackendFactory::querySupport(BackendType type)
+{ return type == BackendType::Playback || type == BackendType::Capture; }
+
+void WinMMBackendFactory::probe(DevProbe 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 DevProbe::Playback:
+ ProbePlaybackDevices();
+ std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
+ break;
+
+ case DevProbe::Capture:
+ ProbeCaptureDevices();
+ std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
+ break;
+ }
+}
+
+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;
+}
diff --git a/alc/backends/winmm.h b/alc/backends/winmm.h
new file mode 100644
index 00000000..e357ec19
--- /dev/null
+++ b/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;
+
+ void probe(DevProbe type, std::string *outnames) override;
+
+ BackendPtr createBackend(ALCdevice *device, BackendType type) override;
+
+ static BackendFactory &getFactory();
+};
+
+#endif /* BACKENDS_WINMM_H */
diff --git a/alc/bformatdec.cpp b/alc/bformatdec.cpp
new file mode 100644
index 00000000..9fbe32b8
--- /dev/null
+++ b/alc/bformatdec.cpp
@@ -0,0 +1,203 @@
+
+#include "config.h"
+
+#include "bformatdec.h"
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cmath>
+#include <iterator>
+#include <numeric>
+
+#include "AL/al.h"
+
+#include "almalloc.h"
+#include "alu.h"
+#include "ambdec.h"
+#include "filters/splitter.h"
+#include "opthelpers.h"
+
+
+namespace {
+
+constexpr ALfloat Ambi3DDecoderHFScale[MAX_AMBI_ORDER+1] = {
+ 1.00000000e+00f, 1.00000000e+00f
+};
+constexpr ALfloat Ambi3DDecoderHFScale2O[MAX_AMBI_ORDER+1] = {
+ 7.45355990e-01f, 1.00000000e+00f
+};
+constexpr ALfloat Ambi3DDecoderHFScale3O[MAX_AMBI_ORDER+1] = {
+ 5.89792205e-01f, 8.79693856e-01f
+};
+
+inline auto GetDecoderHFScales(ALuint order) noexcept -> const ALfloat(&)[MAX_AMBI_ORDER+1]
+{
+ if(order >= 3) return Ambi3DDecoderHFScale3O;
+ if(order == 2) return Ambi3DDecoderHFScale2O;
+ return Ambi3DDecoderHFScale;
+}
+
+inline auto GetAmbiScales(AmbDecScale scaletype) noexcept -> const std::array<float,MAX_AMBI_CHANNELS>&
+{
+ 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 ALuint inchans,
+ const ALuint srate, const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS])
+{
+ mDualBand = allow_2band && (conf->FreqBands == 2);
+ if(!mDualBand)
+ mSamples.resize(2);
+ else
+ {
+ ASSUME(inchans > 0);
+ mSamples.resize(inchans * 2);
+ mSamplesHF = mSamples.data();
+ mSamplesLF = mSamplesHF + inchans;
+ }
+ mNumChannels = inchans;
+
+ mEnabled = std::accumulate(std::begin(chanmap), std::begin(chanmap)+conf->Speakers.size(), 0u,
+ [](ALuint mask, const ALuint &chan) noexcept -> ALuint
+ { return mask | (1 << chan); }
+ );
+
+ const ALfloat xover_norm{conf->XOverFreq / static_cast<float>(srate)};
+
+ const bool periphonic{(conf->ChanMask&AMBI_PERIPHONIC_MASK) != 0};
+ const std::array<float,MAX_AMBI_CHANNELS> &coeff_scale = GetAmbiScales(conf->CoeffScale);
+ const size_t coeff_count{periphonic ? MAX_AMBI_CHANNELS : MAX_AMBI2D_CHANNELS};
+
+ if(!mDualBand)
+ {
+ for(size_t i{0u};i < conf->Speakers.size();i++)
+ {
+ ALfloat (&mtx)[MAX_AMBI_CHANNELS] = mMatrix.Single[chanmap[i]];
+ for(size_t j{0},k{0};j < coeff_count;j++)
+ {
+ const size_t l{periphonic ? j : AmbiIndex::From2D[j]};
+ if(!(conf->ChanMask&(1u<<l))) continue;
+ mtx[j] = conf->HFMatrix[i][k] / coeff_scale[l] *
+ ((l>=9) ? conf->HFOrderGain[3] :
+ (l>=4) ? conf->HFOrderGain[2] :
+ (l>=1) ? conf->HFOrderGain[1] : conf->HFOrderGain[0]);
+ ++k;
+ }
+ }
+ }
+ else
+ {
+ mXOver[0].init(xover_norm);
+ std::fill(std::begin(mXOver)+1, std::end(mXOver), mXOver[0]);
+
+ const float ratio{std::pow(10.0f, conf->XOverRatio / 40.0f)};
+ for(size_t i{0u};i < conf->Speakers.size();i++)
+ {
+ ALfloat (&mtx)[sNumBands][MAX_AMBI_CHANNELS] = mMatrix.Dual[chanmap[i]];
+ for(size_t j{0},k{0};j < coeff_count;j++)
+ {
+ const size_t l{periphonic ? j : AmbiIndex::From2D[j]};
+ if(!(conf->ChanMask&(1u<<l))) continue;
+ mtx[sHFBand][j] = conf->HFMatrix[i][k] / coeff_scale[l] *
+ ((l>=9) ? conf->HFOrderGain[3] :
+ (l>=4) ? conf->HFOrderGain[2] :
+ (l>=1) ? conf->HFOrderGain[1] : conf->HFOrderGain[0]) * ratio;
+ mtx[sLFBand][j] = conf->LFMatrix[i][k] / coeff_scale[l] *
+ ((l>=9) ? conf->LFOrderGain[3] :
+ (l>=4) ? conf->LFOrderGain[2] :
+ (l>=1) ? conf->LFOrderGain[1] : conf->LFOrderGain[0]) / ratio;
+ ++k;
+ }
+ }
+ }
+}
+
+BFormatDec::BFormatDec(const ALuint inchans, const ALsizei chancount,
+ const ChannelDec (&chancoeffs)[MAX_OUTPUT_CHANNELS],
+ const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS])
+{
+ mSamples.resize(2);
+ mNumChannels = inchans;
+
+ ASSUME(chancount > 0);
+ mEnabled = std::accumulate(std::begin(chanmap), std::begin(chanmap)+chancount, 0u,
+ [](ALuint mask, const ALuint &chan) noexcept -> ALuint
+ { return mask | (1 << chan); }
+ );
+
+ const ChannelDec *incoeffs{chancoeffs};
+ auto set_coeffs = [this,inchans,&incoeffs](const ALuint chanidx) noexcept -> void
+ {
+ ALfloat (&mtx)[MAX_AMBI_CHANNELS] = mMatrix.Single[chanidx];
+ const ALfloat (&coeffs)[MAX_AMBI_CHANNELS] = *(incoeffs++);
+
+ ASSUME(inchans > 0);
+ std::copy_n(std::begin(coeffs), inchans, std::begin(mtx));
+ };
+ std::for_each(chanmap, chanmap+chancount, set_coeffs);
+}
+
+
+void BFormatDec::process(const al::span<FloatBufferLine> OutBuffer,
+ const FloatBufferLine *InSamples, const size_t SamplesToDo)
+{
+ ASSUME(SamplesToDo > 0);
+
+ if(mDualBand)
+ {
+ for(ALuint i{0};i < mNumChannels;i++)
+ mXOver[i].process(mSamplesHF[i].data(), mSamplesLF[i].data(), InSamples[i].data(),
+ SamplesToDo);
+
+ ALfloat (*mixmtx)[sNumBands][MAX_AMBI_CHANNELS]{mMatrix.Dual};
+ ALuint enabled{mEnabled};
+ for(FloatBufferLine &outbuf : OutBuffer)
+ {
+ if LIKELY(enabled&1)
+ {
+ const al::span<float> outspan{outbuf.data(), SamplesToDo};
+ MixRowSamples(outspan, {(*mixmtx)[sHFBand], mNumChannels}, mSamplesHF->data(),
+ mSamplesHF->size());
+ MixRowSamples(outspan, {(*mixmtx)[sLFBand], mNumChannels}, mSamplesLF->data(),
+ mSamplesLF->size());
+ }
+ ++mixmtx;
+ enabled >>= 1;
+ }
+ }
+ else
+ {
+ ALfloat (*mixmtx)[MAX_AMBI_CHANNELS]{mMatrix.Single};
+ ALuint enabled{mEnabled};
+ for(FloatBufferLine &outbuf : OutBuffer)
+ {
+ if LIKELY(enabled&1)
+ MixRowSamples({outbuf.data(), SamplesToDo}, {*mixmtx, mNumChannels},
+ InSamples->data(), InSamples->size());
+ ++mixmtx;
+ enabled >>= 1;
+ }
+ }
+}
+
+
+std::array<ALfloat,MAX_AMBI_ORDER+1> BFormatDec::GetHFOrderScales(const ALuint in_order, const ALuint out_order) noexcept
+{
+ std::array<ALfloat,MAX_AMBI_ORDER+1> ret{};
+
+ assert(out_order >= in_order);
+
+ const ALfloat (&target)[MAX_AMBI_ORDER+1] = GetDecoderHFScales(out_order);
+ const ALfloat (&input)[MAX_AMBI_ORDER+1] = GetDecoderHFScales(in_order);
+
+ for(ALuint i{0};i < in_order+1;++i)
+ ret[i] = input[i] / target[i];
+
+ return ret;
+}
diff --git a/alc/bformatdec.h b/alc/bformatdec.h
new file mode 100644
index 00000000..edbb6d50
--- /dev/null
+++ b/alc/bformatdec.h
@@ -0,0 +1,62 @@
+#ifndef BFORMATDEC_H
+#define BFORMATDEC_H
+
+#include <array>
+#include <cstddef>
+
+#include "AL/al.h"
+
+#include "alcmain.h"
+#include "almalloc.h"
+#include "alspan.h"
+#include "ambidefs.h"
+#include "devformat.h"
+#include "filters/splitter.h"
+#include "vector.h"
+
+struct AmbDecConf;
+
+
+using ChannelDec = ALfloat[MAX_AMBI_CHANNELS];
+
+class BFormatDec {
+ static constexpr size_t sHFBand{0};
+ static constexpr size_t sLFBand{1};
+ static constexpr size_t sNumBands{2};
+
+ bool mDualBand{false};
+ ALuint mEnabled{0u}; /* Bitfield of enabled channels. */
+
+ ALuint mNumChannels{0u};
+ union MatrixU {
+ ALfloat Dual[MAX_OUTPUT_CHANNELS][sNumBands][MAX_AMBI_CHANNELS];
+ ALfloat Single[MAX_OUTPUT_CHANNELS][MAX_AMBI_CHANNELS];
+ } mMatrix{};
+
+ /* NOTE: BandSplitter filters are unused with single-band decoding */
+ BandSplitter mXOver[MAX_AMBI_CHANNELS];
+
+ al::vector<FloatBufferLine, 16> mSamples;
+ /* These two alias into Samples */
+ FloatBufferLine *mSamplesHF{nullptr};
+ FloatBufferLine *mSamplesLF{nullptr};
+
+public:
+ BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALuint inchans,
+ const ALuint srate, const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS]);
+ BFormatDec(const ALuint inchans, const ALsizei chancount,
+ const ChannelDec (&chancoeffs)[MAX_OUTPUT_CHANNELS],
+ const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS]);
+
+ /* Decodes the ambisonic input to the given output channels. */
+ void process(const al::span<FloatBufferLine> OutBuffer, const FloatBufferLine *InSamples,
+ const size_t SamplesToDo);
+
+ /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */
+ static std::array<ALfloat,MAX_AMBI_ORDER+1> GetHFOrderScales(const ALuint in_order,
+ const ALuint out_order) noexcept;
+
+ DEF_NEWDEL(BFormatDec)
+};
+
+#endif /* BFORMATDEC_H */
diff --git a/Alc/bs2b.c b/alc/bs2b.cpp
index e235e547..00207bc0 100644
--- a/Alc/bs2b.c
+++ b/alc/bs2b.cpp
@@ -23,18 +23,19 @@
#include "config.h"
-#include <math.h>
-#include <string.h>
+#include <algorithm>
+#include <cmath>
+#include <iterator>
#include "bs2b.h"
-#include "alu.h"
+#include "math_defs.h"
/* Set up all data. */
static void init(struct bs2b *bs2b)
{
float Fc_lo, Fc_hi;
- float G_lo, G_hi;
+ float G_lo, G_hi;
float x, g;
switch(bs2b->level)
@@ -90,11 +91,11 @@ static void init(struct bs2b *bs2b)
* $d = 1 / 2 / pi / $fc;
* $x = exp(-1 / $d);
*/
- x = expf(-2.0f * F_PI * Fc_lo / bs2b->srate);
+ x = std::exp(-al::MathDefs<float>::Tau() * Fc_lo / static_cast<float>(bs2b->srate));
bs2b->b1_lo = x;
bs2b->a0_lo = G_lo * (1.0f - x) * g;
- x = expf(-2.0f * F_PI * Fc_hi / bs2b->srate);
+ x = std::exp(-al::MathDefs<float>::Tau() * Fc_hi / static_cast<float>(bs2b->srate));
bs2b->b1_hi = x;
bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g;
bs2b->a1_hi = -x * g;
@@ -126,60 +127,55 @@ int bs2b_get_srate(struct bs2b *bs2b)
void bs2b_clear(struct bs2b *bs2b)
{
- memset(&bs2b->last_sample, 0, sizeof(bs2b->last_sample));
+ std::fill(std::begin(bs2b->history), std::end(bs2b->history), bs2b::t_last_sample{});
} /* bs2b_clear */
-void bs2b_cross_feed(struct bs2b *bs2b, float *restrict Left, float *restrict Right, int SamplesToDo)
+void bs2b_cross_feed(struct bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo)
{
+ const float a0_lo{bs2b->a0_lo};
+ const float b1_lo{bs2b->b1_lo};
+ const float a0_hi{bs2b->a0_hi};
+ const float a1_hi{bs2b->a1_hi};
+ const float b1_hi{bs2b->b1_hi};
float lsamples[128][2];
float rsamples[128][2];
- int base;
- for(base = 0;base < SamplesToDo;)
+ for(size_t base{0};base < SamplesToDo;)
{
- int todo = mini(128, SamplesToDo-base);
- int i;
+ const size_t todo{std::min<size_t>(128, SamplesToDo-base)};
/* Process left input */
- lsamples[0][0] = bs2b->a0_lo*Left[0] +
- bs2b->b1_lo*bs2b->last_sample[0].lo;
- lsamples[0][1] = bs2b->a0_hi*Left[0] +
- bs2b->a1_hi*bs2b->last_sample[0].asis +
- bs2b->b1_hi*bs2b->last_sample[0].hi;
- for(i = 1;i < todo;i++)
+ float z_lo{bs2b->history[0].lo};
+ float z_hi{bs2b->history[0].hi};
+ for(size_t i{0};i < todo;i++)
{
- lsamples[i][0] = bs2b->a0_lo*Left[i] +
- bs2b->b1_lo*lsamples[i-1][0];
- lsamples[i][1] = bs2b->a0_hi*Left[i] +
- bs2b->a1_hi*Left[i-1] +
- bs2b->b1_hi*lsamples[i-1][1];
+ lsamples[i][0] = a0_lo*Left[i] + z_lo;
+ z_lo = b1_lo*lsamples[i][0];
+
+ lsamples[i][1] = a0_hi*Left[i] + z_hi;
+ z_hi = a1_hi*Left[i] + b1_hi*lsamples[i][1];
}
- bs2b->last_sample[0].asis = Left[i-1];
- bs2b->last_sample[0].lo = lsamples[i-1][0];
- bs2b->last_sample[0].hi = lsamples[i-1][1];
+ bs2b->history[0].lo = z_lo;
+ bs2b->history[0].hi = z_hi;
/* Process right input */
- rsamples[0][0] = bs2b->a0_lo*Right[0] +
- bs2b->b1_lo*bs2b->last_sample[1].lo;
- rsamples[0][1] = bs2b->a0_hi*Right[0] +
- bs2b->a1_hi*bs2b->last_sample[1].asis +
- bs2b->b1_hi*bs2b->last_sample[1].hi;
- for(i = 1;i < todo;i++)
+ z_lo = bs2b->history[1].lo;
+ z_hi = bs2b->history[1].hi;
+ for(size_t i{0};i < todo;i++)
{
- rsamples[i][0] = bs2b->a0_lo*Right[i] +
- bs2b->b1_lo*rsamples[i-1][0];
- rsamples[i][1] = bs2b->a0_hi*Right[i] +
- bs2b->a1_hi*Right[i-1] +
- bs2b->b1_hi*rsamples[i-1][1];
+ rsamples[i][0] = a0_lo*Right[i] + z_lo;
+ z_lo = b1_lo*rsamples[i][0];
+
+ rsamples[i][1] = a0_hi*Right[i] + z_hi;
+ z_hi = a1_hi*Right[i] + b1_hi*rsamples[i][1];
}
- bs2b->last_sample[1].asis = Right[i-1];
- bs2b->last_sample[1].lo = rsamples[i-1][0];
- bs2b->last_sample[1].hi = rsamples[i-1][1];
+ bs2b->history[1].lo = z_lo;
+ bs2b->history[1].hi = z_hi;
/* Crossfeed */
- for(i = 0;i < todo;i++)
+ for(size_t i{0};i < todo;i++)
*(Left++) = lsamples[i][1] + rsamples[i][0];
- for(i = 0;i < todo;i++)
+ for(size_t i{0};i < todo;i++)
*(Right++) = rsamples[i][1] + lsamples[i][0];
base += todo;
diff --git a/OpenAL32/Include/bs2b.h b/alc/bs2b.h
index e845d906..df717cd4 100644
--- a/OpenAL32/Include/bs2b.h
+++ b/alc/bs2b.h
@@ -24,6 +24,8 @@
#ifndef BS2B_H
#define BS2B_H
+#include "almalloc.h"
+
/* Number of crossfeed levels */
#define BS2B_CLEVELS 3
@@ -42,10 +44,6 @@
/* Default sample rate (Hz) */
#define BS2B_DEFAULT_SRATE 44100
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
struct bs2b {
int level; /* Crossfeed level */
int srate; /* Sample rate (Hz) */
@@ -59,14 +57,15 @@ struct bs2b {
float a1_hi;
float b1_hi;
- /* Buffer of last filtered sample.
+ /* Buffer of filter history
* [0] - first channel, [1] - second channel
*/
struct t_last_sample {
- float asis;
float lo;
float hi;
- } last_sample[2];
+ } history[2];
+
+ DEF_NEWDEL(bs2b)
};
/* Clear buffers and set new coefficients with new crossfeed level and sample
@@ -74,21 +73,17 @@ struct bs2b {
* level - crossfeed level of *LEVEL values.
* srate - sample rate by Hz.
*/
-void bs2b_set_params(struct bs2b *bs2b, int level, int srate);
+void bs2b_set_params(bs2b *bs2b, int level, int srate);
/* Return current crossfeed level value */
-int bs2b_get_level(struct bs2b *bs2b);
+int bs2b_get_level(bs2b *bs2b);
/* Return current sample rate value */
-int bs2b_get_srate(struct bs2b *bs2b);
+int bs2b_get_srate(bs2b *bs2b);
/* Clear buffer */
-void bs2b_clear(struct bs2b *bs2b);
-
-void bs2b_cross_feed(struct bs2b *bs2b, float *restrict Left, float *restrict Right, int SamplesToDo);
+void bs2b_clear(bs2b *bs2b);
-#ifdef __cplusplus
-} /* extern "C" */
-#endif /* __cplusplus */
+void bs2b_cross_feed(bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo);
#endif /* BS2B_H */
diff --git a/alc/compat.h b/alc/compat.h
new file mode 100644
index 00000000..960b4b94
--- /dev/null
+++ b/alc/compat.h
@@ -0,0 +1,9 @@
+#ifndef AL_COMPAT_H
+#define AL_COMPAT_H
+
+#include <string>
+
+struct PathNamePair { std::string path, fname; };
+const PathNamePair &GetProcBinary(void);
+
+#endif /* AL_COMPAT_H */
diff --git a/alc/converter.cpp b/alc/converter.cpp
new file mode 100644
index 00000000..553bad58
--- /dev/null
+++ b/alc/converter.cpp
@@ -0,0 +1,360 @@
+
+#include "config.h"
+
+#include "converter.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <iterator>
+
+#include "AL/al.h"
+
+#include "albyte.h"
+#include "alu.h"
+#include "fpu_modes.h"
+#include "mixer/defs.h"
+
+
+namespace {
+
+/* Base template left undefined. Should be marked =delete, but Clang 3.8.1
+ * chokes on that given the inline specializations.
+ */
+template<DevFmtType T>
+inline ALfloat LoadSample(typename DevFmtTypeTraits<T>::Type val) noexcept;
+
+template<> inline ALfloat LoadSample<DevFmtByte>(DevFmtTypeTraits<DevFmtByte>::Type val) noexcept
+{ return val * (1.0f/128.0f); }
+template<> inline ALfloat LoadSample<DevFmtShort>(DevFmtTypeTraits<DevFmtShort>::Type val) noexcept
+{ return val * (1.0f/32768.0f); }
+template<> inline ALfloat LoadSample<DevFmtInt>(DevFmtTypeTraits<DevFmtInt>::Type val) noexcept
+{ return static_cast<float>(val) * (1.0f/2147483648.0f); }
+template<> inline ALfloat LoadSample<DevFmtFloat>(DevFmtTypeTraits<DevFmtFloat>::Type val) noexcept
+{ return val; }
+
+template<> inline ALfloat LoadSample<DevFmtUByte>(DevFmtTypeTraits<DevFmtUByte>::Type val) noexcept
+{ return LoadSample<DevFmtByte>(static_cast<ALbyte>(val - 128)); }
+template<> inline ALfloat LoadSample<DevFmtUShort>(DevFmtTypeTraits<DevFmtUShort>::Type val) noexcept
+{ return LoadSample<DevFmtShort>(static_cast<ALshort>(val - 32768)); }
+template<> inline ALfloat LoadSample<DevFmtUInt>(DevFmtTypeTraits<DevFmtUInt>::Type val) noexcept
+{ return LoadSample<DevFmtInt>(static_cast<ALint>(val - 2147483648u)); }
+
+
+template<DevFmtType T>
+inline void LoadSampleArray(ALfloat *RESTRICT dst, const void *src, const size_t srcstep,
+ const size_t samples) noexcept
+{
+ using SampleType = typename DevFmtTypeTraits<T>::Type;
+
+ const SampleType *ssrc = static_cast<const SampleType*>(src);
+ for(size_t i{0u};i < samples;i++)
+ dst[i] = LoadSample<T>(ssrc[i*srcstep]);
+}
+
+void LoadSamples(ALfloat *dst, const ALvoid *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 typename DevFmtTypeTraits<T>::Type StoreSample(ALfloat) noexcept;
+
+template<> inline ALfloat StoreSample<DevFmtFloat>(ALfloat val) noexcept
+{ return val; }
+template<> inline ALint StoreSample<DevFmtInt>(ALfloat val) noexcept
+{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); }
+template<> inline ALshort StoreSample<DevFmtShort>(ALfloat val) noexcept
+{ return static_cast<ALshort>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
+template<> inline ALbyte StoreSample<DevFmtByte>(ALfloat val) noexcept
+{ return static_cast<ALbyte>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
+
+/* Define unsigned output variations. */
+template<> inline ALuint StoreSample<DevFmtUInt>(ALfloat val) noexcept
+{ return static_cast<ALuint>(StoreSample<DevFmtInt>(val)) + 2147483648u; }
+template<> inline ALushort StoreSample<DevFmtUShort>(ALfloat val) noexcept
+{ return static_cast<ALushort>(StoreSample<DevFmtShort>(val) + 32768); }
+template<> inline ALubyte StoreSample<DevFmtUByte>(ALfloat val) noexcept
+{ return static_cast<ALubyte>(StoreSample<DevFmtByte>(val) + 128); }
+
+template<DevFmtType T>
+inline void StoreSampleArray(void *dst, const ALfloat *RESTRICT src, const size_t dststep,
+ const size_t samples) noexcept
+{
+ using SampleType = typename DevFmtTypeTraits<T>::Type;
+
+ SampleType *sdst = static_cast<SampleType*>(dst);
+ for(size_t i{0u};i < samples;i++)
+ sdst[i*dststep] = StoreSample<T>(src[i]);
+}
+
+
+void StoreSamples(ALvoid *dst, const ALfloat *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(ALfloat *RESTRICT dst, const void *src, const size_t frames) noexcept
+{
+ using SampleType = typename DevFmtTypeTraits<T>::Type;
+
+ const SampleType *ssrc = static_cast<const SampleType*>(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 Stereo2Mono(ALfloat *RESTRICT dst, const void *src, const size_t frames) noexcept
+{
+ using SampleType = typename DevFmtTypeTraits<T>::Type;
+
+ const SampleType *ssrc = static_cast<const SampleType*>(src);
+ for(size_t i{0u};i < frames;i++)
+ dst[i] = (LoadSample<T>(ssrc[i*2 + 0])+LoadSample<T>(ssrc[i*2 + 1])) *
+ 0.707106781187f;
+}
+
+} // namespace
+
+SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
+ ALuint srcRate, ALuint 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<ALuint>(
+ mind(srcRate*double{FRACTIONONE}/dstRate + 0.5, MAX_PITCH*FRACTIONONE));
+ converter->mIncrement = maxu(step, 1);
+ if(converter->mIncrement == FRACTIONONE)
+ converter->mResample = Resample_<CopyTag,CTag>;
+ else
+ converter->mResample = PrepareResampler(resampler, converter->mIncrement,
+ &converter->mState);
+
+ return converter;
+}
+
+ALuint SampleConverter::availableOut(ALuint srcframes) const
+{
+ ALint prepcount{mSrcPrepCount};
+ if(prepcount < 0)
+ {
+ /* Negative prepcount means we need to skip that many input samples. */
+ if(static_cast<ALuint>(-prepcount) >= srcframes)
+ return 0;
+ srcframes -= static_cast<ALuint>(-prepcount);
+ prepcount = 0;
+ }
+
+ if(srcframes < 1)
+ {
+ /* No output samples if there's no input samples. */
+ return 0;
+ }
+
+ if(prepcount < MAX_RESAMPLER_PADDING
+ && static_cast<ALuint>(MAX_RESAMPLER_PADDING - prepcount) >= srcframes)
+ {
+ /* Not enough input samples to generate an output sample. */
+ return 0;
+ }
+
+ auto DataSize64 = static_cast<uint64_t>(prepcount);
+ DataSize64 += srcframes;
+ DataSize64 -= MAX_RESAMPLER_PADDING;
+ DataSize64 <<= FRACTIONBITS;
+ DataSize64 -= mFracOffset;
+
+ /* If we have a full prep, we can generate at least one sample. */
+ return static_cast<ALuint>(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1, BUFFERSIZE));
+}
+
+ALuint SampleConverter::convert(const ALvoid **src, ALuint *srcframes, ALvoid *dst, ALuint dstframes)
+{
+ const ALuint SrcFrameSize{static_cast<ALuint>(mChan.size()) * mSrcTypeSize};
+ const ALuint DstFrameSize{static_cast<ALuint>(mChan.size()) * mDstTypeSize};
+ const ALuint increment{mIncrement};
+ auto SamplesIn = static_cast<const al::byte*>(*src);
+ ALuint NumSrcSamples{*srcframes};
+
+ FPUCtl mixer_mode{};
+ ALuint pos{0};
+ while(pos < dstframes && NumSrcSamples > 0)
+ {
+ ALint prepcount{mSrcPrepCount};
+ if(prepcount < 0)
+ {
+ /* Negative prepcount means we need to skip that many input samples. */
+ if(static_cast<ALuint>(-prepcount) >= NumSrcSamples)
+ {
+ mSrcPrepCount = static_cast<ALint>(NumSrcSamples) + prepcount;
+ NumSrcSamples = 0;
+ break;
+ }
+ SamplesIn += SrcFrameSize*static_cast<ALuint>(-prepcount);
+ NumSrcSamples -= static_cast<ALuint>(-prepcount);
+ mSrcPrepCount = 0;
+ continue;
+ }
+ ALuint toread{minu(NumSrcSamples, BUFFERSIZE - MAX_RESAMPLER_PADDING)};
+
+ if(prepcount < MAX_RESAMPLER_PADDING
+ && static_cast<ALuint>(MAX_RESAMPLER_PADDING - 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<ALint>(toread);
+ NumSrcSamples = 0;
+ break;
+ }
+
+ ALfloat *RESTRICT SrcData{mSrcSamples};
+ ALfloat *RESTRICT DstData{mDstSamples};
+ ALuint DataPosFrac{mFracOffset};
+ auto DataSize64 = static_cast<uint64_t>(prepcount);
+ DataSize64 += toread;
+ DataSize64 -= MAX_RESAMPLER_PADDING;
+ DataSize64 <<= FRACTIONBITS;
+ DataSize64 -= DataPosFrac;
+
+ /* If we have a full prep, we can generate at least one sample. */
+ auto DstSize = static_cast<ALuint>(
+ clampu64((DataSize64 + increment-1)/increment, 1, BUFFERSIZE));
+ 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.
+ */
+ ALuint SrcDataEnd{(DstSize*increment + DataPosFrac)>>FRACTIONBITS};
+ if(SrcDataEnd >= static_cast<ALuint>(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<ALuint>(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 ALfloat *ResampledData{mResample(&mState, SrcData+(MAX_RESAMPLER_PADDING>>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<ALint>(toread - (DataPosFrac>>FRACTIONBITS)),
+ MAX_RESAMPLER_PADDING);
+ mFracOffset = DataPosFrac & FRACTIONMASK;
+
+ /* Update the src and dst pointers in case there's still more to do. */
+ SamplesIn += SrcFrameSize*(DataPosFrac>>FRACTIONBITS);
+ NumSrcSamples -= minu(NumSrcSamples, (DataPosFrac>>FRACTIONBITS));
+
+ dst = static_cast<al::byte*>(dst) + DstFrameSize*DstSize;
+ pos += DstSize;
+ }
+
+ *src = SamplesIn;
+ *srcframes = NumSrcSamples;
+
+ return pos;
+}
+
+
+void ChannelConverter::convert(const ALvoid *src, ALfloat *dst, ALuint frames) const
+{
+ if(mSrcChans == DevFmtStereo && mDstChans == DevFmtMono)
+ {
+ switch(mSrcType)
+ {
+#define HANDLE_FMT(T) case T: Stereo2Mono<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
+ }
+ }
+ else if(mSrcChans == DevFmtMono && 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
+ }
+ }
+ else
+ LoadSamples(dst, src, 1u, mSrcType, frames * ChannelsFromDevFmt(mSrcChans, 0));
+}
diff --git a/alc/converter.h b/alc/converter.h
new file mode 100644
index 00000000..5842df07
--- /dev/null
+++ b/alc/converter.h
@@ -0,0 +1,61 @@
+#ifndef CONVERTER_H
+#define CONVERTER_H
+
+#include <cstddef>
+#include <memory>
+
+#include "AL/al.h"
+
+#include "alcmain.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alu.h"
+#include "devformat.h"
+#include "voice.h"
+
+
+struct SampleConverter {
+ DevFmtType mSrcType{};
+ DevFmtType mDstType{};
+ ALuint mSrcTypeSize{};
+ ALuint mDstTypeSize{};
+
+ ALint mSrcPrepCount{};
+
+ ALuint mFracOffset{};
+ ALuint mIncrement{};
+ InterpState mState{};
+ ResamplerFunc mResample{};
+
+ alignas(16) ALfloat mSrcSamples[BUFFERSIZE]{};
+ alignas(16) ALfloat mDstSamples[BUFFERSIZE]{};
+
+ struct ChanSamples {
+ alignas(16) ALfloat PrevSamples[MAX_RESAMPLER_PADDING];
+ };
+ al::FlexArray<ChanSamples> mChan;
+
+ SampleConverter(size_t numchans) : mChan{numchans} { }
+
+ ALuint convert(const ALvoid **src, ALuint *srcframes, ALvoid *dst, ALuint dstframes);
+ ALuint availableOut(ALuint srcframes) const;
+
+ DEF_FAM_NEWDEL(SampleConverter, mChan)
+};
+using SampleConverterPtr = std::unique_ptr<SampleConverter>;
+
+SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
+ ALuint srcRate, ALuint dstRate, Resampler resampler);
+
+
+struct ChannelConverter {
+ DevFmtType mSrcType;
+ DevFmtChannels mSrcChans;
+ DevFmtChannels mDstChans;
+
+ bool is_active() const noexcept { return mSrcChans != mDstChans; }
+
+ void convert(const ALvoid *src, ALfloat *dst, ALuint frames) const;
+};
+
+#endif /* CONVERTER_H */
diff --git a/Alc/cpu_caps.h b/alc/cpu_caps.h
index 328d470e..64a4ee45 100644
--- a/Alc/cpu_caps.h
+++ b/alc/cpu_caps.h
@@ -1,6 +1,7 @@
#ifndef CPU_CAPS_H
#define CPU_CAPS_H
+
extern int CPUCapFlags;
enum {
CPU_CAP_SSE = 1<<0,
diff --git a/alc/devformat.h b/alc/devformat.h
new file mode 100644
index 00000000..402fb8bd
--- /dev/null
+++ b/alc/devformat.h
@@ -0,0 +1,121 @@
+#ifndef ALC_DEVFORMAT_H
+#define ALC_DEVFORMAT_H
+
+#include <cstdint>
+
+#include "AL/al.h"
+#include "AL/alext.h"
+
+#include "inprogext.h"
+
+
+enum Channel {
+ FrontLeft = 0,
+ FrontRight,
+ FrontCenter,
+ LFE,
+ BackLeft,
+ BackRight,
+ BackCenter,
+ SideLeft,
+ SideRight,
+
+ UpperFrontLeft,
+ UpperFrontRight,
+ UpperBackLeft,
+ UpperBackRight,
+ LowerFrontLeft,
+ LowerFrontRight,
+ LowerBackLeft,
+ LowerBackRight,
+
+ Aux0,
+ Aux1,
+ Aux2,
+ Aux3,
+ Aux4,
+ Aux5,
+ Aux6,
+ Aux7,
+ Aux8,
+ Aux9,
+ Aux10,
+ Aux11,
+ Aux12,
+ Aux13,
+ Aux14,
+ Aux15,
+
+ MaxChannels
+};
+
+
+/* Device formats */
+enum DevFmtType : ALenum {
+ DevFmtByte = ALC_BYTE_SOFT,
+ DevFmtUByte = ALC_UNSIGNED_BYTE_SOFT,
+ DevFmtShort = ALC_SHORT_SOFT,
+ DevFmtUShort = ALC_UNSIGNED_SHORT_SOFT,
+ DevFmtInt = ALC_INT_SOFT,
+ DevFmtUInt = ALC_UNSIGNED_INT_SOFT,
+ DevFmtFloat = ALC_FLOAT_SOFT,
+
+ DevFmtTypeDefault = DevFmtFloat
+};
+enum DevFmtChannels : ALenum {
+ DevFmtMono = ALC_MONO_SOFT,
+ DevFmtStereo = ALC_STEREO_SOFT,
+ DevFmtQuad = ALC_QUAD_SOFT,
+ DevFmtX51 = ALC_5POINT1_SOFT,
+ DevFmtX61 = ALC_6POINT1_SOFT,
+ DevFmtX71 = ALC_7POINT1_SOFT,
+ DevFmtAmbi3D = ALC_BFORMAT3D_SOFT,
+
+ /* Similar to 5.1, except using rear channels instead of sides */
+ DevFmtX51Rear = 0x70000000,
+
+ DevFmtChannelsDefault = DevFmtStereo
+};
+#define MAX_OUTPUT_CHANNELS (16)
+
+/* DevFmtType traits, providing the type, etc given a DevFmtType. */
+template<DevFmtType T>
+struct DevFmtTypeTraits { };
+
+template<>
+struct DevFmtTypeTraits<DevFmtByte> { using Type = int8_t; };
+template<>
+struct DevFmtTypeTraits<DevFmtUByte> { using Type = uint8_t; };
+template<>
+struct DevFmtTypeTraits<DevFmtShort> { using Type = int16_t; };
+template<>
+struct DevFmtTypeTraits<DevFmtUShort> { using Type = uint16_t; };
+template<>
+struct DevFmtTypeTraits<DevFmtInt> { using Type = int32_t; };
+template<>
+struct DevFmtTypeTraits<DevFmtUInt> { using Type = uint32_t; };
+template<>
+struct DevFmtTypeTraits<DevFmtFloat> { using Type = float; };
+
+
+ALuint BytesFromDevFmt(DevFmtType type) noexcept;
+ALuint ChannelsFromDevFmt(DevFmtChannels chans, ALuint ambiorder) noexcept;
+inline ALuint FrameSizeFromDevFmt(DevFmtChannels chans, DevFmtType type, ALuint ambiorder) noexcept
+{ return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type); }
+
+enum class AmbiLayout {
+ FuMa = ALC_FUMA_SOFT, /* FuMa channel order */
+ ACN = ALC_ACN_SOFT, /* ACN channel order */
+
+ Default = ACN
+};
+
+enum class AmbiNorm {
+ FuMa = ALC_FUMA_SOFT, /* FuMa normalization */
+ SN3D = ALC_SN3D_SOFT, /* SN3D normalization */
+ N3D = ALC_N3D_SOFT, /* N3D normalization */
+
+ Default = SN3D
+};
+
+#endif /* ALC_DEVFORMAT_H */
diff --git a/alc/effects/autowah.cpp b/alc/effects/autowah.cpp
new file mode 100644
index 00000000..a79c21d9
--- /dev/null
+++ b/alc/effects/autowah.cpp
@@ -0,0 +1,299 @@
+/**
+ * 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 "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+#include "filters/biquad.h"
+#include "vecmat.h"
+
+namespace {
+
+#define MIN_FREQ 20.0f
+#define MAX_FREQ 2500.0f
+#define Q_FACTOR 5.0f
+
+struct AutowahState final : public EffectState {
+ /* Effect parameters */
+ ALfloat mAttackRate;
+ ALfloat mReleaseRate;
+ ALfloat mResonanceGain;
+ ALfloat mPeakGain;
+ ALfloat mFreqMinNorm;
+ ALfloat mBandwidthNorm;
+ ALfloat mEnvDelay;
+
+ /* Filter components derived from the envelope. */
+ struct {
+ ALfloat cos_w0;
+ ALfloat alpha;
+ } mEnv[BUFFERSIZE];
+
+ struct {
+ /* Effect filters' history. */
+ struct {
+ ALfloat z1, z2;
+ } Filter;
+
+ /* Effect gains for each output channel */
+ ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
+ ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
+ } mChans[MAX_AMBI_CHANNELS];
+
+ /* Effects buffers */
+ alignas(16) ALfloat mBufferOut[BUFFERSIZE];
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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)
+};
+
+ALboolean AutowahState::deviceUpdate(const ALCdevice*)
+{
+ /* (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;
+ }
+
+ return AL_TRUE;
+}
+
+void AutowahState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device{context->mDevice.get()};
+ const auto frequency = static_cast<float>(device->Frequency);
+
+ const ALfloat 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/AL_AUTOWAH_MAX_PEAK_GAIN);
+ mFreqMinNorm = MIN_FREQ / frequency;
+ mBandwidthNorm = (MAX_FREQ-MIN_FREQ) / frequency;
+
+ mOutTarget = target.Main->Buffer;
+ for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
+ {
+ auto coeffs = GetAmbiIdentityRow(i);
+ ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
+ }
+}
+
+void AutowahState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+ const ALfloat attack_rate = mAttackRate;
+ const ALfloat release_rate = mReleaseRate;
+ const ALfloat res_gain = mResonanceGain;
+ const ALfloat peak_gain = mPeakGain;
+ const ALfloat freq_min = mFreqMinNorm;
+ const ALfloat bandwidth = mBandwidthNorm;
+
+ ALfloat env_delay{mEnvDelay};
+ for(size_t i{0u};i < samplesToDo;i++)
+ {
+ ALfloat 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 = cosf(w0);
+ mEnv[i].alpha = sinf(w0)/(2.0f * Q_FACTOR);
+ }
+ 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.
+ */
+ ALfloat z1{chandata->Filter.z1};
+ ALfloat z2{chandata->Filter.z2};
+
+ for(size_t i{0u};i < samplesToDo;i++)
+ {
+ const ALfloat alpha = mEnv[i].alpha;
+ const ALfloat cos_w0 = mEnv[i].cos_w0;
+ ALfloat input, output;
+ ALfloat 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;
+ }
+}
+
+
+void Autowah_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_AUTOWAH_ATTACK_TIME:
+ if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah attack time out of range");
+ props->Autowah.AttackTime = val;
+ break;
+
+ case AL_AUTOWAH_RELEASE_TIME:
+ if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah release time out of range");
+ props->Autowah.ReleaseTime = val;
+ break;
+
+ case AL_AUTOWAH_RESONANCE:
+ if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah resonance out of range");
+ props->Autowah.Resonance = val;
+ break;
+
+ case AL_AUTOWAH_PEAK_GAIN:
+ if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah peak gain out of range");
+ props->Autowah.PeakGain = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
+ }
+}
+void Autowah_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Autowah_setParamf(props, context, param, vals[0]); }
+
+void Autowah_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
+{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
+void Autowah_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
+
+void Autowah_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_AUTOWAH_ATTACK_TIME:
+ *val = props->Autowah.AttackTime;
+ break;
+
+ case AL_AUTOWAH_RELEASE_TIME:
+ *val = props->Autowah.ReleaseTime;
+ break;
+
+ case AL_AUTOWAH_RESONANCE:
+ *val = props->Autowah.Resonance;
+ break;
+
+ case AL_AUTOWAH_PEAK_GAIN:
+ *val = props->Autowah.PeakGain;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
+ }
+
+}
+void Autowah_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Autowah_getParamf(props, context, param, vals); }
+
+void Autowah_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
+void Autowah_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
+
+DEFINE_ALEFFECT_VTABLE(Autowah);
+
+
+struct AutowahStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new AutowahState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Autowah_vtable; }
+};
+
+EffectProps AutowahStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
+ props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
+ props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
+ props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *AutowahStateFactory_getFactory()
+{
+ static AutowahStateFactory AutowahFactory{};
+ return &AutowahFactory;
+}
diff --git a/alc/effects/base.h b/alc/effects/base.h
new file mode 100644
index 00000000..6889308d
--- /dev/null
+++ b/alc/effects/base.h
@@ -0,0 +1,193 @@
+#ifndef EFFECTS_BASE_H
+#define EFFECTS_BASE_H
+
+#include <cstddef>
+
+#include "alcmain.h"
+#include "almalloc.h"
+#include "alspan.h"
+#include "atomic.h"
+#include "intrusive_ptr.h"
+
+struct ALeffectslot;
+
+
+union EffectProps {
+ struct {
+ // Shared Reverb Properties
+ ALfloat Density;
+ ALfloat Diffusion;
+ ALfloat Gain;
+ ALfloat GainHF;
+ ALfloat DecayTime;
+ ALfloat DecayHFRatio;
+ ALfloat ReflectionsGain;
+ ALfloat ReflectionsDelay;
+ ALfloat LateReverbGain;
+ ALfloat LateReverbDelay;
+ ALfloat AirAbsorptionGainHF;
+ ALfloat RoomRolloffFactor;
+ bool DecayHFLimit;
+
+ // Additional EAX Reverb Properties
+ ALfloat GainLF;
+ ALfloat DecayLFRatio;
+ ALfloat ReflectionsPan[3];
+ ALfloat LateReverbPan[3];
+ ALfloat EchoTime;
+ ALfloat EchoDepth;
+ ALfloat ModulationTime;
+ ALfloat ModulationDepth;
+ ALfloat HFReference;
+ ALfloat LFReference;
+ } Reverb;
+
+ struct {
+ ALfloat AttackTime;
+ ALfloat ReleaseTime;
+ ALfloat Resonance;
+ ALfloat PeakGain;
+ } Autowah;
+
+ struct {
+ ALint Waveform;
+ ALint Phase;
+ ALfloat Rate;
+ ALfloat Depth;
+ ALfloat Feedback;
+ ALfloat Delay;
+ } Chorus; /* Also Flanger */
+
+ struct {
+ bool OnOff;
+ } Compressor;
+
+ struct {
+ ALfloat Edge;
+ ALfloat Gain;
+ ALfloat LowpassCutoff;
+ ALfloat EQCenter;
+ ALfloat EQBandwidth;
+ } Distortion;
+
+ struct {
+ ALfloat Delay;
+ ALfloat LRDelay;
+
+ ALfloat Damping;
+ ALfloat Feedback;
+
+ ALfloat Spread;
+ } Echo;
+
+ struct {
+ ALfloat LowCutoff;
+ ALfloat LowGain;
+ ALfloat Mid1Center;
+ ALfloat Mid1Gain;
+ ALfloat Mid1Width;
+ ALfloat Mid2Center;
+ ALfloat Mid2Gain;
+ ALfloat Mid2Width;
+ ALfloat HighCutoff;
+ ALfloat HighGain;
+ } Equalizer;
+
+ struct {
+ ALfloat Frequency;
+ ALint LeftDirection;
+ ALint RightDirection;
+ } Fshifter;
+
+ struct {
+ ALfloat Frequency;
+ ALfloat HighPassCutoff;
+ ALint Waveform;
+ } Modulator;
+
+ struct {
+ ALint CoarseTune;
+ ALint FineTune;
+ } Pshifter;
+
+ struct {
+ ALfloat Rate;
+ ALint PhonemeA;
+ ALint PhonemeB;
+ ALint PhonemeACoarseTuning;
+ ALint PhonemeBCoarseTuning;
+ ALint Waveform;
+ } Vmorpher;
+
+ struct {
+ ALfloat Gain;
+ } Dedicated;
+};
+
+
+struct EffectVtable {
+ void (*const setParami)(EffectProps *props, ALCcontext *context, ALenum param, ALint val);
+ void (*const setParamiv)(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals);
+ void (*const setParamf)(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val);
+ void (*const setParamfv)(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals);
+
+ void (*const getParami)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val);
+ void (*const getParamiv)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals);
+ void (*const getParamf)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val);
+ void (*const getParamfv)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals);
+};
+
+#define DEFINE_ALEFFECT_VTABLE(T) \
+const EffectVtable T##_vtable = { \
+ T##_setParami, T##_setParamiv, \
+ T##_setParamf, T##_setParamfv, \
+ T##_getParami, T##_getParamiv, \
+ T##_getParamf, T##_getParamfv, \
+}
+
+
+struct EffectTarget {
+ MixParams *Main;
+ RealMixParams *RealOut;
+};
+
+struct EffectState : public al::intrusive_ref<EffectState> {
+ al::span<FloatBufferLine> mOutTarget;
+
+
+ virtual ~EffectState() = default;
+
+ virtual ALboolean deviceUpdate(const ALCdevice *device) = 0;
+ virtual void update(const ALCcontext *context, const ALeffectslot *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() { }
+
+ virtual EffectState *create() = 0;
+ virtual EffectProps getDefaultProps() const noexcept = 0;
+ virtual const EffectVtable *getEffectVtable() const noexcept = 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);
+
+
+#endif /* EFFECTS_BASE_H */
diff --git a/alc/effects/chorus.cpp b/alc/effects/chorus.cpp
new file mode 100644
index 00000000..59e05be0
--- /dev/null
+++ b/alc/effects/chorus.cpp
@@ -0,0 +1,534 @@
+/**
+ * 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 "AL/al.h"
+#include "AL/alc.h"
+#include "AL/efx.h"
+
+#include "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alspan.h"
+#include "alu.h"
+#include "ambidefs.h"
+#include "effects/base.h"
+#include "math_defs.h"
+#include "opthelpers.h"
+#include "vector.h"
+
+
+namespace {
+
+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 class WaveForm {
+ Sinusoid,
+ Triangle
+};
+
+void GetTriangleDelays(ALuint *delays, const ALuint start_offset, const ALuint lfo_range,
+ const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const size_t todo)
+{
+ ASSUME(lfo_range > 0);
+ ASSUME(todo > 0);
+
+ ALuint offset{start_offset};
+ auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALuint
+ {
+ offset = (offset+1)%lfo_range;
+ const float offset_norm{static_cast<float>(offset) * lfo_scale};
+ return static_cast<ALuint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay);
+ };
+ std::generate_n(delays, todo, gen_lfo);
+}
+
+void GetSinusoidDelays(ALuint *delays, const ALuint start_offset, const ALuint lfo_range,
+ const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const size_t todo)
+{
+ ASSUME(lfo_range > 0);
+ ASSUME(todo > 0);
+
+ ALuint offset{start_offset};
+ auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALuint
+ {
+ offset = (offset+1)%lfo_range;
+ const float offset_norm{static_cast<float>(offset) * lfo_scale};
+ return static_cast<ALuint>(fastf2i(std::sin(offset_norm)*depth) + delay);
+ };
+ std::generate_n(delays, todo, gen_lfo);
+}
+
+struct ChorusState final : public EffectState {
+ al::vector<ALfloat,16> mSampleBuffer;
+ ALuint mOffset{0};
+
+ ALuint mLfoOffset{0};
+ ALuint mLfoRange{1};
+ ALfloat mLfoScale{0.0f};
+ ALuint mLfoDisp{0};
+
+ /* Gains for left and right sides */
+ struct {
+ ALfloat Current[MAX_OUTPUT_CHANNELS]{};
+ ALfloat Target[MAX_OUTPUT_CHANNELS]{};
+ } mGains[2];
+
+ /* effect parameters */
+ WaveForm mWaveform{};
+ ALint mDelay{0};
+ ALfloat mDepth{0.0f};
+ ALfloat mFeedback{0.0f};
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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)
+};
+
+ALboolean ChorusState::deviceUpdate(const ALCdevice *Device)
+{
+ constexpr ALfloat 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())
+ {
+ mSampleBuffer.resize(maxlen);
+ mSampleBuffer.shrink_to_fit();
+ }
+
+ 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);
+ }
+
+ return AL_TRUE;
+}
+
+void ChorusState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target)
+{
+ constexpr ALsizei mindelay{(MAX_RESAMPLER_PADDING>>1) << FRACTIONBITS};
+
+ switch(props->Chorus.Waveform)
+ {
+ case AL_CHORUS_WAVEFORM_TRIANGLE:
+ mWaveform = WaveForm::Triangle;
+ break;
+ case AL_CHORUS_WAVEFORM_SINUSOID:
+ mWaveform = WaveForm::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.
+ */
+ const ALCdevice *device{Context->mDevice.get()};
+ const auto frequency = static_cast<float>(device->Frequency);
+
+ mDelay = maxi(float2int(props->Chorus.Delay*frequency*FRACTIONONE + 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 */
+ ALfloat coeffs[2][MAX_AMBI_CHANNELS];
+ CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f, coeffs[0]);
+ CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f, coeffs[1]);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs[0], Slot->Params.Gain, mGains[0].Target);
+ ComputePanGains(target.Main, coeffs[1], Slot->Params.Gain, mGains[1].Target);
+
+ ALfloat 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.
+ */
+ ALuint lfo_range{float2uint(minf(frequency/rate + 0.5f, ALfloat{INT_MAX/360 - 180}))};
+
+ mLfoOffset = mLfoOffset * lfo_range / mLfoRange;
+ mLfoRange = lfo_range;
+ switch(mWaveform)
+ {
+ case WaveForm::Triangle:
+ mLfoScale = 4.0f / static_cast<float>(mLfoRange);
+ break;
+ case WaveForm::Sinusoid:
+ mLfoScale = al::MathDefs<float>::Tau() / static_cast<float>(mLfoRange);
+ break;
+ }
+
+ /* Calculate lfo phase displacement */
+ ALint phase{props->Chorus.Phase};
+ if(phase < 0) phase = 360 + phase;
+ mLfoDisp = (mLfoRange*static_cast<ALuint>(phase) + 180) / 360;
+ }
+}
+
+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 ALfloat feedback{mFeedback};
+ const ALuint avgdelay{(static_cast<ALuint>(mDelay) + (FRACTIONONE>>1)) >> FRACTIONBITS};
+ ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
+ ALuint offset{mOffset};
+
+ for(size_t base{0u};base < samplesToDo;)
+ {
+ const size_t todo{minz(256, samplesToDo-base)};
+
+ ALuint moddelays[2][256];
+ if(mWaveform == WaveForm::Sinusoid)
+ {
+ GetSinusoidDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
+ todo);
+ GetSinusoidDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
+ mDepth, mDelay, todo);
+ }
+ else /*if(mWaveform == WaveForm::Triangle)*/
+ {
+ GetTriangleDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
+ todo);
+ GetTriangleDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
+ mDepth, mDelay, todo);
+ }
+ mLfoOffset = (mLfoOffset+static_cast<ALuint>(todo)) % mLfoRange;
+
+ alignas(16) ALfloat temps[2][256];
+ 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.
+ ALuint delay{offset - (moddelays[0][i]>>FRACTIONBITS)};
+ ALfloat mu{static_cast<float>(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 = static_cast<float>(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(ALsizei c{0};c < 2;c++)
+ MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target,
+ samplesToDo-base, base);
+
+ base += todo;
+ }
+
+ mOffset = offset;
+}
+
+
+void Chorus_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ 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:
+ context->setError(AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
+ }
+}
+void Chorus_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Chorus_setParami(props, context, param, vals[0]); }
+void Chorus_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ 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:
+ context->setError(AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
+ }
+}
+void Chorus_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Chorus_setParamf(props, context, param, vals[0]); }
+
+void Chorus_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_CHORUS_WAVEFORM:
+ *val = props->Chorus.Waveform;
+ break;
+
+ case AL_CHORUS_PHASE:
+ *val = props->Chorus.Phase;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
+ }
+}
+void Chorus_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Chorus_getParami(props, context, param, vals); }
+void Chorus_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_CHORUS_RATE:
+ *val = props->Chorus.Rate;
+ break;
+
+ case AL_CHORUS_DEPTH:
+ *val = props->Chorus.Depth;
+ break;
+
+ case AL_CHORUS_FEEDBACK:
+ *val = props->Chorus.Feedback;
+ break;
+
+ case AL_CHORUS_DELAY:
+ *val = props->Chorus.Delay;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
+ }
+}
+void Chorus_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Chorus_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Chorus);
+
+
+struct ChorusStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new ChorusState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Chorus_vtable; }
+};
+
+EffectProps ChorusStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Chorus.Waveform = AL_CHORUS_DEFAULT_WAVEFORM;
+ props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE;
+ props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE;
+ props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH;
+ props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
+ props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY;
+ return props;
+}
+
+
+void Flanger_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ 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:
+ context->setError(AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
+ }
+}
+void Flanger_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Flanger_setParami(props, context, param, vals[0]); }
+void Flanger_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ 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:
+ context->setError(AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
+ }
+}
+void Flanger_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Flanger_setParamf(props, context, param, vals[0]); }
+
+void Flanger_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_FLANGER_WAVEFORM:
+ *val = props->Chorus.Waveform;
+ break;
+
+ case AL_FLANGER_PHASE:
+ *val = props->Chorus.Phase;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
+ }
+}
+void Flanger_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Flanger_getParami(props, context, param, vals); }
+void Flanger_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_FLANGER_RATE:
+ *val = props->Chorus.Rate;
+ break;
+
+ case AL_FLANGER_DEPTH:
+ *val = props->Chorus.Depth;
+ break;
+
+ case AL_FLANGER_FEEDBACK:
+ *val = props->Chorus.Feedback;
+ break;
+
+ case AL_FLANGER_DELAY:
+ *val = props->Chorus.Delay;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
+ }
+}
+void Flanger_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Flanger_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Flanger);
+
+
+/* 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 {
+ EffectState *create() override { return new ChorusState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Flanger_vtable; }
+};
+
+EffectProps FlangerStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Chorus.Waveform = AL_FLANGER_DEFAULT_WAVEFORM;
+ props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE;
+ props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE;
+ props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH;
+ props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
+ props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *ChorusStateFactory_getFactory()
+{
+ static ChorusStateFactory ChorusFactory{};
+ return &ChorusFactory;
+}
+
+EffectStateFactory *FlangerStateFactory_getFactory()
+{
+ static FlangerStateFactory FlangerFactory{};
+ return &FlangerFactory;
+}
diff --git a/alc/effects/compressor.cpp b/alc/effects/compressor.cpp
new file mode 100644
index 00000000..44ffaaef
--- /dev/null
+++ b/alc/effects/compressor.cpp
@@ -0,0 +1,221 @@
+/**
+ * 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 "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.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 */
+ ALfloat mGain[MAX_AMBI_CHANNELS][MAX_OUTPUT_CHANNELS]{};
+
+ /* Effect parameters */
+ ALboolean mEnabled{AL_TRUE};
+ ALfloat mAttackMult{1.0f};
+ ALfloat mReleaseMult{1.0f};
+ ALfloat mEnvFollower{1.0f};
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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)
+};
+
+ALboolean CompressorState::deviceUpdate(const ALCdevice *device)
+{
+ /* Number of samples to do a full attack and release (non-integer sample
+ * counts are okay).
+ */
+ const ALfloat attackCount = static_cast<ALfloat>(device->Frequency) * ATTACK_TIME;
+ const ALfloat releaseCount = static_cast<ALfloat>(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);
+
+ return AL_TRUE;
+}
+
+void CompressorState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ mEnabled = props->Compressor.OnOff;
+
+ mOutTarget = target.Main->Buffer;
+ for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
+ {
+ auto coeffs = GetAmbiIdentityRow(i);
+ ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mGain[i]);
+ }
+}
+
+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;)
+ {
+ ALfloat gains[256];
+ const size_t td{minz(256, samplesToDo-base)};
+
+ /* Generate the per-sample gains from the signal envelope. */
+ ALfloat 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 ALfloat 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 ALfloat 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 ALfloat *outgains{*(changains++)};
+ for(FloatBufferLine &output : samplesOut)
+ {
+ const ALfloat gain{*(outgains++)};
+ if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
+ continue;
+
+ for(size_t i{0u};i < td;i++)
+ output[base+i] += input[base+i] * gains[i] * gain;
+ }
+ }
+
+ base += td;
+ }
+}
+
+
+void Compressor_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ 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 != AL_FALSE;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
+ param);
+ }
+}
+void Compressor_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Compressor_setParami(props, context, param, vals[0]); }
+void Compressor_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat)
+{ context->setError(AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
+void Compressor_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*)
+{ context->setError(AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
+
+void Compressor_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_COMPRESSOR_ONOFF:
+ *val = props->Compressor.OnOff;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
+ param);
+ }
+}
+void Compressor_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Compressor_getParami(props, context, param, vals); }
+void Compressor_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
+{ context->setError(AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
+void Compressor_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
+{ context->setError(AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
+
+DEFINE_ALEFFECT_VTABLE(Compressor);
+
+
+struct CompressorStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new CompressorState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Compressor_vtable; }
+};
+
+EffectProps CompressorStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *CompressorStateFactory_getFactory()
+{
+ static CompressorStateFactory CompressorFactory{};
+ return &CompressorFactory;
+}
diff --git a/alc/effects/dedicated.cpp b/alc/effects/dedicated.cpp
new file mode 100644
index 00000000..aa81e13b
--- /dev/null
+++ b/alc/effects/dedicated.cpp
@@ -0,0 +1,160 @@
+/**
+ * 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 "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+
+
+namespace {
+
+struct DedicatedState final : public EffectState {
+ ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS];
+ ALfloat mTargetGains[MAX_OUTPUT_CHANNELS];
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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)
+};
+
+ALboolean DedicatedState::deviceUpdate(const ALCdevice*)
+{
+ std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
+ return AL_TRUE;
+}
+
+void DedicatedState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
+
+ const ALfloat Gain{slot->Params.Gain * props->Dedicated.Gain};
+
+ if(slot->Params.EffectType == AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT)
+ {
+ const ALuint 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->Params.EffectType == AL_EFFECT_DEDICATED_DIALOGUE)
+ {
+ /* Dialog goes to the front-center speaker if it exists, otherwise it
+ * plays from the front-center location. */
+ const ALuint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
+ GetChannelIdxByName(*target.RealOut, FrontCenter)};
+ if(idx != INVALID_CHANNEL_INDEX)
+ {
+ mOutTarget = target.RealOut->Buffer;
+ mTargetGains[idx] = Gain;
+ }
+ else
+ {
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs, 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);
+}
+
+
+void Dedicated_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
+{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
+void Dedicated_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
+void Dedicated_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_DEDICATED_GAIN:
+ if(!(val >= 0.0f && std::isfinite(val)))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Dedicated gain out of range");
+ props->Dedicated.Gain = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
+ }
+}
+void Dedicated_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Dedicated_setParamf(props, context, param, vals[0]); }
+
+void Dedicated_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
+void Dedicated_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
+void Dedicated_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_DEDICATED_GAIN:
+ *val = props->Dedicated.Gain;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
+ }
+}
+void Dedicated_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Dedicated_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Dedicated);
+
+
+struct DedicatedStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new DedicatedState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Dedicated_vtable; }
+};
+
+EffectProps DedicatedStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Dedicated.Gain = 1.0f;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *DedicatedStateFactory_getFactory()
+{
+ static DedicatedStateFactory DedicatedFactory{};
+ return &DedicatedFactory;
+}
diff --git a/alc/effects/distortion.cpp b/alc/effects/distortion.cpp
new file mode 100644
index 00000000..7dd43008
--- /dev/null
+++ b/alc/effects/distortion.cpp
@@ -0,0 +1,266 @@
+/**
+ * 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 <cmath>
+
+#include "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+#include "filters/biquad.h"
+
+
+namespace {
+
+struct DistortionState final : public EffectState {
+ /* Effect gains for each channel */
+ ALfloat mGain[MAX_OUTPUT_CHANNELS]{};
+
+ /* Effect parameters */
+ BiquadFilter mLowpass;
+ BiquadFilter mBandpass;
+ ALfloat mAttenuation{};
+ ALfloat mEdgeCoeff{};
+
+ ALfloat mBuffer[2][BUFFERSIZE]{};
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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)
+};
+
+ALboolean DistortionState::deviceUpdate(const ALCdevice*)
+{
+ mLowpass.clear();
+ mBandpass.clear();
+ return AL_TRUE;
+}
+
+void DistortionState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device{context->mDevice.get()};
+
+ /* Store waveshaper edge settings. */
+ const ALfloat edge{
+ minf(std::sin(al::MathDefs<float>::Pi()*0.5f * props->Distortion.Edge), 0.99f)};
+ mEdgeCoeff = 2.0f * edge / (1.0f-edge);
+
+ ALfloat cutoff{props->Distortion.LowpassCutoff};
+ /* Bandwidth value is constant in octaves. */
+ ALfloat bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)};
+ /* Multiply sampling frequency by the amount of oversampling done during
+ * processing.
+ */
+ auto frequency = static_cast<ALfloat>(device->Frequency);
+ mLowpass.setParams(BiquadType::LowPass, 1.0f, cutoff / (frequency*4.0f),
+ mLowpass.rcpQFromBandwidth(cutoff / (frequency*4.0f), bandwidth));
+
+ cutoff = props->Distortion.EQCenter;
+ /* Convert bandwidth in Hz to octaves. */
+ bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
+ mBandpass.setParams(BiquadType::BandPass, 1.0f, cutoff / (frequency*4.0f),
+ mBandpass.rcpQFromBandwidth(cutoff / (frequency*4.0f), bandwidth));
+
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs, slot->Params.Gain*props->Distortion.Gain, mGain);
+}
+
+void DistortionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+ const ALfloat 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(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(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[1], mBuffer[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(size_t i{0u};i < todo;i++)
+ {
+ ALfloat smp{mBuffer[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));
+
+ mBuffer[0][i] = smp;
+ }
+
+ /* Third step, do bandpass filtering of distorted signal. */
+ mBandpass.process(mBuffer[1], mBuffer[0], todo);
+
+ todo >>= 2;
+ const ALfloat *outgains{mGain};
+ for(FloatBufferLine &output : samplesOut)
+ {
+ /* Fourth step, final, do attenuation and perform decimation,
+ * storing only one sample out of four.
+ */
+ const ALfloat gain{*(outgains++)};
+ if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
+ continue;
+
+ for(size_t i{0u};i < todo;i++)
+ output[base+i] += gain * mBuffer[1][i*4];
+ }
+
+ base += todo;
+ }
+}
+
+
+void Distortion_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
+{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
+void Distortion_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
+void Distortion_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ 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:
+ context->setError(AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param);
+ }
+}
+void Distortion_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Distortion_setParamf(props, context, param, vals[0]); }
+
+void Distortion_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
+void Distortion_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
+void Distortion_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_DISTORTION_EDGE:
+ *val = props->Distortion.Edge;
+ break;
+
+ case AL_DISTORTION_GAIN:
+ *val = props->Distortion.Gain;
+ break;
+
+ case AL_DISTORTION_LOWPASS_CUTOFF:
+ *val = props->Distortion.LowpassCutoff;
+ break;
+
+ case AL_DISTORTION_EQCENTER:
+ *val = props->Distortion.EQCenter;
+ break;
+
+ case AL_DISTORTION_EQBANDWIDTH:
+ *val = props->Distortion.EQBandwidth;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param);
+ }
+}
+void Distortion_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Distortion_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Distortion);
+
+
+struct DistortionStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new DistortionState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Distortion_vtable; }
+};
+
+EffectProps DistortionStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE;
+ props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN;
+ props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
+ props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
+ props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *DistortionStateFactory_getFactory()
+{
+ static DistortionStateFactory DistortionFactory{};
+ return &DistortionFactory;
+}
diff --git a/alc/effects/echo.cpp b/alc/effects/echo.cpp
new file mode 100644
index 00000000..a9213df5
--- /dev/null
+++ b/alc/effects/echo.cpp
@@ -0,0 +1,267 @@
+/**
+ * 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 "al/auxeffectslot.h"
+#include "al/filter.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+#include "filters/biquad.h"
+#include "vector.h"
+
+
+namespace {
+
+struct EchoState final : public EffectState {
+ al::vector<ALfloat,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 {
+ ALfloat Current[MAX_OUTPUT_CHANNELS]{};
+ ALfloat Target[MAX_OUTPUT_CHANNELS]{};
+ } mGains[2];
+
+ BiquadFilter mFilter;
+ ALfloat mFeedGain{0.0f};
+
+ alignas(16) ALfloat mTempBuffer[2][BUFFERSIZE];
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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)
+};
+
+ALboolean EchoState::deviceUpdate(const ALCdevice *Device)
+{
+ 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 ALuint maxlen{NextPowerOf2(float2uint(AL_ECHO_MAX_DELAY*frequency + 0.5f) +
+ float2uint(AL_ECHO_MAX_LRDELAY*frequency + 0.5f))};
+ if(maxlen != mSampleBuffer.size())
+ {
+ mSampleBuffer.resize(maxlen);
+ mSampleBuffer.shrink_to_fit();
+ }
+
+ 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);
+ }
+
+ return AL_TRUE;
+}
+
+void EchoState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device{context->mDevice.get()};
+ const auto frequency = static_cast<ALfloat>(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 ALfloat gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
+ mFilter.setParams(BiquadType::HighShelf, gainhf, LOWPASSFREQREF/frequency,
+ mFilter.rcpQFromSlope(gainhf, 1.0f));
+
+ mFeedGain = props->Echo.Feedback;
+
+ /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
+ const ALfloat angle{std::asin(props->Echo.Spread)};
+
+ ALfloat coeffs[2][MAX_AMBI_CHANNELS];
+ CalcAngleCoeffs(-angle, 0.0f, 0.0f, coeffs[0]);
+ CalcAngleCoeffs( angle, 0.0f, 0.0f, coeffs[1]);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target);
+ ComputePanGains(target.Main, coeffs[1], slot->Params.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};
+ ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
+ size_t offset{mOffset};
+ size_t tap1{offset - mTap[0].delay};
+ size_t tap2{offset - mTap[1].delay};
+ ALfloat 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);
+}
+
+
+void Echo_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
+{ context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
+void Echo_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
+void Echo_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ 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:
+ context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
+ }
+}
+void Echo_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Echo_setParamf(props, context, param, vals[0]); }
+
+void Echo_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
+void Echo_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
+void Echo_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_ECHO_DELAY:
+ *val = props->Echo.Delay;
+ break;
+
+ case AL_ECHO_LRDELAY:
+ *val = props->Echo.LRDelay;
+ break;
+
+ case AL_ECHO_DAMPING:
+ *val = props->Echo.Damping;
+ break;
+
+ case AL_ECHO_FEEDBACK:
+ *val = props->Echo.Feedback;
+ break;
+
+ case AL_ECHO_SPREAD:
+ *val = props->Echo.Spread;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
+ }
+}
+void Echo_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Echo_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Echo);
+
+
+struct EchoStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new EchoState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Echo_vtable; }
+};
+
+EffectProps EchoStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Echo.Delay = AL_ECHO_DEFAULT_DELAY;
+ props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY;
+ props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING;
+ props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
+ props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *EchoStateFactory_getFactory()
+{
+ static EchoStateFactory EchoFactory{};
+ return &EchoFactory;
+}
diff --git a/Alc/effects/equalizer.c b/alc/effects/equalizer.cpp
index 17106127..929bff14 100644
--- a/Alc/effects/equalizer.c
+++ b/alc/effects/equalizer.cpp
@@ -20,15 +20,21 @@
#include "config.h"
-#include <math.h>
-#include <stdlib.h>
+#include <cmath>
+#include <cstdlib>
-#include "alMain.h"
-#include "alAuxEffectSlot.h"
-#include "alError.h"
+#include <algorithm>
+#include <functional>
+
+#include "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alcontext.h"
#include "alu.h"
-#include "filters/defs.h"
+#include "filters/biquad.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 *
@@ -72,161 +78,108 @@
* http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */
-typedef struct ALequalizerState {
- DERIVE_FROM_TYPE(ALeffectState);
-
+struct EqualizerState final : public EffectState {
struct {
/* Effect parameters */
BiquadFilter filter[4];
/* Effect gains for each channel */
- ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
- ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
- } Chans[MAX_EFFECT_CHANNELS];
+ ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
+ ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
+ } mChans[MAX_AMBI_CHANNELS];
- ALfloat SampleBuffer[MAX_EFFECT_CHANNELS][BUFFERSIZE];
-} ALequalizerState;
+ ALfloat mSampleBuffer[BUFFERSIZE]{};
-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);
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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)
+};
-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))
+ALboolean EqualizerState::deviceUpdate(const ALCdevice*)
{
- ALsizei i, j;
-
- for(i = 0; i < MAX_EFFECT_CHANNELS;i++)
+ for(auto &e : mChans)
{
- 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;
+ 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);
}
return AL_TRUE;
}
-static ALvoid ALequalizerState_update(ALequalizerState *state, const ALCcontext *context, const ALeffectslot *slot, const ALeffectProps *props)
+void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
{
- const ALCdevice *device = context->Device;
- ALfloat frequency = (ALfloat)device->Frequency;
+ const ALCdevice *device{context->mDevice.get()};
+ auto frequency = static_cast<ALfloat>(device->Frequency);
ALfloat gain, f0norm;
- ALuint i;
/* 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.
+ * and peaking filters' gain is for the centerpoint of the transition band,
+ * meaning its dB needs to be doubled for the shelf or peak to reach the
+ * provided gain.
*/
- gain = maxf(sqrtf(props->Equalizer.LowGain), 0.0625f); /* Limit -24dB */
+ gain = maxf(std::sqrt(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)
- );
+ mChans[0].filter[0].setParams(BiquadType::LowShelf, gain, f0norm,
+ BiquadFilter::rcpQFromSlope(gain, 0.75f));
- gain = maxf(props->Equalizer.Mid1Gain, 0.0625f);
+ gain = maxf(std::sqrt(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
- )
- );
+ mChans[0].filter[1].setParams(BiquadType::Peaking, gain, f0norm,
+ BiquadFilter::rcpQFromBandwidth(f0norm, props->Equalizer.Mid1Width));
- gain = maxf(props->Equalizer.Mid2Gain, 0.0625f);
+ gain = maxf(std::sqrt(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
- )
- );
+ mChans[0].filter[2].setParams(BiquadType::Peaking, gain, f0norm,
+ BiquadFilter::rcpQFromBandwidth(f0norm, props->Equalizer.Mid2Width));
- gain = maxf(sqrtf(props->Equalizer.HighGain), 0.0625f);
+ gain = maxf(std::sqrt(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)
- );
+ mChans[0].filter[3].setParams(BiquadType::HighShelf, gain, f0norm,
+ BiquadFilter::rcpQFromSlope(gain, 0.75f));
/* Copy the filter coefficients for the other input channels. */
- for(i = 1;i < MAX_EFFECT_CHANNELS;i++)
+ for(size_t i{1u};i < slot->Wet.Buffer.size();++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]);
+ 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]);
}
- STATIC_CAST(ALeffectState,state)->OutBuffer = device->FOAOut.Buffer;
- STATIC_CAST(ALeffectState,state)->OutChannels = device->FOAOut.NumChannels;
- for(i = 0;i < MAX_EFFECT_CHANNELS;i++)
- ComputePanGains(&device->FOAOut, IdentityMatrixf.m[i], slot->Params.Gain,
- state->Chans[i].TargetGains);
-}
-
-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++)
+ mOutTarget = target.Main->Buffer;
+ for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
{
- 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
- );
+ auto coeffs = GetAmbiIdentityRow(i);
+ ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
}
}
-
-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)
+void EqualizerState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
- static EqualizerStateFactory EqualizerFactory = { { GET_VTABLE2(EqualizerStateFactory, EffectStateFactory) } };
-
- return STATIC_CAST(EffectStateFactory, &EqualizerFactory);
+ auto chandata = std::addressof(mChans[0]);
+ for(const auto &input : samplesIn)
+ {
+ chandata->filter[0].process(mSampleBuffer, input.data(), samplesToDo);
+ chandata->filter[1].process(mSampleBuffer, mSampleBuffer, samplesToDo);
+ chandata->filter[2].process(mSampleBuffer, mSampleBuffer, samplesToDo);
+ chandata->filter[3].process(mSampleBuffer, mSampleBuffer, samplesToDo);
+
+ MixSamples({mSampleBuffer, samplesToDo}, samplesOut, chandata->CurrentGains,
+ chandata->TargetGains, samplesToDo, 0);
+ ++chandata;
+ }
}
-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)
+void Equalizer_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
+{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
+void Equalizer_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
+void Equalizer_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
- ALeffectProps *props = &effect->Props;
switch(param)
{
case AL_EQUALIZER_LOW_GAIN:
@@ -290,19 +243,18 @@ void ALequalizer_setParamf(ALeffect *effect, ALCcontext *context, ALenum param,
break;
default:
- alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
+ context->setError(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)
+void Equalizer_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Equalizer_setParamf(props, context, param, vals[0]); }
+
+void Equalizer_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
+void Equalizer_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
+void Equalizer_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
- const ALeffectProps *props = &effect->Props;
switch(param)
{
case AL_EQUALIZER_LOW_GAIN:
@@ -346,10 +298,41 @@ void ALequalizer_getParamf(const ALeffect *effect, ALCcontext *context, ALenum p
break;
default:
- alSetError(context, AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
+ context->setError(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); }
+void Equalizer_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Equalizer_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Equalizer);
-DEFINE_ALEFFECT_VTABLE(ALequalizer);
+
+struct EqualizerStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new EqualizerState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Equalizer_vtable; }
+};
+
+EffectProps EqualizerStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
+ props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
+ props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
+ props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
+ props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
+ props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
+ props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
+ props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
+ props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
+ props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *EqualizerStateFactory_getFactory()
+{
+ static EqualizerStateFactory EqualizerFactory{};
+ return &EqualizerFactory;
+}
diff --git a/alc/effects/fshifter.cpp b/alc/effects/fshifter.cpp
new file mode 100644
index 00000000..1b935047
--- /dev/null
+++ b/alc/effects/fshifter.cpp
@@ -0,0 +1,332 @@
+/**
+ * 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 "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+
+#include "alcomplex.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. */
+/* Making this constexpr seems to require C++14. */
+std::array<ALdouble,HIL_SIZE> InitHannWindow()
+{
+ std::array<ALdouble,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-1}};
+ const double val{std::sin(static_cast<double>(i) * scale)};
+ ret[i] = ret[HIL_SIZE-1-i] = val * val;
+ }
+ return ret;
+}
+alignas(16) const std::array<ALdouble,HIL_SIZE> HannWindow = InitHannWindow();
+
+
+struct FshifterState final : public EffectState {
+ /* Effect parameters */
+ size_t mCount{};
+ ALsizei mPhaseStep[2]{};
+ ALsizei mPhase[2]{};
+ ALdouble mSign[2]{};
+
+
+ /*Effects buffers*/
+ ALfloat mInFIFO[HIL_SIZE]{};
+ complex_d mOutFIFO[HIL_SIZE]{};
+ complex_d mOutputAccum[HIL_SIZE]{};
+ complex_d mAnalytic[HIL_SIZE]{};
+ complex_d mOutdata[BUFFERSIZE]{};
+
+ alignas(16) ALfloat mBufferOut[BUFFERSIZE]{};
+
+ /* Effect gains for each output channel */
+ struct {
+ ALfloat Current[MAX_OUTPUT_CHANNELS]{};
+ ALfloat Target[MAX_OUTPUT_CHANNELS]{};
+ } mGains[2];
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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)
+};
+
+ALboolean FshifterState::deviceUpdate(const ALCdevice*)
+{
+ /* (Re-)initializing parameters and clear the buffers. */
+ mCount = FIFO_LATENCY;
+
+ std::fill(std::begin(mPhaseStep), std::end(mPhaseStep), 0);
+ std::fill(std::begin(mPhase), std::end(mPhase), 0);
+ std::fill(std::begin(mSign), std::end(mSign), 1.0);
+ std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f);
+ 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);
+ }
+
+ return AL_TRUE;
+}
+
+void FshifterState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device{context->mDevice.get()};
+
+ ALfloat step{props->Fshifter.Frequency / static_cast<ALfloat>(device->Frequency)};
+ mPhaseStep[0] = mPhaseStep[1] = fastf2i(minf(step, 0.5f) * FRACTIONONE);
+
+ switch(props->Fshifter.LeftDirection)
+ {
+ case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN:
+ mSign[0] = -1.0;
+ break;
+
+ case AL_FREQUENCY_SHIFTER_DIRECTION_UP:
+ mSign[0] = 1.0;
+ break;
+
+ case AL_FREQUENCY_SHIFTER_DIRECTION_OFF:
+ mPhase[0] = 0;
+ mPhaseStep[0] = 0;
+ break;
+ }
+
+ switch (props->Fshifter.RightDirection)
+ {
+ case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN:
+ mSign[1] = -1.0;
+ break;
+
+ case AL_FREQUENCY_SHIFTER_DIRECTION_UP:
+ mSign[1] = 1.0;
+ break;
+
+ case AL_FREQUENCY_SHIFTER_DIRECTION_OFF:
+ mPhase[1] = 0;
+ mPhaseStep[1] = 0;
+ break;
+ }
+
+ ALfloat coeffs[2][MAX_AMBI_CHANNELS];
+ CalcDirectionCoeffs({-1.0f, 0.0f, -1.0f}, 0.0f, coeffs[0]);
+ CalcDirectionCoeffs({ 1.0f, 0.0f, -1.0f}, 0.0f, coeffs[1]);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target);
+ ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target);
+}
+
+void FshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+ static constexpr complex_d complex_zero{0.0, 0.0};
+ ALfloat *RESTRICT BufferOut = mBufferOut;
+ size_t j, k;
+
+ for(size_t base{0u};base < samplesToDo;)
+ {
+ const size_t todo{minz(HIL_SIZE-mCount, samplesToDo-base)};
+
+ ASSUME(todo > 0);
+
+ /* Fill FIFO buffer with samples data */
+ k = mCount;
+ for(j = 0;j < todo;j++,k++)
+ {
+ mInFIFO[k] = samplesIn[0][base+j];
+ mOutdata[base+j] = mOutFIFO[k-FIFO_LATENCY];
+ }
+ mCount += todo;
+ base += todo;
+
+ /* Check whether FIFO buffer is filled */
+ if(mCount < HIL_SIZE) continue;
+ mCount = FIFO_LATENCY;
+
+ /* Real signal windowing and store in Analytic buffer */
+ for(k = 0;k < HIL_SIZE;k++)
+ {
+ mAnalytic[k].real(mInFIFO[k] * HannWindow[k]);
+ mAnalytic[k].imag(0.0);
+ }
+
+ /* Processing signal by Discrete Hilbert Transform (analytical signal). */
+ complex_hilbert(mAnalytic);
+
+ /* Windowing and add to output accumulator */
+ for(k = 0;k < HIL_SIZE;k++)
+ mOutputAccum[k] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
+
+ /* Shift accumulator, input & output FIFO */
+ for(k = 0;k < HIL_STEP;k++) mOutFIFO[k] = mOutputAccum[k];
+ for(j = 0;k < HIL_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k];
+ for(;j < HIL_SIZE;j++) mOutputAccum[j] = complex_zero;
+ for(k = 0;k < FIFO_LATENCY;k++)
+ mInFIFO[k] = mInFIFO[k+HIL_STEP];
+ }
+
+ /* Process frequency shifter using the analytic signal obtained. */
+ for(ALsizei c{0};c < 2;++c)
+ {
+ for(k = 0;k < samplesToDo;++k)
+ {
+ double phase = mPhase[c] * ((1.0 / FRACTIONONE) * al::MathDefs<double>::Tau());
+ BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) +
+ mOutdata[k].imag()*std::sin(phase)*mSign[c]);
+
+ mPhase[c] += mPhaseStep[c];
+ mPhase[c] &= FRACTIONMASK;
+ }
+
+ /* Now, mix the processed sound data to the output. */
+ MixSamples({BufferOut, samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
+ maxz(samplesToDo, 512), 0);
+ }
+}
+
+
+void Fshifter_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_FREQUENCY_SHIFTER_FREQUENCY:
+ if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter frequency out of range");
+ props->Fshifter.Frequency = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
+ param);
+ }
+}
+void Fshifter_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Fshifter_setParamf(props, context, param, vals[0]); }
+
+void Fshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
+ if(!(val >= AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter left direction out of range");
+ props->Fshifter.LeftDirection = val;
+ break;
+
+ case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
+ if(!(val >= AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter right direction out of range");
+ props->Fshifter.RightDirection = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x",
+ param);
+ }
+}
+void Fshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Fshifter_setParami(props, context, param, vals[0]); }
+
+void Fshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
+ *val = props->Fshifter.LeftDirection;
+ break;
+ case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
+ *val = props->Fshifter.RightDirection;
+ break;
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x",
+ param);
+ }
+}
+void Fshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Fshifter_getParami(props, context, param, vals); }
+
+void Fshifter_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_FREQUENCY_SHIFTER_FREQUENCY:
+ *val = props->Fshifter.Frequency;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
+ param);
+ }
+}
+void Fshifter_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Fshifter_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Fshifter);
+
+
+struct FshifterStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new FshifterState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Fshifter_vtable; }
+};
+
+EffectProps FshifterStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
+ props.Fshifter.LeftDirection = AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION;
+ props.Fshifter.RightDirection = AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *FshifterStateFactory_getFactory()
+{
+ static FshifterStateFactory FshifterFactory{};
+ return &FshifterFactory;
+}
diff --git a/alc/effects/modulator.cpp b/alc/effects/modulator.cpp
new file mode 100644
index 00000000..aee896fb
--- /dev/null
+++ b/alc/effects/modulator.cpp
@@ -0,0 +1,270 @@
+/**
+ * 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 "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+#include "filters/biquad.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(ALuint index)
+{
+ constexpr float scale{al::MathDefs<float>::Tau() / WAVEFORM_FRACONE};
+ return std::sin(static_cast<float>(index) * scale);
+}
+
+inline float Saw(ALuint index)
+{ return static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; }
+
+inline float Square(ALuint index)
+{ return static_cast<float>(static_cast<int>((index>>(WAVEFORM_FRACBITS-2))&2) - 1); }
+
+inline float One(ALuint) { return 1.0f; }
+
+template<float (&func)(ALuint)>
+void Modulate(float *RESTRICT dst, ALuint index, const ALuint 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, ALuint, const ALuint, size_t){};
+
+ ALuint mIndex{0};
+ ALuint mStep{1};
+
+ struct {
+ BiquadFilter Filter;
+
+ ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
+ ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
+ } mChans[MAX_AMBI_CHANNELS];
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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)
+};
+
+ALboolean ModulatorState::deviceUpdate(const ALCdevice*)
+{
+ for(auto &e : mChans)
+ {
+ e.Filter.clear();
+ std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
+ }
+ return AL_TRUE;
+}
+
+void ModulatorState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device{context->mDevice.get()};
+
+ const float step{props->Modulator.Frequency / static_cast<ALfloat>(device->Frequency)};
+ mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1}));
+
+ if(mStep == 0)
+ mGetSamples = Modulate<One>;
+ else if(props->Modulator.Waveform == AL_RING_MODULATOR_SINUSOID)
+ mGetSamples = Modulate<Sin>;
+ else if(props->Modulator.Waveform == AL_RING_MODULATOR_SAWTOOTH)
+ mGetSamples = Modulate<Saw>;
+ else /*if(props->Modulator.Waveform == AL_RING_MODULATOR_SQUARE)*/
+ mGetSamples = Modulate<Square>;
+
+ ALfloat f0norm{props->Modulator.HighPassCutoff / static_cast<ALfloat>(device->Frequency)};
+ f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
+ /* Bandwidth value is constant in octaves. */
+ mChans[0].Filter.setParams(BiquadType::HighPass, 1.0f, f0norm,
+ BiquadFilter::rcpQFromBandwidth(f0norm, 0.75f));
+ for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
+ mChans[i].Filter.copyParamsFrom(mChans[0].Filter);
+
+ mOutTarget = target.Main->Buffer;
+ for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
+ {
+ auto coeffs = GetAmbiIdentityRow(i);
+ ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
+ }
+}
+
+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) ALfloat modsamples[MAX_UPDATE_SAMPLES];
+ size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
+
+ mGetSamples(modsamples, mIndex, mStep, td);
+ mIndex += static_cast<ALuint>(mStep * td);
+ mIndex &= WAVEFORM_FRACMASK;
+
+ auto chandata = std::addressof(mChans[0]);
+ for(const auto &input : samplesIn)
+ {
+ alignas(16) ALfloat temps[MAX_UPDATE_SAMPLES];
+
+ chandata->Filter.process(temps, &input[base], td);
+ 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;
+ }
+}
+
+
+void Modulator_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ 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:
+ context->setError(AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
+ }
+}
+void Modulator_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Modulator_setParamf(props, context, param, vals[0]); }
+void Modulator_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_RING_MODULATOR_FREQUENCY:
+ case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
+ Modulator_setParamf(props, context, param, static_cast<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:
+ context->setError(AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
+ }
+}
+void Modulator_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Modulator_setParami(props, context, param, vals[0]); }
+
+void Modulator_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_RING_MODULATOR_FREQUENCY:
+ *val = static_cast<ALint>(props->Modulator.Frequency);
+ break;
+ case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
+ *val = static_cast<ALint>(props->Modulator.HighPassCutoff);
+ break;
+ case AL_RING_MODULATOR_WAVEFORM:
+ *val = props->Modulator.Waveform;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
+ }
+}
+void Modulator_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Modulator_getParami(props, context, param, vals); }
+void Modulator_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_RING_MODULATOR_FREQUENCY:
+ *val = props->Modulator.Frequency;
+ break;
+ case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
+ *val = props->Modulator.HighPassCutoff;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
+ }
+}
+void Modulator_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Modulator_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Modulator);
+
+
+struct ModulatorStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new ModulatorState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Modulator_vtable; }
+};
+
+EffectProps ModulatorStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
+ props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
+ props.Modulator.Waveform = AL_RING_MODULATOR_DEFAULT_WAVEFORM;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *ModulatorStateFactory_getFactory()
+{
+ static ModulatorStateFactory ModulatorFactory{};
+ return &ModulatorFactory;
+}
diff --git a/alc/effects/null.cpp b/alc/effects/null.cpp
new file mode 100644
index 00000000..e0497296
--- /dev/null
+++ b/alc/effects/null.cpp
@@ -0,0 +1,165 @@
+
+#include "config.h"
+
+#include "AL/al.h"
+#include "AL/alc.h"
+
+#include "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "almalloc.h"
+#include "alspan.h"
+#include "effects/base.h"
+
+
+namespace {
+
+struct NullState final : public EffectState {
+ NullState();
+ ~NullState() override;
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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.
+ */
+ALboolean NullState::deviceUpdate(const ALCdevice* /*device*/)
+{
+ return AL_TRUE;
+}
+
+/* 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 ALeffectslot* /*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*/)
+{
+}
+
+
+void NullEffect_setParami(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint /*val*/)
+{
+ switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
+ }
+}
+void NullEffect_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{
+ switch(param)
+ {
+ default:
+ NullEffect_setParami(props, context, param, vals[0]);
+ }
+}
+void NullEffect_setParamf(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat /*val*/)
+{
+ switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
+ }
+}
+void NullEffect_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{
+ switch(param)
+ {
+ default:
+ NullEffect_setParamf(props, context, param, vals[0]);
+ }
+}
+
+void NullEffect_getParami(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint* /*val*/)
+{
+ switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
+ }
+}
+void NullEffect_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{
+ switch(param)
+ {
+ default:
+ NullEffect_getParami(props, context, param, vals);
+ }
+}
+void NullEffect_getParamf(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat* /*val*/)
+{
+ switch(param)
+ {
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
+ }
+}
+void NullEffect_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{
+ switch(param)
+ {
+ default:
+ NullEffect_getParamf(props, context, param, vals);
+ }
+}
+
+DEFINE_ALEFFECT_VTABLE(NullEffect);
+
+
+struct NullStateFactory final : public EffectStateFactory {
+ EffectState *create() override;
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override;
+};
+
+/* Creates EffectState objects of the appropriate type. */
+EffectState *NullStateFactory::create()
+{ return new NullState{}; }
+
+/* Returns an ALeffectProps initialized with this effect type's default
+ * property values.
+ */
+EffectProps NullStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ return props;
+}
+
+/* Returns a pointer to this effect type's global set/get vtable. */
+const EffectVtable *NullStateFactory::getEffectVtable() const noexcept
+{ return &NullEffect_vtable; }
+
+} // namespace
+
+EffectStateFactory *NullStateFactory_getFactory()
+{
+ static NullStateFactory NullFactory{};
+ return &NullFactory;
+}
diff --git a/alc/effects/pshifter.cpp b/alc/effects/pshifter.cpp
new file mode 100644
index 00000000..d7ba072e
--- /dev/null
+++ b/alc/effects/pshifter.cpp
@@ -0,0 +1,373 @@
+/**
+ * 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"
+
+#ifdef HAVE_SSE_INTRINSICS
+#include <emmintrin.h>
+#endif
+
+#include <cmath>
+#include <cstdlib>
+#include <array>
+#include <complex>
+#include <algorithm>
+
+#include "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alcomplex.h"
+#include "alcontext.h"
+#include "alnumeric.h"
+#include "alu.h"
+
+
+namespace {
+
+using complex_d = std::complex<double>;
+
+#define STFT_SIZE 1024
+#define STFT_HALF_SIZE (STFT_SIZE>>1)
+#define OVERSAMP (1<<2)
+
+#define STFT_STEP (STFT_SIZE / OVERSAMP)
+#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1))
+
+/* Define a Hann window, used to filter the STFT input and output. */
+/* Making this constexpr seems to require C++14. */
+std::array<ALdouble,STFT_SIZE> InitHannWindow()
+{
+ std::array<ALdouble,STFT_SIZE> ret;
+ /* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
+ for(size_t i{0};i < STFT_SIZE>>1;i++)
+ {
+ constexpr double scale{al::MathDefs<double>::Pi() / double{STFT_SIZE-1}};
+ const double val{std::sin(static_cast<double>(i) * scale)};
+ ret[i] = ret[STFT_SIZE-1-i] = val * val;
+ }
+ return ret;
+}
+alignas(16) const std::array<ALdouble,STFT_SIZE> HannWindow = InitHannWindow();
+
+
+struct ALphasor {
+ ALdouble Amplitude;
+ ALdouble Phase;
+};
+
+struct ALfrequencyDomain {
+ ALdouble Amplitude;
+ ALdouble Frequency;
+};
+
+
+/* Converts complex to ALphasor */
+inline ALphasor rect2polar(const complex_d &number)
+{
+ ALphasor polar;
+ polar.Amplitude = std::abs(number);
+ polar.Phase = std::arg(number);
+ return polar;
+}
+
+/* Converts ALphasor to complex */
+inline complex_d polar2rect(const ALphasor &number)
+{ return std::polar<double>(number.Amplitude, number.Phase); }
+
+
+struct PshifterState final : public EffectState {
+ /* Effect parameters */
+ size_t mCount;
+ ALuint mPitchShiftI;
+ ALfloat mPitchShift;
+ ALfloat mFreqPerBin;
+
+ /* Effects buffers */
+ ALfloat mInFIFO[STFT_SIZE];
+ ALfloat mOutFIFO[STFT_STEP];
+ ALdouble mLastPhase[STFT_HALF_SIZE+1];
+ ALdouble mSumPhase[STFT_HALF_SIZE+1];
+ ALdouble mOutputAccum[STFT_SIZE];
+
+ complex_d mFFTbuffer[STFT_SIZE];
+
+ ALfrequencyDomain mAnalysis_buffer[STFT_HALF_SIZE+1];
+ ALfrequencyDomain mSyntesis_buffer[STFT_HALF_SIZE+1];
+
+ alignas(16) ALfloat mBufferOut[BUFFERSIZE];
+
+ /* Effect gains for each output channel */
+ ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS];
+ ALfloat mTargetGains[MAX_OUTPUT_CHANNELS];
+
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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(PshifterState)
+};
+
+ALboolean PshifterState::deviceUpdate(const ALCdevice *device)
+{
+ /* (Re-)initializing parameters and clear the buffers. */
+ mCount = FIFO_LATENCY;
+ mPitchShiftI = FRACTIONONE;
+ mPitchShift = 1.0f;
+ mFreqPerBin = static_cast<float>(device->Frequency) / float{STFT_SIZE};
+
+ std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f);
+ std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), 0.0f);
+ std::fill(std::begin(mLastPhase), std::end(mLastPhase), 0.0);
+ std::fill(std::begin(mSumPhase), std::end(mSumPhase), 0.0);
+ std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), 0.0);
+ std::fill(std::begin(mFFTbuffer), std::end(mFFTbuffer), complex_d{});
+ std::fill(std::begin(mAnalysis_buffer), std::end(mAnalysis_buffer), ALfrequencyDomain{});
+ std::fill(std::begin(mSyntesis_buffer), std::end(mSyntesis_buffer), ALfrequencyDomain{});
+
+ std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
+ std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
+
+ return AL_TRUE;
+}
+
+void PshifterState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const float pitch{std::pow(2.0f,
+ static_cast<ALfloat>(props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune) / 1200.0f
+ )};
+ mPitchShiftI = fastf2u(pitch*FRACTIONONE);
+ mPitchShift = static_cast<float>(mPitchShiftI) * (1.0f/FRACTIONONE);
+
+ ALfloat coeffs[MAX_AMBI_CHANNELS];
+ CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
+
+ mOutTarget = target.Main->Buffer;
+ ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains);
+}
+
+void PshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+ /* Pitch shifter engine based on the work of Stephan Bernsee.
+ * http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
+ */
+
+ static constexpr ALdouble expected{al::MathDefs<double>::Tau() / OVERSAMP};
+ const ALdouble freq_per_bin{mFreqPerBin};
+ ALfloat *RESTRICT bufferOut{mBufferOut};
+ size_t count{mCount};
+
+ for(size_t i{0u};i < samplesToDo;)
+ {
+ do {
+ /* Fill FIFO buffer with samples data */
+ mInFIFO[count] = samplesIn[0][i];
+ bufferOut[i] = mOutFIFO[count - FIFO_LATENCY];
+
+ count++;
+ } while(++i < samplesToDo && count < STFT_SIZE);
+
+ /* Check whether FIFO buffer is filled */
+ if(count < STFT_SIZE) break;
+ count = FIFO_LATENCY;
+
+ /* Real signal windowing and store in FFTbuffer */
+ for(ALuint k{0u};k < STFT_SIZE;k++)
+ {
+ mFFTbuffer[k].real(mInFIFO[k] * HannWindow[k]);
+ mFFTbuffer[k].imag(0.0);
+ }
+
+ /* ANALYSIS */
+ /* Apply FFT to FFTbuffer data */
+ complex_fft(mFFTbuffer, -1.0);
+
+ /* Analyze the obtained data. Since the real FFT is symmetric, only
+ * STFT_HALF_SIZE+1 samples are needed.
+ */
+ for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++)
+ {
+ /* Compute amplitude and phase */
+ ALphasor component{rect2polar(mFFTbuffer[k])};
+
+ /* Compute phase difference and subtract expected phase difference */
+ double tmp{(component.Phase - mLastPhase[k]) - k*expected};
+
+ /* Map delta phase into +/- Pi interval */
+ int qpd{double2int(tmp / al::MathDefs<double>::Pi())};
+ tmp -= al::MathDefs<double>::Pi() * (qpd + (qpd%2));
+
+ /* Get deviation from bin frequency from the +/- Pi interval */
+ tmp /= expected;
+
+ /* Compute the k-th partials' true frequency, twice the amplitude
+ * for maintain the gain (because half of bins are used) and store
+ * amplitude and true frequency in analysis buffer.
+ */
+ mAnalysis_buffer[k].Amplitude = 2.0 * component.Amplitude;
+ mAnalysis_buffer[k].Frequency = (k + tmp) * freq_per_bin;
+
+ /* Store actual phase[k] for the calculations in the next frame*/
+ mLastPhase[k] = component.Phase;
+ }
+
+ /* PROCESSING */
+ /* pitch shifting */
+ for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++)
+ {
+ mSyntesis_buffer[k].Amplitude = 0.0;
+ mSyntesis_buffer[k].Frequency = 0.0;
+ }
+
+ for(size_t k{0u};k < STFT_HALF_SIZE+1;k++)
+ {
+ size_t j{(k*mPitchShiftI) >> FRACTIONBITS};
+ if(j >= STFT_HALF_SIZE+1) break;
+
+ mSyntesis_buffer[j].Amplitude += mAnalysis_buffer[k].Amplitude;
+ mSyntesis_buffer[j].Frequency = mAnalysis_buffer[k].Frequency * mPitchShift;
+ }
+
+ /* SYNTHESIS */
+ /* Synthesis the processing data */
+ for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++)
+ {
+ ALphasor component;
+ ALdouble tmp;
+
+ /* Compute bin deviation from scaled freq */
+ tmp = mSyntesis_buffer[k].Frequency/freq_per_bin - k;
+
+ /* Calculate actual delta phase and accumulate it to get bin phase */
+ mSumPhase[k] += (k + tmp) * expected;
+
+ component.Amplitude = mSyntesis_buffer[k].Amplitude;
+ component.Phase = mSumPhase[k];
+
+ /* Compute phasor component to cartesian complex number and storage it into FFTbuffer*/
+ mFFTbuffer[k] = polar2rect(component);
+ }
+ /* zero negative frequencies for recontruct a real signal */
+ for(ALuint k{STFT_HALF_SIZE+1};k < STFT_SIZE;k++)
+ mFFTbuffer[k] = complex_d{};
+
+ /* Apply iFFT to buffer data */
+ complex_fft(mFFTbuffer, 1.0);
+
+ /* Windowing and add to output */
+ for(ALuint k{0u};k < STFT_SIZE;k++)
+ mOutputAccum[k] += HannWindow[k] * mFFTbuffer[k].real() /
+ (0.5 * STFT_HALF_SIZE * OVERSAMP);
+
+ /* Shift accumulator, input & output FIFO */
+ size_t j, k;
+ for(k = 0;k < STFT_STEP;k++) mOutFIFO[k] = static_cast<ALfloat>(mOutputAccum[k]);
+ for(j = 0;k < STFT_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k];
+ for(;j < STFT_SIZE;j++) mOutputAccum[j] = 0.0;
+ for(k = 0;k < FIFO_LATENCY;k++)
+ mInFIFO[k] = mInFIFO[k+STFT_STEP];
+ }
+ mCount = count;
+
+ /* Now, mix the processed sound data to the output. */
+ MixSamples({bufferOut, samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
+ maxz(samplesToDo, 512), 0);
+}
+
+
+void Pshifter_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat)
+{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); }
+void Pshifter_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*)
+{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", param); }
+
+void Pshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_PITCH_SHIFTER_COARSE_TUNE:
+ if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter coarse tune out of range");
+ props->Pshifter.CoarseTune = val;
+ break;
+
+ case AL_PITCH_SHIFTER_FINE_TUNE:
+ if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter fine tune out of range");
+ props->Pshifter.FineTune = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
+ param);
+ }
+}
+void Pshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ Pshifter_setParami(props, context, param, vals[0]); }
+
+void Pshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_PITCH_SHIFTER_COARSE_TUNE:
+ *val = props->Pshifter.CoarseTune;
+ break;
+ case AL_PITCH_SHIFTER_FINE_TUNE:
+ *val = props->Pshifter.FineTune;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
+ param);
+ }
+}
+void Pshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ Pshifter_getParami(props, context, param, vals); }
+
+void Pshifter_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
+{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); }
+void Pshifter_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
+{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param); }
+
+DEFINE_ALEFFECT_VTABLE(Pshifter);
+
+
+struct PshifterStateFactory final : public EffectStateFactory {
+ EffectState *create() override;
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Pshifter_vtable; }
+};
+
+EffectState *PshifterStateFactory::create()
+{ return new PshifterState{}; }
+
+EffectProps PshifterStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
+ props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *PshifterStateFactory_getFactory()
+{
+ static PshifterStateFactory PshifterFactory{};
+ return &PshifterFactory;
+}
diff --git a/alc/effects/reverb.cpp b/alc/effects/reverb.cpp
new file mode 100644
index 00000000..6e56adf2
--- /dev/null
+++ b/alc/effects/reverb.cpp
@@ -0,0 +1,2090 @@
+/**
+ * Ambisonic reverb engine for the OpenAL cross platform audio library
+ * Copyright (C) 2008-2017 by Chris Robinson and Christopher Fitzgerald.
+ * 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 <cstdio>
+#include <cstdlib>
+#include <cmath>
+
+#include <array>
+#include <numeric>
+#include <algorithm>
+#include <functional>
+
+#include "al/auxeffectslot.h"
+#include "al/listener.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+#include "bformatdec.h"
+#include "filters/biquad.h"
+#include "vector.h"
+#include "vecmat.h"
+
+/* This is a user config option for modifying the overall output of the reverb
+ * effect.
+ */
+ALfloat ReverbBoost = 1.0f;
+
+namespace {
+
+using namespace std::placeholders;
+
+/* Max samples per process iteration. Used to limit the size needed for
+ * temporary buffers. Must be a multiple of 4 for SIMD alignment.
+ */
+constexpr size_t MAX_UPDATE_SAMPLES{256};
+
+/* The number of spatialized lines or channels to process. Four channels allows
+ * for a 3D A-Format response. NOTE: This can't be changed without taking care
+ * of the conversion matrices, and a few places where the length arrays are
+ * assumed to have 4 elements.
+ */
+constexpr size_t NUM_LINES{4u};
+
+
+/* The B-Format to A-Format conversion matrix. The arrangement of rows is
+ * deliberately chosen to align the resulting lines to their spatial opposites
+ * (0:above front left <-> 3:above back right, 1:below front right <-> 2:below
+ * back left). It's not quite opposite, since the A-Format results in a
+ * tetrahedron, but it's close enough. Should the model be extended to 8-lines
+ * in the future, true opposites can be used.
+ */
+alignas(16) constexpr ALfloat B2A[NUM_LINES][MAX_AMBI_CHANNELS]{
+ { 0.288675134595f, 0.288675134595f, 0.288675134595f, 0.288675134595f },
+ { 0.288675134595f, -0.288675134595f, -0.288675134595f, 0.288675134595f },
+ { 0.288675134595f, 0.288675134595f, -0.288675134595f, -0.288675134595f },
+ { 0.288675134595f, -0.288675134595f, 0.288675134595f, -0.288675134595f }
+};
+
+/* Converts A-Format to B-Format. */
+alignas(16) constexpr ALfloat A2B[NUM_LINES][NUM_LINES]{
+ { 0.866025403785f, 0.866025403785f, 0.866025403785f, 0.866025403785f },
+ { 0.866025403785f, -0.866025403785f, 0.866025403785f, -0.866025403785f },
+ { 0.866025403785f, -0.866025403785f, -0.866025403785f, 0.866025403785f },
+ { 0.866025403785f, 0.866025403785f, -0.866025403785f, -0.866025403785f }
+};
+
+
+/* The all-pass and delay lines have a variable length dependent on the
+ * effect's density parameter, which helps alter the perceived environment
+ * size. The size-to-density conversion is a cubed scale:
+ *
+ * density = min(1.0, pow(size, 3.0) / DENSITY_SCALE);
+ *
+ * The line lengths scale linearly with room size, so the inverse density
+ * conversion is needed, taking the cube root of the re-scaled density to
+ * calculate the line length multiplier:
+ *
+ * length_mult = max(5.0, cbrt(density*DENSITY_SCALE));
+ *
+ * The density scale below will result in a max line multiplier of 50, for an
+ * effective size range of 5m to 50m.
+ */
+constexpr ALfloat DENSITY_SCALE{125000.0f};
+
+/* All delay line lengths are specified in seconds.
+ *
+ * To approximate early reflections, we break them up into primary (those
+ * arriving from the same direction as the source) and secondary (those
+ * arriving from the opposite direction).
+ *
+ * The early taps decorrelate the 4-channel signal to approximate an average
+ * room response for the primary reflections after the initial early delay.
+ *
+ * Given an average room dimension (d_a) and the speed of sound (c) we can
+ * calculate the average reflection delay (r_a) regardless of listener and
+ * source positions as:
+ *
+ * r_a = d_a / c
+ * c = 343.3
+ *
+ * This can extended to finding the average difference (r_d) between the
+ * maximum (r_1) and minimum (r_0) reflection delays:
+ *
+ * r_0 = 2 / 3 r_a
+ * = r_a - r_d / 2
+ * = r_d
+ * r_1 = 4 / 3 r_a
+ * = r_a + r_d / 2
+ * = 2 r_d
+ * r_d = 2 / 3 r_a
+ * = r_1 - r_0
+ *
+ * As can be determined by integrating the 1D model with a source (s) and
+ * listener (l) positioned across the dimension of length (d_a):
+ *
+ * r_d = int_(l=0)^d_a (int_(s=0)^d_a |2 d_a - 2 (l + s)| ds) dl / c
+ *
+ * The initial taps (T_(i=0)^N) are then specified by taking a power series
+ * that ranges between r_0 and half of r_1 less r_0:
+ *
+ * R_i = 2^(i / (2 N - 1)) r_d
+ * = r_0 + (2^(i / (2 N - 1)) - 1) r_d
+ * = r_0 + T_i
+ * T_i = R_i - r_0
+ * = (2^(i / (2 N - 1)) - 1) r_d
+ *
+ * Assuming an average of 1m, we get the following taps:
+ */
+constexpr std::array<ALfloat,NUM_LINES> EARLY_TAP_LENGTHS{{
+ 0.0000000e+0f, 2.0213520e-4f, 4.2531060e-4f, 6.7171600e-4f
+}};
+
+/* The early all-pass filter lengths are based on the early tap lengths:
+ *
+ * A_i = R_i / a
+ *
+ * Where a is the approximate maximum all-pass cycle limit (20).
+ */
+constexpr std::array<ALfloat,NUM_LINES> EARLY_ALLPASS_LENGTHS{{
+ 9.7096800e-5f, 1.0720356e-4f, 1.1836234e-4f, 1.3068260e-4f
+}};
+
+/* The early delay lines are used to transform the primary reflections into
+ * the secondary reflections. The A-format is arranged in such a way that
+ * the channels/lines are spatially opposite:
+ *
+ * C_i is opposite C_(N-i-1)
+ *
+ * The delays of the two opposing reflections (R_i and O_i) from a source
+ * anywhere along a particular dimension always sum to twice its full delay:
+ *
+ * 2 r_a = R_i + O_i
+ *
+ * With that in mind we can determine the delay between the two reflections
+ * and thus specify our early line lengths (L_(i=0)^N) using:
+ *
+ * O_i = 2 r_a - R_(N-i-1)
+ * L_i = O_i - R_(N-i-1)
+ * = 2 (r_a - R_(N-i-1))
+ * = 2 (r_a - T_(N-i-1) - r_0)
+ * = 2 r_a (1 - (2 / 3) 2^((N - i - 1) / (2 N - 1)))
+ *
+ * Using an average dimension of 1m, we get:
+ */
+constexpr std::array<ALfloat,NUM_LINES> EARLY_LINE_LENGTHS{{
+ 5.9850400e-4f, 1.0913150e-3f, 1.5376658e-3f, 1.9419362e-3f
+}};
+
+/* The late all-pass filter lengths are based on the late line lengths:
+ *
+ * A_i = (5 / 3) L_i / r_1
+ */
+constexpr std::array<ALfloat,NUM_LINES> LATE_ALLPASS_LENGTHS{{
+ 1.6182800e-4f, 2.0389060e-4f, 2.8159360e-4f, 3.2365600e-4f
+}};
+
+/* The late lines are used to approximate the decaying cycle of recursive
+ * late reflections.
+ *
+ * Splitting the lines in half, we start with the shortest reflection paths
+ * (L_(i=0)^(N/2)):
+ *
+ * L_i = 2^(i / (N - 1)) r_d
+ *
+ * Then for the opposite (longest) reflection paths (L_(i=N/2)^N):
+ *
+ * L_i = 2 r_a - L_(i-N/2)
+ * = 2 r_a - 2^((i - N / 2) / (N - 1)) r_d
+ *
+ * For our 1m average room, we get:
+ */
+constexpr std::array<ALfloat,NUM_LINES> LATE_LINE_LENGTHS{{
+ 1.9419362e-3f, 2.4466860e-3f, 3.3791220e-3f, 3.8838720e-3f
+}};
+
+
+using ReverbUpdateLine = std::array<float,MAX_UPDATE_SAMPLES>;
+
+struct DelayLineI {
+ /* The delay lines use interleaved samples, with the lengths being powers
+ * of 2 to allow the use of bit-masking instead of a modulus for wrapping.
+ */
+ size_t Mask{0u};
+ union {
+ uintptr_t LineOffset{0u};
+ std::array<float,NUM_LINES> *Line;
+ };
+
+ /* Given the allocated sample buffer, this function updates each delay line
+ * offset.
+ */
+ void realizeLineOffset(std::array<float,NUM_LINES> *sampleBuffer) noexcept
+ { Line = sampleBuffer + LineOffset; }
+
+ /* Calculate the length of a delay line and store its mask and offset. */
+ ALuint calcLineLength(const ALfloat length, const uintptr_t offset, const ALfloat frequency,
+ const ALuint extra)
+ {
+ /* All line lengths are powers of 2, calculated from their lengths in
+ * seconds, rounded up.
+ */
+ ALuint samples{float2uint(std::ceil(length*frequency))};
+ samples = NextPowerOf2(samples + extra);
+
+ /* All lines share a single sample buffer. */
+ Mask = samples - 1;
+ LineOffset = offset;
+
+ /* Return the sample count for accumulation. */
+ return samples;
+ }
+
+ void write(size_t offset, const size_t c, const ALfloat *RESTRICT in, const size_t count) const noexcept
+ {
+ ASSUME(count > 0);
+ for(size_t i{0u};i < count;)
+ {
+ offset &= Mask;
+ size_t td{minz(Mask+1 - offset, count - i)};
+ do {
+ Line[offset++][c] = in[i++];
+ } while(--td);
+ }
+ }
+};
+
+struct VecAllpass {
+ DelayLineI Delay;
+ ALfloat Coeff{0.0f};
+ size_t Offset[NUM_LINES][2]{};
+
+ void processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
+ const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fadeCount, const ALfloat fadeStep,
+ const size_t todo);
+ void processUnfaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
+ const ALfloat xCoeff, const ALfloat yCoeff, const size_t todo);
+};
+
+struct T60Filter {
+ /* Two filters are used to adjust the signal. One to control the low
+ * frequencies, and one to control the high frequencies.
+ */
+ ALfloat MidGain[2]{0.0f, 0.0f};
+ BiquadFilter HFFilter, LFFilter;
+
+ void calcCoeffs(const ALfloat length, const ALfloat lfDecayTime, const ALfloat mfDecayTime,
+ const ALfloat hfDecayTime, const ALfloat lf0norm, const ALfloat hf0norm);
+
+ /* Applies the two T60 damping filter sections. */
+ void process(ALfloat *samples, const size_t todo)
+ {
+ HFFilter.process(samples, samples, todo);
+ LFFilter.process(samples, samples, todo);
+ }
+};
+
+struct EarlyReflections {
+ /* A Gerzon vector all-pass filter is used to simulate initial diffusion.
+ * The spread from this filter also helps smooth out the reverb tail.
+ */
+ VecAllpass VecAp;
+
+ /* An echo line is used to complete the second half of the early
+ * reflections.
+ */
+ DelayLineI Delay;
+ size_t Offset[NUM_LINES][2]{};
+ ALfloat Coeff[NUM_LINES][2]{};
+
+ /* The gain for each output channel based on 3D panning. */
+ ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{};
+ ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{};
+
+ void updateLines(const ALfloat density, const ALfloat diffusion, const ALfloat decayTime,
+ const ALfloat frequency);
+};
+
+struct LateReverb {
+ /* A recursive delay line is used fill in the reverb tail. */
+ DelayLineI Delay;
+ size_t Offset[NUM_LINES][2]{};
+
+ /* Attenuation to compensate for the modal density and decay rate of the
+ * late lines.
+ */
+ ALfloat DensityGain[2]{0.0f, 0.0f};
+
+ /* T60 decay filters are used to simulate absorption. */
+ T60Filter T60[NUM_LINES];
+
+ /* A Gerzon vector all-pass filter is used to simulate diffusion. */
+ VecAllpass VecAp;
+
+ /* The gain for each output channel based on 3D panning. */
+ ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{};
+ ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{};
+
+ void updateLines(const ALfloat density, const ALfloat diffusion, const ALfloat lfDecayTime,
+ const ALfloat mfDecayTime, const ALfloat hfDecayTime, const ALfloat lf0norm,
+ const ALfloat hf0norm, const ALfloat frequency);
+};
+
+struct ReverbState final : public EffectState {
+ /* All delay lines are allocated as a single buffer to reduce memory
+ * fragmentation and management code.
+ */
+ al::vector<std::array<float,NUM_LINES>,16> mSampleBuffer;
+
+ struct {
+ /* Calculated parameters which indicate if cross-fading is needed after
+ * an update.
+ */
+ ALfloat Density{AL_EAXREVERB_DEFAULT_DENSITY};
+ ALfloat Diffusion{AL_EAXREVERB_DEFAULT_DIFFUSION};
+ ALfloat DecayTime{AL_EAXREVERB_DEFAULT_DECAY_TIME};
+ ALfloat HFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_HFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME};
+ ALfloat LFDecayTime{AL_EAXREVERB_DEFAULT_DECAY_LFRATIO * AL_EAXREVERB_DEFAULT_DECAY_TIME};
+ ALfloat HFReference{AL_EAXREVERB_DEFAULT_HFREFERENCE};
+ ALfloat LFReference{AL_EAXREVERB_DEFAULT_LFREFERENCE};
+ } mParams;
+
+ /* Master effect filters */
+ struct {
+ BiquadFilter Lp;
+ BiquadFilter Hp;
+ } mFilter[NUM_LINES];
+
+ /* Core delay line (early reflections and late reverb tap from this). */
+ DelayLineI mDelay;
+
+ /* Tap points for early reflection delay. */
+ size_t mEarlyDelayTap[NUM_LINES][2]{};
+ ALfloat mEarlyDelayCoeff[NUM_LINES][2]{};
+
+ /* Tap points for late reverb feed and delay. */
+ size_t mLateFeedTap{};
+ size_t mLateDelayTap[NUM_LINES][2]{};
+
+ /* Coefficients for the all-pass and line scattering matrices. */
+ ALfloat mMixX{0.0f};
+ ALfloat mMixY{0.0f};
+
+ EarlyReflections mEarly;
+
+ LateReverb mLate;
+
+ bool mDoFading{};
+
+ /* Maximum number of samples to process at once. */
+ size_t mMaxUpdate[2]{MAX_UPDATE_SAMPLES, MAX_UPDATE_SAMPLES};
+
+ /* The current write offset for all delay lines. */
+ size_t mOffset{};
+
+ /* Temporary storage used when processing. */
+ union {
+ alignas(16) FloatBufferLine mTempLine{};
+ alignas(16) std::array<ReverbUpdateLine,NUM_LINES> mTempSamples;
+ };
+ alignas(16) std::array<ReverbUpdateLine,NUM_LINES> mEarlySamples{};
+ alignas(16) std::array<ReverbUpdateLine,NUM_LINES> mLateSamples{};
+
+ using MixOutT = void (ReverbState::*)(const al::span<FloatBufferLine> samplesOut,
+ const size_t counter, const size_t offset, const size_t todo);
+
+ MixOutT mMixOut{&ReverbState::MixOutPlain};
+ std::array<ALfloat,MAX_AMBI_ORDER+1> mOrderScales{};
+ std::array<std::array<BandSplitter,NUM_LINES>,2> mAmbiSplitter;
+
+
+ void MixOutPlain(const al::span<FloatBufferLine> samplesOut, const size_t counter,
+ const size_t offset, const size_t todo)
+ {
+ ASSUME(todo > 0);
+
+ /* Convert back to B-Format, and mix the results to output. */
+ const al::span<float> tmpspan{mTempLine.data(), todo};
+ for(size_t c{0u};c < NUM_LINES;c++)
+ {
+ std::fill(tmpspan.begin(), tmpspan.end(), 0.0f);
+ MixRowSamples(tmpspan, {A2B[c], NUM_LINES}, mEarlySamples[0].data(),
+ mEarlySamples[0].size());
+ MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], counter,
+ offset);
+ }
+ for(size_t c{0u};c < NUM_LINES;c++)
+ {
+ std::fill(tmpspan.begin(), tmpspan.end(), 0.0f);
+ MixRowSamples(tmpspan, {A2B[c], NUM_LINES}, mLateSamples[0].data(),
+ mLateSamples[0].size());
+ MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], counter,
+ offset);
+ }
+ }
+
+ void MixOutAmbiUp(const al::span<FloatBufferLine> samplesOut, const size_t counter,
+ const size_t offset, const size_t todo)
+ {
+ ASSUME(todo > 0);
+
+ const al::span<float> tmpspan{mTempLine.data(), todo};
+ for(size_t c{0u};c < NUM_LINES;c++)
+ {
+ std::fill(tmpspan.begin(), tmpspan.end(), 0.0f);
+ MixRowSamples(tmpspan, {A2B[c], NUM_LINES}, mEarlySamples[0].data(),
+ mEarlySamples[0].size());
+
+ /* Apply scaling to the B-Format's HF response to "upsample" it to
+ * higher-order output.
+ */
+ const ALfloat hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]};
+ mAmbiSplitter[0][c].applyHfScale(tmpspan.data(), hfscale, todo);
+
+ MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], counter,
+ offset);
+ }
+ for(size_t c{0u};c < NUM_LINES;c++)
+ {
+ std::fill(tmpspan.begin(), tmpspan.end(), 0.0f);
+ MixRowSamples(tmpspan, {A2B[c], NUM_LINES}, mLateSamples[0].data(),
+ mLateSamples[0].size());
+
+ const ALfloat hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]};
+ mAmbiSplitter[1][c].applyHfScale(tmpspan.data(), hfscale, todo);
+
+ MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], counter,
+ offset);
+ }
+ }
+
+ bool allocLines(const ALfloat frequency);
+
+ void updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDelay, const ALfloat density,
+ const ALfloat decayTime, const ALfloat frequency);
+ void update3DPanning(const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan,
+ const ALfloat earlyGain, const ALfloat lateGain, const EffectTarget &target);
+
+ void earlyUnfaded(const size_t offset, const size_t todo);
+ void earlyFaded(const size_t offset, const size_t todo, const ALfloat fade,
+ const ALfloat fadeStep);
+
+ void lateUnfaded(const size_t offset, const size_t todo);
+ void lateFaded(const size_t offset, const size_t todo, const ALfloat fade,
+ const ALfloat fadeStep);
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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(ReverbState)
+};
+
+/**************************************
+ * Device Update *
+ **************************************/
+
+inline ALfloat CalcDelayLengthMult(ALfloat density)
+{ return maxf(5.0f, std::cbrt(density*DENSITY_SCALE)); }
+
+/* Calculates the delay line metrics and allocates the shared sample buffer
+ * for all lines given the sample rate (frequency). If an allocation failure
+ * occurs, it returns AL_FALSE.
+ */
+bool ReverbState::allocLines(const ALfloat frequency)
+{
+ /* All delay line lengths are calculated to accomodate the full range of
+ * lengths given their respective paramters.
+ */
+ size_t totalSamples{0u};
+
+ /* Multiplier for the maximum density value, i.e. density=1, which is
+ * actually the least density...
+ */
+ ALfloat multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)};
+
+ /* The main delay length includes the maximum early reflection delay, the
+ * largest early tap width, the maximum late reverb delay, and the
+ * largest late tap width. Finally, it must also be extended by the
+ * update size (BUFFERSIZE) for block processing.
+ */
+ ALfloat length{AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS.back()*multiplier +
+ AL_EAXREVERB_MAX_LATE_REVERB_DELAY +
+ (LATE_LINE_LENGTHS.back() - LATE_LINE_LENGTHS.front())/float{NUM_LINES}*multiplier};
+ totalSamples += mDelay.calcLineLength(length, totalSamples, frequency, BUFFERSIZE);
+
+ /* The early vector all-pass line. */
+ length = EARLY_ALLPASS_LENGTHS.back() * multiplier;
+ totalSamples += mEarly.VecAp.Delay.calcLineLength(length, totalSamples, frequency, 0);
+
+ /* The early reflection line. */
+ length = EARLY_LINE_LENGTHS.back() * multiplier;
+ totalSamples += mEarly.Delay.calcLineLength(length, totalSamples, frequency, 0);
+
+ /* The late vector all-pass line. */
+ length = LATE_ALLPASS_LENGTHS.back() * multiplier;
+ totalSamples += mLate.VecAp.Delay.calcLineLength(length, totalSamples, frequency, 0);
+
+ /* The late delay lines are calculated from the largest maximum density
+ * line length.
+ */
+ length = LATE_LINE_LENGTHS.back() * multiplier;
+ totalSamples += mLate.Delay.calcLineLength(length, totalSamples, frequency, 0);
+
+ if(totalSamples != mSampleBuffer.size())
+ {
+ mSampleBuffer.resize(totalSamples);
+ mSampleBuffer.shrink_to_fit();
+ }
+
+ /* Clear the sample buffer. */
+ std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), std::array<float,NUM_LINES>{});
+
+ /* Update all delays to reflect the new sample buffer. */
+ mDelay.realizeLineOffset(mSampleBuffer.data());
+ mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data());
+ mEarly.Delay.realizeLineOffset(mSampleBuffer.data());
+ mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data());
+ mLate.Delay.realizeLineOffset(mSampleBuffer.data());
+
+ return true;
+}
+
+ALboolean ReverbState::deviceUpdate(const ALCdevice *device)
+{
+ const auto frequency = static_cast<ALfloat>(device->Frequency);
+
+ /* Allocate the delay lines. */
+ if(!allocLines(frequency))
+ return AL_FALSE;
+
+ const ALfloat multiplier{CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY)};
+
+ /* The late feed taps are set a fixed position past the latest delay tap. */
+ mLateFeedTap = float2uint(
+ (AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS.back()*multiplier) * frequency);
+
+ /* Clear filters and gain coefficients since the delay lines were all just
+ * cleared (if not reallocated).
+ */
+ for(auto &filter : mFilter)
+ {
+ filter.Lp.clear();
+ filter.Hp.clear();
+ }
+
+ for(auto &coeff : mEarlyDelayCoeff)
+ std::fill(std::begin(coeff), std::end(coeff), 0.0f);
+ for(auto &coeff : mEarly.Coeff)
+ std::fill(std::begin(coeff), std::end(coeff), 0.0f);
+
+ mLate.DensityGain[0] = 0.0f;
+ mLate.DensityGain[1] = 0.0f;
+ for(auto &t60 : mLate.T60)
+ {
+ t60.MidGain[0] = 0.0f;
+ t60.MidGain[1] = 0.0f;
+ t60.HFFilter.clear();
+ t60.LFFilter.clear();
+ }
+
+ for(auto &gains : mEarly.CurrentGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
+ for(auto &gains : mEarly.PanGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
+ for(auto &gains : mLate.CurrentGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
+ for(auto &gains : mLate.PanGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
+
+ /* Reset fading and offset base. */
+ mDoFading = true;
+ std::fill(std::begin(mMaxUpdate), std::end(mMaxUpdate), MAX_UPDATE_SAMPLES);
+ mOffset = 0;
+
+ if(device->mAmbiOrder > 1)
+ {
+ mMixOut = &ReverbState::MixOutAmbiUp;
+ mOrderScales = BFormatDec::GetHFOrderScales(1, device->mAmbiOrder);
+ }
+ else
+ {
+ mMixOut = &ReverbState::MixOutPlain;
+ mOrderScales.fill(1.0f);
+ }
+ mAmbiSplitter[0][0].init(400.0f / frequency);
+ std::fill(mAmbiSplitter[0].begin()+1, mAmbiSplitter[0].end(), mAmbiSplitter[0][0]);
+ std::fill(mAmbiSplitter[1].begin(), mAmbiSplitter[1].end(), mAmbiSplitter[0][0]);
+
+ return AL_TRUE;
+}
+
+/**************************************
+ * Effect Update *
+ **************************************/
+
+/* Calculate a decay coefficient given the length of each cycle and the time
+ * until the decay reaches -60 dB.
+ */
+inline ALfloat CalcDecayCoeff(const ALfloat length, const ALfloat decayTime)
+{ return std::pow(REVERB_DECAY_GAIN, length/decayTime); }
+
+/* Calculate a decay length from a coefficient and the time until the decay
+ * reaches -60 dB.
+ */
+inline ALfloat CalcDecayLength(const ALfloat coeff, const ALfloat decayTime)
+{ return std::log10(coeff) * decayTime / std::log10(REVERB_DECAY_GAIN); }
+
+/* Calculate an attenuation to be applied to the input of any echo models to
+ * compensate for modal density and decay time.
+ */
+inline ALfloat CalcDensityGain(const ALfloat a)
+{
+ /* The energy of a signal can be obtained by finding the area under the
+ * squared signal. This takes the form of Sum(x_n^2), where x is the
+ * amplitude for the sample n.
+ *
+ * Decaying feedback matches exponential decay of the form Sum(a^n),
+ * where a is the attenuation coefficient, and n is the sample. The area
+ * under this decay curve can be calculated as: 1 / (1 - a).
+ *
+ * Modifying the above equation to find the area under the squared curve
+ * (for energy) yields: 1 / (1 - a^2). Input attenuation can then be
+ * calculated by inverting the square root of this approximation,
+ * yielding: 1 / sqrt(1 / (1 - a^2)), simplified to: sqrt(1 - a^2).
+ */
+ return std::sqrt(1.0f - a*a);
+}
+
+/* Calculate the scattering matrix coefficients given a diffusion factor. */
+inline ALvoid CalcMatrixCoeffs(const ALfloat diffusion, ALfloat *x, ALfloat *y)
+{
+ /* The matrix is of order 4, so n is sqrt(4 - 1). */
+ ALfloat n{std::sqrt(3.0f)};
+ ALfloat t{diffusion * std::atan(n)};
+
+ /* Calculate the first mixing matrix coefficient. */
+ *x = std::cos(t);
+ /* Calculate the second mixing matrix coefficient. */
+ *y = std::sin(t) / n;
+}
+
+/* Calculate the limited HF ratio for use with the late reverb low-pass
+ * filters.
+ */
+ALfloat CalcLimitedHfRatio(const ALfloat hfRatio, const ALfloat airAbsorptionGainHF,
+ const ALfloat decayTime)
+{
+ /* Find the attenuation due to air absorption in dB (converting delay
+ * time to meters using the speed of sound). Then reversing the decay
+ * equation, solve for HF ratio. The delay length is cancelled out of
+ * the equation, so it can be calculated once for all lines.
+ */
+ ALfloat limitRatio{1.0f /
+ (CalcDecayLength(airAbsorptionGainHF, decayTime) * SPEEDOFSOUNDMETRESPERSEC)};
+
+ /* Using the limit calculated above, apply the upper bound to the HF ratio.
+ */
+ return minf(limitRatio, hfRatio);
+}
+
+
+/* Calculates the 3-band T60 damping coefficients for a particular delay line
+ * of specified length, using a combination of two shelf filter sections given
+ * decay times for each band split at two reference frequencies.
+ */
+void T60Filter::calcCoeffs(const ALfloat length, const ALfloat lfDecayTime,
+ const ALfloat mfDecayTime, const ALfloat hfDecayTime, const ALfloat lf0norm,
+ const ALfloat hf0norm)
+{
+ const ALfloat mfGain{CalcDecayCoeff(length, mfDecayTime)};
+ const ALfloat lfGain{maxf(CalcDecayCoeff(length, lfDecayTime)/mfGain, 0.001f)};
+ const ALfloat hfGain{maxf(CalcDecayCoeff(length, hfDecayTime)/mfGain, 0.001f)};
+
+ MidGain[1] = mfGain;
+ LFFilter.setParams(BiquadType::LowShelf, lfGain, lf0norm,
+ LFFilter.rcpQFromSlope(lfGain, 1.0f));
+ HFFilter.setParams(BiquadType::HighShelf, hfGain, hf0norm,
+ HFFilter.rcpQFromSlope(hfGain, 1.0f));
+}
+
+/* Update the early reflection line lengths and gain coefficients. */
+void EarlyReflections::updateLines(const ALfloat density, const ALfloat diffusion,
+ const ALfloat decayTime, const ALfloat frequency)
+{
+ const ALfloat multiplier{CalcDelayLengthMult(density)};
+
+ /* Calculate the all-pass feed-back/forward coefficient. */
+ VecAp.Coeff = std::sqrt(0.5f) * std::pow(diffusion, 2.0f);
+
+ for(size_t i{0u};i < NUM_LINES;i++)
+ {
+ /* Calculate the length (in seconds) of each all-pass line. */
+ ALfloat length{EARLY_ALLPASS_LENGTHS[i] * multiplier};
+
+ /* Calculate the delay offset for each all-pass line. */
+ VecAp.Offset[i][1] = float2uint(length * frequency);
+
+ /* Calculate the length (in seconds) of each delay line. */
+ length = EARLY_LINE_LENGTHS[i] * multiplier;
+
+ /* Calculate the delay offset for each delay line. */
+ Offset[i][1] = float2uint(length * frequency);
+
+ /* Calculate the gain (coefficient) for each line. */
+ Coeff[i][1] = CalcDecayCoeff(length, decayTime);
+ }
+}
+
+/* Update the late reverb line lengths and T60 coefficients. */
+void LateReverb::updateLines(const ALfloat density, const ALfloat diffusion,
+ const ALfloat lfDecayTime, const ALfloat mfDecayTime, const ALfloat hfDecayTime,
+ const ALfloat lf0norm, const ALfloat hf0norm, const ALfloat frequency)
+{
+ /* Scaling factor to convert the normalized reference frequencies from
+ * representing 0...freq to 0...max_reference.
+ */
+ const ALfloat norm_weight_factor{frequency / AL_EAXREVERB_MAX_HFREFERENCE};
+
+ const ALfloat late_allpass_avg{
+ std::accumulate(LATE_ALLPASS_LENGTHS.begin(), LATE_ALLPASS_LENGTHS.end(), 0.0f) /
+ float{NUM_LINES}};
+
+ /* To compensate for changes in modal density and decay time of the late
+ * reverb signal, the input is attenuated based on the maximal energy of
+ * the outgoing signal. This approximation is used to keep the apparent
+ * energy of the signal equal for all ranges of density and decay time.
+ *
+ * The average length of the delay lines is used to calculate the
+ * attenuation coefficient.
+ */
+ const ALfloat multiplier{CalcDelayLengthMult(density)};
+ ALfloat length{std::accumulate(LATE_LINE_LENGTHS.begin(), LATE_LINE_LENGTHS.end(), 0.0f) /
+ float{NUM_LINES} * multiplier};
+ length += late_allpass_avg * multiplier;
+ /* The density gain calculation uses an average decay time weighted by
+ * approximate bandwidth. This attempts to compensate for losses of energy
+ * that reduce decay time due to scattering into highly attenuated bands.
+ */
+ const ALfloat decayTimeWeighted{
+ (lf0norm*norm_weight_factor)*lfDecayTime +
+ (hf0norm*norm_weight_factor - lf0norm*norm_weight_factor)*mfDecayTime +
+ (1.0f - hf0norm*norm_weight_factor)*hfDecayTime};
+ DensityGain[1] = CalcDensityGain(CalcDecayCoeff(length, decayTimeWeighted));
+
+ /* Calculate the all-pass feed-back/forward coefficient. */
+ VecAp.Coeff = std::sqrt(0.5f) * std::pow(diffusion, 2.0f);
+
+ for(size_t i{0u};i < NUM_LINES;i++)
+ {
+ /* Calculate the length (in seconds) of each all-pass line. */
+ length = LATE_ALLPASS_LENGTHS[i] * multiplier;
+
+ /* Calculate the delay offset for each all-pass line. */
+ VecAp.Offset[i][1] = float2uint(length * frequency);
+
+ /* Calculate the length (in seconds) of each delay line. */
+ length = LATE_LINE_LENGTHS[i] * multiplier;
+
+ /* Calculate the delay offset for each delay line. */
+ Offset[i][1] = float2uint(length*frequency + 0.5f);
+
+ /* Approximate the absorption that the vector all-pass would exhibit
+ * given the current diffusion so we don't have to process a full T60
+ * filter for each of its four lines.
+ */
+ length += lerp(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion) * multiplier;
+
+ /* Calculate the T60 damping coefficients for each line. */
+ T60[i].calcCoeffs(length, lfDecayTime, mfDecayTime, hfDecayTime, lf0norm, hf0norm);
+ }
+}
+
+
+/* Update the offsets for the main effect delay line. */
+void ReverbState::updateDelayLine(const ALfloat earlyDelay, const ALfloat lateDelay,
+ const ALfloat density, const ALfloat decayTime, const ALfloat frequency)
+{
+ const ALfloat multiplier{CalcDelayLengthMult(density)};
+
+ /* Early reflection taps are decorrelated by means of an average room
+ * reflection approximation described above the definition of the taps.
+ * This approximation is linear and so the above density multiplier can
+ * be applied to adjust the width of the taps. A single-band decay
+ * coefficient is applied to simulate initial attenuation and absorption.
+ *
+ * Late reverb taps are based on the late line lengths to allow a zero-
+ * delay path and offsets that would continue the propagation naturally
+ * into the late lines.
+ */
+ for(size_t i{0u};i < NUM_LINES;i++)
+ {
+ ALfloat length{earlyDelay + EARLY_TAP_LENGTHS[i]*multiplier};
+ mEarlyDelayTap[i][1] = float2uint(length * frequency);
+
+ length = EARLY_TAP_LENGTHS[i]*multiplier;
+ mEarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime);
+
+ length = (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS.front())/float{NUM_LINES}*multiplier +
+ lateDelay;
+ mLateDelayTap[i][1] = mLateFeedTap + float2uint(length * frequency);
+ }
+}
+
+/* Creates a transform matrix given a reverb vector. The vector pans the reverb
+ * reflections toward the given direction, using its magnitude (up to 1) as a
+ * focal strength. This function results in a B-Format transformation matrix
+ * that spatially focuses the signal in the desired direction.
+ */
+alu::Matrix GetTransformFromVector(const ALfloat *vec)
+{
+ constexpr float sqrt_3{1.73205080756887719318f};
+
+ /* Normalize the panning vector according to the N3D scale, which has an
+ * extra sqrt(3) term on the directional components. Converting from OpenAL
+ * to B-Format also requires negating X (ACN 1) and Z (ACN 3). Note however
+ * that the reverb panning vectors use left-handed coordinates, unlike the
+ * rest of OpenAL which use right-handed. This is fixed by negating Z,
+ * which cancels out with the B-Format Z negation.
+ */
+ ALfloat norm[3];
+ ALfloat mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])};
+ if(mag > 1.0f)
+ {
+ norm[0] = vec[0] / mag * -sqrt_3;
+ norm[1] = vec[1] / mag * sqrt_3;
+ norm[2] = vec[2] / mag * sqrt_3;
+ mag = 1.0f;
+ }
+ else
+ {
+ /* If the magnitude is less than or equal to 1, just apply the sqrt(3)
+ * term. There's no need to renormalize the magnitude since it would
+ * just be reapplied in the matrix.
+ */
+ norm[0] = vec[0] * -sqrt_3;
+ norm[1] = vec[1] * sqrt_3;
+ norm[2] = vec[2] * sqrt_3;
+ }
+
+ return alu::Matrix{
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ norm[0], 1.0f-mag, 0.0f, 0.0f,
+ norm[1], 0.0f, 1.0f-mag, 0.0f,
+ norm[2], 0.0f, 0.0f, 1.0f-mag
+ };
+}
+
+/* Update the early and late 3D panning gains. */
+void ReverbState::update3DPanning(const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan,
+ const ALfloat earlyGain, const ALfloat lateGain, const EffectTarget &target)
+{
+ /* Create matrices that transform a B-Format signal according to the
+ * panning vectors.
+ */
+ const alu::Matrix earlymat{GetTransformFromVector(ReflectionsPan)};
+ const alu::Matrix latemat{GetTransformFromVector(LateReverbPan)};
+
+ mOutTarget = target.Main->Buffer;
+ for(size_t i{0u};i < NUM_LINES;i++)
+ {
+ const ALfloat coeffs[MAX_AMBI_CHANNELS]{earlymat[0][i], earlymat[1][i], earlymat[2][i],
+ earlymat[3][i]};
+ ComputePanGains(target.Main, coeffs, earlyGain, mEarly.PanGain[i]);
+ }
+ for(size_t i{0u};i < NUM_LINES;i++)
+ {
+ const ALfloat coeffs[MAX_AMBI_CHANNELS]{latemat[0][i], latemat[1][i], latemat[2][i],
+ latemat[3][i]};
+ ComputePanGains(target.Main, coeffs, lateGain, mLate.PanGain[i]);
+ }
+}
+
+void ReverbState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *Device{Context->mDevice.get()};
+ const auto frequency = static_cast<ALfloat>(Device->Frequency);
+
+ /* Calculate the master filters */
+ ALfloat hf0norm{minf(props->Reverb.HFReference / frequency, 0.49f)};
+ /* Restrict the filter gains from going below -60dB to keep the filter from
+ * killing most of the signal.
+ */
+ ALfloat gainhf{maxf(props->Reverb.GainHF, 0.001f)};
+ mFilter[0].Lp.setParams(BiquadType::HighShelf, gainhf, hf0norm,
+ mFilter[0].Lp.rcpQFromSlope(gainhf, 1.0f));
+ ALfloat lf0norm{minf(props->Reverb.LFReference / frequency, 0.49f)};
+ ALfloat gainlf{maxf(props->Reverb.GainLF, 0.001f)};
+ mFilter[0].Hp.setParams(BiquadType::LowShelf, gainlf, lf0norm,
+ mFilter[0].Hp.rcpQFromSlope(gainlf, 1.0f));
+ for(size_t i{1u};i < NUM_LINES;i++)
+ {
+ mFilter[i].Lp.copyParamsFrom(mFilter[0].Lp);
+ mFilter[i].Hp.copyParamsFrom(mFilter[0].Hp);
+ }
+
+ /* Update the main effect delay and associated taps. */
+ updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay,
+ props->Reverb.Density, props->Reverb.DecayTime, frequency);
+
+ /* Update the early lines. */
+ mEarly.updateLines(props->Reverb.Density, props->Reverb.Diffusion, props->Reverb.DecayTime,
+ frequency);
+
+ /* Get the mixing matrix coefficients. */
+ CalcMatrixCoeffs(props->Reverb.Diffusion, &mMixX, &mMixY);
+
+ /* If the HF limit parameter is flagged, calculate an appropriate limit
+ * based on the air absorption parameter.
+ */
+ ALfloat hfRatio{props->Reverb.DecayHFRatio};
+ if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f)
+ hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF,
+ props->Reverb.DecayTime);
+
+ /* Calculate the LF/HF decay times. */
+ const ALfloat lfDecayTime{clampf(props->Reverb.DecayTime * props->Reverb.DecayLFRatio,
+ AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME)};
+ const ALfloat hfDecayTime{clampf(props->Reverb.DecayTime * hfRatio,
+ AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME)};
+
+ /* Update the late lines. */
+ mLate.updateLines(props->Reverb.Density, props->Reverb.Diffusion, lfDecayTime,
+ props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm, frequency);
+
+ /* Update early and late 3D panning. */
+ const ALfloat gain{props->Reverb.Gain * Slot->Params.Gain * ReverbBoost};
+ update3DPanning(props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan,
+ props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, target);
+
+ /* Calculate the max update size from the smallest relevant delay. */
+ mMaxUpdate[1] = minz(MAX_UPDATE_SAMPLES, minz(mEarly.Offset[0][1], mLate.Offset[0][1]));
+
+ /* Determine if delay-line cross-fading is required. Density is essentially
+ * a master control for the feedback delays, so changes the offsets of many
+ * delay lines.
+ */
+ mDoFading |= (mParams.Density != props->Reverb.Density ||
+ /* Diffusion and decay times influences the decay rate (gain) of the
+ * late reverb T60 filter.
+ */
+ mParams.Diffusion != props->Reverb.Diffusion ||
+ mParams.DecayTime != props->Reverb.DecayTime ||
+ mParams.HFDecayTime != hfDecayTime ||
+ mParams.LFDecayTime != lfDecayTime ||
+ /* HF/LF References control the weighting used to calculate the density
+ * gain.
+ */
+ mParams.HFReference != props->Reverb.HFReference ||
+ mParams.LFReference != props->Reverb.LFReference);
+ if(mDoFading)
+ {
+ mParams.Density = props->Reverb.Density;
+ mParams.Diffusion = props->Reverb.Diffusion;
+ mParams.DecayTime = props->Reverb.DecayTime;
+ mParams.HFDecayTime = hfDecayTime;
+ mParams.LFDecayTime = lfDecayTime;
+ mParams.HFReference = props->Reverb.HFReference;
+ mParams.LFReference = props->Reverb.LFReference;
+ }
+}
+
+
+/**************************************
+ * Effect Processing *
+ **************************************/
+
+/* Applies a scattering matrix to the 4-line (vector) input. This is used
+ * for both the below vector all-pass model and to perform modal feed-back
+ * delay network (FDN) mixing.
+ *
+ * The matrix is derived from a skew-symmetric matrix to form a 4D rotation
+ * matrix with a single unitary rotational parameter:
+ *
+ * [ d, a, b, c ] 1 = a^2 + b^2 + c^2 + d^2
+ * [ -a, d, c, -b ]
+ * [ -b, -c, d, a ]
+ * [ -c, b, -a, d ]
+ *
+ * The rotation is constructed from the effect's diffusion parameter,
+ * yielding:
+ *
+ * 1 = x^2 + 3 y^2
+ *
+ * Where a, b, and c are the coefficient y with differing signs, and d is the
+ * coefficient x. The final matrix is thus:
+ *
+ * [ x, y, -y, y ] n = sqrt(matrix_order - 1)
+ * [ -y, x, y, y ] t = diffusion_parameter * atan(n)
+ * [ y, -y, x, y ] x = cos(t)
+ * [ -y, -y, -y, x ] y = sin(t) / n
+ *
+ * Any square orthogonal matrix with an order that is a power of two will
+ * work (where ^T is transpose, ^-1 is inverse):
+ *
+ * M^T = M^-1
+ *
+ * Using that knowledge, finding an appropriate matrix can be accomplished
+ * naively by searching all combinations of:
+ *
+ * M = D + S - S^T
+ *
+ * Where D is a diagonal matrix (of x), and S is a triangular matrix (of y)
+ * whose combination of signs are being iterated.
+ */
+inline auto VectorPartialScatter(const std::array<float,NUM_LINES> &RESTRICT in,
+ const ALfloat xCoeff, const ALfloat yCoeff) -> std::array<float,NUM_LINES>
+{
+ std::array<float,NUM_LINES> out;
+ out[0] = xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]);
+ out[1] = xCoeff*in[1] + yCoeff*(-in[0] + in[2] + in[3]);
+ out[2] = xCoeff*in[2] + yCoeff*( in[0] + -in[1] + in[3]);
+ out[3] = xCoeff*in[3] + yCoeff*(-in[0] + -in[1] + -in[2] );
+ return out;
+}
+
+/* Utilizes the above, but reverses the input channels. */
+void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const ALfloat xCoeff,
+ const ALfloat yCoeff, const al::span<const ReverbUpdateLine,NUM_LINES> in, const size_t count)
+{
+ ASSUME(count > 0);
+
+ for(size_t i{0u};i < count;)
+ {
+ offset &= delay.Mask;
+ size_t td{minz(delay.Mask+1 - offset, count-i)};
+ do {
+ std::array<float,NUM_LINES> f;
+ for(size_t j{0u};j < NUM_LINES;j++)
+ f[NUM_LINES-1-j] = in[j][i];
+ ++i;
+
+ delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff);
+ } while(--td);
+ }
+}
+
+/* This applies a Gerzon multiple-in/multiple-out (MIMO) vector all-pass
+ * filter to the 4-line input.
+ *
+ * It works by vectorizing a regular all-pass filter and replacing the delay
+ * element with a scattering matrix (like the one above) and a diagonal
+ * matrix of delay elements.
+ *
+ * Two static specializations are used for transitional (cross-faded) delay
+ * line processing and non-transitional processing.
+ */
+void VecAllpass::processUnfaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
+ const ALfloat xCoeff, const ALfloat yCoeff, const size_t todo)
+{
+ const DelayLineI delay{Delay};
+ const ALfloat feedCoeff{Coeff};
+
+ ASSUME(todo > 0);
+
+ size_t vap_offset[NUM_LINES];
+ for(size_t j{0u};j < NUM_LINES;j++)
+ vap_offset[j] = offset - Offset[j][0];
+ for(size_t i{0u};i < todo;)
+ {
+ for(size_t j{0u};j < NUM_LINES;j++)
+ vap_offset[j] &= delay.Mask;
+ offset &= delay.Mask;
+
+ size_t maxoff{offset};
+ for(size_t j{0u};j < NUM_LINES;j++)
+ maxoff = maxz(maxoff, vap_offset[j]);
+ size_t td{minz(delay.Mask+1 - maxoff, todo - i)};
+
+ do {
+ std::array<float,NUM_LINES> f;
+ for(size_t j{0u};j < NUM_LINES;j++)
+ {
+ const ALfloat input{samples[j][i]};
+ const ALfloat out{delay.Line[vap_offset[j]++][j] - feedCoeff*input};
+ f[j] = input + feedCoeff*out;
+
+ samples[j][i] = out;
+ }
+ ++i;
+
+ delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff);
+ } while(--td);
+ }
+}
+void VecAllpass::processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
+ const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fadeCount, const ALfloat fadeStep,
+ const size_t todo)
+{
+ const DelayLineI delay{Delay};
+ const ALfloat feedCoeff{Coeff};
+
+ ASSUME(todo > 0);
+
+ size_t vap_offset[NUM_LINES][2];
+ for(size_t j{0u};j < NUM_LINES;j++)
+ {
+ vap_offset[j][0] = offset - Offset[j][0];
+ vap_offset[j][1] = offset - Offset[j][1];
+ }
+ for(size_t i{0u};i < todo;)
+ {
+ for(size_t j{0u};j < NUM_LINES;j++)
+ {
+ vap_offset[j][0] &= delay.Mask;
+ vap_offset[j][1] &= delay.Mask;
+ }
+ offset &= delay.Mask;
+
+ size_t maxoff{offset};
+ for(size_t j{0u};j < NUM_LINES;j++)
+ maxoff = maxz(maxoff, maxz(vap_offset[j][0], vap_offset[j][1]));
+ size_t td{minz(delay.Mask+1 - maxoff, todo - i)};
+
+ do {
+ fadeCount += 1.0f;
+ const float fade{fadeCount * fadeStep};
+
+ std::array<float,NUM_LINES> f;
+ for(size_t j{0u};j < NUM_LINES;j++)
+ f[j] = delay.Line[vap_offset[j][0]++][j]*(1.0f-fade) +
+ delay.Line[vap_offset[j][1]++][j]*fade;
+
+ for(size_t j{0u};j < NUM_LINES;j++)
+ {
+ const ALfloat input{samples[j][i]};
+ const ALfloat out{f[j] - feedCoeff*input};
+ f[j] = input + feedCoeff*out;
+
+ samples[j][i] = out;
+ }
+ ++i;
+
+ delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff);
+ } while(--td);
+ }
+}
+
+/* This generates early reflections.
+ *
+ * This is done by obtaining the primary reflections (those arriving from the
+ * same direction as the source) from the main delay line. These are
+ * attenuated and all-pass filtered (based on the diffusion parameter).
+ *
+ * The early lines are then fed in reverse (according to the approximately
+ * opposite spatial location of the A-Format lines) to create the secondary
+ * reflections (those arriving from the opposite direction as the source).
+ *
+ * The early response is then completed by combining the primary reflections
+ * with the delayed and attenuated output from the early lines.
+ *
+ * Finally, the early response is reversed, scattered (based on diffusion),
+ * and fed into the late reverb section of the main delay line.
+ *
+ * Two static specializations are used for transitional (cross-faded) delay
+ * line processing and non-transitional processing.
+ */
+void ReverbState::earlyUnfaded(const size_t offset, const size_t todo)
+{
+ const DelayLineI early_delay{mEarly.Delay};
+ const DelayLineI main_delay{mDelay};
+ const ALfloat mixX{mMixX};
+ const ALfloat mixY{mMixY};
+
+ ASSUME(todo > 0);
+
+ /* First, load decorrelated samples from the main delay line as the primary
+ * reflections.
+ */
+ for(size_t j{0u};j < NUM_LINES;j++)
+ {
+ size_t early_delay_tap{offset - mEarlyDelayTap[j][0]};
+ const ALfloat coeff{mEarlyDelayCoeff[j][0]};
+ for(size_t i{0u};i < todo;)
+ {
+ early_delay_tap &= main_delay.Mask;
+ size_t td{minz(main_delay.Mask+1 - early_delay_tap, todo - i)};
+ do {
+ mTempSamples[j][i++] = main_delay.Line[early_delay_tap++][j] * coeff;
+ } while(--td);
+ }
+ }
+
+ /* Apply a vector all-pass, to help color the initial reflections based on
+ * the diffusion strength.
+ */
+ mEarly.VecAp.processUnfaded(mTempSamples, offset, mixX, mixY, todo);
+
+ /* Apply a delay and bounce to generate secondary reflections, combine with
+ * the primary reflections and write out the result for mixing.
+ */
+ for(size_t j{0u};j < NUM_LINES;j++)
+ {
+ size_t feedb_tap{offset - mEarly.Offset[j][0]};
+ const ALfloat feedb_coeff{mEarly.Coeff[j][0]};
+ float *out = mEarlySamples[j].data();
+
+ for(size_t i{0u};i < todo;)
+ {
+ feedb_tap &= early_delay.Mask;
+ size_t td{minz(early_delay.Mask+1 - feedb_tap, todo - i)};
+ do {
+ out[i] = mTempSamples[j][i] + early_delay.Line[feedb_tap++][j]*feedb_coeff;
+ ++i;
+ } while(--td);
+ }
+ }
+ for(size_t j{0u};j < NUM_LINES;j++)
+ early_delay.write(offset, NUM_LINES-1-j, mTempSamples[j].data(), todo);
+
+ /* Also write the result back to the main delay line for the late reverb
+ * stage to pick up at the appropriate time, appplying a scatter and
+ * bounce to improve the initial diffusion in the late reverb.
+ */
+ const size_t late_feed_tap{offset - mLateFeedTap};
+ VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, mEarlySamples, todo);
+}
+void ReverbState::earlyFaded(const size_t offset, const size_t todo, const ALfloat fade,
+ const ALfloat fadeStep)
+{
+ const DelayLineI early_delay{mEarly.Delay};
+ const DelayLineI main_delay{mDelay};
+ const ALfloat mixX{mMixX};
+ const ALfloat mixY{mMixY};
+
+ ASSUME(todo > 0);
+
+ for(size_t j{0u};j < NUM_LINES;j++)
+ {
+ size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]};
+ size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]};
+ const ALfloat oldCoeff{mEarlyDelayCoeff[j][0]};
+ const ALfloat oldCoeffStep{-oldCoeff * fadeStep};
+ const ALfloat newCoeffStep{mEarlyDelayCoeff[j][1] * fadeStep};
+ ALfloat fadeCount{fade};
+
+ for(size_t i{0u};i < todo;)
+ {
+ early_delay_tap0 &= main_delay.Mask;
+ early_delay_tap1 &= main_delay.Mask;
+ size_t td{minz(main_delay.Mask+1 - maxz(early_delay_tap0, early_delay_tap1), todo-i)};
+ do {
+ fadeCount += 1.0f;
+ const ALfloat fade0{oldCoeff + oldCoeffStep*fadeCount};
+ const ALfloat fade1{newCoeffStep*fadeCount};
+ mTempSamples[j][i++] =
+ main_delay.Line[early_delay_tap0++][j]*fade0 +
+ main_delay.Line[early_delay_tap1++][j]*fade1;
+ } while(--td);
+ }
+ }
+
+ mEarly.VecAp.processFaded(mTempSamples, offset, mixX, mixY, fade, fadeStep, todo);
+
+ for(size_t j{0u};j < NUM_LINES;j++)
+ {
+ size_t feedb_tap0{offset - mEarly.Offset[j][0]};
+ size_t feedb_tap1{offset - mEarly.Offset[j][1]};
+ const ALfloat feedb_oldCoeff{mEarly.Coeff[j][0]};
+ const ALfloat feedb_oldCoeffStep{-feedb_oldCoeff * fadeStep};
+ const ALfloat feedb_newCoeffStep{mEarly.Coeff[j][1] * fadeStep};
+ float *out = mEarlySamples[j].data();
+ ALfloat fadeCount{fade};
+
+ for(size_t i{0u};i < todo;)
+ {
+ feedb_tap0 &= early_delay.Mask;
+ feedb_tap1 &= early_delay.Mask;
+ size_t td{minz(early_delay.Mask+1 - maxz(feedb_tap0, feedb_tap1), todo - i)};
+
+ do {
+ fadeCount += 1.0f;
+ const ALfloat fade0{feedb_oldCoeff + feedb_oldCoeffStep*fadeCount};
+ const ALfloat fade1{feedb_newCoeffStep*fadeCount};
+ out[i] = mTempSamples[j][i] +
+ early_delay.Line[feedb_tap0++][j]*fade0 +
+ early_delay.Line[feedb_tap1++][j]*fade1;
+ ++i;
+ } while(--td);
+ }
+ }
+ for(size_t j{0u};j < NUM_LINES;j++)
+ early_delay.write(offset, NUM_LINES-1-j, mTempSamples[j].data(), todo);
+
+ const size_t late_feed_tap{offset - mLateFeedTap};
+ VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, mEarlySamples, todo);
+}
+
+/* This generates the reverb tail using a modified feed-back delay network
+ * (FDN).
+ *
+ * Results from the early reflections are mixed with the output from the late
+ * delay lines.
+ *
+ * The late response is then completed by T60 and all-pass filtering the mix.
+ *
+ * Finally, the lines are reversed (so they feed their opposite directions)
+ * and scattered with the FDN matrix before re-feeding the delay lines.
+ *
+ * Two variations are made, one for for transitional (cross-faded) delay line
+ * processing and one for non-transitional processing.
+ */
+void ReverbState::lateUnfaded(const size_t offset, const size_t todo)
+{
+ const DelayLineI late_delay{mLate.Delay};
+ const DelayLineI main_delay{mDelay};
+ const ALfloat mixX{mMixX};
+ const ALfloat mixY{mMixY};
+
+ ASSUME(todo > 0);
+
+ /* First, load decorrelated samples from the main and feedback delay lines.
+ * Filter the signal to apply its frequency-dependent decay.
+ */
+ for(size_t j{0u};j < NUM_LINES;j++)
+ {
+ size_t late_delay_tap{offset - mLateDelayTap[j][0]};
+ size_t late_feedb_tap{offset - mLate.Offset[j][0]};
+ const ALfloat midGain{mLate.T60[j].MidGain[0]};
+ const ALfloat densityGain{mLate.DensityGain[0] * midGain};
+ for(size_t i{0u};i < todo;)
+ {
+ late_delay_tap &= main_delay.Mask;
+ late_feedb_tap &= late_delay.Mask;
+ size_t td{minz(todo - i,
+ minz(main_delay.Mask+1 - late_delay_tap, late_delay.Mask+1 - late_feedb_tap))};
+ do {
+ mTempSamples[j][i++] =
+ main_delay.Line[late_delay_tap++][j]*densityGain +
+ late_delay.Line[late_feedb_tap++][j]*midGain;
+ } while(--td);
+ }
+ mLate.T60[j].process(mTempSamples[j].data(), todo);
+ }
+
+ /* Apply a vector all-pass to improve micro-surface diffusion, and write
+ * out the results for mixing.
+ */
+ mLate.VecAp.processUnfaded(mTempSamples, offset, mixX, mixY, todo);
+ for(size_t j{0u};j < NUM_LINES;j++)
+ std::copy_n(mTempSamples[j].begin(), todo, mLateSamples[j].begin());
+
+ /* Finally, scatter and bounce the results to refeed the feedback buffer. */
+ VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, mTempSamples, todo);
+}
+void ReverbState::lateFaded(const size_t offset, const size_t todo, const ALfloat fade,
+ const ALfloat fadeStep)
+{
+ const DelayLineI late_delay{mLate.Delay};
+ const DelayLineI main_delay{mDelay};
+ const ALfloat mixX{mMixX};
+ const ALfloat mixY{mMixY};
+
+ ASSUME(todo > 0);
+
+ for(size_t j{0u};j < NUM_LINES;j++)
+ {
+ const ALfloat oldMidGain{mLate.T60[j].MidGain[0]};
+ const ALfloat midGain{mLate.T60[j].MidGain[1]};
+ const ALfloat oldMidStep{-oldMidGain * fadeStep};
+ const ALfloat midStep{midGain * fadeStep};
+ const ALfloat oldDensityGain{mLate.DensityGain[0] * oldMidGain};
+ const ALfloat densityGain{mLate.DensityGain[1] * midGain};
+ const ALfloat oldDensityStep{-oldDensityGain * fadeStep};
+ const ALfloat densityStep{densityGain * fadeStep};
+ size_t late_delay_tap0{offset - mLateDelayTap[j][0]};
+ size_t late_delay_tap1{offset - mLateDelayTap[j][1]};
+ size_t late_feedb_tap0{offset - mLate.Offset[j][0]};
+ size_t late_feedb_tap1{offset - mLate.Offset[j][1]};
+ ALfloat fadeCount{fade};
+
+ for(size_t i{0u};i < todo;)
+ {
+ late_delay_tap0 &= main_delay.Mask;
+ late_delay_tap1 &= main_delay.Mask;
+ late_feedb_tap0 &= late_delay.Mask;
+ late_feedb_tap1 &= late_delay.Mask;
+ size_t td{minz(todo - i,
+ minz(main_delay.Mask+1 - maxz(late_delay_tap0, late_delay_tap1),
+ late_delay.Mask+1 - maxz(late_feedb_tap0, late_feedb_tap1)))};
+ do {
+ fadeCount += 1.0f;
+ const ALfloat fade0{oldDensityGain + oldDensityStep*fadeCount};
+ const ALfloat fade1{densityStep*fadeCount};
+ const ALfloat gfade0{oldMidGain + oldMidStep*fadeCount};
+ const ALfloat gfade1{midStep*fadeCount};
+ mTempSamples[j][i++] =
+ main_delay.Line[late_delay_tap0++][j]*fade0 +
+ main_delay.Line[late_delay_tap1++][j]*fade1 +
+ late_delay.Line[late_feedb_tap0++][j]*gfade0 +
+ late_delay.Line[late_feedb_tap1++][j]*gfade1;
+ } while(--td);
+ }
+ mLate.T60[j].process(mTempSamples[j].data(), todo);
+ }
+
+ mLate.VecAp.processFaded(mTempSamples, offset, mixX, mixY, fade, fadeStep, todo);
+ for(size_t j{0u};j < NUM_LINES;j++)
+ std::copy_n(mTempSamples[j].begin(), todo, mLateSamples[j].begin());
+
+ VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, mTempSamples, todo);
+}
+
+void ReverbState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+ size_t offset{mOffset};
+
+ ASSUME(samplesToDo > 0);
+
+ /* Convert B-Format to A-Format for processing. */
+ const size_t numInput{samplesIn.size()};
+ const al::span<float> tmpspan{mTempLine.data(), samplesToDo};
+ for(size_t c{0u};c < NUM_LINES;c++)
+ {
+ std::fill(tmpspan.begin(), tmpspan.end(), 0.0f);
+ MixRowSamples(tmpspan, {B2A[c], numInput}, samplesIn[0].data(), samplesIn[0].size());
+
+ /* Band-pass the incoming samples and feed the initial delay line. */
+ mFilter[c].Lp.process(mTempLine.data(), mTempLine.data(), samplesToDo);
+ mFilter[c].Hp.process(mTempLine.data(), mTempLine.data(), samplesToDo);
+ mDelay.write(offset, c, mTempLine.data(), samplesToDo);
+ }
+
+ /* Process reverb for these samples. */
+ if LIKELY(!mDoFading)
+ {
+ for(size_t base{0};base < samplesToDo;)
+ {
+ /* Calculate the number of samples we can do this iteration. */
+ size_t todo{minz(samplesToDo - base, mMaxUpdate[0])};
+ /* Some mixers require maintaining a 4-sample alignment, so ensure
+ * that if it's not the last iteration.
+ */
+ if(base+todo < samplesToDo) todo &= ~size_t{3};
+ ASSUME(todo > 0);
+
+ /* Generate non-faded early reflections and late reverb. */
+ earlyUnfaded(offset, todo);
+ lateUnfaded(offset, todo);
+
+ /* Finally, mix early reflections and late reverb. */
+ (this->*mMixOut)(samplesOut, samplesToDo-base, base, todo);
+
+ offset += todo;
+ base += todo;
+ }
+ }
+ else
+ {
+ const float fadeStep{1.0f / static_cast<float>(samplesToDo)};
+ for(size_t base{0};base < samplesToDo;)
+ {
+ size_t todo{minz(samplesToDo - base, minz(mMaxUpdate[0], mMaxUpdate[1]))};
+ if(base+todo < samplesToDo) todo &= ~size_t{3};
+ ASSUME(todo > 0);
+
+ /* Generate cross-faded early reflections and late reverb. */
+ auto fadeCount = static_cast<ALfloat>(base);
+ earlyFaded(offset, todo, fadeCount, fadeStep);
+ lateFaded(offset, todo, fadeCount, fadeStep);
+
+ (this->*mMixOut)(samplesOut, samplesToDo-base, base, todo);
+
+ offset += todo;
+ base += todo;
+ }
+
+ /* Update the cross-fading delay line taps. */
+ for(size_t c{0u};c < NUM_LINES;c++)
+ {
+ mEarlyDelayTap[c][0] = mEarlyDelayTap[c][1];
+ mEarlyDelayCoeff[c][0] = mEarlyDelayCoeff[c][1];
+ mEarly.VecAp.Offset[c][0] = mEarly.VecAp.Offset[c][1];
+ mEarly.Offset[c][0] = mEarly.Offset[c][1];
+ mEarly.Coeff[c][0] = mEarly.Coeff[c][1];
+ mLateDelayTap[c][0] = mLateDelayTap[c][1];
+ mLate.VecAp.Offset[c][0] = mLate.VecAp.Offset[c][1];
+ mLate.Offset[c][0] = mLate.Offset[c][1];
+ mLate.T60[c].MidGain[0] = mLate.T60[c].MidGain[1];
+ }
+ mLate.DensityGain[0] = mLate.DensityGain[1];
+ mMaxUpdate[0] = mMaxUpdate[1];
+ mDoFading = false;
+ }
+ mOffset = offset;
+}
+
+
+void EAXReverb_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_DECAY_HFLIMIT:
+ if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hflimit out of range");
+ props->Reverb.DecayHFLimit = val != AL_FALSE;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
+ param);
+ }
+}
+void EAXReverb_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ EAXReverb_setParami(props, context, param, vals[0]); }
+void EAXReverb_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_DENSITY:
+ if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb density out of range");
+ props->Reverb.Density = val;
+ break;
+
+ case AL_EAXREVERB_DIFFUSION:
+ if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb diffusion out of range");
+ props->Reverb.Diffusion = val;
+ break;
+
+ case AL_EAXREVERB_GAIN:
+ if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gain out of range");
+ props->Reverb.Gain = val;
+ break;
+
+ case AL_EAXREVERB_GAINHF:
+ if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainhf out of range");
+ props->Reverb.GainHF = val;
+ break;
+
+ case AL_EAXREVERB_GAINLF:
+ if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainlf out of range");
+ props->Reverb.GainLF = val;
+ break;
+
+ case AL_EAXREVERB_DECAY_TIME:
+ if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay time out of range");
+ props->Reverb.DecayTime = val;
+ break;
+
+ case AL_EAXREVERB_DECAY_HFRATIO:
+ if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hfratio out of range");
+ props->Reverb.DecayHFRatio = val;
+ break;
+
+ case AL_EAXREVERB_DECAY_LFRATIO:
+ if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay lfratio out of range");
+ props->Reverb.DecayLFRatio = val;
+ break;
+
+ case AL_EAXREVERB_REFLECTIONS_GAIN:
+ if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections gain out of range");
+ props->Reverb.ReflectionsGain = val;
+ break;
+
+ case AL_EAXREVERB_REFLECTIONS_DELAY:
+ if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections delay out of range");
+ props->Reverb.ReflectionsDelay = val;
+ break;
+
+ case AL_EAXREVERB_LATE_REVERB_GAIN:
+ if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb gain out of range");
+ props->Reverb.LateReverbGain = val;
+ break;
+
+ case AL_EAXREVERB_LATE_REVERB_DELAY:
+ if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb delay out of range");
+ props->Reverb.LateReverbDelay = val;
+ break;
+
+ case AL_EAXREVERB_AIR_ABSORPTION_GAINHF:
+ if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb air absorption gainhf out of range");
+ props->Reverb.AirAbsorptionGainHF = val;
+ break;
+
+ case AL_EAXREVERB_ECHO_TIME:
+ if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo time out of range");
+ props->Reverb.EchoTime = val;
+ break;
+
+ case AL_EAXREVERB_ECHO_DEPTH:
+ if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo depth out of range");
+ props->Reverb.EchoDepth = val;
+ break;
+
+ case AL_EAXREVERB_MODULATION_TIME:
+ if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation time out of range");
+ props->Reverb.ModulationTime = val;
+ break;
+
+ case AL_EAXREVERB_MODULATION_DEPTH:
+ if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation depth out of range");
+ props->Reverb.ModulationDepth = val;
+ break;
+
+ case AL_EAXREVERB_HFREFERENCE:
+ if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb hfreference out of range");
+ props->Reverb.HFReference = val;
+ break;
+
+ case AL_EAXREVERB_LFREFERENCE:
+ if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb lfreference out of range");
+ props->Reverb.LFReference = val;
+ break;
+
+ case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR:
+ if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb room rolloff factor out of range");
+ props->Reverb.RoomRolloffFactor = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param);
+ }
+}
+void EAXReverb_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_REFLECTIONS_PAN:
+ if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2])))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections pan out of range");
+ props->Reverb.ReflectionsPan[0] = vals[0];
+ props->Reverb.ReflectionsPan[1] = vals[1];
+ props->Reverb.ReflectionsPan[2] = vals[2];
+ break;
+ case AL_EAXREVERB_LATE_REVERB_PAN:
+ if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2])))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb pan out of range");
+ props->Reverb.LateReverbPan[0] = vals[0];
+ props->Reverb.LateReverbPan[1] = vals[1];
+ props->Reverb.LateReverbPan[2] = vals[2];
+ break;
+
+ default:
+ EAXReverb_setParamf(props, context, param, vals[0]);
+ break;
+ }
+}
+
+void EAXReverb_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_DECAY_HFLIMIT:
+ *val = props->Reverb.DecayHFLimit;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
+ param);
+ }
+}
+void EAXReverb_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ EAXReverb_getParami(props, context, param, vals); }
+void EAXReverb_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_DENSITY:
+ *val = props->Reverb.Density;
+ break;
+
+ case AL_EAXREVERB_DIFFUSION:
+ *val = props->Reverb.Diffusion;
+ break;
+
+ case AL_EAXREVERB_GAIN:
+ *val = props->Reverb.Gain;
+ break;
+
+ case AL_EAXREVERB_GAINHF:
+ *val = props->Reverb.GainHF;
+ break;
+
+ case AL_EAXREVERB_GAINLF:
+ *val = props->Reverb.GainLF;
+ break;
+
+ case AL_EAXREVERB_DECAY_TIME:
+ *val = props->Reverb.DecayTime;
+ break;
+
+ case AL_EAXREVERB_DECAY_HFRATIO:
+ *val = props->Reverb.DecayHFRatio;
+ break;
+
+ case AL_EAXREVERB_DECAY_LFRATIO:
+ *val = props->Reverb.DecayLFRatio;
+ break;
+
+ case AL_EAXREVERB_REFLECTIONS_GAIN:
+ *val = props->Reverb.ReflectionsGain;
+ break;
+
+ case AL_EAXREVERB_REFLECTIONS_DELAY:
+ *val = props->Reverb.ReflectionsDelay;
+ break;
+
+ case AL_EAXREVERB_LATE_REVERB_GAIN:
+ *val = props->Reverb.LateReverbGain;
+ break;
+
+ case AL_EAXREVERB_LATE_REVERB_DELAY:
+ *val = props->Reverb.LateReverbDelay;
+ break;
+
+ case AL_EAXREVERB_AIR_ABSORPTION_GAINHF:
+ *val = props->Reverb.AirAbsorptionGainHF;
+ break;
+
+ case AL_EAXREVERB_ECHO_TIME:
+ *val = props->Reverb.EchoTime;
+ break;
+
+ case AL_EAXREVERB_ECHO_DEPTH:
+ *val = props->Reverb.EchoDepth;
+ break;
+
+ case AL_EAXREVERB_MODULATION_TIME:
+ *val = props->Reverb.ModulationTime;
+ break;
+
+ case AL_EAXREVERB_MODULATION_DEPTH:
+ *val = props->Reverb.ModulationDepth;
+ break;
+
+ case AL_EAXREVERB_HFREFERENCE:
+ *val = props->Reverb.HFReference;
+ break;
+
+ case AL_EAXREVERB_LFREFERENCE:
+ *val = props->Reverb.LFReference;
+ break;
+
+ case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR:
+ *val = props->Reverb.RoomRolloffFactor;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param);
+ }
+}
+void EAXReverb_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{
+ switch(param)
+ {
+ case AL_EAXREVERB_REFLECTIONS_PAN:
+ vals[0] = props->Reverb.ReflectionsPan[0];
+ vals[1] = props->Reverb.ReflectionsPan[1];
+ vals[2] = props->Reverb.ReflectionsPan[2];
+ break;
+ case AL_EAXREVERB_LATE_REVERB_PAN:
+ vals[0] = props->Reverb.LateReverbPan[0];
+ vals[1] = props->Reverb.LateReverbPan[1];
+ vals[2] = props->Reverb.LateReverbPan[2];
+ break;
+
+ default:
+ EAXReverb_getParamf(props, context, param, vals);
+ break;
+ }
+}
+
+DEFINE_ALEFFECT_VTABLE(EAXReverb);
+
+
+struct ReverbStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new ReverbState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &EAXReverb_vtable; }
+};
+
+EffectProps ReverbStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY;
+ props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION;
+ props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN;
+ props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF;
+ props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF;
+ props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME;
+ props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO;
+ props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO;
+ props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN;
+ props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY;
+ props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+ props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+ props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+ props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN;
+ props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY;
+ props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+ props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+ props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+ props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME;
+ props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH;
+ props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME;
+ props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH;
+ props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
+ props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE;
+ props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE;
+ props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
+ props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT;
+ return props;
+}
+
+
+void StdReverb_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_REVERB_DECAY_HFLIMIT:
+ if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hflimit out of range");
+ props->Reverb.DecayHFLimit = val != AL_FALSE;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param);
+ }
+}
+void StdReverb_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
+{ StdReverb_setParami(props, context, param, vals[0]); }
+void StdReverb_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_REVERB_DENSITY:
+ if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb density out of range");
+ props->Reverb.Density = val;
+ break;
+
+ case AL_REVERB_DIFFUSION:
+ if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb diffusion out of range");
+ props->Reverb.Diffusion = val;
+ break;
+
+ case AL_REVERB_GAIN:
+ if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gain out of range");
+ props->Reverb.Gain = val;
+ break;
+
+ case AL_REVERB_GAINHF:
+ if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gainhf out of range");
+ props->Reverb.GainHF = val;
+ break;
+
+ case AL_REVERB_DECAY_TIME:
+ if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay time out of range");
+ props->Reverb.DecayTime = val;
+ break;
+
+ case AL_REVERB_DECAY_HFRATIO:
+ if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hfratio out of range");
+ props->Reverb.DecayHFRatio = val;
+ break;
+
+ case AL_REVERB_REFLECTIONS_GAIN:
+ if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections gain out of range");
+ props->Reverb.ReflectionsGain = val;
+ break;
+
+ case AL_REVERB_REFLECTIONS_DELAY:
+ if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections delay out of range");
+ props->Reverb.ReflectionsDelay = val;
+ break;
+
+ case AL_REVERB_LATE_REVERB_GAIN:
+ if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb gain out of range");
+ props->Reverb.LateReverbGain = val;
+ break;
+
+ case AL_REVERB_LATE_REVERB_DELAY:
+ if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb delay out of range");
+ props->Reverb.LateReverbDelay = val;
+ break;
+
+ case AL_REVERB_AIR_ABSORPTION_GAINHF:
+ if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb air absorption gainhf out of range");
+ props->Reverb.AirAbsorptionGainHF = val;
+ break;
+
+ case AL_REVERB_ROOM_ROLLOFF_FACTOR:
+ if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb room rolloff factor out of range");
+ props->Reverb.RoomRolloffFactor = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param);
+ }
+}
+void StdReverb_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ StdReverb_setParamf(props, context, param, vals[0]); }
+
+void StdReverb_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
+{
+ switch(param)
+ {
+ case AL_REVERB_DECAY_HFLIMIT:
+ *val = props->Reverb.DecayHFLimit;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param);
+ }
+}
+void StdReverb_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
+{ StdReverb_getParami(props, context, param, vals); }
+void StdReverb_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_REVERB_DENSITY:
+ *val = props->Reverb.Density;
+ break;
+
+ case AL_REVERB_DIFFUSION:
+ *val = props->Reverb.Diffusion;
+ break;
+
+ case AL_REVERB_GAIN:
+ *val = props->Reverb.Gain;
+ break;
+
+ case AL_REVERB_GAINHF:
+ *val = props->Reverb.GainHF;
+ break;
+
+ case AL_REVERB_DECAY_TIME:
+ *val = props->Reverb.DecayTime;
+ break;
+
+ case AL_REVERB_DECAY_HFRATIO:
+ *val = props->Reverb.DecayHFRatio;
+ break;
+
+ case AL_REVERB_REFLECTIONS_GAIN:
+ *val = props->Reverb.ReflectionsGain;
+ break;
+
+ case AL_REVERB_REFLECTIONS_DELAY:
+ *val = props->Reverb.ReflectionsDelay;
+ break;
+
+ case AL_REVERB_LATE_REVERB_GAIN:
+ *val = props->Reverb.LateReverbGain;
+ break;
+
+ case AL_REVERB_LATE_REVERB_DELAY:
+ *val = props->Reverb.LateReverbDelay;
+ break;
+
+ case AL_REVERB_AIR_ABSORPTION_GAINHF:
+ *val = props->Reverb.AirAbsorptionGainHF;
+ break;
+
+ case AL_REVERB_ROOM_ROLLOFF_FACTOR:
+ *val = props->Reverb.RoomRolloffFactor;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param);
+ }
+}
+void StdReverb_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ StdReverb_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(StdReverb);
+
+
+struct StdReverbStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new ReverbState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &StdReverb_vtable; }
+};
+
+EffectProps StdReverbStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Reverb.Density = AL_REVERB_DEFAULT_DENSITY;
+ props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION;
+ props.Reverb.Gain = AL_REVERB_DEFAULT_GAIN;
+ props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF;
+ props.Reverb.GainLF = 1.0f;
+ props.Reverb.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME;
+ props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO;
+ props.Reverb.DecayLFRatio = 1.0f;
+ props.Reverb.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN;
+ props.Reverb.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY;
+ props.Reverb.ReflectionsPan[0] = 0.0f;
+ props.Reverb.ReflectionsPan[1] = 0.0f;
+ props.Reverb.ReflectionsPan[2] = 0.0f;
+ props.Reverb.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN;
+ props.Reverb.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY;
+ props.Reverb.LateReverbPan[0] = 0.0f;
+ props.Reverb.LateReverbPan[1] = 0.0f;
+ props.Reverb.LateReverbPan[2] = 0.0f;
+ props.Reverb.EchoTime = 0.25f;
+ props.Reverb.EchoDepth = 0.0f;
+ props.Reverb.ModulationTime = 0.25f;
+ props.Reverb.ModulationDepth = 0.0f;
+ props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
+ props.Reverb.HFReference = 5000.0f;
+ props.Reverb.LFReference = 250.0f;
+ props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
+ props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *ReverbStateFactory_getFactory()
+{
+ static ReverbStateFactory ReverbFactory{};
+ return &ReverbFactory;
+}
+
+EffectStateFactory *StdReverbStateFactory_getFactory()
+{
+ static StdReverbStateFactory ReverbFactory{};
+ return &ReverbFactory;
+}
diff --git a/alc/effects/vmorpher.cpp b/alc/effects/vmorpher.cpp
new file mode 100644
index 00000000..b1b7cc06
--- /dev/null
+++ b/alc/effects/vmorpher.cpp
@@ -0,0 +1,432 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2019 by Anis A. Hireche
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * 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 "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alcontext.h"
+#include "alu.h"
+
+namespace {
+
+#define MAX_UPDATE_SAMPLES 128
+#define NUM_FORMANTS 4
+#define NUM_FILTERS 2
+#define Q_FACTOR 5.0f
+
+#define VOWEL_A_INDEX 0
+#define VOWEL_B_INDEX 1
+
+#define WAVEFORM_FRACBITS 24
+#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
+#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
+
+inline float Sin(ALuint index)
+{
+ constexpr float scale{al::MathDefs<float>::Tau() / WAVEFORM_FRACONE};
+ return std::sin(static_cast<float>(index) * scale)*0.5f + 0.5f;
+}
+
+inline float Saw(ALuint index)
+{ return static_cast<float>(index) / float{WAVEFORM_FRACONE}; }
+
+inline float Triangle(ALuint index)
+{ return std::fabs(static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); }
+
+inline float Half(ALuint) { return 0.5f; }
+
+template<float (&func)(ALuint)>
+void Oscillate(float *RESTRICT dst, ALuint index, const ALuint step, size_t todo)
+{
+ for(size_t i{0u};i < todo;i++)
+ {
+ index += step;
+ index &= WAVEFORM_FRACMASK;
+ dst[i] = func(index);
+ }
+}
+
+struct FormantFilter
+{
+ ALfloat mCoeff{0.0f};
+ ALfloat mGain{1.0f};
+ ALfloat mS1{0.0f};
+ ALfloat mS2{0.0f};
+
+ FormantFilter() = default;
+ FormantFilter(ALfloat f0norm, ALfloat gain)
+ : mCoeff{std::tan(al::MathDefs<float>::Pi() * f0norm)}, mGain{gain}
+ { }
+
+ inline void process(const ALfloat *samplesIn, ALfloat *samplesOut, const size_t numInput)
+ {
+ /* A state variable filter from a topology-preserving transform.
+ * Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg
+ */
+ const ALfloat g{mCoeff};
+ const ALfloat gain{mGain};
+ const ALfloat h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))};
+ ALfloat s1{mS1};
+ ALfloat s2{mS2};
+
+ for(size_t i{0u};i < numInput;i++)
+ {
+ const ALfloat H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h};
+ const ALfloat B{g*H + s1};
+ const ALfloat L{g*B + s2};
+
+ s1 = g*H + B;
+ s2 = g*B + L;
+
+ // Apply peak and accumulate samples.
+ samplesOut[i] += B * gain;
+ }
+ mS1 = s1;
+ mS2 = s2;
+ }
+
+ inline void clear()
+ {
+ mS1 = 0.0f;
+ mS2 = 0.0f;
+ }
+};
+
+
+struct VmorpherState final : public EffectState {
+ struct {
+ /* Effect parameters */
+ FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS];
+
+ /* Effect gains for each channel */
+ ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
+ ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
+ } mChans[MAX_AMBI_CHANNELS];
+
+ void (*mGetSamples)(float*RESTRICT, ALuint, const ALuint, size_t){};
+
+ ALuint mIndex{0};
+ ALuint mStep{1};
+
+ /* Effects buffers */
+ ALfloat mSampleBufferA[MAX_UPDATE_SAMPLES]{};
+ ALfloat mSampleBufferB[MAX_UPDATE_SAMPLES]{};
+
+ ALboolean deviceUpdate(const ALCdevice *device) override;
+ void update(const ALCcontext *context, const ALeffectslot *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;
+
+ static std::array<FormantFilter,4> getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch);
+
+ DEF_NEWDEL(VmorpherState)
+};
+
+std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch)
+{
+ /* Using soprano formant set of values to
+ * better match mid-range frequency space.
+ *
+ * See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html
+ */
+ switch(phoneme)
+ {
+ case AL_VOCAL_MORPHER_PHONEME_A:
+ return {{
+ {( 800 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
+ {(1150 * pitch) / frequency, 0.501187f}, /* std::pow(10.0f, -6 / 20.0f); */
+ {(2900 * pitch) / frequency, 0.025118f}, /* std::pow(10.0f, -32 / 20.0f); */
+ {(3900 * pitch) / frequency, 0.100000f} /* std::pow(10.0f, -20 / 20.0f); */
+ }};
+ case AL_VOCAL_MORPHER_PHONEME_E:
+ return {{
+ {( 350 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
+ {(2000 * pitch) / frequency, 0.100000f}, /* std::pow(10.0f, -20 / 20.0f); */
+ {(2800 * pitch) / frequency, 0.177827f}, /* std::pow(10.0f, -15 / 20.0f); */
+ {(3600 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
+ }};
+ case AL_VOCAL_MORPHER_PHONEME_I:
+ return {{
+ {( 270 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
+ {(2140 * pitch) / frequency, 0.251188f}, /* std::pow(10.0f, -12 / 20.0f); */
+ {(2950 * pitch) / frequency, 0.050118f}, /* std::pow(10.0f, -26 / 20.0f); */
+ {(3900 * pitch) / frequency, 0.050118f} /* std::pow(10.0f, -26 / 20.0f); */
+ }};
+ case AL_VOCAL_MORPHER_PHONEME_O:
+ return {{
+ {( 450 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
+ {( 800 * pitch) / frequency, 0.281838f}, /* std::pow(10.0f, -11 / 20.0f); */
+ {(2830 * pitch) / frequency, 0.079432f}, /* std::pow(10.0f, -22 / 20.0f); */
+ {(3800 * pitch) / frequency, 0.079432f} /* std::pow(10.0f, -22 / 20.0f); */
+ }};
+ case AL_VOCAL_MORPHER_PHONEME_U:
+ return {{
+ {( 325 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
+ {( 700 * pitch) / frequency, 0.158489f}, /* std::pow(10.0f, -16 / 20.0f); */
+ {(2700 * pitch) / frequency, 0.017782f}, /* std::pow(10.0f, -35 / 20.0f); */
+ {(3800 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
+ }};
+ }
+ return {};
+}
+
+
+ALboolean VmorpherState::deviceUpdate(const ALCdevice* /*device*/)
+{
+ for(auto &e : mChans)
+ {
+ std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[VOWEL_A_INDEX]),
+ std::mem_fn(&FormantFilter::clear));
+ std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]),
+ std::mem_fn(&FormantFilter::clear));
+ std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
+ }
+
+ return AL_TRUE;
+}
+
+void VmorpherState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
+{
+ const ALCdevice *device{context->mDevice.get()};
+ const ALfloat frequency{static_cast<ALfloat>(device->Frequency)};
+ const ALfloat step{props->Vmorpher.Rate / frequency};
+ mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1}));
+
+ if(mStep == 0)
+ mGetSamples = Oscillate<Half>;
+ else if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_SINUSOID)
+ mGetSamples = Oscillate<Sin>;
+ else if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH)
+ mGetSamples = Oscillate<Saw>;
+ else /*if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE)*/
+ mGetSamples = Oscillate<Triangle>;
+
+ const ALfloat pitchA{std::pow(2.0f,
+ static_cast<float>(props->Vmorpher.PhonemeACoarseTuning) / 12.0f)};
+ const ALfloat pitchB{std::pow(2.0f,
+ static_cast<float>(props->Vmorpher.PhonemeBCoarseTuning) / 12.0f)};
+
+ auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA);
+ auto vowelB = getFiltersByPhoneme(props->Vmorpher.PhonemeB, frequency, pitchB);
+
+ /* Copy the filter coefficients to the input channels. */
+ for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
+ {
+ std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].Formants[VOWEL_A_INDEX]));
+ std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].Formants[VOWEL_B_INDEX]));
+ }
+
+ mOutTarget = target.Main->Buffer;
+ for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
+ {
+ auto coeffs = GetAmbiIdentityRow(i);
+ ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
+ }
+}
+
+void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
+{
+ /* Following the EFX specification for a conformant implementation which describes
+ * the effect as a pair of 4-band formant filters blended together using an LFO.
+ */
+ for(size_t base{0u};base < samplesToDo;)
+ {
+ alignas(16) ALfloat lfo[MAX_UPDATE_SAMPLES];
+ const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
+
+ mGetSamples(lfo, mIndex, mStep, td);
+ mIndex += static_cast<ALuint>(mStep * td);
+ mIndex &= WAVEFORM_FRACMASK;
+
+ auto chandata = std::addressof(mChans[0]);
+ for(const auto &input : samplesIn)
+ {
+ std::fill_n(std::begin(mSampleBufferA), td, 0.0f);
+ std::fill_n(std::begin(mSampleBufferB), td, 0.0f);
+
+ auto& vowelA = chandata->Formants[VOWEL_A_INDEX];
+ auto& vowelB = chandata->Formants[VOWEL_B_INDEX];
+
+ /* Process first vowel. */
+ vowelA[0].process(&input[base], mSampleBufferA, td);
+ vowelA[1].process(&input[base], mSampleBufferA, td);
+ vowelA[2].process(&input[base], mSampleBufferA, td);
+ vowelA[3].process(&input[base], mSampleBufferA, td);
+
+ /* Process second vowel. */
+ vowelB[0].process(&input[base], mSampleBufferB, td);
+ vowelB[1].process(&input[base], mSampleBufferB, td);
+ vowelB[2].process(&input[base], mSampleBufferB, td);
+ vowelB[3].process(&input[base], mSampleBufferB, td);
+
+ alignas(16) ALfloat blended[MAX_UPDATE_SAMPLES];
+ for(size_t i{0u};i < td;i++)
+ blended[i] = lerp(mSampleBufferA[i], mSampleBufferB[i], lfo[i]);
+
+ /* Now, mix the processed sound data to the output. */
+ MixSamples({blended, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
+ samplesToDo-base, base);
+ ++chandata;
+ }
+
+ base += td;
+ }
+}
+
+
+void Vmorpher_setParami(EffectProps* props, ALCcontext *context, ALenum param, ALint val)
+{
+ switch(param)
+ {
+ case AL_VOCAL_MORPHER_WAVEFORM:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_WAVEFORM && val <= AL_VOCAL_MORPHER_MAX_WAVEFORM))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher waveform out of range");
+ props->Vmorpher.Waveform = val;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEA:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-a out of range");
+ props->Vmorpher.PhonemeA = val;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEB:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-b out of range");
+ props->Vmorpher.PhonemeB = val;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-a coarse tuning out of range");
+ props->Vmorpher.PhonemeACoarseTuning = val;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-b coarse tuning out of range");
+ props->Vmorpher.PhonemeBCoarseTuning = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
+ param);
+ }
+}
+void Vmorpher_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param); }
+void Vmorpher_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
+{
+ switch(param)
+ {
+ case AL_VOCAL_MORPHER_RATE:
+ if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE))
+ SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher rate out of range");
+ props->Vmorpher.Rate = val;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x",
+ param);
+ }
+}
+void Vmorpher_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
+{ Vmorpher_setParamf(props, context, param, vals[0]); }
+
+void Vmorpher_getParami(const EffectProps* props, ALCcontext *context, ALenum param, ALint* val)
+{
+ switch(param)
+ {
+ case AL_VOCAL_MORPHER_PHONEMEA:
+ *val = props->Vmorpher.PhonemeA;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEB:
+ *val = props->Vmorpher.PhonemeB;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
+ *val = props->Vmorpher.PhonemeACoarseTuning;
+ break;
+
+ case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
+ *val = props->Vmorpher.PhonemeBCoarseTuning;
+ break;
+
+ case AL_VOCAL_MORPHER_WAVEFORM:
+ *val = props->Vmorpher.Waveform;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
+ param);
+ }
+}
+void Vmorpher_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
+{ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param); }
+void Vmorpher_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
+{
+ switch(param)
+ {
+ case AL_VOCAL_MORPHER_RATE:
+ *val = props->Vmorpher.Rate;
+ break;
+
+ default:
+ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x",
+ param);
+ }
+}
+void Vmorpher_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
+{ Vmorpher_getParamf(props, context, param, vals); }
+
+DEFINE_ALEFFECT_VTABLE(Vmorpher);
+
+
+struct VmorpherStateFactory final : public EffectStateFactory {
+ EffectState *create() override { return new VmorpherState{}; }
+ EffectProps getDefaultProps() const noexcept override;
+ const EffectVtable *getEffectVtable() const noexcept override { return &Vmorpher_vtable; }
+};
+
+EffectProps VmorpherStateFactory::getDefaultProps() const noexcept
+{
+ EffectProps props{};
+ props.Vmorpher.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE;
+ props.Vmorpher.PhonemeA = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA;
+ props.Vmorpher.PhonemeB = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB;
+ props.Vmorpher.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING;
+ props.Vmorpher.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING;
+ props.Vmorpher.Waveform = AL_VOCAL_MORPHER_DEFAULT_WAVEFORM;
+ return props;
+}
+
+} // namespace
+
+EffectStateFactory *VmorpherStateFactory_getFactory()
+{
+ static VmorpherStateFactory VmorpherFactory{};
+ return &VmorpherFactory;
+}
diff --git a/Alc/filters/filter.c b/alc/filters/biquad.cpp
index 2b370f89..8a8810e2 100644
--- a/Alc/filters/filter.c
+++ b/alc/filters/biquad.cpp
@@ -1,39 +1,35 @@
#include "config.h"
-#include "AL/alc.h"
-#include "AL/al.h"
+#include "biquad.h"
-#include "alMain.h"
-#include "defs.h"
+#include <algorithm>
+#include <cassert>
+#include <cmath>
-extern inline void BiquadFilter_clear(BiquadFilter *filter);
-extern inline void BiquadFilter_copyParams(BiquadFilter *restrict dst, const BiquadFilter *restrict src);
-extern inline void BiquadFilter_passthru(BiquadFilter *filter, ALsizei numsamples);
-extern inline ALfloat calc_rcpQ_from_slope(ALfloat gain, ALfloat slope);
-extern inline ALfloat calc_rcpQ_from_bandwidth(ALfloat f0norm, ALfloat bandwidth);
+#include "opthelpers.h"
-void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain, ALfloat f0norm, ALfloat rcpQ)
+template<typename Real>
+void BiquadFilterR<Real>::setParams(BiquadType type, Real gain, Real f0norm, Real rcpQ)
{
- ALfloat alpha, sqrtgain_alpha_2;
- ALfloat w0, sin_w0, cos_w0;
- ALfloat a[3] = { 1.0f, 0.0f, 0.0f };
- ALfloat b[3] = { 1.0f, 0.0f, 0.0f };
-
// Limit gain to -100dB
assert(gain > 0.00001f);
- w0 = F_TAU * f0norm;
- sin_w0 = sinf(w0);
- cos_w0 = cosf(w0);
- alpha = sin_w0/2.0f * rcpQ;
+ const Real w0{al::MathDefs<Real>::Tau() * f0norm};
+ const Real sin_w0{std::sin(w0)};
+ const Real cos_w0{std::cos(w0)};
+ const Real alpha{sin_w0/2.0f * rcpQ};
+
+ Real sqrtgain_alpha_2;
+ Real a[3]{ 1.0f, 0.0f, 0.0f };
+ Real b[3]{ 1.0f, 0.0f, 0.0f };
/* Calculate filter coefficients depending on filter type */
switch(type)
{
- case BiquadType_HighShelf:
- sqrtgain_alpha_2 = 2.0f * sqrtf(gain) * alpha;
+ case BiquadType::HighShelf:
+ sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha;
b[0] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
b[1] = -2.0f*gain*((gain-1.0f) + (gain+1.0f)*cos_w0 );
b[2] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
@@ -41,8 +37,8 @@ void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain,
a[1] = 2.0f* ((gain-1.0f) - (gain+1.0f)*cos_w0 );
a[2] = (gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
break;
- case BiquadType_LowShelf:
- sqrtgain_alpha_2 = 2.0f * sqrtf(gain) * alpha;
+ case BiquadType::LowShelf:
+ sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha;
b[0] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
b[1] = 2.0f*gain*((gain-1.0f) - (gain+1.0f)*cos_w0 );
b[2] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
@@ -50,8 +46,7 @@ void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain,
a[1] = -2.0f* ((gain-1.0f) + (gain+1.0f)*cos_w0 );
a[2] = (gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
break;
- case BiquadType_Peaking:
- gain = sqrtf(gain);
+ case BiquadType::Peaking:
b[0] = 1.0f + alpha * gain;
b[1] = -2.0f * cos_w0;
b[2] = 1.0f - alpha * gain;
@@ -60,7 +55,7 @@ void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain,
a[2] = 1.0f - alpha / gain;
break;
- case BiquadType_LowPass:
+ case BiquadType::LowPass:
b[0] = (1.0f - cos_w0) / 2.0f;
b[1] = 1.0f - cos_w0;
b[2] = (1.0f - cos_w0) / 2.0f;
@@ -68,7 +63,7 @@ void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain,
a[1] = -2.0f * cos_w0;
a[2] = 1.0f - alpha;
break;
- case BiquadType_HighPass:
+ case BiquadType::HighPass:
b[0] = (1.0f + cos_w0) / 2.0f;
b[1] = -(1.0f + cos_w0);
b[2] = (1.0f + cos_w0) / 2.0f;
@@ -76,9 +71,9 @@ void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain,
a[1] = -2.0f * cos_w0;
a[2] = 1.0f - alpha;
break;
- case BiquadType_BandPass:
+ case BiquadType::BandPass:
b[0] = alpha;
- b[1] = 0;
+ b[1] = 0.0f;
b[2] = -alpha;
a[0] = 1.0f + alpha;
a[1] = -2.0f * cos_w0;
@@ -86,27 +81,26 @@ void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain,
break;
}
- filter->a1 = a[1] / a[0];
- filter->a2 = a[2] / a[0];
- filter->b0 = b[0] / a[0];
- filter->b1 = b[1] / a[0];
- filter->b2 = b[2] / a[0];
+ mA1 = a[1] / a[0];
+ mA2 = a[2] / a[0];
+ mB0 = b[0] / a[0];
+ mB1 = b[1] / a[0];
+ mB2 = b[2] / a[0];
}
-
-void BiquadFilter_processC(BiquadFilter *filter, ALfloat *restrict dst, const ALfloat *restrict src, ALsizei numsamples)
+template<typename Real>
+void BiquadFilterR<Real>::process(Real *dst, const Real *src, const size_t numsamples)
{
- const ALfloat a1 = filter->a1;
- const ALfloat a2 = filter->a2;
- const ALfloat b0 = filter->b0;
- const ALfloat b1 = filter->b1;
- const ALfloat b2 = filter->b2;
- ALfloat z1 = filter->z1;
- ALfloat z2 = filter->z2;
- ALsizei i;
-
ASSUME(numsamples > 0);
+ const Real b0{mB0};
+ const Real b1{mB1};
+ const Real b2{mB2};
+ const Real a1{mA1};
+ const Real a2{mA2};
+ Real z1{mZ1};
+ Real z2{mZ2};
+
/* Processing loop is Transposed Direct Form II. This requires less storage
* compared to Direct Form I (only two delay components, instead of a four-
* sample history; the last two inputs and outputs), and works better for
@@ -115,15 +109,18 @@ void BiquadFilter_processC(BiquadFilter *filter, ALfloat *restrict dst, const AL
*
* See: http://www.earlevel.com/main/2003/02/28/biquads/
*/
- for(i = 0;i < numsamples;i++)
+ auto proc_sample = [b0,b1,b2,a1,a2,&z1,&z2](Real input) noexcept -> Real
{
- ALfloat input = src[i];
- ALfloat output = input*b0 + z1;
+ Real output = input*b0 + z1;
z1 = input*b1 - output*a1 + z2;
z2 = input*b2 - output*a2;
- dst[i] = output;
- }
+ return output;
+ };
+ std::transform(src, src+numsamples, dst, proc_sample);
- filter->z1 = z1;
- filter->z2 = z2;
+ mZ1 = z1;
+ mZ2 = z2;
}
+
+template class BiquadFilterR<float>;
+template class BiquadFilterR<double>;
diff --git a/alc/filters/biquad.h b/alc/filters/biquad.h
new file mode 100644
index 00000000..9af954ae
--- /dev/null
+++ b/alc/filters/biquad.h
@@ -0,0 +1,113 @@
+#ifndef FILTERS_BIQUAD_H
+#define FILTERS_BIQUAD_H
+
+#include <cmath>
+#include <cstddef>
+#include <utility>
+
+#include "math_defs.h"
+
+
+/* Filters 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
+ */
+/* Implementation note: For the shelf and peaking filters, the specified gain
+ * is for the centerpoint of the transition band. This better fits EFX filter
+ * behavior, which expects the shelf's reference frequency to reach the given
+ * gain. To set the gain for the shelf or peak itself, use the square root of
+ * the desired linear gain (or halve the dB gain).
+ */
+
+enum class BiquadType {
+ /** EFX-style low-pass filter, specifying a gain and reference frequency. */
+ HighShelf,
+ /** EFX-style high-pass filter, specifying a gain and reference frequency. */
+ LowShelf,
+ /** Peaking filter, specifying a gain and reference frequency. */
+ Peaking,
+
+ /** Low-pass cut-off filter, specifying a cut-off frequency. */
+ LowPass,
+ /** High-pass cut-off filter, specifying a cut-off frequency. */
+ HighPass,
+ /** Band-pass filter, specifying a center frequency. */
+ BandPass,
+};
+
+template<typename Real>
+class BiquadFilterR {
+ /* Last two delayed components for direct form II. */
+ Real mZ1{0.0f}, mZ2{0.0f};
+ /* Transfer function coefficients "b" (numerator) */
+ Real mB0{1.0f}, mB1{0.0f}, mB2{0.0f};
+ /* Transfer function coefficients "a" (denominator; a0 is pre-applied). */
+ Real mA1{0.0f}, mA2{0.0f};
+
+public:
+ void clear() noexcept { mZ1 = mZ2 = 0.0f; }
+
+ /**
+ * Sets the filter state for the specified filter type and its parameters.
+ *
+ * \param type The type of filter to apply.
+ * \param gain The gain for the reference frequency response. Only used by
+ * the Shelf and Peaking filter types.
+ * \param f0norm The reference frequency normal (ref_freq / sample_rate).
+ * This is the center point for the Shelf, Peaking, and
+ * BandPass filter types, or the cutoff frequency for the
+ * LowPass and HighPass filter types.
+ * \param rcpQ The reciprocal of the Q coefficient for the filter's
+ * transition band. Can be generated from rcpQFromSlope or
+ * rcpQFromBandwidth as needed.
+ */
+ void setParams(BiquadType type, Real gain, Real f0norm, Real rcpQ);
+
+ void copyParamsFrom(const BiquadFilterR &other)
+ {
+ mB0 = other.mB0;
+ mB1 = other.mB1;
+ mB2 = other.mB2;
+ mA1 = other.mA1;
+ mA2 = other.mA2;
+ }
+
+
+ void process(Real *dst, const Real *src, const size_t numsamples);
+
+ /* Rather hacky. It's just here to support "manual" processing. */
+ std::pair<Real,Real> getComponents() const noexcept { return {mZ1, mZ2}; }
+ void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; }
+ Real processOne(const Real in, Real &z1, Real &z2) const noexcept
+ {
+ Real out{in*mB0 + z1};
+ z1 = in*mB1 - out*mA1 + z2;
+ z2 = in*mB2 - out*mA2;
+ return out;
+ }
+
+ /**
+ * Calculates the rcpQ (i.e. 1/Q) coefficient for shelving filters, using
+ * the reference gain and shelf slope parameter.
+ * \param gain 0 < gain
+ * \param slope 0 < slope <= 1
+ */
+ static Real rcpQFromSlope(Real gain, Real slope)
+ { return std::sqrt((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f); }
+
+ /**
+ * Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the
+ * normalized reference frequency and bandwidth.
+ * \param f0norm 0 < f0norm < 0.5.
+ * \param bandwidth 0 < bandwidth
+ */
+ static Real rcpQFromBandwidth(Real f0norm, Real bandwidth)
+ {
+ const Real w0{al::MathDefs<Real>::Tau() * f0norm};
+ return 2.0f*std::sinh(std::log(Real{2.0f})/2.0f*bandwidth*w0/std::sin(w0));
+ }
+};
+
+using BiquadFilter = BiquadFilterR<float>;
+
+#endif /* FILTERS_BIQUAD_H */
diff --git a/alc/filters/nfc.cpp b/alc/filters/nfc.cpp
new file mode 100644
index 00000000..e4436b27
--- /dev/null
+++ b/alc/filters/nfc.cpp
@@ -0,0 +1,391 @@
+
+#include "config.h"
+
+#include "nfc.h"
+
+#include <algorithm>
+
+#include "opthelpers.h"
+
+
+/* Near-field control filters are the basis for handling the near-field effect.
+ * The near-field effect is a bass-boost present in the directional components
+ * of a recorded signal, created as a result of the wavefront curvature (itself
+ * a function of sound distance). Proper reproduction dictates this be
+ * compensated for using a bass-cut given the playback speaker distance, to
+ * avoid excessive bass in the playback.
+ *
+ * For real-time rendered audio, emulating the near-field effect based on the
+ * sound source's distance, and subsequently compensating for it at output
+ * based on the speaker distances, can create a more realistic perception of
+ * sound distance beyond a simple 1/r attenuation.
+ *
+ * These filters do just that. Each one applies a low-shelf filter, created as
+ * the combination of a bass-boost for a given sound source distance (near-
+ * field emulation) along with a bass-cut for a given control/speaker distance
+ * (near-field compensation).
+ *
+ * Note that it is necessary to apply a cut along with the boost, since the
+ * boost alone is unstable in higher-order ambisonics as it causes an infinite
+ * DC gain (even first-order ambisonics requires there to be no DC offset for
+ * the boost to work). Consequently, ambisonics requires a control parameter to
+ * be used to avoid an unstable boost-only filter. NFC-HOA defines this control
+ * as a reference delay, calculated with:
+ *
+ * reference_delay = control_distance / speed_of_sound
+ *
+ * This means w0 (for input) or w1 (for output) should be set to:
+ *
+ * wN = 1 / (reference_delay * sample_rate)
+ *
+ * when dealing with NFC-HOA content. For FOA input content, which does not
+ * specify a reference_delay variable, w0 should be set to 0 to apply only
+ * near-field compensation for output. It's important that w1 be a finite,
+ * positive, non-0 value or else the bass-boost will become unstable again.
+ * Also, w0 should not be too large compared to w1, to avoid excessively loud
+ * low frequencies.
+ */
+
+namespace {
+
+constexpr float B[5][4] = {
+ { 0.0f },
+ { 1.0f },
+ { 3.0f, 3.0f },
+ { 3.6778f, 6.4595f, 2.3222f },
+ { 4.2076f, 11.4877f, 5.7924f, 9.1401f }
+};
+
+NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept
+{
+ NfcFilter1 nfc{};
+ float b_00, g_0;
+ float r;
+
+ nfc.base_gain = 1.0f;
+ nfc.gain = 1.0f;
+
+ /* Calculate bass-boost coefficients. */
+ r = 0.5f * w0;
+ b_00 = B[1][0] * r;
+ g_0 = 1.0f + b_00;
+
+ nfc.gain *= g_0;
+ nfc.b1 = 2.0f * b_00 / g_0;
+
+ /* Calculate bass-cut coefficients. */
+ r = 0.5f * w1;
+ b_00 = B[1][0] * r;
+ g_0 = 1.0f + b_00;
+
+ nfc.base_gain /= g_0;
+ nfc.gain /= g_0;
+ nfc.a1 = 2.0f * b_00 / g_0;
+
+ return nfc;
+}
+
+void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept
+{
+ const float r{0.5f * w0};
+ const float b_00{B[1][0] * r};
+ const float g_0{1.0f + b_00};
+
+ nfc->gain = nfc->base_gain * g_0;
+ nfc->b1 = 2.0f * b_00 / g_0;
+}
+
+
+NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
+{
+ NfcFilter2 nfc{};
+ float b_10, b_11, g_1;
+ float r;
+
+ nfc.base_gain = 1.0f;
+ nfc.gain = 1.0f;
+
+ /* Calculate bass-boost coefficients. */
+ r = 0.5f * w0;
+ b_10 = B[2][0] * r;
+ b_11 = B[2][1] * r * r;
+ g_1 = 1.0f + b_10 + b_11;
+
+ nfc.gain *= g_1;
+ nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc.b2 = 4.0f * b_11 / g_1;
+
+ /* Calculate bass-cut coefficients. */
+ r = 0.5f * w1;
+ b_10 = B[2][0] * r;
+ b_11 = B[2][1] * r * r;
+ g_1 = 1.0f + b_10 + b_11;
+
+ nfc.base_gain /= g_1;
+ nfc.gain /= g_1;
+ nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc.a2 = 4.0f * b_11 / g_1;
+
+ return nfc;
+}
+
+void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept
+{
+ const float r{0.5f * w0};
+ const float b_10{B[2][0] * r};
+ const float b_11{B[2][1] * r * r};
+ const float g_1{1.0f + b_10 + b_11};
+
+ nfc->gain = nfc->base_gain * g_1;
+ nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc->b2 = 4.0f * b_11 / g_1;
+}
+
+
+NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
+{
+ NfcFilter3 nfc{};
+ float b_10, b_11, g_1;
+ float b_00, g_0;
+ float r;
+
+ nfc.base_gain = 1.0f;
+ nfc.gain = 1.0f;
+
+ /* Calculate bass-boost coefficients. */
+ r = 0.5f * w0;
+ b_10 = B[3][0] * r;
+ b_11 = B[3][1] * r * r;
+ b_00 = B[3][2] * r;
+ g_1 = 1.0f + b_10 + b_11;
+ g_0 = 1.0f + b_00;
+
+ nfc.gain *= g_1 * g_0;
+ nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc.b2 = 4.0f * b_11 / g_1;
+ nfc.b3 = 2.0f * b_00 / g_0;
+
+ /* Calculate bass-cut coefficients. */
+ r = 0.5f * w1;
+ b_10 = B[3][0] * r;
+ b_11 = B[3][1] * r * r;
+ b_00 = B[3][2] * r;
+ g_1 = 1.0f + b_10 + b_11;
+ g_0 = 1.0f + b_00;
+
+ nfc.base_gain /= g_1 * g_0;
+ nfc.gain /= g_1 * g_0;
+ nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc.a2 = 4.0f * b_11 / g_1;
+ nfc.a3 = 2.0f * b_00 / g_0;
+
+ return nfc;
+}
+
+void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept
+{
+ const float r{0.5f * w0};
+ const float b_10{B[3][0] * r};
+ const float b_11{B[3][1] * r * r};
+ const float b_00{B[3][2] * r};
+ const float g_1{1.0f + b_10 + b_11};
+ const float g_0{1.0f + b_00};
+
+ nfc->gain = nfc->base_gain * g_1 * g_0;
+ nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc->b2 = 4.0f * b_11 / g_1;
+ nfc->b3 = 2.0f * b_00 / g_0;
+}
+
+
+NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
+{
+ NfcFilter4 nfc{};
+ float b_10, b_11, g_1;
+ float b_00, b_01, g_0;
+ float r;
+
+ nfc.base_gain = 1.0f;
+ nfc.gain = 1.0f;
+
+ /* Calculate bass-boost coefficients. */
+ r = 0.5f * w0;
+ b_10 = B[4][0] * r;
+ b_11 = B[4][1] * r * r;
+ b_00 = B[4][2] * r;
+ b_01 = B[4][3] * r * r;
+ g_1 = 1.0f + b_10 + b_11;
+ g_0 = 1.0f + b_00 + b_01;
+
+ nfc.gain *= g_1 * g_0;
+ nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc.b2 = 4.0f * b_11 / g_1;
+ nfc.b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
+ nfc.b4 = 4.0f * b_01 / g_0;
+
+ /* Calculate bass-cut coefficients. */
+ r = 0.5f * w1;
+ b_10 = B[4][0] * r;
+ b_11 = B[4][1] * r * r;
+ b_00 = B[4][2] * r;
+ b_01 = B[4][3] * r * r;
+ g_1 = 1.0f + b_10 + b_11;
+ g_0 = 1.0f + b_00 + b_01;
+
+ nfc.base_gain /= g_1 * g_0;
+ nfc.gain /= g_1 * g_0;
+ nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc.a2 = 4.0f * b_11 / g_1;
+ nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
+ nfc.a4 = 4.0f * b_01 / g_0;
+
+ return nfc;
+}
+
+void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept
+{
+ const float r{0.5f * w0};
+ const float b_10{B[4][0] * r};
+ const float b_11{B[4][1] * r * r};
+ const float b_00{B[4][2] * r};
+ const float b_01{B[4][3] * r * r};
+ const float g_1{1.0f + b_10 + b_11};
+ const float g_0{1.0f + b_00 + b_01};
+
+ nfc->gain = nfc->base_gain * g_1 * g_0;
+ nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc->b2 = 4.0f * b_11 / g_1;
+ nfc->b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
+ nfc->b4 = 4.0f * b_01 / g_0;
+}
+
+} // namespace
+
+void NfcFilter::init(const float w1) noexcept
+{
+ first = NfcFilterCreate1(0.0f, w1);
+ second = NfcFilterCreate2(0.0f, w1);
+ third = NfcFilterCreate3(0.0f, w1);
+ fourth = NfcFilterCreate4(0.0f, w1);
+}
+
+void NfcFilter::adjust(const float w0) noexcept
+{
+ NfcFilterAdjust1(&first, w0);
+ NfcFilterAdjust2(&second, w0);
+ NfcFilterAdjust3(&third, w0);
+ NfcFilterAdjust4(&fourth, w0);
+}
+
+
+void NfcFilter::process1(float *RESTRICT dst, const float *RESTRICT src, const size_t count)
+{
+ ASSUME(count > 0);
+
+ const float gain{first.gain};
+ const float b1{first.b1};
+ const float a1{first.a1};
+ float z1{first.z[0]};
+ auto proc_sample = [gain,b1,a1,&z1](const float in) noexcept -> float
+ {
+ const float y{in*gain - a1*z1};
+ const float out{y + b1*z1};
+ z1 += y;
+ return out;
+ };
+ std::transform(src, src+count, dst, proc_sample);
+ first.z[0] = z1;
+}
+
+void NfcFilter::process2(float *RESTRICT dst, const float *RESTRICT src, const size_t count)
+{
+ ASSUME(count > 0);
+
+ const float gain{second.gain};
+ const float b1{second.b1};
+ const float b2{second.b2};
+ const float a1{second.a1};
+ const float a2{second.a2};
+ float z1{second.z[0]};
+ float z2{second.z[1]};
+ auto proc_sample = [gain,b1,b2,a1,a2,&z1,&z2](const float in) noexcept -> float
+ {
+ const float y{in*gain - a1*z1 - a2*z2};
+ const float out{y + b1*z1 + b2*z2};
+ z2 += z1;
+ z1 += y;
+ return out;
+ };
+ std::transform(src, src+count, dst, proc_sample);
+ second.z[0] = z1;
+ second.z[1] = z2;
+}
+
+void NfcFilter::process3(float *RESTRICT dst, const float *RESTRICT src, const size_t count)
+{
+ ASSUME(count > 0);
+
+ const float gain{third.gain};
+ const float b1{third.b1};
+ const float b2{third.b2};
+ const float b3{third.b3};
+ const float a1{third.a1};
+ const float a2{third.a2};
+ const float a3{third.a3};
+ float z1{third.z[0]};
+ float z2{third.z[1]};
+ float z3{third.z[2]};
+ auto proc_sample = [gain,b1,b2,b3,a1,a2,a3,&z1,&z2,&z3](const float in) noexcept -> float
+ {
+ float y{in*gain - a1*z1 - a2*z2};
+ float out{y + b1*z1 + b2*z2};
+ z2 += z1;
+ z1 += y;
+
+ y = out - a3*z3;
+ out = y + b3*z3;
+ z3 += y;
+ return out;
+ };
+ std::transform(src, src+count, dst, proc_sample);
+ third.z[0] = z1;
+ third.z[1] = z2;
+ third.z[2] = z3;
+}
+
+void NfcFilter::process4(float *RESTRICT dst, const float *RESTRICT src, const size_t count)
+{
+ ASSUME(count > 0);
+
+ const float gain{fourth.gain};
+ const float b1{fourth.b1};
+ const float b2{fourth.b2};
+ const float b3{fourth.b3};
+ const float b4{fourth.b4};
+ const float a1{fourth.a1};
+ const float a2{fourth.a2};
+ const float a3{fourth.a3};
+ const float a4{fourth.a4};
+ float z1{fourth.z[0]};
+ float z2{fourth.z[1]};
+ float z3{fourth.z[2]};
+ float z4{fourth.z[3]};
+ auto proc_sample = [gain,b1,b2,b3,b4,a1,a2,a3,a4,&z1,&z2,&z3,&z4](const float in) noexcept -> float
+ {
+ float y{in*gain - a1*z1 - a2*z2};
+ float out{y + b1*z1 + b2*z2};
+ z2 += z1;
+ z1 += y;
+
+ y = out - a3*z3 - a4*z4;
+ out = y + b3*z3 + b4*z4;
+ z4 += z3;
+ z3 += y;
+ return out;
+ };
+ std::transform(src, src+count, dst, proc_sample);
+ fourth.z[0] = z1;
+ fourth.z[1] = z2;
+ fourth.z[2] = z3;
+ fourth.z[3] = z4;
+}
diff --git a/alc/filters/nfc.h b/alc/filters/nfc.h
new file mode 100644
index 00000000..d2bf3339
--- /dev/null
+++ b/alc/filters/nfc.h
@@ -0,0 +1,61 @@
+#ifndef FILTER_NFC_H
+#define FILTER_NFC_H
+
+#include <cstddef>
+
+
+struct NfcFilter1 {
+ float base_gain, gain;
+ float b1, a1;
+ float z[1];
+};
+struct NfcFilter2 {
+ float base_gain, gain;
+ float b1, b2, a1, a2;
+ float z[2];
+};
+struct NfcFilter3 {
+ float base_gain, gain;
+ float b1, b2, b3, a1, a2, a3;
+ float z[3];
+};
+struct NfcFilter4 {
+ float base_gain, gain;
+ float b1, b2, b3, b4, a1, a2, a3, a4;
+ float z[4];
+};
+
+class NfcFilter {
+ NfcFilter1 first;
+ NfcFilter2 second;
+ NfcFilter3 third;
+ NfcFilter4 fourth;
+
+public:
+ /* NOTE:
+ * w0 = speed_of_sound / (source_distance * sample_rate);
+ * w1 = speed_of_sound / (control_distance * sample_rate);
+ *
+ * Generally speaking, the control distance should be approximately the
+ * average speaker distance, or based on the reference delay if outputing
+ * NFC-HOA. It must not be negative, 0, or infinite. The source distance
+ * should not be too small relative to the control distance.
+ */
+
+ void init(const float w1) noexcept;
+ void adjust(const float w0) noexcept;
+
+ /* Near-field control filter for first-order ambisonic channels (1-3). */
+ void process1(float *RESTRICT dst, const float *RESTRICT src, const size_t count);
+
+ /* Near-field control filter for second-order ambisonic channels (4-8). */
+ void process2(float *RESTRICT dst, const float *RESTRICT src, const size_t count);
+
+ /* Near-field control filter for third-order ambisonic channels (9-15). */
+ void process3(float *RESTRICT dst, const float *RESTRICT src, const size_t count);
+
+ /* Near-field control filter for fourth-order ambisonic channels (16-24). */
+ void process4(float *RESTRICT dst, const float *RESTRICT src, const size_t count);
+};
+
+#endif /* FILTER_NFC_H */
diff --git a/alc/filters/splitter.cpp b/alc/filters/splitter.cpp
new file mode 100644
index 00000000..c6218e70
--- /dev/null
+++ b/alc/filters/splitter.cpp
@@ -0,0 +1,117 @@
+
+#include "config.h"
+
+#include "splitter.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include "math_defs.h"
+#include "opthelpers.h"
+
+
+template<typename Real>
+void BandSplitterR<Real>::init(Real f0norm)
+{
+ const Real w{f0norm * al::MathDefs<Real>::Tau()};
+ const Real cw{std::cos(w)};
+ if(cw > std::numeric_limits<float>::epsilon())
+ mCoeff = (std::sin(w) - 1.0f) / cw;
+ else
+ mCoeff = cw * -0.5f;
+
+ mLpZ1 = 0.0f;
+ mLpZ2 = 0.0f;
+ mApZ1 = 0.0f;
+}
+
+template<typename Real>
+void BandSplitterR<Real>::process(Real *hpout, Real *lpout, const Real *input, const size_t count)
+{
+ ASSUME(count > 0);
+
+ const Real ap_coeff{mCoeff};
+ const Real lp_coeff{mCoeff*0.5f + 0.5f};
+ Real lp_z1{mLpZ1};
+ Real lp_z2{mLpZ2};
+ Real ap_z1{mApZ1};
+ auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpout](const Real in) noexcept -> Real
+ {
+ /* Low-pass sample processing. */
+ Real d{(in - lp_z1) * lp_coeff};
+ Real lp_y{lp_z1 + d};
+ lp_z1 = lp_y + d;
+
+ d = (lp_y - lp_z2) * lp_coeff;
+ lp_y = lp_z2 + d;
+ lp_z2 = lp_y + d;
+
+ *(lpout++) = lp_y;
+
+ /* All-pass sample processing. */
+ Real ap_y{in*ap_coeff + ap_z1};
+ ap_z1 = in - ap_y*ap_coeff;
+
+ /* High-pass generated from removing low-passed output. */
+ return ap_y - lp_y;
+ };
+ std::transform(input, input+count, hpout, proc_sample);
+ mLpZ1 = lp_z1;
+ mLpZ2 = lp_z2;
+ mApZ1 = ap_z1;
+}
+
+template<typename Real>
+void BandSplitterR<Real>::applyHfScale(Real *samples, const Real hfscale, const size_t count)
+{
+ ASSUME(count > 0);
+
+ const Real ap_coeff{mCoeff};
+ const Real lp_coeff{mCoeff*0.5f + 0.5f};
+ Real lp_z1{mLpZ1};
+ Real lp_z2{mLpZ2};
+ Real ap_z1{mApZ1};
+ auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real
+ {
+ /* Low-pass sample processing. */
+ Real d{(in - lp_z1) * lp_coeff};
+ Real lp_y{lp_z1 + d};
+ lp_z1 = lp_y + d;
+
+ d = (lp_y - lp_z2) * lp_coeff;
+ lp_y = lp_z2 + d;
+ lp_z2 = lp_y + d;
+
+ /* All-pass sample processing. */
+ Real ap_y{in*ap_coeff + ap_z1};
+ ap_z1 = in - ap_y*ap_coeff;
+
+ /* High-pass generated from removing low-passed output. */
+ return (ap_y-lp_y)*hfscale + lp_y;
+ };
+ std::transform(samples, samples+count, samples, proc_sample);
+ mLpZ1 = lp_z1;
+ mLpZ2 = lp_z2;
+ mApZ1 = ap_z1;
+}
+
+template<typename Real>
+void BandSplitterR<Real>::applyAllpass(Real *samples, const size_t count) const
+{
+ ASSUME(count > 0);
+
+ const Real coeff{mCoeff};
+ Real z1{0.0f};
+ auto proc_sample = [coeff,&z1](const Real in) noexcept -> Real
+ {
+ const Real out{in*coeff + z1};
+ z1 = in - out*coeff;
+ return out;
+ };
+ std::transform(samples, samples+count, samples, proc_sample);
+}
+
+
+template class BandSplitterR<float>;
+template class BandSplitterR<double>;
diff --git a/alc/filters/splitter.h b/alc/filters/splitter.h
new file mode 100644
index 00000000..5117a244
--- /dev/null
+++ b/alc/filters/splitter.h
@@ -0,0 +1,34 @@
+#ifndef FILTER_SPLITTER_H
+#define FILTER_SPLITTER_H
+
+#include <cstddef>
+
+
+/* Band splitter. Splits a signal into two phase-matching frequency bands. */
+template<typename Real>
+class BandSplitterR {
+ Real mCoeff{0.0f};
+ Real mLpZ1{0.0f};
+ Real mLpZ2{0.0f};
+ Real mApZ1{0.0f};
+
+public:
+ BandSplitterR() = default;
+ BandSplitterR(const BandSplitterR&) = default;
+ BandSplitterR(Real f0norm) { init(f0norm); }
+
+ void init(Real f0norm);
+ void clear() noexcept { mLpZ1 = mLpZ2 = mApZ1 = 0.0f; }
+ void process(Real *hpout, Real *lpout, const Real *input, const size_t count);
+
+ void applyHfScale(Real *samples, const Real hfscale, const size_t count);
+
+ /* The all-pass portion of the band splitter. Applies the same phase shift
+ * without splitting the signal. Note that each use of this method is
+ * indepedent, it does not track history between calls.
+ */
+ void applyAllpass(Real *samples, const size_t count) const;
+};
+using BandSplitter = BandSplitterR<float>;
+
+#endif /* FILTER_SPLITTER_H */
diff --git a/alc/fpu_modes.h b/alc/fpu_modes.h
new file mode 100644
index 00000000..5465e9cf
--- /dev/null
+++ b/alc/fpu_modes.h
@@ -0,0 +1,25 @@
+#ifndef FPU_MODES_H
+#define FPU_MODES_H
+
+class FPUCtl {
+#if defined(HAVE_SSE_INTRINSICS) || (defined(__GNUC__) && defined(HAVE_SSE))
+ unsigned int sse_state{};
+#endif
+ bool in_mode{};
+
+public:
+ FPUCtl();
+ /* HACK: 32-bit targets for GCC seem to have a problem here with certain
+ * noexcept methods (which destructors are) causing an internal compiler
+ * error. No idea why it's these methods specifically, but this is needed
+ * to get it to compile.
+ */
+ ~FPUCtl() noexcept(false) { leave(); }
+
+ FPUCtl(const FPUCtl&) = delete;
+ FPUCtl& operator=(const FPUCtl&) = delete;
+
+ void leave();
+};
+
+#endif /* FPU_MODES_H */
diff --git a/alc/helpers.cpp b/alc/helpers.cpp
new file mode 100644
index 00000000..4ea94c7d
--- /dev/null
+++ b/alc/helpers.cpp
@@ -0,0 +1,649 @@
+/**
+ * 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
+ */
+
+#ifdef _WIN32
+#ifdef __MINGW32__
+#define _WIN32_IE 0x501
+#else
+#define _WIN32_IE 0x400
+#endif
+#endif
+
+#include "config.h"
+
+#include <algorithm>
+#include <cerrno>
+#include <cstdarg>
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <mutex>
+#include <string>
+
+#ifdef HAVE_DIRENT_H
+#include <dirent.h>
+#endif
+#ifdef HAVE_INTRIN_H
+#include <intrin.h>
+#endif
+#ifdef HAVE_CPUID_H
+#include <cpuid.h>
+#endif
+#ifdef HAVE_SSE_INTRINSICS
+#include <xmmintrin.h>
+#endif
+#ifdef HAVE_SYS_SYSCONF_H
+#include <sys/sysconf.h>
+#endif
+
+#ifdef HAVE_PROC_PIDPATH
+#include <libproc.h>
+#endif
+
+#ifdef __FreeBSD__
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#endif
+
+#ifndef _WIN32
+#include <unistd.h>
+#elif defined(_WIN32_IE)
+#include <shlobj.h>
+#endif
+
+#include "alcmain.h"
+#include "almalloc.h"
+#include "alfstream.h"
+#include "alspan.h"
+#include "alstring.h"
+#include "compat.h"
+#include "cpu_caps.h"
+#include "fpu_modes.h"
+#include "logging.h"
+#include "strutils.h"
+#include "vector.h"
+
+
+#if defined(HAVE_GCC_GET_CPUID) && (defined(__i386__) || defined(__x86_64__) || \
+ defined(_M_IX86) || defined(_M_X64))
+using reg_type = unsigned int;
+static inline void get_cpuid(unsigned int f, reg_type *regs)
+{ __get_cpuid(f, &regs[0], &regs[1], &regs[2], &regs[3]); }
+#define CAN_GET_CPUID
+#elif defined(HAVE_CPUID_INTRINSIC) && (defined(__i386__) || defined(__x86_64__) || \
+ defined(_M_IX86) || defined(_M_X64))
+using reg_type = int;
+static inline void get_cpuid(unsigned int f, reg_type *regs)
+{ (__cpuid)(regs, f); }
+#define CAN_GET_CPUID
+#endif
+
+int CPUCapFlags = 0;
+
+void FillCPUCaps(int capfilter)
+{
+ int caps = 0;
+
+/* FIXME: We really should get this for all available CPUs in case different
+ * CPUs have different caps (is that possible on one machine?). */
+#ifdef CAN_GET_CPUID
+ union {
+ reg_type regs[4];
+ char str[sizeof(reg_type[4])];
+ } cpuinf[3]{};
+
+ get_cpuid(0, cpuinf[0].regs);
+ if(cpuinf[0].regs[0] == 0)
+ ERR("Failed to get CPUID\n");
+ else
+ {
+ unsigned int maxfunc = cpuinf[0].regs[0];
+ unsigned int maxextfunc;
+
+ get_cpuid(0x80000000, cpuinf[0].regs);
+ maxextfunc = cpuinf[0].regs[0];
+
+ TRACE("Detected max CPUID function: 0x%x (ext. 0x%x)\n", maxfunc, maxextfunc);
+
+ TRACE("Vendor ID: \"%.4s%.4s%.4s\"\n", cpuinf[0].str+4, cpuinf[0].str+12, cpuinf[0].str+8);
+ if(maxextfunc >= 0x80000004)
+ {
+ get_cpuid(0x80000002, cpuinf[0].regs);
+ get_cpuid(0x80000003, cpuinf[1].regs);
+ get_cpuid(0x80000004, cpuinf[2].regs);
+ TRACE("Name: \"%.16s%.16s%.16s\"\n", cpuinf[0].str, cpuinf[1].str, cpuinf[2].str);
+ }
+
+ if(maxfunc >= 1)
+ {
+ get_cpuid(1, cpuinf[0].regs);
+ if((cpuinf[0].regs[3]&(1<<25)))
+ caps |= CPU_CAP_SSE;
+ if((caps&CPU_CAP_SSE) && (cpuinf[0].regs[3]&(1<<26)))
+ caps |= CPU_CAP_SSE2;
+ if((caps&CPU_CAP_SSE2) && (cpuinf[0].regs[2]&(1<<0)))
+ caps |= CPU_CAP_SSE3;
+ if((caps&CPU_CAP_SSE3) && (cpuinf[0].regs[2]&(1<<19)))
+ caps |= CPU_CAP_SSE4_1;
+ }
+ }
+#else
+ /* Assume support for whatever's supported if we can't check for it */
+#if defined(HAVE_SSE4_1)
+#warning "Assuming SSE 4.1 run-time support!"
+ caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1;
+#elif defined(HAVE_SSE3)
+#warning "Assuming SSE 3 run-time support!"
+ caps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3;
+#elif defined(HAVE_SSE2)
+#warning "Assuming SSE 2 run-time support!"
+ caps |= CPU_CAP_SSE | CPU_CAP_SSE2;
+#elif defined(HAVE_SSE)
+#warning "Assuming SSE run-time support!"
+ caps |= CPU_CAP_SSE;
+#endif
+#endif
+#ifdef HAVE_NEON
+ al::ifstream file{"/proc/cpuinfo"};
+ if(!file.is_open())
+ ERR("Failed to open /proc/cpuinfo, cannot check for NEON support\n");
+ else
+ {
+ std::string features;
+
+ auto getline = [](std::istream &f, std::string &output) -> bool
+ {
+ while(f.good() && f.peek() == '\n')
+ f.ignore();
+ return std::getline(f, output) && !output.empty();
+
+ };
+ while(getline(file, features))
+ {
+ if(features.compare(0, 10, "Features\t:", 10) == 0)
+ break;
+ }
+ file.close();
+
+ size_t extpos{9};
+ while((extpos=features.find("neon", extpos+1)) != std::string::npos)
+ {
+ if((extpos == 0 || std::isspace(features[extpos-1])) &&
+ (extpos+4 == features.length() || std::isspace(features[extpos+4])))
+ {
+ caps |= CPU_CAP_NEON;
+ break;
+ }
+ }
+ }
+#endif
+
+ 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;
+}
+
+
+FPUCtl::FPUCtl()
+{
+#if defined(HAVE_SSE_INTRINSICS)
+ this->sse_state = _mm_getcsr();
+ unsigned int sseState = this->sse_state;
+ sseState |= 0x8000; /* set flush-to-zero */
+ sseState |= 0x0040; /* set denormals-are-zero */
+ _mm_setcsr(sseState);
+
+#elif defined(__GNUC__) && defined(HAVE_SSE)
+
+ if((CPUCapFlags&CPU_CAP_SSE))
+ {
+ __asm__ __volatile__("stmxcsr %0" : "=m" (*&this->sse_state));
+ unsigned int sseState = this->sse_state;
+ sseState |= 0x8000; /* set flush-to-zero */
+ if((CPUCapFlags&CPU_CAP_SSE2))
+ sseState |= 0x0040; /* set denormals-are-zero */
+ __asm__ __volatile__("ldmxcsr %0" : : "m" (*&sseState));
+ }
+#endif
+
+ this->in_mode = true;
+}
+
+void FPUCtl::leave()
+{
+ if(!this->in_mode) return;
+
+#if defined(HAVE_SSE_INTRINSICS)
+ _mm_setcsr(this->sse_state);
+
+#elif defined(__GNUC__) && defined(HAVE_SSE)
+
+ if((CPUCapFlags&CPU_CAP_SSE))
+ __asm__ __volatile__("ldmxcsr %0" : : "m" (*&this->sse_state));
+#endif
+ this->in_mode = false;
+}
+
+
+#ifdef _WIN32
+
+const PathNamePair &GetProcBinary()
+{
+ static PathNamePair ret;
+ if(!ret.fname.empty() || !ret.path.empty())
+ return ret;
+
+ al::vector<WCHAR> fullpath(256);
+ DWORD len;
+ while((len=GetModuleFileNameW(nullptr, fullpath.data(), static_cast<DWORD>(fullpath.size()))) == fullpath.size())
+ fullpath.resize(fullpath.size() << 1);
+ if(len == 0)
+ {
+ ERR("Failed to get process name: error %lu\n", GetLastError());
+ return ret;
+ }
+
+ fullpath.resize(len);
+ if(fullpath.back() != 0)
+ fullpath.push_back(0);
+
+ auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\');
+ sep = std::find(fullpath.rbegin()+1, sep, '/');
+ if(sep != fullpath.rend())
+ {
+ *sep = 0;
+ ret.fname = wstr_to_utf8(&*sep + 1);
+ ret.path = wstr_to_utf8(fullpath.data());
+ }
+ else
+ ret.fname = wstr_to_utf8(fullpath.data());
+
+ TRACE("Got binary: %s, %s\n", ret.path.c_str(), ret.fname.c_str());
+ return ret;
+}
+
+
+void al_print(FILE *logfile, const char *fmt, ...)
+{
+ al::vector<char> dynmsg;
+ char stcmsg[256];
+ char *str{stcmsg};
+
+ va_list args, args2;
+ va_start(args, fmt);
+ va_copy(args2, args);
+ int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)};
+ if UNLIKELY(msglen >= 0 && static_cast<size_t>(msglen) >= sizeof(stcmsg))
+ {
+ dynmsg.resize(static_cast<size_t>(msglen) + 1u);
+ str = dynmsg.data();
+ msglen = std::vsnprintf(str, dynmsg.size(), fmt, args2);
+ }
+ va_end(args2);
+ va_end(args);
+
+ std::wstring wstr{utf8_to_wstr(str)};
+ fputws(wstr.c_str(), logfile);
+ fflush(logfile);
+}
+
+
+static inline int is_slash(int c)
+{ return (c == '\\' || c == '/'); }
+
+static void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results)
+{
+ std::string pathstr{path};
+ pathstr += "\\*";
+ pathstr += ext;
+ TRACE("Searching %s\n", pathstr.c_str());
+
+ std::wstring wpath{utf8_to_wstr(pathstr.c_str())};
+ WIN32_FIND_DATAW fdata;
+ HANDLE hdl{FindFirstFileW(wpath.c_str(), &fdata)};
+ if(hdl == INVALID_HANDLE_VALUE) return;
+
+ const auto base = results->size();
+
+ do {
+ results->emplace_back();
+ std::string &str = results->back();
+ str = path;
+ str += '\\';
+ str += wstr_to_utf8(fdata.cFileName);
+ } while(FindNextFileW(hdl, &fdata));
+ FindClose(hdl);
+
+ const al::span<std::string> newlist{results->data()+base, results->size()-base};
+ std::sort(newlist.begin(), newlist.end());
+ for(const auto &name : newlist)
+ TRACE(" got %s\n", name.c_str());
+}
+
+al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
+{
+ static std::mutex search_lock;
+ std::lock_guard<std::mutex> _{search_lock};
+
+ /* If the path is absolute, use it directly. */
+ al::vector<std::string> results;
+ if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2]))
+ {
+ std::string path{subdir};
+ std::replace(path.begin(), path.end(), '/', '\\');
+ DirectorySearch(path.c_str(), ext, &results);
+ return results;
+ }
+ if(subdir[0] == '\\' && subdir[1] == '\\' && subdir[2] == '?' && subdir[3] == '\\')
+ {
+ DirectorySearch(subdir, ext, &results);
+ return results;
+ }
+
+ std::string path;
+
+ /* Search the app-local directory. */
+ if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH"))
+ {
+ path = wstr_to_utf8(localpath->c_str());
+ if(is_slash(path.back()))
+ path.pop_back();
+ }
+ else if(WCHAR *cwdbuf{_wgetcwd(nullptr, 0)})
+ {
+ path = wstr_to_utf8(cwdbuf);
+ if(is_slash(path.back()))
+ path.pop_back();
+ free(cwdbuf);
+ }
+ else
+ path = ".";
+ std::replace(path.begin(), path.end(), '/', '\\');
+ DirectorySearch(path.c_str(), ext, &results);
+
+ /* Search the local and global data dirs. */
+ static const int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA };
+ for(int id : ids)
+ {
+ WCHAR buffer[MAX_PATH];
+ if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE)
+ continue;
+
+ path = wstr_to_utf8(buffer);
+ if(!is_slash(path.back()))
+ path += '\\';
+ path += subdir;
+ std::replace(path.begin(), path.end(), '/', '\\');
+
+ DirectorySearch(path.c_str(), ext, &results);
+ }
+
+ return results;
+}
+
+void SetRTPriority(void)
+{
+ bool failed = false;
+ if(RTPrioLevel > 0)
+ failed = !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
+ if(failed) ERR("Failed to set priority level for thread\n");
+}
+
+#else
+
+#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
+#include <pthread.h>
+#include <sched.h>
+#endif
+
+const PathNamePair &GetProcBinary()
+{
+ static PathNamePair ret;
+ if(!ret.fname.empty() || !ret.path.empty())
+ return ret;
+
+ al::vector<char> pathname;
+#ifdef __FreeBSD__
+ size_t pathlen;
+ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
+ if(sysctl(mib, 4, nullptr, &pathlen, nullptr, 0) == -1)
+ WARN("Failed to sysctl kern.proc.pathname: %s\n", strerror(errno));
+ else
+ {
+ pathname.resize(pathlen + 1);
+ sysctl(mib, 4, pathname.data(), &pathlen, nullptr, 0);
+ pathname.resize(pathlen);
+ }
+#endif
+#ifdef HAVE_PROC_PIDPATH
+ if(pathname.empty())
+ {
+ char procpath[PROC_PIDPATHINFO_MAXSIZE]{};
+ const pid_t pid{getpid()};
+ if(proc_pidpath(pid, procpath, sizeof(procpath)) < 1)
+ ERR("proc_pidpath(%d, ...) failed: %s\n", pid, strerror(errno));
+ else
+ pathname.insert(pathname.end(), procpath, procpath+strlen(procpath));
+ }
+#endif
+ if(pathname.empty())
+ {
+ pathname.resize(256);
+
+ const char *selfname{"/proc/self/exe"};
+ ssize_t len{readlink(selfname, pathname.data(), pathname.size())};
+ if(len == -1 && errno == ENOENT)
+ {
+ selfname = "/proc/self/file";
+ len = readlink(selfname, pathname.data(), pathname.size());
+ }
+ if(len == -1 && errno == ENOENT)
+ {
+ selfname = "/proc/curproc/exe";
+ len = readlink(selfname, pathname.data(), pathname.size());
+ }
+ if(len == -1 && errno == ENOENT)
+ {
+ selfname = "/proc/curproc/file";
+ len = readlink(selfname, pathname.data(), pathname.size());
+ }
+
+ while(len > 0 && static_cast<size_t>(len) == pathname.size())
+ {
+ pathname.resize(pathname.size() << 1);
+ len = readlink(selfname, pathname.data(), pathname.size());
+ }
+ if(len <= 0)
+ {
+ WARN("Failed to readlink %s: %s\n", selfname, strerror(errno));
+ return ret;
+ }
+
+ pathname.resize(static_cast<size_t>(len));
+ }
+ while(!pathname.empty() && pathname.back() == 0)
+ pathname.pop_back();
+
+ auto sep = std::find(pathname.crbegin(), pathname.crend(), '/');
+ if(sep != pathname.crend())
+ {
+ ret.path = std::string(pathname.cbegin(), sep.base()-1);
+ ret.fname = std::string(sep.base(), pathname.cend());
+ }
+ else
+ ret.fname = std::string(pathname.cbegin(), pathname.cend());
+
+ TRACE("Got binary: %s, %s\n", ret.path.c_str(), ret.fname.c_str());
+ return ret;
+}
+
+
+void al_print(FILE *logfile, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(logfile, fmt, ap);
+ va_end(ap);
+
+ fflush(logfile);
+}
+
+
+static void DirectorySearch(const char *path, const char *ext, al::vector<std::string> *const results)
+{
+ TRACE("Searching %s for *%s\n", path, ext);
+ DIR *dir{opendir(path)};
+ if(!dir) return;
+
+ const auto base = results->size();
+ const size_t extlen{strlen(ext)};
+
+ struct dirent *dirent;
+ while((dirent=readdir(dir)) != nullptr)
+ {
+ if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0)
+ continue;
+
+ const size_t len{strlen(dirent->d_name)};
+ if(len <= extlen) continue;
+ if(al::strcasecmp(dirent->d_name+len-extlen, ext) != 0)
+ continue;
+
+ results->emplace_back();
+ std::string &str = results->back();
+ str = path;
+ if(str.back() != '/')
+ str.push_back('/');
+ str += dirent->d_name;
+ }
+ closedir(dir);
+
+ const al::span<std::string> newlist{results->data()+base, results->size()-base};
+ std::sort(newlist.begin(), newlist.end());
+ for(const auto &name : newlist)
+ TRACE(" got %s\n", name.c_str());
+}
+
+al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
+{
+ static std::mutex search_lock;
+ std::lock_guard<std::mutex> _{search_lock};
+
+ al::vector<std::string> results;
+ if(subdir[0] == '/')
+ {
+ DirectorySearch(subdir, ext, &results);
+ return results;
+ }
+
+ /* Search the app-local directory. */
+ if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH"))
+ DirectorySearch(localpath->c_str(), ext, &results);
+ else
+ {
+ al::vector<char> cwdbuf(256);
+ while(!getcwd(cwdbuf.data(), cwdbuf.size()))
+ {
+ if(errno != ERANGE)
+ {
+ cwdbuf.clear();
+ break;
+ }
+ cwdbuf.resize(cwdbuf.size() << 1);
+ }
+ if(cwdbuf.empty())
+ DirectorySearch(".", ext, &results);
+ else
+ {
+ DirectorySearch(cwdbuf.data(), ext, &results);
+ cwdbuf.clear();
+ }
+ }
+
+ // Search local data dir
+ if(auto datapath = al::getenv("XDG_DATA_HOME"))
+ {
+ std::string &path = *datapath;
+ if(path.back() != '/')
+ path += '/';
+ path += subdir;
+ DirectorySearch(path.c_str(), ext, &results);
+ }
+ else if(auto homepath = al::getenv("HOME"))
+ {
+ std::string &path = *homepath;
+ if(path.back() == '/')
+ path.pop_back();
+ path += "/.local/share/";
+ path += subdir;
+ DirectorySearch(path.c_str(), ext, &results);
+ }
+
+ // Search global data dirs
+ std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")};
+
+ size_t curpos{0u};
+ while(curpos < datadirs.size())
+ {
+ size_t nextpos{datadirs.find(':', curpos)};
+
+ std::string path{(nextpos != std::string::npos) ?
+ datadirs.substr(curpos, nextpos++ - curpos) : datadirs.substr(curpos)};
+ curpos = nextpos;
+
+ if(path.empty()) continue;
+ if(path.back() != '/')
+ path += '/';
+ path += subdir;
+
+ DirectorySearch(path.c_str(), ext, &results);
+ }
+
+ return results;
+}
+
+void SetRTPriority()
+{
+ bool failed = false;
+#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
+ if(RTPrioLevel > 0)
+ {
+ struct sched_param param;
+ /* Use the minimum real-time priority possible for now (on Linux this
+ * should be 1 for SCHED_RR) */
+ param.sched_priority = sched_get_priority_min(SCHED_RR);
+ failed = !!pthread_setschedparam(pthread_self(), SCHED_RR, &param);
+ }
+#else
+ /* Real-time priority not available */
+ failed = (RTPrioLevel>0);
+#endif
+ if(failed)
+ ERR("Failed to set priority level for thread\n");
+}
+
+#endif
diff --git a/alc/hrtf.cpp b/alc/hrtf.cpp
new file mode 100644
index 00000000..8e416cf1
--- /dev/null
+++ b/alc/hrtf.cpp
@@ -0,0 +1,1424 @@
+/**
+ * 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 "hrtf.h"
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cctype>
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <functional>
+#include <fstream>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <new>
+#include <numeric>
+#include <type_traits>
+#include <utility>
+
+#include "AL/al.h"
+
+#include "alcmain.h"
+#include "alconfig.h"
+#include "alfstream.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alspan.h"
+#include "filters/splitter.h"
+#include "logging.h"
+#include "math_defs.h"
+#include "opthelpers.h"
+
+
+struct HrtfHandle {
+ std::unique_ptr<HrtfEntry> mEntry;
+ al::FlexArray<char> mFilename;
+
+ HrtfHandle(size_t fname_len) : mFilename{fname_len} { }
+
+ static std::unique_ptr<HrtfHandle> Create(size_t fname_len)
+ { return std::unique_ptr<HrtfHandle>{new (FamCount{fname_len}) HrtfHandle{fname_len}}; }
+
+ DEF_FAM_NEWDEL(HrtfHandle, mFilename)
+};
+
+namespace {
+
+using namespace std::placeholders;
+
+using HrtfHandlePtr = std::unique_ptr<HrtfHandle>;
+
+/* Data set limits must be the same as or more flexible than those defined in
+ * the makemhr utility.
+ */
+#define MIN_IR_SIZE (8)
+#define MAX_IR_SIZE (512)
+#define MOD_IR_SIZE (2)
+
+#define MIN_FD_COUNT (1)
+#define MAX_FD_COUNT (16)
+
+#define MIN_FD_DISTANCE (50)
+#define MAX_FD_DISTANCE (2500)
+
+#define MIN_EV_COUNT (5)
+#define MAX_EV_COUNT (181)
+
+#define MIN_AZ_COUNT (1)
+#define MAX_AZ_COUNT (255)
+
+#define MAX_HRIR_DELAY (HRTF_HISTORY_LENGTH-1)
+
+constexpr ALchar magicMarker00[8]{'M','i','n','P','H','R','0','0'};
+constexpr ALchar magicMarker01[8]{'M','i','n','P','H','R','0','1'};
+constexpr ALchar magicMarker02[8]{'M','i','n','P','H','R','0','2'};
+
+/* First value for pass-through coefficients (remaining are 0), used for omni-
+ * directional sounds. */
+constexpr ALfloat PassthruCoeff{0.707106781187f/*sqrt(0.5)*/};
+
+std::mutex LoadedHrtfLock;
+al::vector<HrtfHandlePtr> LoadedHrtfs;
+
+
+class databuf final : public std::streambuf {
+ int_type underflow() override
+ { return traits_type::eof(); }
+
+ pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override
+ {
+ if((mode&std::ios_base::out) || !(mode&std::ios_base::in))
+ return traits_type::eof();
+
+ char_type *cur;
+ switch(whence)
+ {
+ case std::ios_base::beg:
+ if(offset < 0 || offset > egptr()-eback())
+ return traits_type::eof();
+ cur = eback() + offset;
+ break;
+
+ case std::ios_base::cur:
+ if((offset >= 0 && offset > egptr()-gptr()) ||
+ (offset < 0 && -offset > gptr()-eback()))
+ return traits_type::eof();
+ cur = gptr() + offset;
+ break;
+
+ case std::ios_base::end:
+ if(offset > 0 || -offset > egptr()-eback())
+ return traits_type::eof();
+ cur = egptr() + offset;
+ break;
+
+ default:
+ return traits_type::eof();
+ }
+
+ setg(eback(), cur, egptr());
+ return cur - eback();
+ }
+
+ pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override
+ {
+ // Simplified version of seekoff
+ if((mode&std::ios_base::out) || !(mode&std::ios_base::in))
+ return traits_type::eof();
+
+ if(pos < 0 || pos > egptr()-eback())
+ return traits_type::eof();
+
+ setg(eback(), eback() + static_cast<size_t>(pos), egptr());
+ return pos;
+ }
+
+public:
+ databuf(const char_type *start_, const char_type *end_) noexcept
+ {
+ setg(const_cast<char_type*>(start_), const_cast<char_type*>(start_),
+ const_cast<char_type*>(end_));
+ }
+};
+
+class idstream final : public std::istream {
+ databuf mStreamBuf;
+
+public:
+ idstream(const char *start_, const char *end_)
+ : std::istream{nullptr}, mStreamBuf{start_, end_}
+ { init(&mStreamBuf); }
+};
+
+
+struct IdxBlend { ALsizei idx; ALfloat blend; };
+/* Calculate the elevation index given the polar elevation in radians. This
+ * will return an index between 0 and (evcount - 1).
+ */
+IdxBlend CalcEvIndex(ALsizei evcount, ALfloat ev)
+{
+ ev = (al::MathDefs<float>::Pi()*0.5f + ev) * static_cast<float>(evcount-1) /
+ al::MathDefs<float>::Pi();
+ ALsizei idx{float2int(ev)};
+
+ return IdxBlend{mini(idx, evcount-1), ev-static_cast<float>(idx)};
+}
+
+/* Calculate the azimuth index given the polar azimuth in radians. This will
+ * return an index between 0 and (azcount - 1).
+ */
+IdxBlend CalcAzIndex(ALsizei azcount, ALfloat az)
+{
+ az = (al::MathDefs<float>::Tau()+az) * static_cast<float>(azcount) /
+ al::MathDefs<float>::Tau();
+ ALsizei idx{float2int(az)};
+
+ return IdxBlend{idx%azcount, az-static_cast<float>(idx)};
+}
+
+} // namespace
+
+
+/* Calculates static HRIR coefficients and delays for the given polar elevation
+ * and azimuth in radians. The coefficients are normalized.
+ */
+void GetHrtfCoeffs(const HrtfEntry *Hrtf, ALfloat elevation, ALfloat azimuth, ALfloat distance,
+ ALfloat spread, HrirArray &coeffs, ALsizei (&delays)[2])
+{
+ const ALfloat dirfact{1.0f - (spread / al::MathDefs<float>::Tau())};
+
+ const auto *field = Hrtf->field;
+ const auto *field_end = field + Hrtf->fdCount-1;
+ ALsizei ebase{0};
+ while(distance < field->distance && field != field_end)
+ {
+ ebase += field->evCount;
+ ++field;
+ }
+
+ /* Claculate the elevation indinces. */
+ const auto elev0 = CalcEvIndex(field->evCount, elevation);
+ const ALsizei elev1_idx{mini(elev0.idx+1, field->evCount-1)};
+ const ALsizei ir0offset{Hrtf->elev[ebase + elev0.idx].irOffset};
+ const ALsizei ir1offset{Hrtf->elev[ebase + elev1_idx].irOffset};
+
+ /* Calculate azimuth indices. */
+ const auto az0 = CalcAzIndex(Hrtf->elev[ebase + elev0.idx].azCount, azimuth);
+ const auto az1 = CalcAzIndex(Hrtf->elev[ebase + elev1_idx].azCount, azimuth);
+
+ /* Calculate the HRIR indices to blend. */
+ ALsizei idx[4]{
+ ir0offset + az0.idx,
+ ir0offset + ((az0.idx+1) % Hrtf->elev[ebase + elev0.idx].azCount),
+ ir1offset + az1.idx,
+ ir1offset + ((az1.idx+1) % Hrtf->elev[ebase + elev1_idx].azCount)
+ };
+
+ /* Calculate bilinear blending weights, attenuated according to the
+ * directional panning factor.
+ */
+ const ALfloat blend[4]{
+ (1.0f-elev0.blend) * (1.0f-az0.blend) * dirfact,
+ (1.0f-elev0.blend) * ( az0.blend) * dirfact,
+ ( elev0.blend) * (1.0f-az1.blend) * dirfact,
+ ( elev0.blend) * ( az1.blend) * dirfact
+ };
+
+ /* Calculate the blended HRIR delays. */
+ delays[0] = fastf2i(
+ Hrtf->delays[idx[0]][0]*blend[0] + Hrtf->delays[idx[1]][0]*blend[1] +
+ Hrtf->delays[idx[2]][0]*blend[2] + Hrtf->delays[idx[3]][0]*blend[3]
+ );
+ delays[1] = fastf2i(
+ Hrtf->delays[idx[0]][1]*blend[0] + Hrtf->delays[idx[1]][1]*blend[1] +
+ Hrtf->delays[idx[2]][1]*blend[2] + Hrtf->delays[idx[3]][1]*blend[3]
+ );
+
+ const ALuint irSize{Hrtf->irSize};
+ ASSUME(irSize >= MIN_IR_SIZE);
+
+ /* Calculate the sample offsets for the HRIR indices. */
+ idx[0] *= HRIR_LENGTH;
+ idx[1] *= HRIR_LENGTH;
+ idx[2] *= HRIR_LENGTH;
+ idx[3] *= HRIR_LENGTH;
+
+ /* Calculate the blended HRIR coefficients. */
+ ALfloat *coeffout{al::assume_aligned<16>(&coeffs[0][0])};
+ coeffout[0] = PassthruCoeff * (1.0f-dirfact);
+ coeffout[1] = PassthruCoeff * (1.0f-dirfact);
+ std::fill(coeffout+2, coeffout + HRIR_LENGTH*2, 0.0f);
+ for(ALsizei c{0};c < 4;c++)
+ {
+ const ALfloat *srccoeffs{al::assume_aligned<16>(Hrtf->coeffs[idx[c]])};
+ const ALfloat mult{blend[c]};
+ auto blend_coeffs = [mult](const ALfloat src, const ALfloat coeff) noexcept -> ALfloat
+ { return src*mult + coeff; };
+ std::transform(srccoeffs, srccoeffs + irSize*2, coeffout, coeffout, blend_coeffs);
+ }
+}
+
+
+std::unique_ptr<DirectHrtfState> DirectHrtfState::Create(size_t num_chans)
+{
+ return std::unique_ptr<DirectHrtfState>{new (FamCount{num_chans}) DirectHrtfState{num_chans}};
+}
+
+void BuildBFormatHrtf(const HrtfEntry *Hrtf, DirectHrtfState *state,
+ const al::span<const AngularPoint> AmbiPoints, const ALfloat (*AmbiMatrix)[MAX_AMBI_CHANNELS],
+ const ALfloat *AmbiOrderHFGain)
+{
+ using double2 = std::array<double,2>;
+ struct ImpulseResponse {
+ alignas(16) std::array<double2,HRIR_LENGTH> hrir;
+ ALuint ldelay, rdelay;
+ };
+
+ static const int OrderFromChan[MAX_AMBI_CHANNELS]{
+ 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3,
+ };
+ /* Set this to true for dual-band HRTF processing. May require better
+ * calculation of the new IR length to deal with the head and tail
+ * generated by the HF scaling.
+ */
+ static constexpr bool DualBand{true};
+
+ ALuint min_delay{HRTF_HISTORY_LENGTH};
+ ALuint max_delay{0};
+ al::vector<ImpulseResponse> impres; impres.reserve(AmbiPoints.size());
+ auto calc_res = [Hrtf,&max_delay,&min_delay](const AngularPoint &pt) -> ImpulseResponse
+ {
+ ImpulseResponse res;
+
+ auto &field = Hrtf->field[0];
+
+ /* Calculate the elevation indices. */
+ const auto elev0 = CalcEvIndex(field.evCount, pt.Elev.value);
+ const ALsizei elev1_idx{mini(elev0.idx+1, field.evCount-1)};
+ const ALsizei ir0offset{Hrtf->elev[elev0.idx].irOffset};
+ const ALsizei ir1offset{Hrtf->elev[elev1_idx].irOffset};
+
+ /* Calculate azimuth indices. */
+ const auto az0 = CalcAzIndex(Hrtf->elev[elev0.idx].azCount, pt.Azim.value);
+ const auto az1 = CalcAzIndex(Hrtf->elev[elev1_idx].azCount, pt.Azim.value);
+
+ /* Calculate the HRIR indices to blend. */
+ const ALuint idx[4]{
+ static_cast<ALuint>(ir0offset + az0.idx),
+ static_cast<ALuint>(ir0offset + ((az0.idx+1) % Hrtf->elev[elev0.idx].azCount)),
+ static_cast<ALuint>(ir1offset + az1.idx),
+ static_cast<ALuint>(ir1offset + ((az1.idx+1) % Hrtf->elev[elev1_idx].azCount))};
+
+ /* Calculate bilinear blending weights. */
+ const ALfloat blend[4]{
+ (1.0f-elev0.blend) * (1.0f-az0.blend),
+ (1.0f-elev0.blend) * ( az0.blend),
+ ( elev0.blend) * (1.0f-az1.blend),
+ ( elev0.blend) * ( az1.blend)};
+
+ /* Calculate the blended HRIR delays. */
+ res.ldelay = fastf2u(
+ Hrtf->delays[idx[0]][0]*blend[0] + Hrtf->delays[idx[1]][0]*blend[1] +
+ Hrtf->delays[idx[2]][0]*blend[2] + Hrtf->delays[idx[3]][0]*blend[3]);
+ res.rdelay = fastf2u(
+ Hrtf->delays[idx[0]][1]*blend[0] + Hrtf->delays[idx[1]][1]*blend[1] +
+ Hrtf->delays[idx[2]][1]*blend[2] + Hrtf->delays[idx[3]][1]*blend[3]);
+
+ const size_t irSize{Hrtf->irSize};
+ ASSUME(irSize >= MIN_IR_SIZE);
+
+ /* Calculate the blended HRIR coefficients. */
+ double *coeffout{al::assume_aligned<16>(&res.hrir[0][0])};
+ std::fill(coeffout, coeffout + HRIR_LENGTH*2, 0.0);
+ for(ALsizei c{0};c < 4;c++)
+ {
+ const ALfloat *srccoeffs{al::assume_aligned<16>(Hrtf->coeffs[idx[c]*HRIR_LENGTH])};
+ const ALfloat mult{blend[c]};
+ auto blend_coeffs = [mult](const float src, const double coeff) noexcept -> double
+ { return src*mult + coeff; };
+ std::transform(srccoeffs, srccoeffs + HRIR_LENGTH*2, coeffout, coeffout, blend_coeffs);
+ }
+
+ min_delay = minu(min_delay, minu(res.ldelay, res.rdelay));
+ max_delay = maxu(max_delay, maxu(res.ldelay, res.rdelay));
+
+ return res;
+ };
+ std::transform(AmbiPoints.begin(), AmbiPoints.end(), std::back_inserter(impres), calc_res);
+
+ /* For dual-band processing, add a 16-sample delay to compensate for the HF
+ * scale on the minimum-phase response.
+ */
+ static constexpr ALsizei base_delay{DualBand ? 16 : 0};
+ const ALdouble xover_norm{400.0 / Hrtf->sampleRate};
+ BandSplitterR<double> splitter{xover_norm};
+
+ auto tmpres = al::vector<std::array<double2,HRIR_LENGTH>>(state->Coeffs.size());
+ auto tmpflt = al::vector<std::array<double,HRIR_LENGTH*4>>(3);
+ for(size_t c{0u};c < AmbiPoints.size();++c)
+ {
+ const al::span<const double2,HRIR_LENGTH> hrir{impres[c].hrir};
+ const ALuint ldelay{impres[c].ldelay - min_delay + base_delay};
+ const ALuint rdelay{impres[c].rdelay - min_delay + base_delay};
+
+ if /*constexpr*/(!DualBand)
+ {
+ /* For single-band decoding, apply the HF scale to the response. */
+ for(size_t i{0u};i < state->Coeffs.size();++i)
+ {
+ const double mult{double{AmbiOrderHFGain[OrderFromChan[i]]} * AmbiMatrix[c][i]};
+ const ALuint numirs{HRIR_LENGTH - maxu(ldelay, rdelay)};
+ ALuint lidx{ldelay}, ridx{rdelay};
+ for(ALuint j{0};j < numirs;++j)
+ {
+ tmpres[i][lidx++][0] += hrir[j][0] * mult;
+ tmpres[i][ridx++][1] += hrir[j][1] * mult;
+ }
+ }
+ continue;
+ }
+
+ /* For dual-band processing, the HRIR needs to be split into low and
+ * high frequency responses. The band-splitter alone creates frequency-
+ * dependent phase-shifts, which is not ideal. To counteract it,
+ * combine it with a backwards phase-shift.
+ */
+
+ /* Load the (left) HRIR backwards, into a temp buffer with padding. */
+ std::fill(tmpflt[2].begin(), tmpflt[2].end(), 0.0);
+ std::transform(hrir.cbegin(), hrir.cend(), tmpflt[2].rbegin() + HRIR_LENGTH*3,
+ [](const double2 &ir) noexcept -> double { return ir[0]; });
+
+ /* Apply the all-pass on the reversed signal and reverse the resulting
+ * sample array. This produces the forward response with a backwards
+ * phase-shift (+n degrees becomes -n degrees).
+ */
+ splitter.applyAllpass(tmpflt[2].data(), tmpflt[2].size());
+ std::reverse(tmpflt[2].begin(), tmpflt[2].end());
+
+ /* Now apply the band-splitter. This applies the normal phase-shift,
+ * which cancels out with the backwards phase-shift to get the original
+ * phase on the split signal.
+ */
+ splitter.clear();
+ splitter.process(tmpflt[0].data(), tmpflt[1].data(), tmpflt[2].data(), tmpflt[2].size());
+
+ /* Apply left ear response with delay and HF scale. */
+ for(size_t i{0u};i < state->Coeffs.size();++i)
+ {
+ const ALdouble mult{AmbiMatrix[c][i]};
+ const ALdouble hfgain{AmbiOrderHFGain[OrderFromChan[i]]};
+ ALuint j{HRIR_LENGTH*3 - ldelay};
+ for(ALuint lidx{0};lidx < HRIR_LENGTH;++lidx,++j)
+ tmpres[i][lidx][0] += (tmpflt[0][j]*hfgain + tmpflt[1][j]) * mult;
+ }
+
+ /* Now run the same process on the right HRIR. */
+ std::fill(tmpflt[2].begin(), tmpflt[2].end(), 0.0);
+ std::transform(hrir.cbegin(), hrir.cend(), tmpflt[2].rbegin() + HRIR_LENGTH*3,
+ [](const double2 &ir) noexcept -> double { return ir[1]; });
+
+ splitter.applyAllpass(tmpflt[2].data(), tmpflt[2].size());
+ std::reverse(tmpflt[2].begin(), tmpflt[2].end());
+
+ splitter.clear();
+ splitter.process(tmpflt[0].data(), tmpflt[1].data(), tmpflt[2].data(), tmpflt[2].size());
+
+ for(size_t i{0u};i < state->Coeffs.size();++i)
+ {
+ const ALdouble mult{AmbiMatrix[c][i]};
+ const ALdouble hfgain{AmbiOrderHFGain[OrderFromChan[i]]};
+ ALuint j{HRIR_LENGTH*3 - rdelay};
+ for(ALuint ridx{0};ridx < HRIR_LENGTH;++ridx,++j)
+ tmpres[i][ridx][1] += (tmpflt[0][j]*hfgain + tmpflt[1][j]) * mult;
+ }
+ }
+ tmpflt.clear();
+ impres.clear();
+
+ for(size_t i{0u};i < state->Coeffs.size();++i)
+ {
+ auto copy_arr = [](const double2 &in) noexcept -> float2
+ { return float2{{static_cast<float>(in[0]), static_cast<float>(in[1])}}; };
+ std::transform(tmpres[i].cbegin(), tmpres[i].cend(), state->Coeffs[i].begin(),
+ copy_arr);
+ }
+ tmpres.clear();
+
+ ALuint max_length{HRIR_LENGTH};
+ /* Increase the IR size by double the base delay with dual-band processing
+ * to account for the head and tail from the HF response scale.
+ */
+ const ALuint irsize{minu(Hrtf->irSize + base_delay*2, max_length)};
+ max_length = minu(max_delay-min_delay + irsize, max_length);
+
+ /* Round up to the next IR size multiple. */
+ max_length += MOD_IR_SIZE-1;
+ max_length -= max_length%MOD_IR_SIZE;
+
+ TRACE("Skipped delay: %u, max delay: %u, new FIR length: %u\n", min_delay, max_delay-min_delay,
+ max_length);
+ state->IrSize = max_length;
+}
+
+
+namespace {
+
+std::unique_ptr<HrtfEntry> CreateHrtfStore(ALuint rate, ALushort irSize, const ALuint fdCount,
+ const ALubyte *evCount, const ALushort *distance, const ALushort *azCount,
+ const ALushort *irOffset, ALushort irCount, const ALfloat (*coeffs)[2],
+ const ALubyte (*delays)[2], const char *filename)
+{
+ std::unique_ptr<HrtfEntry> Hrtf;
+
+ ALuint evTotal{std::accumulate(evCount, evCount+fdCount, 0u)};
+ size_t total{sizeof(HrtfEntry)};
+ total = RoundUp(total, alignof(HrtfEntry::Field)); /* Align for field infos */
+ total += sizeof(HrtfEntry::Field)*fdCount;
+ total = RoundUp(total, alignof(HrtfEntry::Elevation)); /* Align for elevation infos */
+ total += sizeof(Hrtf->elev[0])*evTotal;
+ total = RoundUp(total, 16); /* Align for coefficients using SIMD */
+ total += sizeof(Hrtf->coeffs[0])*HRIR_LENGTH*irCount;
+ total += sizeof(Hrtf->delays[0])*irCount;
+
+ Hrtf.reset(new (al_calloc(16, total)) HrtfEntry{});
+ if(!Hrtf)
+ ERR("Out of memory allocating storage for %s.\n", filename);
+ else
+ {
+ InitRef(Hrtf->mRef, 1u);
+ Hrtf->sampleRate = rate;
+ Hrtf->irSize = irSize;
+ Hrtf->fdCount = fdCount;
+
+ /* Set up pointers to storage following the main HRTF struct. */
+ char *base = reinterpret_cast<char*>(Hrtf.get());
+ uintptr_t offset = sizeof(HrtfEntry);
+
+ offset = RoundUp(offset, alignof(HrtfEntry::Field)); /* Align for field infos */
+ auto field_ = reinterpret_cast<HrtfEntry::Field*>(base + offset);
+ offset += sizeof(field_[0])*fdCount;
+
+ offset = RoundUp(offset, alignof(HrtfEntry::Elevation)); /* Align for elevation infos */
+ auto elev_ = reinterpret_cast<HrtfEntry::Elevation*>(base + offset);
+ offset += sizeof(elev_[0])*evTotal;
+
+ offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */
+ auto coeffs_ = reinterpret_cast<ALfloat(*)[2]>(base + offset);
+ offset += sizeof(coeffs_[0])*HRIR_LENGTH*irCount;
+
+ auto delays_ = reinterpret_cast<ALubyte(*)[2]>(base + offset);
+ offset += sizeof(delays_[0])*irCount;
+
+ assert(offset == total);
+
+ /* Copy input data to storage. */
+ for(ALuint i{0};i < fdCount;i++)
+ {
+ field_[i].distance = distance[i] / 1000.0f;
+ field_[i].evCount = evCount[i];
+ }
+ for(ALuint i{0};i < evTotal;i++)
+ {
+ elev_[i].azCount = azCount[i];
+ elev_[i].irOffset = irOffset[i];
+ }
+ for(ALuint i{0};i < irCount;i++)
+ {
+ for(ALuint j{0};j < ALuint{irSize};j++)
+ {
+ coeffs_[i*HRIR_LENGTH + j][0] = coeffs[i*irSize + j][0];
+ coeffs_[i*HRIR_LENGTH + j][1] = coeffs[i*irSize + j][1];
+ }
+ for(ALuint j{irSize};j < HRIR_LENGTH;j++)
+ {
+ coeffs_[i*HRIR_LENGTH + j][0] = 0.0f;
+ coeffs_[i*HRIR_LENGTH + j][1] = 0.0f;
+ }
+ }
+ for(ALuint i{0};i < irCount;i++)
+ {
+ delays_[i][0] = delays[i][0];
+ delays_[i][1] = delays[i][1];
+ }
+
+ /* Finally, assign the storage pointers. */
+ Hrtf->field = field_;
+ Hrtf->elev = elev_;
+ Hrtf->coeffs = coeffs_;
+ Hrtf->delays = delays_;
+ }
+
+ return Hrtf;
+}
+
+ALubyte GetLE_ALubyte(std::istream &data)
+{
+ return static_cast<ALubyte>(data.get());
+}
+
+ALshort GetLE_ALshort(std::istream &data)
+{
+ int ret = data.get();
+ ret |= data.get() << 8;
+ return static_cast<ALshort>((ret^32768) - 32768);
+}
+
+ALushort GetLE_ALushort(std::istream &data)
+{
+ int ret = data.get();
+ ret |= data.get() << 8;
+ return static_cast<ALushort>(ret);
+}
+
+ALint GetLE_ALint24(std::istream &data)
+{
+ int ret = data.get();
+ ret |= data.get() << 8;
+ ret |= data.get() << 16;
+ return (ret^8388608) - 8388608;
+}
+
+ALuint GetLE_ALuint(std::istream &data)
+{
+ int ret = data.get();
+ ret |= data.get() << 8;
+ ret |= data.get() << 16;
+ ret |= data.get() << 24;
+ return static_cast<ALuint>(ret);
+}
+
+std::unique_ptr<HrtfEntry> LoadHrtf00(std::istream &data, const char *filename)
+{
+ ALuint rate{GetLE_ALuint(data)};
+ ALushort irCount{GetLE_ALushort(data)};
+ ALushort irSize{GetLE_ALushort(data)};
+ ALubyte evCount{GetLE_ALubyte(data)};
+ if(!data || data.eof())
+ {
+ ERR("Failed reading %s\n", filename);
+ return nullptr;
+ }
+
+ ALboolean failed{AL_FALSE};
+ if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE))
+ {
+ ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n",
+ irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE);
+ failed = AL_TRUE;
+ }
+ if(evCount < MIN_EV_COUNT || evCount > MAX_EV_COUNT)
+ {
+ ERR("Unsupported elevation count: evCount=%d (%d to %d)\n",
+ evCount, MIN_EV_COUNT, MAX_EV_COUNT);
+ failed = AL_TRUE;
+ }
+ if(failed)
+ return nullptr;
+
+ auto evOffset = al::vector<ALushort>(evCount);
+ for(auto &val : evOffset)
+ val = GetLE_ALushort(data);
+ if(!data || data.eof())
+ {
+ ERR("Failed reading %s\n", filename);
+ return nullptr;
+ }
+ for(size_t i{1};i < evCount;i++)
+ {
+ if(evOffset[i] <= evOffset[i-1])
+ {
+ ERR("Invalid evOffset: evOffset[%zu]=%d (last=%d)\n", i, evOffset[i], evOffset[i-1]);
+ failed = AL_TRUE;
+ }
+ }
+ if(irCount <= evOffset.back())
+ {
+ ERR("Invalid evOffset: evOffset[%zu]=%d (irCount=%d)\n",
+ evOffset.size()-1, evOffset.back(), irCount);
+ failed = AL_TRUE;
+ }
+ if(failed)
+ return nullptr;
+
+ auto azCount = al::vector<ALushort>(evCount);
+ for(size_t i{1};i < evCount;i++)
+ {
+ azCount[i-1] = static_cast<ALushort>(evOffset[i] - evOffset[i-1]);
+ if(azCount[i-1] < MIN_AZ_COUNT || azCount[i-1] > MAX_AZ_COUNT)
+ {
+ ERR("Unsupported azimuth count: azCount[%zd]=%d (%d to %d)\n",
+ i-1, azCount[i-1], MIN_AZ_COUNT, MAX_AZ_COUNT);
+ failed = AL_TRUE;
+ }
+ }
+ azCount.back() = static_cast<ALushort>(irCount - evOffset.back());
+ if(azCount.back() < MIN_AZ_COUNT || azCount.back() > MAX_AZ_COUNT)
+ {
+ ERR("Unsupported azimuth count: azCount[%zu]=%d (%d to %d)\n",
+ azCount.size()-1, azCount.back(), MIN_AZ_COUNT, MAX_AZ_COUNT);
+ failed = AL_TRUE;
+ }
+ if(failed)
+ return nullptr;
+
+ auto coeffs = al::vector<std::array<ALfloat,2>>(irSize*irCount);
+ auto delays = al::vector<std::array<ALubyte,2>>(irCount);
+ for(auto &val : coeffs)
+ val[0] = GetLE_ALshort(data) / 32768.0f;
+ for(auto &val : delays)
+ val[0] = GetLE_ALubyte(data);
+ if(!data || data.eof())
+ {
+ ERR("Failed reading %s\n", filename);
+ return nullptr;
+ }
+ for(size_t i{0};i < irCount;i++)
+ {
+ if(delays[i][0] > MAX_HRIR_DELAY)
+ {
+ ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY);
+ failed = AL_TRUE;
+ }
+ }
+ if(failed)
+ return nullptr;
+
+ /* Mirror the left ear responses to the right ear. */
+ for(size_t i{0};i < evCount;i++)
+ {
+ const ALushort evoffset{evOffset[i]};
+ const ALushort azcount{azCount[i]};
+ for(size_t j{0};j < azcount;j++)
+ {
+ const size_t lidx{evoffset + j};
+ const size_t ridx{evoffset + ((azcount-j) % azcount)};
+
+ for(size_t k{0};k < irSize;k++)
+ coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0];
+ delays[ridx][1] = delays[lidx][0];
+ }
+ }
+
+ static const ALushort distance{0};
+ return CreateHrtfStore(rate, irSize, 1, &evCount, &distance, azCount.data(), evOffset.data(),
+ irCount, &reinterpret_cast<ALfloat(&)[2]>(coeffs[0]),
+ &reinterpret_cast<ALubyte(&)[2]>(delays[0]), filename);
+}
+
+std::unique_ptr<HrtfEntry> LoadHrtf01(std::istream &data, const char *filename)
+{
+ ALuint rate{GetLE_ALuint(data)};
+ ALushort irSize{GetLE_ALubyte(data)};
+ ALubyte evCount{GetLE_ALubyte(data)};
+ if(!data || data.eof())
+ {
+ ERR("Failed reading %s\n", filename);
+ return nullptr;
+ }
+
+ ALboolean failed{AL_FALSE};
+ if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE))
+ {
+ ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n",
+ irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE);
+ failed = AL_TRUE;
+ }
+ if(evCount < MIN_EV_COUNT || evCount > MAX_EV_COUNT)
+ {
+ ERR("Unsupported elevation count: evCount=%d (%d to %d)\n",
+ evCount, MIN_EV_COUNT, MAX_EV_COUNT);
+ failed = AL_TRUE;
+ }
+ if(failed)
+ return nullptr;
+
+ auto azCount = al::vector<ALushort>(evCount);
+ std::generate(azCount.begin(), azCount.end(), std::bind(GetLE_ALubyte, std::ref(data)));
+ if(!data || data.eof())
+ {
+ ERR("Failed reading %s\n", filename);
+ return nullptr;
+ }
+ for(size_t i{0};i < evCount;++i)
+ {
+ if(azCount[i] < MIN_AZ_COUNT || azCount[i] > MAX_AZ_COUNT)
+ {
+ ERR("Unsupported azimuth count: azCount[%zd]=%d (%d to %d)\n", i, azCount[i],
+ MIN_AZ_COUNT, MAX_AZ_COUNT);
+ failed = AL_TRUE;
+ }
+ }
+ if(failed)
+ return nullptr;
+
+ auto evOffset = al::vector<ALushort>(evCount);
+ evOffset[0] = 0;
+ ALushort irCount{azCount[0]};
+ for(size_t i{1};i < evCount;i++)
+ {
+ evOffset[i] = static_cast<ALushort>(evOffset[i-1] + azCount[i-1]);
+ irCount = static_cast<ALushort>(irCount + azCount[i]);
+ }
+
+ auto coeffs = al::vector<std::array<ALfloat,2>>(irSize*irCount);
+ auto delays = al::vector<std::array<ALubyte,2>>(irCount);
+ for(auto &val : coeffs)
+ val[0] = GetLE_ALshort(data) / 32768.0f;
+ for(auto &val : delays)
+ val[0] = GetLE_ALubyte(data);
+ if(!data || data.eof())
+ {
+ ERR("Failed reading %s\n", filename);
+ return nullptr;
+ }
+ for(size_t i{0};i < irCount;i++)
+ {
+ if(delays[i][0] > MAX_HRIR_DELAY)
+ {
+ ERR("Invalid delays[%zd]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY);
+ failed = AL_TRUE;
+ }
+ }
+ if(failed)
+ return nullptr;
+
+ /* Mirror the left ear responses to the right ear. */
+ for(size_t i{0};i < evCount;i++)
+ {
+ const ALushort evoffset{evOffset[i]};
+ const ALushort azcount{azCount[i]};
+ for(size_t j{0};j < azcount;j++)
+ {
+ const size_t lidx{evoffset + j};
+ const size_t ridx{evoffset + ((azcount-j) % azcount)};
+
+ for(size_t k{0};k < irSize;k++)
+ coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0];
+ delays[ridx][1] = delays[lidx][0];
+ }
+ }
+
+ static const ALushort distance{0};
+ return CreateHrtfStore(rate, irSize, 1, &evCount, &distance, azCount.data(), evOffset.data(),
+ irCount, &reinterpret_cast<ALfloat(&)[2]>(coeffs[0]),
+ &reinterpret_cast<ALubyte(&)[2]>(delays[0]), filename);
+}
+
+#define SAMPLETYPE_S16 0
+#define SAMPLETYPE_S24 1
+
+#define CHANTYPE_LEFTONLY 0
+#define CHANTYPE_LEFTRIGHT 1
+
+std::unique_ptr<HrtfEntry> LoadHrtf02(std::istream &data, const char *filename)
+{
+ ALuint rate{GetLE_ALuint(data)};
+ ALubyte sampleType{GetLE_ALubyte(data)};
+ ALubyte channelType{GetLE_ALubyte(data)};
+ ALushort irSize{GetLE_ALubyte(data)};
+ ALubyte fdCount{GetLE_ALubyte(data)};
+ if(!data || data.eof())
+ {
+ ERR("Failed reading %s\n", filename);
+ return nullptr;
+ }
+
+ ALboolean failed{AL_FALSE};
+ if(sampleType > SAMPLETYPE_S24)
+ {
+ ERR("Unsupported sample type: %d\n", sampleType);
+ failed = AL_TRUE;
+ }
+ if(channelType > CHANTYPE_LEFTRIGHT)
+ {
+ ERR("Unsupported channel type: %d\n", channelType);
+ failed = AL_TRUE;
+ }
+
+ if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE))
+ {
+ ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n",
+ irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE);
+ failed = AL_TRUE;
+ }
+ if(fdCount < 1 || fdCount > MAX_FD_COUNT)
+ {
+ ERR("Multiple field-depths not supported: fdCount=%d (%d to %d)\n",
+ fdCount, MIN_FD_COUNT, MAX_FD_COUNT);
+ failed = AL_TRUE;
+ }
+ if(failed)
+ return nullptr;
+
+ auto distance = al::vector<ALushort>(fdCount);
+ auto evCount = al::vector<ALubyte>(fdCount);
+ auto azCount = al::vector<ALushort>{};
+ for(size_t f{0};f < fdCount;f++)
+ {
+ distance[f] = GetLE_ALushort(data);
+ evCount[f] = GetLE_ALubyte(data);
+ if(!data || data.eof())
+ {
+ ERR("Failed reading %s\n", filename);
+ return nullptr;
+ }
+
+ if(distance[f] < MIN_FD_DISTANCE || distance[f] > MAX_FD_DISTANCE)
+ {
+ ERR("Unsupported field distance[%zu]=%d (%d to %d millimeters)\n", f, distance[f],
+ MIN_FD_DISTANCE, MAX_FD_DISTANCE);
+ failed = AL_TRUE;
+ }
+ if(f > 0 && distance[f] <= distance[f-1])
+ {
+ ERR("Field distance[%zu] is not after previous (%d > %d)\n", f, distance[f],
+ distance[f-1]);
+ failed = AL_TRUE;
+ }
+ if(evCount[f] < MIN_EV_COUNT || evCount[f] > MAX_EV_COUNT)
+ {
+ ERR("Unsupported elevation count: evCount[%zu]=%d (%d to %d)\n", f, evCount[f],
+ MIN_EV_COUNT, MAX_EV_COUNT);
+ failed = AL_TRUE;
+ }
+ if(failed)
+ return nullptr;
+
+ const size_t ebase{azCount.size()};
+ azCount.resize(ebase + evCount[f]);
+ std::generate(azCount.begin()+static_cast<ptrdiff_t>(ebase), azCount.end(),
+ std::bind(GetLE_ALubyte, std::ref(data)));
+ if(!data || data.eof())
+ {
+ ERR("Failed reading %s\n", filename);
+ return nullptr;
+ }
+
+ for(size_t e{0};e < evCount[f];e++)
+ {
+ if(azCount[ebase+e] < MIN_AZ_COUNT || azCount[ebase+e] > MAX_AZ_COUNT)
+ {
+ ERR("Unsupported azimuth count: azCount[%zu][%zu]=%d (%d to %d)\n", f, e,
+ azCount[ebase+e], MIN_AZ_COUNT, MAX_AZ_COUNT);
+ failed = AL_TRUE;
+ }
+ }
+ if(failed)
+ return nullptr;
+ }
+
+ auto evOffset = al::vector<ALushort>(azCount.size());
+ evOffset[0] = 0;
+ std::partial_sum(azCount.cbegin(), azCount.cend()-1, evOffset.begin()+1);
+ const auto irTotal = static_cast<ALushort>(evOffset.back() + azCount.back());
+
+ auto coeffs = al::vector<std::array<ALfloat,2>>(irSize*irTotal);
+ auto delays = al::vector<std::array<ALubyte,2>>(irTotal);
+ if(channelType == CHANTYPE_LEFTONLY)
+ {
+ if(sampleType == SAMPLETYPE_S16)
+ {
+ for(auto &val : coeffs)
+ val[0] = GetLE_ALshort(data) / 32768.0f;
+ }
+ else if(sampleType == SAMPLETYPE_S24)
+ {
+ for(auto &val : coeffs)
+ val[0] = static_cast<float>(GetLE_ALint24(data)) / 8388608.0f;
+ }
+ for(auto &val : delays)
+ val[0] = GetLE_ALubyte(data);
+ if(!data || data.eof())
+ {
+ ERR("Failed reading %s\n", filename);
+ return nullptr;
+ }
+ for(size_t i{0};i < irTotal;++i)
+ {
+ if(delays[i][0] > MAX_HRIR_DELAY)
+ {
+ ERR("Invalid delays[%zu][0]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY);
+ failed = AL_TRUE;
+ }
+ }
+ }
+ else if(channelType == CHANTYPE_LEFTRIGHT)
+ {
+ if(sampleType == SAMPLETYPE_S16)
+ {
+ for(auto &val : coeffs)
+ {
+ val[0] = GetLE_ALshort(data) / 32768.0f;
+ val[1] = GetLE_ALshort(data) / 32768.0f;
+ }
+ }
+ else if(sampleType == SAMPLETYPE_S24)
+ {
+ for(auto &val : coeffs)
+ {
+ val[0] = static_cast<float>(GetLE_ALint24(data)) / 8388608.0f;
+ val[1] = static_cast<float>(GetLE_ALint24(data)) / 8388608.0f;
+ }
+ }
+ for(auto &val : delays)
+ {
+ val[0] = GetLE_ALubyte(data);
+ val[1] = GetLE_ALubyte(data);
+ }
+ if(!data || data.eof())
+ {
+ ERR("Failed reading %s\n", filename);
+ return nullptr;
+ }
+
+ for(size_t i{0};i < irTotal;++i)
+ {
+ if(delays[i][0] > MAX_HRIR_DELAY)
+ {
+ ERR("Invalid delays[%zu][0]: %d (%d)\n", i, delays[i][0], MAX_HRIR_DELAY);
+ failed = AL_TRUE;
+ }
+ if(delays[i][1] > MAX_HRIR_DELAY)
+ {
+ ERR("Invalid delays[%zu][1]: %d (%d)\n", i, delays[i][1], MAX_HRIR_DELAY);
+ failed = AL_TRUE;
+ }
+ }
+ }
+ if(failed)
+ return nullptr;
+
+ if(channelType == CHANTYPE_LEFTONLY)
+ {
+ /* Mirror the left ear responses to the right ear. */
+ size_t ebase{0};
+ for(size_t f{0};f < fdCount;f++)
+ {
+ for(size_t e{0};e < evCount[f];e++)
+ {
+ const ALushort evoffset{evOffset[ebase+e]};
+ const ALushort azcount{azCount[ebase+e]};
+ for(size_t a{0};a < azcount;a++)
+ {
+ const size_t lidx{evoffset + a};
+ const size_t ridx{evoffset + ((azcount-a) % azcount)};
+
+ for(size_t k{0};k < irSize;k++)
+ coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0];
+ delays[ridx][1] = delays[lidx][0];
+ }
+ }
+ ebase += evCount[f];
+ }
+ }
+
+ if(fdCount > 1)
+ {
+ auto distance_ = al::vector<ALushort>(distance.size());
+ auto evCount_ = al::vector<ALubyte>(evCount.size());
+ auto azCount_ = al::vector<ALushort>(azCount.size());
+ auto evOffset_ = al::vector<ALushort>(evOffset.size());
+ auto coeffs_ = al::vector<float2>(coeffs.size());
+ auto delays_ = al::vector<std::array<ALubyte,2>>(delays.size());
+
+ /* Simple reverse for the per-field elements. */
+ std::reverse_copy(distance.cbegin(), distance.cend(), distance_.begin());
+ std::reverse_copy(evCount.cbegin(), evCount.cend(), evCount_.begin());
+
+ /* Each field has a group of elevations, which each have an azimuth
+ * count. Reverse the order of the groups, keeping the relative order
+ * of per-group azimuth counts.
+ */
+ auto azcnt_end = azCount_.end();
+ auto copy_azs = [&azCount,&azcnt_end](const ptrdiff_t ebase, const ALubyte num_evs) -> ptrdiff_t
+ {
+ auto azcnt_src = azCount.begin()+ebase;
+ azcnt_end = std::copy_backward(azcnt_src, azcnt_src+num_evs, azcnt_end);
+ return ebase + num_evs;
+ };
+ std::accumulate(evCount.cbegin(), evCount.cend(), ptrdiff_t{0}, copy_azs);
+ assert(azCount_.begin() == azcnt_end);
+
+ /* Reestablish the IR offset for each elevation index, given the new
+ * ordering of elevations.
+ */
+ evOffset_[0] = 0;
+ std::partial_sum(azCount_.cbegin(), azCount_.cend()-1, evOffset_.begin()+1);
+
+ /* Reverse the order of each field's group of IRs. */
+ auto coeffs_end = coeffs_.end();
+ auto delays_end = delays_.end();
+ auto copy_irs = [irSize,&azCount,&coeffs,&delays,&coeffs_end,&delays_end](const ptrdiff_t ebase, const ALubyte num_evs) -> ptrdiff_t
+ {
+ const ALsizei abase{std::accumulate(azCount.cbegin(), azCount.cbegin()+ebase, 0)};
+ const ALsizei num_azs{std::accumulate(azCount.cbegin()+ebase,
+ azCount.cbegin() + (ebase+num_evs), 0)};
+
+ coeffs_end = std::copy_backward(coeffs.cbegin() + abase*irSize,
+ coeffs.cbegin() + (abase+num_azs)*irSize, coeffs_end);
+ delays_end = std::copy_backward(delays.cbegin() + abase,
+ delays.cbegin() + (abase+num_azs), delays_end);
+
+ return ebase + num_evs;
+ };
+ std::accumulate(evCount.cbegin(), evCount.cend(), ptrdiff_t{0}, copy_irs);
+ assert(coeffs_.begin() == coeffs_end);
+ assert(delays_.begin() == delays_end);
+
+ distance = std::move(distance_);
+ evCount = std::move(evCount_);
+ azCount = std::move(azCount_);
+ evOffset = std::move(evOffset_);
+ coeffs = std::move(coeffs_);
+ delays = std::move(delays_);
+ }
+
+ return CreateHrtfStore(rate, irSize, fdCount, evCount.data(), distance.data(), azCount.data(),
+ evOffset.data(), irTotal, &reinterpret_cast<ALfloat(&)[2]>(coeffs[0]),
+ &reinterpret_cast<ALubyte(&)[2]>(delays[0]), filename);
+}
+
+
+bool checkName(al::vector<EnumeratedHrtf> &list, const std::string &name)
+{
+ return std::find_if(list.cbegin(), list.cend(),
+ [&name](const EnumeratedHrtf &entry)
+ { return name == entry.name; }
+ ) != list.cend();
+}
+
+void AddFileEntry(al::vector<EnumeratedHrtf> &list, const std::string &filename)
+{
+ /* Check if this file has already been loaded globally. */
+ auto loaded_entry = LoadedHrtfs.begin();
+ for(;loaded_entry != LoadedHrtfs.end();++loaded_entry)
+ {
+ if(filename != (*loaded_entry)->mFilename.data())
+ continue;
+
+ /* Check if this entry has already been added to the list. */
+ auto iter = std::find_if(list.cbegin(), list.cend(),
+ [loaded_entry](const EnumeratedHrtf &entry) -> bool
+ { return loaded_entry->get() == entry.hrtf; }
+ );
+ if(iter != list.cend())
+ {
+ TRACE("Skipping duplicate file entry %s\n", filename.c_str());
+ return;
+ }
+
+ break;
+ }
+
+ const char *new_mark{""};
+ if(loaded_entry == LoadedHrtfs.end())
+ {
+ new_mark = " (new)";
+
+ LoadedHrtfs.emplace_back(HrtfHandle::Create(filename.length()+1));
+ loaded_entry = LoadedHrtfs.end()-1;
+ std::copy(filename.begin(), filename.end(), (*loaded_entry)->mFilename.begin());
+ (*loaded_entry)->mFilename.back() = '\0';
+ }
+
+ /* TODO: Get a human-readable name from the HRTF data (possibly coming in a
+ * format update). */
+ size_t namepos = filename.find_last_of('/')+1;
+ if(!namepos) namepos = filename.find_last_of('\\')+1;
+
+ size_t extpos{filename.find_last_of('.')};
+ if(extpos <= namepos) extpos = std::string::npos;
+
+ const std::string basename{(extpos == std::string::npos) ?
+ filename.substr(namepos) : filename.substr(namepos, extpos-namepos)};
+ std::string newname{basename};
+ int count{1};
+ while(checkName(list, newname))
+ {
+ newname = basename;
+ newname += " #";
+ newname += std::to_string(++count);
+ }
+ list.emplace_back(EnumeratedHrtf{newname, loaded_entry->get()});
+ const EnumeratedHrtf &entry = list.back();
+
+ TRACE("Adding file entry \"%s\"%s\n", entry.name.c_str(), new_mark);
+}
+
+/* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer
+ * for input instead of opening the given filename.
+ */
+void AddBuiltInEntry(al::vector<EnumeratedHrtf> &list, const std::string &filename, ALuint residx)
+{
+ auto loaded_entry = LoadedHrtfs.begin();
+ for(;loaded_entry != LoadedHrtfs.end();++loaded_entry)
+ {
+ if(filename != (*loaded_entry)->mFilename.data())
+ continue;
+
+ /* Check if this entry has already been added to the list. */
+ auto iter = std::find_if(list.cbegin(), list.cend(),
+ [loaded_entry](const EnumeratedHrtf &entry) -> bool
+ { return loaded_entry->get() == entry.hrtf; }
+ );
+ if(iter != list.cend())
+ {
+ TRACE("Skipping duplicate file entry %s\n", filename.c_str());
+ return;
+ }
+
+ break;
+ }
+
+ const char *new_mark{""};
+ if(loaded_entry == LoadedHrtfs.end())
+ {
+ new_mark = " (new)";
+
+ LoadedHrtfs.emplace_back(HrtfHandle::Create(filename.length()+32));
+ loaded_entry = LoadedHrtfs.end()-1;
+ snprintf((*loaded_entry)->mFilename.data(), (*loaded_entry)->mFilename.size(), "!%u_%s",
+ residx, filename.c_str());
+ }
+
+ /* TODO: Get a human-readable name from the HRTF data (possibly coming in a
+ * format update). */
+
+ std::string newname{filename};
+ int count{1};
+ while(checkName(list, newname))
+ {
+ newname = filename;
+ newname += " #";
+ newname += std::to_string(++count);
+ }
+ list.emplace_back(EnumeratedHrtf{newname, loaded_entry->get()});
+ const EnumeratedHrtf &entry = list.back();
+
+ TRACE("Adding built-in entry \"%s\"%s\n", entry.name.c_str(), new_mark);
+}
+
+
+#define IDR_DEFAULT_44100_MHR 1
+#define IDR_DEFAULT_48000_MHR 2
+
+using ResData = al::span<const char>;
+#ifndef ALSOFT_EMBED_HRTF_DATA
+
+ResData GetResource(int /*name*/)
+{ return ResData{}; }
+
+#else
+
+#include "default-44100.mhr.h"
+#include "default-48000.mhr.h"
+
+ResData GetResource(int name)
+{
+ if(name == IDR_DEFAULT_44100_MHR)
+ return {reinterpret_cast<const char*>(hrtf_default_44100), sizeof(hrtf_default_44100)};
+ if(name == IDR_DEFAULT_48000_MHR)
+ return {reinterpret_cast<const char*>(hrtf_default_48000), sizeof(hrtf_default_48000)};
+ return ResData{};
+}
+#endif
+
+} // namespace
+
+
+al::vector<EnumeratedHrtf> EnumerateHrtf(const char *devname)
+{
+ al::vector<EnumeratedHrtf> list;
+
+ bool usedefaults{true};
+ if(auto pathopt = ConfigValueStr(devname, nullptr, "hrtf-paths"))
+ {
+ const char *pathlist{pathopt->c_str()};
+ while(pathlist && *pathlist)
+ {
+ const char *next, *end;
+
+ while(isspace(*pathlist) || *pathlist == ',')
+ pathlist++;
+ if(*pathlist == '\0')
+ continue;
+
+ next = strchr(pathlist, ',');
+ if(next)
+ end = next++;
+ else
+ {
+ end = pathlist + strlen(pathlist);
+ usedefaults = false;
+ }
+
+ while(end != pathlist && isspace(*(end-1)))
+ --end;
+ if(end != pathlist)
+ {
+ const std::string pname{pathlist, end};
+ for(const auto &fname : SearchDataFiles(".mhr", pname.c_str()))
+ AddFileEntry(list, fname);
+ }
+
+ pathlist = next;
+ }
+ }
+
+ if(usedefaults)
+ {
+ for(const auto &fname : SearchDataFiles(".mhr", "openal/hrtf"))
+ AddFileEntry(list, fname);
+
+ if(!GetResource(IDR_DEFAULT_44100_MHR).empty())
+ AddBuiltInEntry(list, "Built-In 44100hz", IDR_DEFAULT_44100_MHR);
+
+ if(!GetResource(IDR_DEFAULT_48000_MHR).empty())
+ AddBuiltInEntry(list, "Built-In 48000hz", IDR_DEFAULT_48000_MHR);
+ }
+
+ if(auto defhrtfopt = ConfigValueStr(devname, nullptr, "default-hrtf"))
+ {
+ auto find_entry = [&defhrtfopt](const EnumeratedHrtf &entry) -> bool
+ { return entry.name == *defhrtfopt; };
+ auto iter = std::find_if(list.begin(), list.end(), find_entry);
+ if(iter == list.end())
+ WARN("Failed to find default HRTF \"%s\"\n", defhrtfopt->c_str());
+ else if(iter != list.begin())
+ std::rotate(list.begin(), iter, iter+1);
+ }
+
+ return list;
+}
+
+HrtfEntry *GetLoadedHrtf(HrtfHandle *handle)
+{
+ std::lock_guard<std::mutex> _{LoadedHrtfLock};
+
+ if(handle->mEntry)
+ {
+ HrtfEntry *hrtf{handle->mEntry.get()};
+ hrtf->IncRef();
+ return hrtf;
+ }
+
+ std::unique_ptr<std::istream> stream;
+ const char *name{""};
+ ALint residx{};
+ char ch{};
+ if(sscanf(handle->mFilename.data(), "!%d%c", &residx, &ch) == 2 && ch == '_')
+ {
+ name = strchr(handle->mFilename.data(), ch)+1;
+
+ TRACE("Loading %s...\n", name);
+ ResData res{GetResource(residx)};
+ if(res.empty())
+ {
+ ERR("Could not get resource %u, %s\n", residx, name);
+ return nullptr;
+ }
+ stream = al::make_unique<idstream>(res.begin(), res.end());
+ }
+ else
+ {
+ name = handle->mFilename.data();
+
+ TRACE("Loading %s...\n", handle->mFilename.data());
+ auto fstr = al::make_unique<al::ifstream>(handle->mFilename.data(), std::ios::binary);
+ if(!fstr->is_open())
+ {
+ ERR("Could not open %s\n", handle->mFilename.data());
+ return nullptr;
+ }
+ stream = std::move(fstr);
+ }
+
+ std::unique_ptr<HrtfEntry> hrtf;
+ char magic[sizeof(magicMarker02)];
+ stream->read(magic, sizeof(magic));
+ if(stream->gcount() < static_cast<std::streamsize>(sizeof(magicMarker02)))
+ ERR("%s data is too short (%zu bytes)\n", name, stream->gcount());
+ else if(memcmp(magic, magicMarker02, sizeof(magicMarker02)) == 0)
+ {
+ TRACE("Detected data set format v2\n");
+ hrtf = LoadHrtf02(*stream, name);
+ }
+ else if(memcmp(magic, magicMarker01, sizeof(magicMarker01)) == 0)
+ {
+ TRACE("Detected data set format v1\n");
+ hrtf = LoadHrtf01(*stream, name);
+ }
+ else if(memcmp(magic, magicMarker00, sizeof(magicMarker00)) == 0)
+ {
+ TRACE("Detected data set format v0\n");
+ hrtf = LoadHrtf00(*stream, name);
+ }
+ else
+ ERR("Invalid header in %s: \"%.8s\"\n", name, magic);
+ stream.reset();
+
+ if(!hrtf)
+ {
+ ERR("Failed to load %s\n", name);
+ return nullptr;
+ }
+
+ TRACE("Loaded HRTF support for sample rate: %uhz\n", hrtf->sampleRate);
+ handle->mEntry = std::move(hrtf);
+
+ return handle->mEntry.get();
+}
+
+
+void HrtfEntry::IncRef()
+{
+ auto ref = IncrementRef(mRef);
+ TRACE("HrtfEntry %p increasing refcount to %u\n", decltype(std::declval<void*>()){this}, ref);
+}
+
+void HrtfEntry::DecRef()
+{
+ auto ref = DecrementRef(mRef);
+ TRACE("HrtfEntry %p decreasing refcount to %u\n", decltype(std::declval<void*>()){this}, ref);
+ if(ref == 0)
+ {
+ std::lock_guard<std::mutex> _{LoadedHrtfLock};
+
+ /* Go through and clear all unused HRTFs. */
+ auto delete_unused = [](HrtfHandlePtr &handle) -> void
+ {
+ HrtfEntry *entry{handle->mEntry.get()};
+ if(entry && ReadRef(entry->mRef) == 0)
+ {
+ TRACE("Unloading unused HRTF %s\n", handle->mFilename.data());
+ handle->mEntry = nullptr;
+ }
+ };
+ std::for_each(LoadedHrtfs.begin(), LoadedHrtfs.end(), delete_unused);
+ }
+}
diff --git a/alc/hrtf.h b/alc/hrtf.h
new file mode 100644
index 00000000..98df801b
--- /dev/null
+++ b/alc/hrtf.h
@@ -0,0 +1,115 @@
+#ifndef ALC_HRTF_H
+#define ALC_HRTF_H
+
+#include <array>
+#include <cstddef>
+#include <memory>
+#include <string>
+
+#include "AL/al.h"
+
+#include "almalloc.h"
+#include "alspan.h"
+#include "ambidefs.h"
+#include "atomic.h"
+#include "vector.h"
+
+struct HrtfHandle;
+
+
+#define HRTF_HISTORY_BITS (6)
+#define HRTF_HISTORY_LENGTH (1<<HRTF_HISTORY_BITS)
+#define HRTF_HISTORY_MASK (HRTF_HISTORY_LENGTH-1)
+
+#define HRIR_BITS (7)
+#define HRIR_LENGTH (1<<HRIR_BITS)
+#define HRIR_MASK (HRIR_LENGTH-1)
+
+
+struct HrtfEntry {
+ RefCount mRef;
+
+ ALuint sampleRate;
+ ALuint irSize;
+
+ struct Field {
+ ALfloat distance;
+ ALubyte evCount;
+ };
+ /* NOTE: Fields are stored *backwards*. field[0] is the farthest field, and
+ * field[fdCount-1] is the nearest.
+ */
+ ALuint fdCount;
+ const Field *field;
+
+ struct Elevation {
+ ALushort azCount;
+ ALushort irOffset;
+ };
+ Elevation *elev;
+ const ALfloat (*coeffs)[2];
+ const ALubyte (*delays)[2];
+
+ void IncRef();
+ void DecRef();
+
+ DEF_PLACE_NEWDEL()
+};
+
+struct EnumeratedHrtf {
+ std::string name;
+
+ HrtfHandle *hrtf;
+};
+
+
+using float2 = std::array<float,2>;
+using HrirArray = std::array<float2,HRIR_LENGTH>;
+
+struct HrtfState {
+ alignas(16) std::array<ALfloat,HRTF_HISTORY_LENGTH> History;
+};
+
+struct HrtfFilter {
+ alignas(16) HrirArray Coeffs;
+ ALsizei Delay[2];
+ ALfloat Gain;
+};
+
+struct DirectHrtfState {
+ /* HRTF filter state for dry buffer content */
+ ALuint IrSize{0};
+ al::FlexArray<HrirArray,16> Coeffs;
+
+ DirectHrtfState(size_t numchans) : Coeffs{numchans} { }
+
+ static std::unique_ptr<DirectHrtfState> Create(size_t num_chans);
+
+ DEF_FAM_NEWDEL(DirectHrtfState, Coeffs)
+};
+
+struct ElevRadius { float value; };
+struct AzimRadius { float value; };
+struct AngularPoint {
+ ElevRadius Elev;
+ AzimRadius Azim;
+};
+
+
+al::vector<EnumeratedHrtf> EnumerateHrtf(const char *devname);
+HrtfEntry *GetLoadedHrtf(HrtfHandle *handle);
+
+void GetHrtfCoeffs(const HrtfEntry *Hrtf, ALfloat elevation, ALfloat azimuth, ALfloat distance,
+ ALfloat spread, HrirArray &coeffs, ALsizei (&delays)[2]);
+
+/**
+ * Produces HRTF filter coefficients for decoding B-Format, given a set of
+ * virtual speaker positions, a matching decoding matrix, and per-order high-
+ * frequency gains for the decoder. The calculated impulse responses are
+ * ordered and scaled according to the matrix input.
+ */
+void BuildBFormatHrtf(const HrtfEntry *Hrtf, DirectHrtfState *state,
+ const al::span<const AngularPoint> AmbiPoints, const ALfloat (*AmbiMatrix)[MAX_AMBI_CHANNELS],
+ const ALfloat *AmbiOrderHFGain);
+
+#endif /* ALC_HRTF_H */
diff --git a/Alc/inprogext.h b/alc/inprogext.h
index 3025abe2..ad3ea288 100644
--- a/Alc/inprogext.h
+++ b/alc/inprogext.h
@@ -9,23 +9,23 @@
extern "C" {
#endif
-#ifndef ALC_SOFT_loopback2
-#define ALC_SOFT_loopback2 1
-#define ALC_AMBISONIC_LAYOUT_SOFT 0xfff0
-#define ALC_AMBISONIC_SCALING_SOFT 0xfff1
-#define ALC_AMBISONIC_ORDER_SOFT 0xfff2
-#define ALC_MAX_AMBISONIC_ORDER_SOFT 0xfff3
+#ifndef ALC_SOFT_loopback_bformat
+#define ALC_SOFT_loopback_bformat 1
+#define ALC_AMBISONIC_LAYOUT_SOFT 0x1997
+#define ALC_AMBISONIC_SCALING_SOFT 0x1998
+#define ALC_AMBISONIC_ORDER_SOFT 0x1999
+#define ALC_MAX_AMBISONIC_ORDER_SOFT 0x199B
#define ALC_BFORMAT3D_SOFT 0x1508
/* Ambisonic layouts */
-#define ALC_ACN_SOFT 0xfff4
-#define ALC_FUMA_SOFT 0xfff5
+#define ALC_FUMA_SOFT 0x0000
+#define ALC_ACN_SOFT 0x0001
/* Ambisonic scalings (normalization) */
/*#define ALC_FUMA_SOFT*/
-#define ALC_SN3D_SOFT 0xfff6
-#define ALC_N3D_SOFT 0xfff7
+#define ALC_SN3D_SOFT 0x0001
+#define ALC_N3D_SOFT 0x0002
#endif
#ifndef AL_SOFT_map_buffer
@@ -72,12 +72,9 @@ AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values);
#endif
#endif
-#ifndef AL_SOFT_buffer_layers
-#define AL_SOFT_buffer_layers
-typedef void (AL_APIENTRY*LPALSOURCEQUEUEBUFFERLAYERSSOFT)(ALuint src, ALsizei nb, const ALuint *buffers);
-#ifdef AL_ALEXT_PROTOTYPES
-AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, const ALuint *buffers);
-#endif
+#ifndef AL_SOFT_effect_chain
+#define AL_SOFT_effect_chain
+#define AL_EFFECTSLOT_TARGET_SOFT 0xf000
#endif
#ifdef __cplusplus
diff --git a/alc/logging.h b/alc/logging.h
new file mode 100644
index 00000000..ec6023a5
--- /dev/null
+++ b/alc/logging.h
@@ -0,0 +1,59 @@
+#ifndef LOGGING_H
+#define LOGGING_H
+
+#include <stdio.h>
+
+#include "opthelpers.h"
+
+
+#ifdef __GNUC__
+#define DECL_FORMAT(x, y, z) __attribute__((format(x, (y), (z))))
+#else
+#define DECL_FORMAT(x, y, z)
+#endif
+
+
+extern FILE *gLogFile;
+
+void al_print(FILE *logfile, const char *fmt, ...) DECL_FORMAT(printf, 2,3);
+#if !defined(_WIN32)
+#define AL_PRINT fprintf
+#else
+#define AL_PRINT al_print
+#endif
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#define LOG_ANDROID(T, ...) __android_log_print(T, "openal", "AL lib: " __VA_ARGS__)
+#else
+#define LOG_ANDROID(T, ...) ((void)0)
+#endif
+
+enum LogLevel {
+ NoLog,
+ LogError,
+ LogWarning,
+ LogTrace,
+ LogRef
+};
+extern LogLevel gLogLevel;
+
+#define TRACE(...) do { \
+ if UNLIKELY(gLogLevel >= LogTrace) \
+ AL_PRINT(gLogFile, "AL lib: (II) " __VA_ARGS__); \
+ LOG_ANDROID(ANDROID_LOG_DEBUG, __VA_ARGS__); \
+} while(0)
+
+#define WARN(...) do { \
+ if UNLIKELY(gLogLevel >= LogWarning) \
+ AL_PRINT(gLogFile, "AL lib: (WW) " __VA_ARGS__); \
+ LOG_ANDROID(ANDROID_LOG_WARN, __VA_ARGS__); \
+} while(0)
+
+#define ERR(...) do { \
+ if UNLIKELY(gLogLevel >= LogError) \
+ AL_PRINT(gLogFile, "AL lib: (EE) " __VA_ARGS__); \
+ LOG_ANDROID(ANDROID_LOG_ERROR, __VA_ARGS__); \
+} while(0)
+
+#endif /* LOGGING_H */
diff --git a/alc/mastering.cpp b/alc/mastering.cpp
new file mode 100644
index 00000000..46cc3134
--- /dev/null
+++ b/alc/mastering.cpp
@@ -0,0 +1,468 @@
+
+#include "config.h"
+
+#include "mastering.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <new>
+
+#include "AL/al.h"
+
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "alu.h"
+#include "opthelpers.h"
+
+
+/* These structures assume BUFFERSIZE is a power of 2. */
+static_assert((BUFFERSIZE & (BUFFERSIZE-1)) == 0, "BUFFERSIZE is not a power of 2");
+
+struct SlidingHold {
+ alignas(16) ALfloat mValues[BUFFERSIZE];
+ ALuint mExpiries[BUFFERSIZE];
+ ALuint mLowerIndex;
+ ALuint mUpperIndex;
+ ALuint mLength;
+};
+
+
+namespace {
+
+using namespace std::placeholders;
+
+/* This sliding hold follows the input level with an instant attack and a
+ * fixed duration hold before an instant release to the next highest level.
+ * It is a sliding window maximum (descending maxima) implementation based on
+ * Richard Harter's ascending minima algorithm available at:
+ *
+ * http://www.richardhartersworld.com/cri/2001/slidingmin.html
+ */
+ALfloat UpdateSlidingHold(SlidingHold *Hold, const ALuint i, const ALfloat in)
+{
+ static constexpr ALuint mask{BUFFERSIZE - 1};
+ const ALuint length{Hold->mLength};
+ ALfloat (&values)[BUFFERSIZE] = Hold->mValues;
+ ALuint (&expiries)[BUFFERSIZE] = Hold->mExpiries;
+ ALuint lowerIndex{Hold->mLowerIndex};
+ ALuint upperIndex{Hold->mUpperIndex};
+
+ if(i >= expiries[upperIndex])
+ upperIndex = (upperIndex + 1) & mask;
+
+ if(in >= values[upperIndex])
+ {
+ values[upperIndex] = in;
+ expiries[upperIndex] = i + length;
+ lowerIndex = upperIndex;
+ }
+ else
+ {
+ do {
+ do {
+ if(!(in >= values[lowerIndex]))
+ goto found_place;
+ } while(lowerIndex--);
+ lowerIndex = mask;
+ } while(1);
+ found_place:
+
+ lowerIndex = (lowerIndex + 1) & mask;
+ values[lowerIndex] = in;
+ expiries[lowerIndex] = i + length;
+ }
+
+ Hold->mLowerIndex = lowerIndex;
+ Hold->mUpperIndex = upperIndex;
+
+ return values[upperIndex];
+}
+
+void ShiftSlidingHold(SlidingHold *Hold, const ALuint n)
+{
+ auto exp_begin = std::begin(Hold->mExpiries) + Hold->mUpperIndex;
+ auto exp_last = std::begin(Hold->mExpiries) + Hold->mLowerIndex;
+ if(exp_last-exp_begin < 0)
+ {
+ std::transform(exp_begin, std::end(Hold->mExpiries), exp_begin,
+ std::bind(std::minus<ALuint>{}, _1, n));
+ exp_begin = std::begin(Hold->mExpiries);
+ }
+ std::transform(exp_begin, exp_last+1, exp_begin, std::bind(std::minus<ALuint>{}, _1, n));
+}
+
+
+/* Multichannel compression is linked via the absolute maximum of all
+ * channels.
+ */
+void LinkChannels(Compressor *Comp, const ALuint SamplesToDo, const FloatBufferLine *OutBuffer)
+{
+ const ALuint numChans{Comp->mNumChans};
+
+ ASSUME(SamplesToDo > 0);
+ ASSUME(numChans > 0);
+
+ auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
+ std::fill(side_begin, side_begin+SamplesToDo, 0.0f);
+
+ auto fill_max = [SamplesToDo,side_begin](const FloatBufferLine &input) -> void
+ {
+ const ALfloat *RESTRICT buffer{al::assume_aligned<16>(input.data())};
+ auto max_abs = std::bind(maxf, _1, std::bind(static_cast<float(&)(float)>(std::fabs), _2));
+ std::transform(side_begin, side_begin+SamplesToDo, buffer, side_begin, max_abs);
+ };
+ std::for_each(OutBuffer, OutBuffer+numChans, fill_max);
+}
+
+/* This calculates the squared crest factor of the control signal for the
+ * basic automation of the attack/release times. As suggested by the paper,
+ * it uses an instantaneous squared peak detector and a squared RMS detector
+ * both with 200ms release times.
+ */
+static void CrestDetector(Compressor *Comp, const ALuint SamplesToDo)
+{
+ const ALfloat a_crest{Comp->mCrestCoeff};
+ ALfloat y2_peak{Comp->mLastPeakSq};
+ ALfloat y2_rms{Comp->mLastRmsSq};
+
+ ASSUME(SamplesToDo > 0);
+
+ auto calc_crest = [&y2_rms,&y2_peak,a_crest](const ALfloat x_abs) noexcept -> ALfloat
+ {
+ const ALfloat x2{clampf(x_abs * x_abs, 0.000001f, 1000000.0f)};
+
+ y2_peak = maxf(x2, lerp(x2, y2_peak, a_crest));
+ y2_rms = lerp(x2, y2_rms, a_crest);
+ return y2_peak / y2_rms;
+ };
+ auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
+ std::transform(side_begin, side_begin+SamplesToDo, std::begin(Comp->mCrestFactor), calc_crest);
+
+ Comp->mLastPeakSq = y2_peak;
+ Comp->mLastRmsSq = y2_rms;
+}
+
+/* The side-chain starts with a simple peak detector (based on the absolute
+ * value of the incoming signal) and performs most of its operations in the
+ * log domain.
+ */
+void PeakDetector(Compressor *Comp, const ALuint SamplesToDo)
+{
+ ASSUME(SamplesToDo > 0);
+
+ /* Clamp the minimum amplitude to near-zero and convert to logarithm. */
+ auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
+ std::transform(side_begin, side_begin+SamplesToDo, side_begin,
+ std::bind(static_cast<float(&)(float)>(std::log), std::bind(maxf, 0.000001f, _1)));
+}
+
+/* An optional hold can be used to extend the peak detector so it can more
+ * solidly detect fast transients. This is best used when operating as a
+ * limiter.
+ */
+void PeakHoldDetector(Compressor *Comp, const ALuint SamplesToDo)
+{
+ ASSUME(SamplesToDo > 0);
+
+ SlidingHold *hold{Comp->mHold};
+ ALuint i{0};
+ auto detect_peak = [&i,hold](const ALfloat x_abs) -> ALfloat
+ {
+ const ALfloat x_G{std::log(maxf(0.000001f, x_abs))};
+ return UpdateSlidingHold(hold, i++, x_G);
+ };
+ auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
+ std::transform(side_begin, side_begin+SamplesToDo, side_begin, detect_peak);
+
+ ShiftSlidingHold(hold, SamplesToDo);
+}
+
+/* This is the heart of the feed-forward compressor. It operates in the log
+ * domain (to better match human hearing) and can apply some basic automation
+ * to knee width, attack/release times, make-up/post gain, and clipping
+ * reduction.
+ */
+void GainCompressor(Compressor *Comp, const ALuint SamplesToDo)
+{
+ const bool autoKnee{Comp->mAuto.Knee};
+ const bool autoAttack{Comp->mAuto.Attack};
+ const bool autoRelease{Comp->mAuto.Release};
+ const bool autoPostGain{Comp->mAuto.PostGain};
+ const bool autoDeclip{Comp->mAuto.Declip};
+ const ALuint lookAhead{Comp->mLookAhead};
+ const ALfloat threshold{Comp->mThreshold};
+ const ALfloat slope{Comp->mSlope};
+ const ALfloat attack{Comp->mAttack};
+ const ALfloat release{Comp->mRelease};
+ const ALfloat c_est{Comp->mGainEstimate};
+ const ALfloat a_adp{Comp->mAdaptCoeff};
+ const ALfloat *crestFactor{Comp->mCrestFactor};
+ ALfloat postGain{Comp->mPostGain};
+ ALfloat knee{Comp->mKnee};
+ ALfloat t_att{attack};
+ ALfloat t_rel{release - attack};
+ ALfloat a_att{std::exp(-1.0f / t_att)};
+ ALfloat a_rel{std::exp(-1.0f / t_rel)};
+ ALfloat y_1{Comp->mLastRelease};
+ ALfloat y_L{Comp->mLastAttack};
+ ALfloat c_dev{Comp->mLastGainDev};
+
+ ASSUME(SamplesToDo > 0);
+
+ for(ALfloat &sideChain : al::span<float>{Comp->mSideChain, SamplesToDo})
+ {
+ if(autoKnee)
+ knee = maxf(0.0f, 2.5f * (c_dev + c_est));
+ const ALfloat knee_h{0.5f * knee};
+
+ /* This is the gain computer. It applies a static compression curve
+ * to the control signal.
+ */
+ const ALfloat x_over{std::addressof(sideChain)[lookAhead] - threshold};
+ const ALfloat y_G{
+ (x_over <= -knee_h) ? 0.0f :
+ (std::fabs(x_over) < knee_h) ? (x_over + knee_h) * (x_over + knee_h) / (2.0f * knee) :
+ x_over
+ };
+
+ const ALfloat y2_crest{*(crestFactor++)};
+ if(autoAttack)
+ {
+ t_att = 2.0f*attack/y2_crest;
+ a_att = std::exp(-1.0f / t_att);
+ }
+ if(autoRelease)
+ {
+ t_rel = 2.0f*release/y2_crest - t_att;
+ a_rel = std::exp(-1.0f / t_rel);
+ }
+
+ /* Gain smoothing (ballistics) is done via a smooth decoupled peak
+ * detector. The attack time is subtracted from the release time
+ * above to compensate for the chained operating mode.
+ */
+ const ALfloat x_L{-slope * y_G};
+ y_1 = maxf(x_L, lerp(x_L, y_1, a_rel));
+ y_L = lerp(y_1, y_L, a_att);
+
+ /* Knee width and make-up gain automation make use of a smoothed
+ * measurement of deviation between the control signal and estimate.
+ * The estimate is also used to bias the measurement to hot-start its
+ * average.
+ */
+ c_dev = lerp(-(y_L+c_est), c_dev, a_adp);
+
+ if(autoPostGain)
+ {
+ /* Clipping reduction is only viable when make-up gain is being
+ * automated. It modifies the deviation to further attenuate the
+ * control signal when clipping is detected. The adaptation time
+ * is sufficiently long enough to suppress further clipping at the
+ * same output level.
+ */
+ if(autoDeclip)
+ c_dev = maxf(c_dev, sideChain - y_L - threshold - c_est);
+
+ postGain = -(c_dev + c_est);
+ }
+
+ sideChain = std::exp(postGain - y_L);
+ }
+
+ Comp->mLastRelease = y_1;
+ Comp->mLastAttack = y_L;
+ Comp->mLastGainDev = c_dev;
+}
+
+/* Combined with the hold time, a look-ahead delay can improve handling of
+ * fast transients by allowing the envelope time to converge prior to
+ * reaching the offending impulse. This is best used when operating as a
+ * limiter.
+ */
+void SignalDelay(Compressor *Comp, const ALuint SamplesToDo, FloatBufferLine *OutBuffer)
+{
+ const ALuint numChans{Comp->mNumChans};
+ const ALuint lookAhead{Comp->mLookAhead};
+
+ ASSUME(SamplesToDo > 0);
+ ASSUME(numChans > 0);
+ ASSUME(lookAhead > 0);
+
+ for(ALuint c{0};c < numChans;c++)
+ {
+ ALfloat *inout{al::assume_aligned<16>(OutBuffer[c].data())};
+ ALfloat *delaybuf{al::assume_aligned<16>(Comp->mDelay[c].data())};
+
+ auto inout_end = inout + SamplesToDo;
+ if LIKELY(SamplesToDo >= lookAhead)
+ {
+ auto delay_end = std::rotate(inout, inout_end - lookAhead, inout_end);
+ std::swap_ranges(inout, delay_end, delaybuf);
+ }
+ else
+ {
+ auto delay_start = std::swap_ranges(inout, inout_end, delaybuf);
+ std::rotate(delaybuf, delay_start, delaybuf + lookAhead);
+ }
+ }
+}
+
+} // namespace
+
+/* The compressor is initialized with the following settings:
+ *
+ * NumChans - Number of channels to process.
+ * SampleRate - Sample rate to process.
+ * AutoKnee - Whether to automate the knee width parameter.
+ * AutoAttack - Whether to automate the attack time parameter.
+ * AutoRelease - Whether to automate the release time parameter.
+ * AutoPostGain - Whether to automate the make-up (post) gain parameter.
+ * AutoDeclip - Whether to automate clipping reduction. Ignored when
+ * not automating make-up gain.
+ * LookAheadTime - Look-ahead time (in seconds).
+ * HoldTime - Peak hold-time (in seconds).
+ * PreGainDb - Gain applied before detection (in dB).
+ * PostGainDb - Make-up gain applied after compression (in dB).
+ * ThresholdDb - Triggering threshold (in dB).
+ * Ratio - Compression ratio (x:1). Set to INFINITY for true
+ * limiting. Ignored when automating knee width.
+ * KneeDb - Knee width (in dB). Ignored when automating knee
+ * width.
+ * AttackTimeMin - Attack time (in seconds). Acts as a maximum when
+ * automating attack time.
+ * ReleaseTimeMin - Release time (in seconds). Acts as a maximum when
+ * automating release time.
+ */
+std::unique_ptr<Compressor> CompressorInit(const ALuint NumChans, const ALfloat SampleRate,
+ const ALboolean AutoKnee, const ALboolean AutoAttack, const ALboolean AutoRelease,
+ const ALboolean AutoPostGain, const ALboolean AutoDeclip, const ALfloat LookAheadTime,
+ const ALfloat HoldTime, const ALfloat PreGainDb, const ALfloat PostGainDb,
+ const ALfloat ThresholdDb, const ALfloat Ratio, const ALfloat KneeDb, const ALfloat AttackTime,
+ const ALfloat ReleaseTime)
+{
+ const auto lookAhead = static_cast<ALuint>(
+ clampf(std::round(LookAheadTime*SampleRate), 0.0f, BUFFERSIZE-1));
+ const auto hold = static_cast<ALuint>(
+ clampf(std::round(HoldTime*SampleRate), 0.0f, BUFFERSIZE-1));
+
+ size_t size{sizeof(Compressor)};
+ if(lookAhead > 0)
+ {
+ size += sizeof(*Compressor::mDelay) * NumChans;
+ /* The sliding hold implementation doesn't handle a length of 1. A 1-
+ * sample hold is useless anyway, it would only ever give back what was
+ * just given to it.
+ */
+ if(hold > 1)
+ size += sizeof(*Compressor::mHold);
+ }
+
+ auto Comp = std::unique_ptr<Compressor>{new (al_calloc(16, size)) Compressor{}};
+ Comp->mNumChans = NumChans;
+ Comp->mAuto.Knee = AutoKnee != AL_FALSE;
+ Comp->mAuto.Attack = AutoAttack != AL_FALSE;
+ Comp->mAuto.Release = AutoRelease != AL_FALSE;
+ Comp->mAuto.PostGain = AutoPostGain != AL_FALSE;
+ Comp->mAuto.Declip = AutoPostGain && AutoDeclip;
+ Comp->mLookAhead = lookAhead;
+ Comp->mPreGain = std::pow(10.0f, PreGainDb / 20.0f);
+ Comp->mPostGain = PostGainDb * std::log(10.0f) / 20.0f;
+ Comp->mThreshold = ThresholdDb * std::log(10.0f) / 20.0f;
+ Comp->mSlope = 1.0f / maxf(1.0f, Ratio) - 1.0f;
+ Comp->mKnee = maxf(0.0f, KneeDb * std::log(10.0f) / 20.0f);
+ Comp->mAttack = maxf(1.0f, AttackTime * SampleRate);
+ Comp->mRelease = maxf(1.0f, ReleaseTime * SampleRate);
+
+ /* Knee width automation actually treats the compressor as a limiter. By
+ * varying the knee width, it can effectively be seen as applying
+ * compression over a wide range of ratios.
+ */
+ if(AutoKnee)
+ Comp->mSlope = -1.0f;
+
+ if(lookAhead > 0)
+ {
+ if(hold > 1)
+ {
+ Comp->mHold = ::new (static_cast<void*>(Comp.get() + 1)) SlidingHold{};
+ Comp->mHold->mValues[0] = -std::numeric_limits<float>::infinity();
+ Comp->mHold->mExpiries[0] = hold;
+ Comp->mHold->mLength = hold;
+ Comp->mDelay = ::new (static_cast<void*>(Comp->mHold + 1)) FloatBufferLine[NumChans];
+ }
+ else
+ {
+ Comp->mDelay = ::new (static_cast<void*>(Comp.get() + 1)) FloatBufferLine[NumChans];
+ }
+ std::fill_n(Comp->mDelay, NumChans, FloatBufferLine{});
+ }
+
+ Comp->mCrestCoeff = std::exp(-1.0f / (0.200f * SampleRate)); // 200ms
+ Comp->mGainEstimate = Comp->mThreshold * -0.5f * Comp->mSlope;
+ Comp->mAdaptCoeff = std::exp(-1.0f / (2.0f * SampleRate)); // 2s
+
+ return Comp;
+}
+
+Compressor::~Compressor()
+{
+ if(mHold)
+ al::destroy_at(mHold);
+ mHold = nullptr;
+ if(mDelay)
+ al::destroy_n(mDelay, mNumChans);
+ mDelay = nullptr;
+}
+
+
+void Compressor::process(const ALuint SamplesToDo, FloatBufferLine *OutBuffer)
+{
+ const ALuint numChans{mNumChans};
+
+ ASSUME(SamplesToDo > 0);
+ ASSUME(numChans > 0);
+
+ const ALfloat preGain{mPreGain};
+ if(preGain != 1.0f)
+ {
+ auto apply_gain = [SamplesToDo,preGain](FloatBufferLine &input) noexcept -> void
+ {
+ ALfloat *buffer{al::assume_aligned<16>(input.data())};
+ std::transform(buffer, buffer+SamplesToDo, buffer,
+ std::bind(std::multiplies<float>{}, _1, preGain));
+ };
+ std::for_each(OutBuffer, OutBuffer+numChans, apply_gain);
+ }
+
+ LinkChannels(this, SamplesToDo, OutBuffer);
+
+ if(mAuto.Attack || mAuto.Release)
+ CrestDetector(this, SamplesToDo);
+
+ if(mHold)
+ PeakHoldDetector(this, SamplesToDo);
+ else
+ PeakDetector(this, SamplesToDo);
+
+ GainCompressor(this, SamplesToDo);
+
+ if(mDelay)
+ SignalDelay(this, SamplesToDo, OutBuffer);
+
+ const ALfloat (&sideChain)[BUFFERSIZE*2] = mSideChain;
+ auto apply_comp = [SamplesToDo,&sideChain](FloatBufferLine &input) noexcept -> void
+ {
+ ALfloat *buffer{al::assume_aligned<16>(input.data())};
+ const ALfloat *gains{al::assume_aligned<16>(&sideChain[0])};
+ std::transform(gains, gains+SamplesToDo, buffer, buffer,
+ std::bind(std::multiplies<float>{}, _1, _2));
+ };
+ std::for_each(OutBuffer, OutBuffer+numChans, apply_comp);
+
+ auto side_begin = std::begin(mSideChain) + SamplesToDo;
+ std::copy(side_begin, side_begin+mLookAhead, std::begin(mSideChain));
+}
diff --git a/alc/mastering.h b/alc/mastering.h
new file mode 100644
index 00000000..851381e9
--- /dev/null
+++ b/alc/mastering.h
@@ -0,0 +1,103 @@
+#ifndef MASTERING_H
+#define MASTERING_H
+
+#include <memory>
+
+#include "AL/al.h"
+
+/* For FloatBufferLine/BUFFERSIZE. */
+#include "alcmain.h"
+#include "almalloc.h"
+
+struct SlidingHold;
+
+
+/* General topology and basic automation was based on the following paper:
+ *
+ * D. Giannoulis, M. Massberg and J. D. Reiss,
+ * "Parameter Automation in a Dynamic Range Compressor,"
+ * Journal of the Audio Engineering Society, v61 (10), Oct. 2013
+ *
+ * Available (along with supplemental reading) at:
+ *
+ * http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/
+ */
+struct Compressor {
+ ALuint mNumChans{0u};
+
+ struct {
+ bool Knee : 1;
+ bool Attack : 1;
+ bool Release : 1;
+ bool PostGain : 1;
+ bool Declip : 1;
+ } mAuto{};
+
+ ALuint mLookAhead{0};
+
+ ALfloat mPreGain{0.0f};
+ ALfloat mPostGain{0.0f};
+
+ ALfloat mThreshold{0.0f};
+ ALfloat mSlope{0.0f};
+ ALfloat mKnee{0.0f};
+
+ ALfloat mAttack{0.0f};
+ ALfloat mRelease{0.0f};
+
+ alignas(16) ALfloat mSideChain[2*BUFFERSIZE]{};
+ alignas(16) ALfloat mCrestFactor[BUFFERSIZE]{};
+
+ SlidingHold *mHold{nullptr};
+ FloatBufferLine *mDelay{nullptr};
+
+ ALfloat mCrestCoeff{0.0f};
+ ALfloat mGainEstimate{0.0f};
+ ALfloat mAdaptCoeff{0.0f};
+
+ ALfloat mLastPeakSq{0.0f};
+ ALfloat mLastRmsSq{0.0f};
+ ALfloat mLastRelease{0.0f};
+ ALfloat mLastAttack{0.0f};
+ ALfloat mLastGainDev{0.0f};
+
+
+ ~Compressor();
+ void process(const ALuint SamplesToDo, FloatBufferLine *OutBuffer);
+ ALsizei getLookAhead() const noexcept { return static_cast<ALsizei>(mLookAhead); }
+
+ DEF_PLACE_NEWDEL()
+};
+
+/* The compressor is initialized with the following settings:
+ *
+ * NumChans - Number of channels to process.
+ * SampleRate - Sample rate to process.
+ * AutoKnee - Whether to automate the knee width parameter.
+ * AutoAttack - Whether to automate the attack time parameter.
+ * AutoRelease - Whether to automate the release time parameter.
+ * AutoPostGain - Whether to automate the make-up (post) gain parameter.
+ * AutoDeclip - Whether to automate clipping reduction. Ignored when
+ * not automating make-up gain.
+ * LookAheadTime - Look-ahead time (in seconds).
+ * HoldTime - Peak hold-time (in seconds).
+ * PreGainDb - Gain applied before detection (in dB).
+ * PostGainDb - Make-up gain applied after compression (in dB).
+ * ThresholdDb - Triggering threshold (in dB).
+ * Ratio - Compression ratio (x:1). Set to INFINIFTY for true
+ * limiting. Ignored when automating knee width.
+ * KneeDb - Knee width (in dB). Ignored when automating knee
+ * width.
+ * AttackTimeMin - Attack time (in seconds). Acts as a maximum when
+ * automating attack time.
+ * ReleaseTimeMin - Release time (in seconds). Acts as a maximum when
+ * automating release time.
+ */
+std::unique_ptr<Compressor> CompressorInit(const ALuint NumChans, const ALfloat SampleRate,
+ const ALboolean AutoKnee, const ALboolean AutoAttack, const ALboolean AutoRelease,
+ const ALboolean AutoPostGain, const ALboolean AutoDeclip, const ALfloat LookAheadTime,
+ const ALfloat HoldTime, const ALfloat PreGainDb, const ALfloat PostGainDb,
+ const ALfloat ThresholdDb, const ALfloat Ratio, const ALfloat KneeDb, const ALfloat AttackTime,
+ const ALfloat ReleaseTime);
+
+#endif /* MASTERING_H */
diff --git a/alc/mixer/defs.h b/alc/mixer/defs.h
new file mode 100644
index 00000000..1e5b40d8
--- /dev/null
+++ b/alc/mixer/defs.h
@@ -0,0 +1,68 @@
+#ifndef MIXER_DEFS_H
+#define MIXER_DEFS_H
+
+#include "AL/al.h"
+
+#include "alcmain.h"
+#include "alspan.h"
+#include "hrtf.h"
+
+union InterpState;
+struct MixHrtfFilter;
+
+
+enum InstSetType {
+ CTag,
+ SSETag,
+ SSE2Tag,
+ SSE3Tag,
+ SSE4Tag,
+ NEONTag
+};
+
+enum ResampleType {
+ CopyTag,
+ PointTag,
+ LerpTag,
+ CubicTag,
+ BSincTag,
+ FastBSincTag
+};
+
+template<ResampleType TypeTag, InstSetType InstTag>
+const ALfloat *Resample_(const InterpState *state, const ALfloat *RESTRICT src, ALuint frac,
+ ALuint increment, const al::span<float> dst);
+
+template<InstSetType InstTag>
+void Mix_(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer,
+ float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos);
+template<InstSetType InstTag>
+void MixRow_(const al::span<float> OutBuffer, const al::span<const float> Gains,
+ const float *InSamples, const size_t InStride);
+
+template<InstSetType InstTag>
+void MixHrtf_(const float *InSamples, float2 *AccumSamples, const ALuint IrSize,
+ MixHrtfFilter *hrtfparams, const size_t BufferSize);
+template<InstSetType InstTag>
+void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const ALuint IrSize,
+ const HrtfFilter *oldparams, MixHrtfFilter *newparams, const size_t BufferSize);
+template<InstSetType InstTag>
+void MixDirectHrtf_(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
+ const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State,
+ const size_t BufferSize);
+
+/* Vectorized resampler helpers */
+inline void InitPosArrays(ALuint frac, ALuint increment, ALuint *frac_arr, ALuint *pos_arr,
+ size_t size)
+{
+ pos_arr[0] = 0;
+ frac_arr[0] = frac;
+ for(size_t i{1};i < size;i++)
+ {
+ const ALuint frac_tmp{frac_arr[i-1] + increment};
+ pos_arr[i] = pos_arr[i-1] + (frac_tmp>>FRACTIONBITS);
+ frac_arr[i] = frac_tmp&FRACTIONMASK;
+ }
+}
+
+#endif /* MIXER_DEFS_H */
diff --git a/alc/mixer/hrtfbase.h b/alc/mixer/hrtfbase.h
new file mode 100644
index 00000000..4a6eab50
--- /dev/null
+++ b/alc/mixer/hrtfbase.h
@@ -0,0 +1,119 @@
+#ifndef MIXER_HRTFBASE_H
+#define MIXER_HRTFBASE_H
+
+#include <algorithm>
+
+#include "alu.h"
+#include "../hrtf.h"
+#include "opthelpers.h"
+#include "voice.h"
+
+
+using ApplyCoeffsT = void(&)(float2 *RESTRICT Values, const ALuint irSize, const HrirArray &Coeffs,
+ const float left, const float right);
+
+template<ApplyCoeffsT ApplyCoeffs>
+inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, const ALuint IrSize,
+ MixHrtfFilter *hrtfparams, const size_t BufferSize)
+{
+ ASSUME(BufferSize > 0);
+
+ const HrirArray &Coeffs = *hrtfparams->Coeffs;
+ const float gainstep{hrtfparams->GainStep};
+ const float gain{hrtfparams->Gain};
+
+ ALsizei Delay[2]{
+ HRTF_HISTORY_LENGTH - hrtfparams->Delay[0],
+ HRTF_HISTORY_LENGTH - hrtfparams->Delay[1] };
+ ASSUME(Delay[0] >= 0 && Delay[1] >= 0);
+ float stepcount{0.0f};
+ for(size_t i{0u};i < BufferSize;++i)
+ {
+ const float g{gain + gainstep*stepcount};
+ const float left{InSamples[Delay[0]++] * g};
+ const float right{InSamples[Delay[1]++] * g};
+ ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, left, right);
+
+ stepcount += 1.0f;
+ }
+
+ hrtfparams->Gain = gain + gainstep*stepcount;
+}
+
+template<ApplyCoeffsT ApplyCoeffs>
+inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSamples,
+ const ALuint IrSize, const HrtfFilter *oldparams, MixHrtfFilter *newparams,
+ const size_t BufferSize)
+{
+ const auto &OldCoeffs = oldparams->Coeffs;
+ const float oldGain{oldparams->Gain};
+ const float oldGainStep{-oldGain / static_cast<float>(BufferSize)};
+ const auto &NewCoeffs = *newparams->Coeffs;
+ const float newGainStep{newparams->GainStep};
+
+ ASSUME(BufferSize > 0);
+
+ ALsizei Delay[2]{
+ HRTF_HISTORY_LENGTH - oldparams->Delay[0],
+ HRTF_HISTORY_LENGTH - oldparams->Delay[1] };
+ ASSUME(Delay[0] >= 0 && Delay[1] >= 0);
+ float stepcount{0.0f};
+ for(size_t i{0u};i < BufferSize;++i)
+ {
+ const float g{oldGain + oldGainStep*stepcount};
+ const float left{InSamples[Delay[0]++] * g};
+ const float right{InSamples[Delay[1]++] * g};
+ ApplyCoeffs(AccumSamples+i, IrSize, OldCoeffs, left, right);
+
+ stepcount += 1.0f;
+ }
+
+ Delay[0] = HRTF_HISTORY_LENGTH - newparams->Delay[0];
+ Delay[1] = HRTF_HISTORY_LENGTH - newparams->Delay[1];
+ ASSUME(Delay[0] >= 0 && Delay[1] >= 0);
+ stepcount = 0.0f;
+ for(size_t i{0u};i < BufferSize;++i)
+ {
+ const float g{newGainStep*stepcount};
+ const float left{InSamples[Delay[0]++] * g};
+ const float right{InSamples[Delay[1]++] * g};
+ ApplyCoeffs(AccumSamples+i, IrSize, NewCoeffs, left, right);
+
+ stepcount += 1.0f;
+ }
+
+ newparams->Gain = newGainStep*stepcount;
+}
+
+template<ApplyCoeffsT ApplyCoeffs>
+inline void MixDirectHrtfBase(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
+ const al::span<const FloatBufferLine> InSamples, float2 *RESTRICT AccumSamples,
+ DirectHrtfState *State, const size_t BufferSize)
+{
+ ASSUME(BufferSize > 0);
+
+ const ALuint IrSize{State->IrSize};
+
+ auto coeff_iter = State->Coeffs.begin();
+ for(const FloatBufferLine &input : InSamples)
+ {
+ const auto &Coeffs = *(coeff_iter++);
+ for(size_t i{0u};i < BufferSize;++i)
+ {
+ const float insample{input[i]};
+ ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, insample, insample);
+ }
+ }
+ for(size_t i{0u};i < BufferSize;++i)
+ LeftOut[i] += AccumSamples[i][0];
+ for(size_t i{0u};i < BufferSize;++i)
+ RightOut[i] += AccumSamples[i][1];
+
+ /* Copy the new in-progress accumulation values to the front and clear the
+ * following samples for the next mix.
+ */
+ auto accum_iter = std::copy_n(AccumSamples+BufferSize, HRIR_LENGTH, AccumSamples);
+ std::fill_n(accum_iter, BufferSize, float2{});
+}
+
+#endif /* MIXER_HRTFBASE_H */
diff --git a/alc/mixer/mixer_c.cpp b/alc/mixer/mixer_c.cpp
new file mode 100644
index 00000000..fad33746
--- /dev/null
+++ b/alc/mixer/mixer_c.cpp
@@ -0,0 +1,219 @@
+#include "config.h"
+
+#include <cassert>
+
+#include <limits>
+
+#include "alcmain.h"
+#include "alu.h"
+
+#include "defs.h"
+#include "hrtfbase.h"
+
+
+namespace {
+
+inline float do_point(const InterpState&, const float *RESTRICT vals, const ALuint)
+{ return vals[0]; }
+inline float do_lerp(const InterpState&, const float *RESTRICT vals, const ALuint frac)
+{ return lerp(vals[0], vals[1], static_cast<float>(frac)*(1.0f/FRACTIONONE)); }
+inline float do_cubic(const InterpState&, const float *RESTRICT vals, const ALuint frac)
+{ return cubic(vals[0], vals[1], vals[2], vals[3], static_cast<float>(frac)*(1.0f/FRACTIONONE)); }
+inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, const ALuint frac)
+{
+ const size_t m{istate.bsinc.m};
+
+ // Calculate the phase index and factor.
+#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
+ const ALuint pi{frac >> FRAC_PHASE_BITDIFF};
+ const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) *
+ (1.0f/(1<<FRAC_PHASE_BITDIFF))};
+#undef FRAC_PHASE_BITDIFF
+
+ const float *fil{istate.bsinc.filter + m*pi*4};
+ const float *phd{fil + m};
+ const float *scd{phd + m};
+ const float *spd{scd + m};
+
+ // Apply the scale and phase interpolated filter.
+ float r{0.0f};
+ for(size_t j_f{0};j_f < m;j_f++)
+ r += (fil[j_f] + istate.bsinc.sf*scd[j_f] + pf*(phd[j_f] + istate.bsinc.sf*spd[j_f])) * vals[j_f];
+ return r;
+}
+inline float do_fastbsinc(const InterpState &istate, const float *RESTRICT vals, const ALuint frac)
+{
+ const size_t m{istate.bsinc.m};
+
+ // Calculate the phase index and factor.
+#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
+ const ALuint pi{frac >> FRAC_PHASE_BITDIFF};
+ const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) *
+ (1.0f/(1<<FRAC_PHASE_BITDIFF))};
+#undef FRAC_PHASE_BITDIFF
+
+ const float *fil{istate.bsinc.filter + m*pi*4};
+ const float *phd{fil + m};
+
+ // Apply the phase interpolated filter.
+ float r{0.0f};
+ for(size_t j_f{0};j_f < m;j_f++)
+ r += (fil[j_f] + pf*phd[j_f]) * vals[j_f];
+ return r;
+}
+
+using SamplerT = float(&)(const InterpState&, const float*RESTRICT, const ALuint);
+template<SamplerT Sampler>
+const float *DoResample(const InterpState *state, const float *RESTRICT src, ALuint frac,
+ ALuint increment, const al::span<float> dst)
+{
+ const InterpState istate{*state};
+ auto proc_sample = [&src,&frac,istate,increment]() -> float
+ {
+ const float ret{Sampler(istate, src, frac)};
+
+ frac += increment;
+ src += frac>>FRACTIONBITS;
+ frac &= FRACTIONMASK;
+
+ return ret;
+ };
+ std::generate(dst.begin(), dst.end(), proc_sample);
+
+ return dst.begin();
+}
+
+inline void ApplyCoeffs(float2 *RESTRICT Values, const ALuint IrSize, const HrirArray &Coeffs,
+ const float left, const float right)
+{
+ ASSUME(IrSize >= 4);
+ for(ALuint c{0};c < IrSize;++c)
+ {
+ Values[c][0] += Coeffs[c][0] * left;
+ Values[c][1] += Coeffs[c][1] * right;
+ }
+}
+
+} // namespace
+
+template<>
+const ALfloat *Resample_<CopyTag,CTag>(const InterpState*, const ALfloat *RESTRICT src, ALuint,
+ ALuint, const al::span<float> dst)
+{
+#if defined(HAVE_SSE) || defined(HAVE_NEON)
+ /* Avoid copying the source data if it's aligned like the destination. */
+ if((reinterpret_cast<intptr_t>(src)&15) == (reinterpret_cast<intptr_t>(dst.data())&15))
+ return src;
+#endif
+ std::copy_n(src, dst.size(), dst.begin());
+ return dst.begin();
+}
+
+template<>
+const ALfloat *Resample_<PointTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src,
+ ALuint frac, ALuint increment, const al::span<float> dst)
+{ return DoResample<do_point>(state, src, frac, increment, dst); }
+
+template<>
+const ALfloat *Resample_<LerpTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src,
+ ALuint frac, ALuint increment, const al::span<float> dst)
+{ return DoResample<do_lerp>(state, src, frac, increment, dst); }
+
+template<>
+const ALfloat *Resample_<CubicTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src,
+ ALuint frac, ALuint increment, const al::span<float> dst)
+{ return DoResample<do_cubic>(state, src-1, frac, increment, dst); }
+
+template<>
+const ALfloat *Resample_<BSincTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src,
+ ALuint frac, ALuint increment, const al::span<float> dst)
+{ return DoResample<do_bsinc>(state, src-state->bsinc.l, frac, increment, dst); }
+
+template<>
+const ALfloat *Resample_<FastBSincTag,CTag>(const InterpState *state, const ALfloat *RESTRICT src,
+ ALuint frac, ALuint increment, const al::span<float> dst)
+{ return DoResample<do_fastbsinc>(state, src-state->bsinc.l, frac, increment, dst); }
+
+
+template<>
+void MixHrtf_<CTag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize,
+ MixHrtfFilter *hrtfparams, const size_t BufferSize)
+{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); }
+
+template<>
+void MixHrtfBlend_<CTag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize,
+ const HrtfFilter *oldparams, MixHrtfFilter *newparams, const size_t BufferSize)
+{
+ MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams,
+ BufferSize);
+}
+
+template<>
+void MixDirectHrtf_<CTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
+ const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State,
+ const size_t BufferSize)
+{ MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, State, BufferSize); }
+
+
+template<>
+void Mix_<CTag>(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer,
+ float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos)
+{
+ const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f};
+ const bool reached_target{InSamples.size() >= Counter};
+ const auto min_end = reached_target ? InSamples.begin() + Counter : InSamples.end();
+ for(FloatBufferLine &output : OutBuffer)
+ {
+ ALfloat *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)};
+ ALfloat gain{*CurrentGains};
+ const ALfloat diff{*TargetGains - gain};
+
+ auto in_iter = InSamples.begin();
+ if(std::fabs(diff) > std::numeric_limits<float>::epsilon())
+ {
+ const ALfloat step{diff * delta};
+ ALfloat step_count{0.0f};
+ while(in_iter != min_end)
+ {
+ *(dst++) += *(in_iter++) * (gain + step*step_count);
+ step_count += 1.0f;
+ }
+ if(reached_target)
+ gain = *TargetGains;
+ else
+ gain += step*step_count;
+ *CurrentGains = gain;
+ }
+ ++CurrentGains;
+ ++TargetGains;
+
+ if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
+ continue;
+ while(in_iter != InSamples.end())
+ *(dst++) += *(in_iter++) * gain;
+ }
+}
+
+/* Basically the inverse of the above. Rather than one input going to multiple
+ * outputs (each with its own gain), it's multiple inputs (each with its own
+ * gain) going to one output. This applies one row (vs one column) of a matrix
+ * transform. And as the matrices are more or less static once set up, no
+ * stepping is necessary.
+ */
+template<>
+void MixRow_<CTag>(const al::span<float> OutBuffer, const al::span<const float> Gains,
+ const float *InSamples, const size_t InStride)
+{
+ for(const float gain : Gains)
+ {
+ const float *RESTRICT input{InSamples};
+ InSamples += InStride;
+
+ if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
+ continue;
+
+ auto do_mix = [gain](const float cur, const float src) noexcept -> float
+ { return cur + src*gain; };
+ std::transform(OutBuffer.begin(), OutBuffer.end(), input, OutBuffer.begin(), do_mix);
+ }
+}
diff --git a/alc/mixer/mixer_neon.cpp b/alc/mixer/mixer_neon.cpp
new file mode 100644
index 00000000..67bf9c71
--- /dev/null
+++ b/alc/mixer/mixer_neon.cpp
@@ -0,0 +1,324 @@
+#include "config.h"
+
+#include <arm_neon.h>
+
+#include <limits>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "alcmain.h"
+#include "alu.h"
+#include "hrtf.h"
+#include "defs.h"
+#include "hrtfbase.h"
+
+
+namespace {
+
+inline void ApplyCoeffs(float2 *RESTRICT Values, const ALuint IrSize, const HrirArray &Coeffs,
+ const float left, const float right)
+{
+ float32x4_t leftright4;
+ {
+ float32x2_t leftright2 = vdup_n_f32(0.0);
+ leftright2 = vset_lane_f32(left, leftright2, 0);
+ leftright2 = vset_lane_f32(right, leftright2, 1);
+ leftright4 = vcombine_f32(leftright2, leftright2);
+ }
+
+ ASSUME(IrSize >= 4);
+ for(ALuint c{0};c < IrSize;c += 2)
+ {
+ float32x4_t vals = vld1q_f32(&Values[c][0]);
+ float32x4_t coefs = vld1q_f32(&Coeffs[c][0]);
+
+ vals = vmlaq_f32(vals, coefs, leftright4);
+
+ vst1q_f32(&Values[c][0], vals);
+ }
+}
+
+} // namespace
+
+template<>
+const ALfloat *Resample_<LerpTag,NEONTag>(const InterpState*, const ALfloat *RESTRICT src,
+ ALuint frac, ALuint increment, const al::span<float> dst)
+{
+ const int32x4_t increment4 = vdupq_n_s32(static_cast<int>(increment*4));
+ const float32x4_t fracOne4 = vdupq_n_f32(1.0f/FRACTIONONE);
+ const int32x4_t fracMask4 = vdupq_n_s32(FRACTIONMASK);
+ alignas(16) ALuint pos_[4], frac_[4];
+ int32x4_t pos4, frac4;
+
+ InitPosArrays(frac, increment, frac_, pos_, 4);
+ frac4 = vld1q_s32(reinterpret_cast<int*>(frac_));
+ pos4 = vld1q_s32(reinterpret_cast<int*>(pos_));
+
+ auto dst_iter = dst.begin();
+ const auto aligned_end = (dst.size()&~3u) + dst_iter;
+ while(dst_iter != aligned_end)
+ {
+ const int pos0{vgetq_lane_s32(pos4, 0)};
+ const int pos1{vgetq_lane_s32(pos4, 1)};
+ const int pos2{vgetq_lane_s32(pos4, 2)};
+ const int pos3{vgetq_lane_s32(pos4, 3)};
+ const float32x4_t val1{src[pos0], src[pos1], src[pos2], src[pos3]};
+ const float32x4_t val2{src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1]};
+
+ /* val1 + (val2-val1)*mu */
+ const float32x4_t r0{vsubq_f32(val2, val1)};
+ const float32x4_t mu{vmulq_f32(vcvtq_f32_s32(frac4), fracOne4)};
+ const float32x4_t out{vmlaq_f32(val1, mu, r0)};
+
+ vst1q_f32(dst_iter, out);
+ dst_iter += 4;
+
+ frac4 = vaddq_s32(frac4, increment4);
+ pos4 = vaddq_s32(pos4, vshrq_n_s32(frac4, FRACTIONBITS));
+ frac4 = vandq_s32(frac4, fracMask4);
+ }
+
+ if(dst_iter != dst.end())
+ {
+ src += static_cast<ALuint>(vgetq_lane_s32(pos4, 0));
+ frac = static_cast<ALuint>(vgetq_lane_s32(frac4, 0));
+
+ do {
+ *(dst_iter++) = lerp(src[0], src[1], static_cast<float>(frac) * (1.0f/FRACTIONONE));
+
+ frac += increment;
+ src += frac>>FRACTIONBITS;
+ frac &= FRACTIONMASK;
+ } while(dst_iter != dst.end());
+ }
+ return dst.begin();
+}
+
+template<>
+const ALfloat *Resample_<BSincTag,NEONTag>(const InterpState *state, const ALfloat *RESTRICT src,
+ ALuint frac, ALuint increment, const al::span<float> dst)
+{
+ const float *const filter{state->bsinc.filter};
+ const float32x4_t sf4{vdupq_n_f32(state->bsinc.sf)};
+ const size_t m{state->bsinc.m};
+
+ src -= state->bsinc.l;
+ for(float &out_sample : dst)
+ {
+ // Calculate the phase index and factor.
+#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
+ const ALuint pi{frac >> FRAC_PHASE_BITDIFF};
+ const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) *
+ (1.0f/(1<<FRAC_PHASE_BITDIFF))};
+#undef FRAC_PHASE_BITDIFF
+
+ // Apply the scale and phase interpolated filter.
+ float32x4_t r4{vdupq_n_f32(0.0f)};
+ {
+ const float32x4_t pf4{vdupq_n_f32(pf)};
+ const float *fil{filter + m*pi*4};
+ const float *phd{fil + m};
+ const float *scd{phd + m};
+ const float *spd{scd + m};
+ size_t td{m >> 2};
+ size_t j{0u};
+
+ do {
+ /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */
+ const float32x4_t f4 = vmlaq_f32(
+ vmlaq_f32(vld1q_f32(fil), sf4, vld1q_f32(scd)),
+ pf4, vmlaq_f32(vld1q_f32(phd), sf4, vld1q_f32(spd)));
+ fil += 4; scd += 4; phd += 4; spd += 4;
+ /* r += f*src */
+ r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j]));
+ j += 4;
+ } while(--td);
+ }
+ r4 = vaddq_f32(r4, vrev64q_f32(r4));
+ out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
+
+ frac += increment;
+ src += frac>>FRACTIONBITS;
+ frac &= FRACTIONMASK;
+ }
+ return dst.begin();
+}
+
+template<>
+const ALfloat *Resample_<FastBSincTag,NEONTag>(const InterpState *state,
+ const ALfloat *RESTRICT src, ALuint frac, ALuint increment, const al::span<float> dst)
+{
+ const float *const filter{state->bsinc.filter};
+ const size_t m{state->bsinc.m};
+
+ src -= state->bsinc.l;
+ for(float &out_sample : dst)
+ {
+ // Calculate the phase index and factor.
+#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
+ const ALuint pi{frac >> FRAC_PHASE_BITDIFF};
+ const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) *
+ (1.0f/(1<<FRAC_PHASE_BITDIFF))};
+#undef FRAC_PHASE_BITDIFF
+
+ // Apply the phase interpolated filter.
+ float32x4_t r4{vdupq_n_f32(0.0f)};
+ {
+ const float32x4_t pf4{vdupq_n_f32(pf)};
+ const float *fil{filter + m*pi*4};
+ const float *phd{fil + m};
+ size_t td{m >> 2};
+ size_t j{0u};
+
+ do {
+ /* f = fil + pf*phd */
+ const float32x4_t f4 = vmlaq_f32(vld1q_f32(fil), pf4, vld1q_f32(phd));
+ /* r += f*src */
+ r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j]));
+ fil += 4; phd += 4; j += 4;
+ } while(--td);
+ }
+ r4 = vaddq_f32(r4, vrev64q_f32(r4));
+ out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
+
+ frac += increment;
+ src += frac>>FRACTIONBITS;
+ frac &= FRACTIONMASK;
+ }
+ return dst.begin();
+}
+
+
+template<>
+void MixHrtf_<NEONTag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize,
+ MixHrtfFilter *hrtfparams, const size_t BufferSize)
+{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); }
+
+template<>
+void MixHrtfBlend_<NEONTag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize,
+ const HrtfFilter *oldparams, MixHrtfFilter *newparams, const size_t BufferSize)
+{
+ MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams,
+ BufferSize);
+}
+
+template<>
+void MixDirectHrtf_<NEONTag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
+ const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State,
+ const size_t BufferSize)
+{ MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, State, BufferSize); }
+
+
+template<>
+void Mix_<NEONTag>(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer,
+ float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos)
+{
+ const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f};
+ const bool reached_target{InSamples.size() >= Counter};
+ const auto min_end = reached_target ? InSamples.begin() + Counter : InSamples.end();
+ const auto aligned_end = minz(static_cast<uintptr_t>(min_end-InSamples.begin()+3) & ~3u,
+ InSamples.size()) + InSamples.begin();
+ for(FloatBufferLine &output : OutBuffer)
+ {
+ ALfloat *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)};
+ ALfloat gain{*CurrentGains};
+ const ALfloat diff{*TargetGains - gain};
+
+ auto in_iter = InSamples.begin();
+ if(std::fabs(diff) > std::numeric_limits<float>::epsilon())
+ {
+ const ALfloat step{diff * delta};
+ ALfloat step_count{0.0f};
+ /* Mix with applying gain steps in aligned multiples of 4. */
+ if(ptrdiff_t todo{(min_end-in_iter) >> 2})
+ {
+ const float32x4_t four4{vdupq_n_f32(4.0f)};
+ const float32x4_t step4{vdupq_n_f32(step)};
+ const float32x4_t gain4{vdupq_n_f32(gain)};
+ float32x4_t step_count4{vsetq_lane_f32(0.0f,
+ vsetq_lane_f32(1.0f,
+ vsetq_lane_f32(2.0f,
+ vsetq_lane_f32(3.0f, vdupq_n_f32(0.0f), 3),
+ 2), 1), 0
+ )};
+ do {
+ const float32x4_t val4 = vld1q_f32(in_iter);
+ float32x4_t dry4 = vld1q_f32(dst);
+ dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4));
+ step_count4 = vaddq_f32(step_count4, four4);
+ vst1q_f32(dst, dry4);
+ in_iter += 4; dst += 4;
+ } while(--todo);
+ /* NOTE: step_count4 now represents the next four counts after
+ * the last four mixed samples, so the lowest element
+ * represents the next step count to apply.
+ */
+ step_count = vgetq_lane_f32(step_count4, 0);
+ }
+ /* Mix with applying left over gain steps that aren't aligned multiples of 4. */
+ while(in_iter != min_end)
+ {
+ *(dst++) += *(in_iter++) * (gain + step*step_count);
+ step_count += 1.0f;
+ }
+ if(reached_target)
+ gain = *TargetGains;
+ else
+ gain += step*step_count;
+ *CurrentGains = gain;
+
+ /* Mix until pos is aligned with 4 or the mix is done. */
+ while(in_iter != aligned_end)
+ *(dst++) += *(in_iter++) * gain;
+ }
+ ++CurrentGains;
+ ++TargetGains;
+
+ if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
+ continue;
+ if(ptrdiff_t todo{(InSamples.end()-in_iter) >> 2})
+ {
+ const float32x4_t gain4 = vdupq_n_f32(gain);
+ do {
+ const float32x4_t val4 = vld1q_f32(in_iter);
+ float32x4_t dry4 = vld1q_f32(dst);
+ dry4 = vmlaq_f32(dry4, val4, gain4);
+ vst1q_f32(dst, dry4);
+ in_iter += 4; dst += 4;
+ } while(--todo);
+ }
+ while(in_iter != InSamples.end())
+ *(dst++) += *(in_iter++) * gain;
+ }
+}
+
+template<>
+void MixRow_<NEONTag>(const al::span<float> OutBuffer, const al::span<const float> Gains,
+ const float *InSamples, const size_t InStride)
+{
+ for(const ALfloat gain : Gains)
+ {
+ const ALfloat *RESTRICT input{InSamples};
+ InSamples += InStride;
+
+ if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
+ continue;
+
+ auto out_iter = OutBuffer.begin();
+ if(size_t todo{OutBuffer.size() >> 2})
+ {
+ const float32x4_t gain4{vdupq_n_f32(gain)};
+ do {
+ const float32x4_t val4 = vld1q_f32(input);
+ float32x4_t dry4 = vld1q_f32(out_iter);
+ dry4 = vmlaq_f32(dry4, val4, gain4);
+ vst1q_f32(out_iter, dry4);
+ out_iter += 4; input += 4;
+ } while(--todo);
+ }
+
+ auto do_mix = [gain](const float cur, const float src) noexcept -> float
+ { return cur + src*gain; };
+ std::transform(out_iter, OutBuffer.end(), input, out_iter, do_mix);
+ }
+}
diff --git a/alc/mixer/mixer_sse.cpp b/alc/mixer/mixer_sse.cpp
new file mode 100644
index 00000000..aaf37df6
--- /dev/null
+++ b/alc/mixer/mixer_sse.cpp
@@ -0,0 +1,297 @@
+#include "config.h"
+
+#include <xmmintrin.h>
+
+#include <limits>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "alcmain.h"
+
+#include "alu.h"
+#include "defs.h"
+#include "hrtfbase.h"
+
+
+namespace {
+
+inline void ApplyCoeffs(float2 *RESTRICT Values, const ALuint IrSize, const HrirArray &Coeffs,
+ const float left, const float right)
+{
+ const __m128 lrlr{_mm_setr_ps(left, right, left, right)};
+
+ ASSUME(IrSize >= 4);
+ /* This isn't technically correct to test alignment, but it's true for
+ * systems that support SSE, which is the only one that needs to know the
+ * alignment of Values (which alternates between 8- and 16-byte aligned).
+ */
+ if(reinterpret_cast<intptr_t>(Values)&0x8)
+ {
+ __m128 imp0, imp1;
+ __m128 coeffs{_mm_load_ps(&Coeffs[0][0])};
+ __m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(&Values[0][0]))};
+ imp0 = _mm_mul_ps(lrlr, coeffs);
+ vals = _mm_add_ps(imp0, vals);
+ _mm_storel_pi(reinterpret_cast<__m64*>(&Values[0][0]), vals);
+ ALuint i{1};
+ for(;i < IrSize-1;i += 2)
+ {
+ coeffs = _mm_load_ps(&Coeffs[i+1][0]);
+ vals = _mm_load_ps(&Values[i][0]);
+ imp1 = _mm_mul_ps(lrlr, coeffs);
+ imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2));
+ vals = _mm_add_ps(imp0, vals);
+ _mm_store_ps(&Values[i][0], vals);
+ imp0 = imp1;
+ }
+ vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(&Values[i][0]));
+ imp0 = _mm_movehl_ps(imp0, imp0);
+ vals = _mm_add_ps(imp0, vals);
+ _mm_storel_pi(reinterpret_cast<__m64*>(&Values[i][0]), vals);
+ }
+ else
+ {
+ for(ALuint i{0};i < IrSize;i += 2)
+ {
+ __m128 coeffs{_mm_load_ps(&Coeffs[i][0])};
+ __m128 vals{_mm_load_ps(&Values[i][0])};
+ vals = _mm_add_ps(vals, _mm_mul_ps(lrlr, coeffs));
+ _mm_store_ps(&Values[i][0], vals);
+ }
+ }
+}
+
+} // namespace
+
+template<>
+const ALfloat *Resample_<BSincTag,SSETag>(const InterpState *state, const ALfloat *RESTRICT src,
+ ALuint frac, ALuint increment, const al::span<float> dst)
+{
+ const float *const filter{state->bsinc.filter};
+ const __m128 sf4{_mm_set1_ps(state->bsinc.sf)};
+ const size_t m{state->bsinc.m};
+
+ src -= state->bsinc.l;
+ for(float &out_sample : dst)
+ {
+ // Calculate the phase index and factor.
+#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
+ const ALuint pi{frac >> FRAC_PHASE_BITDIFF};
+ const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) *
+ (1.0f/(1<<FRAC_PHASE_BITDIFF))};
+#undef FRAC_PHASE_BITDIFF
+
+ // Apply the scale and phase interpolated filter.
+ __m128 r4{_mm_setzero_ps()};
+ {
+ const __m128 pf4{_mm_set1_ps(pf)};
+ const float *fil{filter + m*pi*4};
+ const float *phd{fil + m};
+ const float *scd{phd + m};
+ const float *spd{scd + m};
+ size_t td{m >> 2};
+ size_t j{0u};
+
+#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z))
+ do {
+ /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */
+ const __m128 f4 = MLA4(
+ MLA4(_mm_load_ps(fil), sf4, _mm_load_ps(scd)),
+ pf4, MLA4(_mm_load_ps(phd), sf4, _mm_load_ps(spd)));
+ fil += 4; scd += 4; phd += 4; spd += 4;
+ /* r += f*src */
+ r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j]));
+ j += 4;
+ } while(--td);
+#undef MLA4
+ }
+ 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));
+ out_sample = _mm_cvtss_f32(r4);
+
+ frac += increment;
+ src += frac>>FRACTIONBITS;
+ frac &= FRACTIONMASK;
+ }
+ return dst.begin();
+}
+
+template<>
+const ALfloat *Resample_<FastBSincTag,SSETag>(const InterpState *state,
+ const ALfloat *RESTRICT src, ALuint frac, ALuint increment, const al::span<float> dst)
+{
+ const float *const filter{state->bsinc.filter};
+ const size_t m{state->bsinc.m};
+
+ src -= state->bsinc.l;
+ for(float &out_sample : dst)
+ {
+ // Calculate the phase index and factor.
+#define FRAC_PHASE_BITDIFF (FRACTIONBITS-BSINC_PHASE_BITS)
+ const ALuint pi{frac >> FRAC_PHASE_BITDIFF};
+ const float pf{static_cast<float>(frac & ((1<<FRAC_PHASE_BITDIFF)-1)) *
+ (1.0f/(1<<FRAC_PHASE_BITDIFF))};
+#undef FRAC_PHASE_BITDIFF
+
+ // Apply the phase interpolated filter.
+ __m128 r4{_mm_setzero_ps()};
+ {
+ const __m128 pf4{_mm_set1_ps(pf)};
+ const float *fil{filter + m*pi*4};
+ const float *phd{fil + m};
+ size_t td{m >> 2};
+ size_t j{0u};
+
+#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z))
+ do {
+ /* f = fil + pf*phd */
+ const __m128 f4 = MLA4(_mm_load_ps(fil), pf4, _mm_load_ps(phd));
+ /* r += f*src */
+ r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j]));
+ fil += 4; phd += 4; j += 4;
+ } while(--td);
+#undef MLA4
+ }
+ 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));
+ out_sample = _mm_cvtss_f32(r4);
+
+ frac += increment;
+ src += frac>>FRACTIONBITS;
+ frac &= FRACTIONMASK;
+ }
+ return dst.begin();
+}
+
+
+template<>
+void MixHrtf_<SSETag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize,
+ MixHrtfFilter *hrtfparams, const size_t BufferSize)
+{ MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); }
+
+template<>
+void MixHrtfBlend_<SSETag>(const float *InSamples, float2 *AccumSamples, const ALuint IrSize,
+ const HrtfFilter *oldparams, MixHrtfFilter *newparams, const size_t BufferSize)
+{
+ MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams,
+ BufferSize);
+}
+
+template<>
+void MixDirectHrtf_<SSETag>(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
+ const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State,
+ const size_t BufferSize)
+{ MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, State, BufferSize); }
+
+
+template<>
+void Mix_<SSETag>(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer,
+ float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos)
+{
+ const ALfloat delta{(Counter > 0) ? 1.0f / static_cast<ALfloat>(Counter) : 0.0f};
+ const bool reached_target{InSamples.size() >= Counter};
+ const auto min_end = reached_target ? InSamples.begin() + Counter : InSamples.end();
+ const auto aligned_end = minz(static_cast<uintptr_t>(min_end-InSamples.begin()+3) & ~3u,
+ InSamples.size()) + InSamples.begin();
+ for(FloatBufferLine &output : OutBuffer)
+ {
+ ALfloat *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)};
+ ALfloat gain{*CurrentGains};
+ const ALfloat diff{*TargetGains - gain};
+
+ auto in_iter = InSamples.begin();
+ if(std::fabs(diff) > std::numeric_limits<float>::epsilon())
+ {
+ const ALfloat step{diff * delta};
+ ALfloat step_count{0.0f};
+ /* Mix with applying gain steps in aligned multiples of 4. */
+ if(ptrdiff_t todo{(min_end-in_iter) >> 2})
+ {
+ const __m128 four4{_mm_set1_ps(4.0f)};
+ const __m128 step4{_mm_set1_ps(step)};
+ const __m128 gain4{_mm_set1_ps(gain)};
+ __m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)};
+ do {
+ const __m128 val4{_mm_load_ps(in_iter)};
+ __m128 dry4{_mm_load_ps(dst)};
+#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z))
+ /* dry += val * (gain + step*step_count) */
+ dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4));
+#undef MLA4
+ _mm_store_ps(dst, dry4);
+ step_count4 = _mm_add_ps(step_count4, four4);
+ in_iter += 4; dst += 4;
+ } while(--todo);
+ /* NOTE: step_count4 now represents the next four counts after
+ * the last four mixed samples, so the lowest element
+ * represents the next step count to apply.
+ */
+ step_count = _mm_cvtss_f32(step_count4);
+ }
+ /* Mix with applying left over gain steps that aren't aligned multiples of 4. */
+ while(in_iter != min_end)
+ {
+ *(dst++) += *(in_iter++) * (gain + step*step_count);
+ step_count += 1.0f;
+ }
+ if(reached_target)
+ gain = *TargetGains;
+ else
+ gain += step*step_count;
+ *CurrentGains = gain;
+
+ /* Mix until pos is aligned with 4 or the mix is done. */
+ while(in_iter != aligned_end)
+ *(dst++) += *(in_iter++) * gain;
+ }
+ ++CurrentGains;
+ ++TargetGains;
+
+ if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
+ continue;
+ if(ptrdiff_t todo{(InSamples.end()-in_iter) >> 2})
+ {
+ const __m128 gain4{_mm_set1_ps(gain)};
+ do {
+ const __m128 val4{_mm_load_ps(in_iter)};
+ __m128 dry4{_mm_load_ps(dst)};
+ dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4));
+ _mm_store_ps(dst, dry4);
+ in_iter += 4; dst += 4;
+ } while(--todo);
+ }
+ while(in_iter != InSamples.end())
+ *(dst++) += *(in_iter++) * gain;
+ }
+}
+
+template<>
+void MixRow_<SSETag>(const al::span<float> OutBuffer, const al::span<const float> Gains,
+ const float *InSamples, const size_t InStride)
+{
+ for(const float gain : Gains)
+ {
+ const float *RESTRICT input{InSamples};
+ InSamples += InStride;
+
+ if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
+ continue;
+
+ auto out_iter = OutBuffer.begin();
+ if(size_t todo{OutBuffer.size() >> 2})
+ {
+ const __m128 gain4 = _mm_set1_ps(gain);
+ do {
+ const __m128 val4{_mm_load_ps(input)};
+ __m128 dry4{_mm_load_ps(out_iter)};
+ dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4));
+ _mm_store_ps(out_iter, dry4);
+ out_iter += 4; input += 4;
+ } while(--todo);
+ }
+
+ auto do_mix = [gain](const float cur, const float src) noexcept -> float
+ { return cur + src*gain; };
+ std::transform(out_iter, OutBuffer.end(), input, out_iter, do_mix);
+ }
+}
diff --git a/alc/mixer/mixer_sse2.cpp b/alc/mixer/mixer_sse2.cpp
new file mode 100644
index 00000000..897cd1f7
--- /dev/null
+++ b/alc/mixer/mixer_sse2.cpp
@@ -0,0 +1,83 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2014 by Timothy Arceri <[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 <xmmintrin.h>
+#include <emmintrin.h>
+
+#include "alu.h"
+#include "defs.h"
+
+
+template<>
+const ALfloat *Resample_<LerpTag,SSE2Tag>(const InterpState*, const ALfloat *RESTRICT src,
+ ALuint frac, ALuint increment, const al::span<float> dst)
+{
+ const __m128i increment4{_mm_set1_epi32(static_cast<int>(increment*4))};
+ const __m128 fracOne4{_mm_set1_ps(1.0f/FRACTIONONE)};
+ const __m128i fracMask4{_mm_set1_epi32(FRACTIONMASK)};
+
+ alignas(16) ALuint pos_[4], frac_[4];
+ InitPosArrays(frac, increment, frac_, pos_, 4);
+ __m128i frac4{_mm_setr_epi32(static_cast<int>(frac_[0]), static_cast<int>(frac_[1]),
+ static_cast<int>(frac_[2]), static_cast<int>(frac_[3]))};
+ __m128i pos4{_mm_setr_epi32(static_cast<int>(pos_[0]), static_cast<int>(pos_[1]),
+ static_cast<int>(pos_[2]), static_cast<int>(pos_[3]))};
+
+ auto dst_iter = dst.begin();
+ const auto aligned_end = (dst.size()&~3u) + dst_iter;
+ while(dst_iter != aligned_end)
+ {
+ const int pos0{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(0, 0, 0, 0)))};
+ const int pos1{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(1, 1, 1, 1)))};
+ const int pos2{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(2, 2, 2, 2)))};
+ const int pos3{_mm_cvtsi128_si32(_mm_shuffle_epi32(pos4, _MM_SHUFFLE(3, 3, 3, 3)))};
+ const __m128 val1{_mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ])};
+ const __m128 val2{_mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])};
+
+ /* val1 + (val2-val1)*mu */
+ const __m128 r0{_mm_sub_ps(val2, val1)};
+ const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)};
+ const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))};
+
+ _mm_store_ps(dst_iter, out);
+ dst_iter += 4;
+
+ frac4 = _mm_add_epi32(frac4, increment4);
+ pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS));
+ frac4 = _mm_and_si128(frac4, fracMask4);
+ }
+
+ if(dst_iter != dst.end())
+ {
+ src += static_cast<ALuint>(_mm_cvtsi128_si32(pos4));
+ frac = static_cast<ALuint>(_mm_cvtsi128_si32(frac4));
+
+ do {
+ *(dst_iter++) = lerp(src[0], src[1], static_cast<float>(frac) * (1.0f/FRACTIONONE));
+
+ frac += increment;
+ src += frac>>FRACTIONBITS;
+ frac &= FRACTIONMASK;
+ } while(dst_iter != dst.end());
+ }
+ return dst.begin();
+}
diff --git a/Alc/mixer/mixer_sse3.c b/alc/mixer/mixer_sse3.cpp
index e69de29b..e69de29b 100644
--- a/Alc/mixer/mixer_sse3.c
+++ b/alc/mixer/mixer_sse3.cpp
diff --git a/alc/mixer/mixer_sse41.cpp b/alc/mixer/mixer_sse41.cpp
new file mode 100644
index 00000000..cfa21e99
--- /dev/null
+++ b/alc/mixer/mixer_sse41.cpp
@@ -0,0 +1,88 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 2014 by Timothy Arceri <[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 <xmmintrin.h>
+#include <emmintrin.h>
+#include <smmintrin.h>
+
+#include "alu.h"
+#include "defs.h"
+
+
+template<>
+const ALfloat *Resample_<LerpTag,SSE4Tag>(const InterpState*, const ALfloat *RESTRICT src,
+ ALuint frac, ALuint increment, const al::span<float> dst)
+{
+ const __m128i increment4{_mm_set1_epi32(static_cast<int>(increment*4))};
+ const __m128 fracOne4{_mm_set1_ps(1.0f/FRACTIONONE)};
+ const __m128i fracMask4{_mm_set1_epi32(FRACTIONMASK)};
+
+ alignas(16) ALuint pos_[4], frac_[4];
+ InitPosArrays(frac, increment, frac_, pos_, 4);
+ __m128i frac4{_mm_setr_epi32(static_cast<int>(frac_[0]), static_cast<int>(frac_[1]),
+ static_cast<int>(frac_[2]), static_cast<int>(frac_[3]))};
+ __m128i pos4{_mm_setr_epi32(static_cast<int>(pos_[0]), static_cast<int>(pos_[1]),
+ static_cast<int>(pos_[2]), static_cast<int>(pos_[3]))};
+
+ auto dst_iter = dst.begin();
+ const auto aligned_end = (dst.size()&~3u) + dst_iter;
+ while(dst_iter != aligned_end)
+ {
+ const int pos0{_mm_extract_epi32(pos4, 0)};
+ const int pos1{_mm_extract_epi32(pos4, 1)};
+ const int pos2{_mm_extract_epi32(pos4, 2)};
+ const int pos3{_mm_extract_epi32(pos4, 3)};
+ const __m128 val1{_mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ])};
+ const __m128 val2{_mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])};
+
+ /* val1 + (val2-val1)*mu */
+ const __m128 r0{_mm_sub_ps(val2, val1)};
+ const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)};
+ const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))};
+
+ _mm_store_ps(dst_iter, out);
+ dst_iter += 4;
+
+ frac4 = _mm_add_epi32(frac4, increment4);
+ pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS));
+ frac4 = _mm_and_si128(frac4, fracMask4);
+ }
+
+ if(dst_iter != dst.end())
+ {
+ /* NOTE: These four elements represent the position *after* the last
+ * four samples, so the lowest element is the next position to
+ * resample.
+ */
+ src += static_cast<ALuint>(_mm_cvtsi128_si32(pos4));
+ frac = static_cast<ALuint>(_mm_cvtsi128_si32(frac4));
+
+ do {
+ *(dst_iter++) = lerp(src[0], src[1], static_cast<float>(frac) * (1.0f/FRACTIONONE));
+
+ frac += increment;
+ src += frac>>FRACTIONBITS;
+ frac &= FRACTIONMASK;
+ } while(dst_iter != dst.end());
+ }
+ return dst.begin();
+}
diff --git a/alc/panning.cpp b/alc/panning.cpp
new file mode 100644
index 00000000..e85222bd
--- /dev/null
+++ b/alc/panning.cpp
@@ -0,0 +1,1021 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 1999-2010 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 <algorithm>
+#include <array>
+#include <chrono>
+#include <cmath>
+#include <cstdio>
+#include <cstring>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <new>
+#include <numeric>
+#include <string>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+#include "AL/alext.h"
+
+#include "al/auxeffectslot.h"
+#include "alcmain.h"
+#include "alconfig.h"
+#include "almalloc.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alspan.h"
+#include "alstring.h"
+#include "alu.h"
+#include "ambdec.h"
+#include "ambidefs.h"
+#include "bformatdec.h"
+#include "bs2b.h"
+#include "devformat.h"
+#include "hrtf.h"
+#include "logging.h"
+#include "math_defs.h"
+#include "opthelpers.h"
+#include "uhjfilter.h"
+
+
+constexpr std::array<float,MAX_AMBI_CHANNELS> AmbiScale::FromN3D;
+constexpr std::array<float,MAX_AMBI_CHANNELS> AmbiScale::FromSN3D;
+constexpr std::array<float,MAX_AMBI_CHANNELS> AmbiScale::FromFuMa;
+constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> AmbiIndex::FromFuMa;
+constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> AmbiIndex::FromACN;
+constexpr std::array<uint8_t,MAX_AMBI2D_CHANNELS> AmbiIndex::From2D;
+constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> AmbiIndex::From3D;
+
+
+namespace {
+
+using namespace std::placeholders;
+using std::chrono::seconds;
+using std::chrono::nanoseconds;
+
+inline const char *GetLabelFromChannel(Channel channel)
+{
+ switch(channel)
+ {
+ case FrontLeft: return "front-left";
+ case FrontRight: return "front-right";
+ case FrontCenter: return "front-center";
+ case LFE: return "lfe";
+ case BackLeft: return "back-left";
+ case BackRight: return "back-right";
+ case BackCenter: return "back-center";
+ case SideLeft: return "side-left";
+ case SideRight: return "side-right";
+
+ case UpperFrontLeft: return "upper-front-left";
+ case UpperFrontRight: return "upper-front-right";
+ case UpperBackLeft: return "upper-back-left";
+ case UpperBackRight: return "upper-back-right";
+ case LowerFrontLeft: return "lower-front-left";
+ case LowerFrontRight: return "lower-front-right";
+ case LowerBackLeft: return "lower-back-left";
+ case LowerBackRight: return "lower-back-right";
+
+ case Aux0: return "aux-0";
+ case Aux1: return "aux-1";
+ case Aux2: return "aux-2";
+ case Aux3: return "aux-3";
+ case Aux4: return "aux-4";
+ case Aux5: return "aux-5";
+ case Aux6: return "aux-6";
+ case Aux7: return "aux-7";
+ case Aux8: return "aux-8";
+ case Aux9: return "aux-9";
+ case Aux10: return "aux-10";
+ case Aux11: return "aux-11";
+ case Aux12: return "aux-12";
+ case Aux13: return "aux-13";
+ case Aux14: return "aux-14";
+ case Aux15: return "aux-15";
+
+ case MaxChannels: break;
+ }
+ return "(unknown)";
+}
+
+
+void AllocChannels(ALCdevice *device, const ALuint main_chans, const ALuint real_chans)
+{
+ TRACE("Channel config, Main: %u, Real: %u\n", main_chans, real_chans);
+
+ /* Allocate extra channels for any post-filter output. */
+ const ALuint num_chans{main_chans + real_chans};
+
+ TRACE("Allocating %u channels, %zu bytes\n", num_chans,
+ num_chans*sizeof(device->MixBuffer[0]));
+ device->MixBuffer.resize(num_chans);
+ al::span<FloatBufferLine> buffer{device->MixBuffer.data(), device->MixBuffer.size()};
+
+ device->Dry.Buffer = buffer.first(main_chans);
+ buffer = buffer.subspan(main_chans);
+ if(real_chans != 0)
+ {
+ device->RealOut.Buffer = buffer.first(real_chans);
+ buffer = buffer.subspan(real_chans);
+ }
+ else
+ device->RealOut.Buffer = device->Dry.Buffer;
+}
+
+
+struct ChannelMap {
+ Channel ChanName;
+ ALfloat Config[MAX_AMBI2D_CHANNELS];
+};
+
+bool MakeSpeakerMap(ALCdevice *device, const AmbDecConf *conf, ALuint (&speakermap)[MAX_OUTPUT_CHANNELS])
+{
+ auto map_spkr = [device](const AmbDecConf::SpeakerConf &speaker) -> ALuint
+ {
+ /* NOTE: AmbDec does not define any standard speaker names, however
+ * for this to work we have to by able to find the output channel
+ * the speaker definition corresponds to. Therefore, OpenAL Soft
+ * requires these channel labels to be recognized:
+ *
+ * LF = Front left
+ * RF = Front right
+ * LS = Side left
+ * RS = Side right
+ * LB = Back left
+ * RB = Back right
+ * CE = Front center
+ * CB = Back center
+ *
+ * Additionally, surround51 will acknowledge back speakers for side
+ * channels, and surround51rear will acknowledge side speakers for
+ * back channels, to avoid issues with an ambdec expecting 5.1 to
+ * use the side channels when the device is configured for back,
+ * and vice-versa.
+ */
+ Channel ch{};
+ if(speaker.Name == "LF")
+ ch = FrontLeft;
+ else if(speaker.Name == "RF")
+ ch = FrontRight;
+ else if(speaker.Name == "CE")
+ ch = FrontCenter;
+ else if(speaker.Name == "LS")
+ {
+ if(device->FmtChans == DevFmtX51Rear)
+ ch = BackLeft;
+ else
+ ch = SideLeft;
+ }
+ else if(speaker.Name == "RS")
+ {
+ if(device->FmtChans == DevFmtX51Rear)
+ ch = BackRight;
+ else
+ ch = SideRight;
+ }
+ else if(speaker.Name == "LB")
+ {
+ if(device->FmtChans == DevFmtX51)
+ ch = SideLeft;
+ else
+ ch = BackLeft;
+ }
+ else if(speaker.Name == "RB")
+ {
+ if(device->FmtChans == DevFmtX51)
+ ch = SideRight;
+ else
+ ch = BackRight;
+ }
+ else if(speaker.Name == "CB")
+ ch = BackCenter;
+ else
+ {
+ const char *name{speaker.Name.c_str()};
+ unsigned int n;
+ char c;
+
+ if(sscanf(name, "AUX%u%c", &n, &c) == 1 && n < 16)
+ ch = static_cast<Channel>(Aux0+n);
+ else
+ {
+ ERR("AmbDec speaker label \"%s\" not recognized\n", name);
+ return INVALID_CHANNEL_INDEX;
+ }
+ }
+ const ALuint chidx{GetChannelIdxByName(device->RealOut, ch)};
+ if(chidx == INVALID_CHANNEL_INDEX)
+ ERR("Failed to lookup AmbDec speaker label %s\n", speaker.Name.c_str());
+ return chidx;
+ };
+ std::transform(conf->Speakers.begin(), conf->Speakers.end(), std::begin(speakermap), map_spkr);
+ /* Return success if no invalid entries are found. */
+ auto spkrmap_end = std::begin(speakermap) + conf->Speakers.size();
+ return std::find(std::begin(speakermap), spkrmap_end, INVALID_CHANNEL_INDEX) == spkrmap_end;
+}
+
+
+constexpr ChannelMap MonoCfg[1] = {
+ { FrontCenter, { 1.0f } },
+}, StereoCfg[2] = {
+ { FrontLeft, { 5.00000000e-1f, 2.88675135e-1f, 5.52305643e-2f } },
+ { FrontRight, { 5.00000000e-1f, -2.88675135e-1f, 5.52305643e-2f } },
+}, QuadCfg[4] = {
+ { BackLeft, { 3.53553391e-1f, 2.04124145e-1f, -2.04124145e-1f } },
+ { FrontLeft, { 3.53553391e-1f, 2.04124145e-1f, 2.04124145e-1f } },
+ { FrontRight, { 3.53553391e-1f, -2.04124145e-1f, 2.04124145e-1f } },
+ { BackRight, { 3.53553391e-1f, -2.04124145e-1f, -2.04124145e-1f } },
+}, X51SideCfg[4] = {
+ { SideLeft, { 3.33000782e-1f, 1.89084803e-1f, -2.00042375e-1f, -2.12307769e-2f, -1.14579885e-2f } },
+ { FrontLeft, { 1.88542860e-1f, 1.27709292e-1f, 1.66295695e-1f, 7.30571517e-2f, 2.10901184e-2f } },
+ { FrontRight, { 1.88542860e-1f, -1.27709292e-1f, 1.66295695e-1f, -7.30571517e-2f, 2.10901184e-2f } },
+ { SideRight, { 3.33000782e-1f, -1.89084803e-1f, -2.00042375e-1f, 2.12307769e-2f, -1.14579885e-2f } },
+}, X51RearCfg[4] = {
+ { BackLeft, { 3.33000782e-1f, 1.89084803e-1f, -2.00042375e-1f, -2.12307769e-2f, -1.14579885e-2f } },
+ { FrontLeft, { 1.88542860e-1f, 1.27709292e-1f, 1.66295695e-1f, 7.30571517e-2f, 2.10901184e-2f } },
+ { FrontRight, { 1.88542860e-1f, -1.27709292e-1f, 1.66295695e-1f, -7.30571517e-2f, 2.10901184e-2f } },
+ { BackRight, { 3.33000782e-1f, -1.89084803e-1f, -2.00042375e-1f, 2.12307769e-2f, -1.14579885e-2f } },
+}, X61Cfg[6] = {
+ { SideLeft, { 2.04460341e-1f, 2.17177926e-1f, -4.39996780e-2f, -2.60790269e-2f, -6.87239792e-2f } },
+ { FrontLeft, { 1.58923161e-1f, 9.21772680e-2f, 1.59658796e-1f, 6.66278083e-2f, 3.84686854e-2f } },
+ { FrontRight, { 1.58923161e-1f, -9.21772680e-2f, 1.59658796e-1f, -6.66278083e-2f, 3.84686854e-2f } },
+ { SideRight, { 2.04460341e-1f, -2.17177926e-1f, -4.39996780e-2f, 2.60790269e-2f, -6.87239792e-2f } },
+ { BackCenter, { 2.50001688e-1f, 0.00000000e+0f, -2.50000094e-1f, 0.00000000e+0f, 6.05133395e-2f } },
+}, X71Cfg[6] = {
+ { BackLeft, { 2.04124145e-1f, 1.08880247e-1f, -1.88586120e-1f, -1.29099444e-1f, 7.45355993e-2f, 3.73460789e-2f, 0.00000000e+0f } },
+ { SideLeft, { 2.04124145e-1f, 2.17760495e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.49071198e-1f, -3.73460789e-2f, 0.00000000e+0f } },
+ { FrontLeft, { 2.04124145e-1f, 1.08880247e-1f, 1.88586120e-1f, 1.29099444e-1f, 7.45355993e-2f, 3.73460789e-2f, 0.00000000e+0f } },
+ { FrontRight, { 2.04124145e-1f, -1.08880247e-1f, 1.88586120e-1f, -1.29099444e-1f, 7.45355993e-2f, -3.73460789e-2f, 0.00000000e+0f } },
+ { SideRight, { 2.04124145e-1f, -2.17760495e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.49071198e-1f, 3.73460789e-2f, 0.00000000e+0f } },
+ { BackRight, { 2.04124145e-1f, -1.08880247e-1f, -1.88586120e-1f, 1.29099444e-1f, 7.45355993e-2f, -3.73460789e-2f, 0.00000000e+0f } },
+};
+
+void InitNearFieldCtrl(ALCdevice *device, ALfloat ctrl_dist, ALuint order,
+ const al::span<const ALuint,MAX_AMBI_ORDER+1> chans_per_order)
+{
+ /* NFC is only used when AvgSpeakerDist is greater than 0. */
+ const char *devname{device->DeviceName.c_str()};
+ if(!GetConfigValueBool(devname, "decoder", "nfc", 0) || !(ctrl_dist > 0.0f))
+ return;
+
+ device->AvgSpeakerDist = clampf(ctrl_dist, 0.1f, 10.0f);
+ TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist);
+
+ auto iter = std::copy(chans_per_order.begin(), chans_per_order.begin()+order+1,
+ std::begin(device->NumChannelsPerOrder));
+ std::fill(iter, std::end(device->NumChannelsPerOrder), 0u);
+}
+
+void InitDistanceComp(ALCdevice *device, const AmbDecConf *conf,
+ const ALuint (&speakermap)[MAX_OUTPUT_CHANNELS])
+{
+ auto get_max = std::bind(maxf, _1,
+ std::bind(std::mem_fn(&AmbDecConf::SpeakerConf::Distance), _2));
+ const ALfloat maxdist{
+ std::accumulate(conf->Speakers.begin(), conf->Speakers.end(), float{0.0f}, get_max)};
+
+ const char *devname{device->DeviceName.c_str()};
+ if(!GetConfigValueBool(devname, "decoder", "distance-comp", 1) || !(maxdist > 0.0f))
+ return;
+
+ const auto distSampleScale = static_cast<ALfloat>(device->Frequency)/SPEEDOFSOUNDMETRESPERSEC;
+ const auto ChanDelay = device->ChannelDelay.as_span();
+ size_t total{0u};
+ for(size_t i{0u};i < conf->Speakers.size();i++)
+ {
+ const AmbDecConf::SpeakerConf &speaker = conf->Speakers[i];
+ const ALuint chan{speakermap[i]};
+
+ /* Distance compensation only delays in steps of the sample rate. This
+ * is a bit less accurate since the delay time falls to the nearest
+ * sample time, but it's far simpler as it doesn't have to deal with
+ * phase offsets. This means at 48khz, for instance, the distance delay
+ * will be in steps of about 7 millimeters.
+ */
+ ALfloat delay{std::floor((maxdist - speaker.Distance)*distSampleScale + 0.5f)};
+ if(delay > ALfloat{MAX_DELAY_LENGTH-1})
+ {
+ ERR("Delay for speaker \"%s\" exceeds buffer length (%f > %d)\n",
+ speaker.Name.c_str(), delay, MAX_DELAY_LENGTH-1);
+ delay = ALfloat{MAX_DELAY_LENGTH-1};
+ }
+
+ ChanDelay[chan].Length = static_cast<ALuint>(delay);
+ ChanDelay[chan].Gain = speaker.Distance / maxdist;
+ TRACE("Channel %u \"%s\" distance compensation: %u samples, %f gain\n", chan,
+ speaker.Name.c_str(), ChanDelay[chan].Length, ChanDelay[chan].Gain);
+
+ /* Round up to the next 4th sample, so each channel buffer starts
+ * 16-byte aligned.
+ */
+ total += RoundUp(ChanDelay[chan].Length, 4);
+ }
+
+ if(total > 0)
+ {
+ device->ChannelDelay.setSampleCount(total);
+ ChanDelay[0].Buffer = device->ChannelDelay.getSamples();
+ auto set_bufptr = [](const DistanceComp::DistData &last, const DistanceComp::DistData &cur) -> DistanceComp::DistData
+ {
+ DistanceComp::DistData ret{cur};
+ ret.Buffer = last.Buffer + RoundUp(last.Length, 4);
+ return ret;
+ };
+ std::partial_sum(ChanDelay.begin(), ChanDelay.end(), ChanDelay.begin(), set_bufptr);
+ }
+}
+
+
+auto GetAmbiScales(AmbiNorm scaletype) noexcept -> const std::array<float,MAX_AMBI_CHANNELS>&
+{
+ if(scaletype == AmbiNorm::FuMa) return AmbiScale::FromFuMa;
+ if(scaletype == AmbiNorm::SN3D) return AmbiScale::FromSN3D;
+ return AmbiScale::FromN3D;
+}
+
+auto GetAmbiLayout(AmbiLayout layouttype) noexcept -> const std::array<uint8_t,MAX_AMBI_CHANNELS>&
+{
+ if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa;
+ return AmbiIndex::FromACN;
+}
+
+
+void InitPanning(ALCdevice *device)
+{
+ al::span<const ChannelMap> chanmap;
+ ALuint coeffcount{};
+
+ switch(device->FmtChans)
+ {
+ case DevFmtMono:
+ chanmap = MonoCfg;
+ coeffcount = 1;
+ break;
+
+ case DevFmtStereo:
+ chanmap = StereoCfg;
+ coeffcount = 3;
+ break;
+
+ case DevFmtQuad:
+ chanmap = QuadCfg;
+ coeffcount = 3;
+ break;
+
+ case DevFmtX51:
+ chanmap = X51SideCfg;
+ coeffcount = 5;
+ break;
+
+ case DevFmtX51Rear:
+ chanmap = X51RearCfg;
+ coeffcount = 5;
+ break;
+
+ case DevFmtX61:
+ chanmap = X61Cfg;
+ coeffcount = 5;
+ break;
+
+ case DevFmtX71:
+ chanmap = X71Cfg;
+ coeffcount = 7;
+ break;
+
+ case DevFmtAmbi3D:
+ break;
+ }
+
+ if(device->FmtChans == DevFmtAmbi3D)
+ {
+ const char *devname{device->DeviceName.c_str()};
+ const std::array<uint8_t,MAX_AMBI_CHANNELS> &acnmap = GetAmbiLayout(device->mAmbiLayout);
+ const std::array<float,MAX_AMBI_CHANNELS> &n3dscale = GetAmbiScales(device->mAmbiScale);
+
+ /* For DevFmtAmbi3D, the ambisonic order is already set. */
+ const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)};
+ std::transform(acnmap.begin(), acnmap.begin()+count, std::begin(device->Dry.AmbiMap),
+ [&n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig
+ { return BFChannelConfig{1.0f/n3dscale[acn], acn}; }
+ );
+ AllocChannels(device, static_cast<ALuint>(count), 0);
+
+ ALfloat nfc_delay{ConfigValueFloat(devname, "decoder", "nfc-ref-delay").value_or(0.0f)};
+ if(nfc_delay > 0.0f)
+ {
+ static const ALuint chans_per_order[MAX_AMBI_ORDER+1]{ 1, 3, 5, 7 };
+ InitNearFieldCtrl(device, nfc_delay * SPEEDOFSOUNDMETRESPERSEC, device->mAmbiOrder,
+ chans_per_order);
+ }
+ }
+ else
+ {
+ ChannelDec chancoeffs[MAX_OUTPUT_CHANNELS]{};
+ ALuint idxmap[MAX_OUTPUT_CHANNELS]{};
+ for(size_t i{0u};i < chanmap.size();++i)
+ {
+ const ALuint idx{GetChannelIdxByName(device->RealOut, chanmap[i].ChanName)};
+ if(idx == INVALID_CHANNEL_INDEX)
+ {
+ ERR("Failed to find %s channel in device\n",
+ GetLabelFromChannel(chanmap[i].ChanName));
+ continue;
+ }
+ idxmap[i] = idx;
+ std::copy_n(chanmap[i].Config, coeffcount, chancoeffs[i]);
+ }
+
+ /* For non-DevFmtAmbi3D, set the ambisonic order given the mixing
+ * channel count. Built-in speaker decoders are always 2D, so just
+ * reverse that calculation.
+ */
+ device->mAmbiOrder = (coeffcount-1) / 2;
+
+ std::transform(AmbiIndex::From2D.begin(), AmbiIndex::From2D.begin()+coeffcount,
+ std::begin(device->Dry.AmbiMap),
+ [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; }
+ );
+ AllocChannels(device, coeffcount, device->channelsFromFmt());
+
+ TRACE("Enabling %s-order%s ambisonic decoder\n",
+ (coeffcount > 5) ? "third" :
+ (coeffcount > 3) ? "second" : "first",
+ ""
+ );
+ device->AmbiDecoder = al::make_unique<BFormatDec>(coeffcount,
+ static_cast<ALsizei>(chanmap.size()), chancoeffs, idxmap);
+ }
+}
+
+void InitCustomPanning(ALCdevice *device, bool hqdec, const AmbDecConf *conf,
+ const ALuint (&speakermap)[MAX_OUTPUT_CHANNELS])
+{
+ static const ALuint chans_per_order2d[MAX_AMBI_ORDER+1] = { 1, 2, 2, 2 };
+ static const ALuint chans_per_order3d[MAX_AMBI_ORDER+1] = { 1, 3, 5, 7 };
+
+ if(!hqdec && conf->FreqBands != 1)
+ ERR("Basic renderer uses the high-frequency matrix as single-band (xover_freq = %.0fhz)\n",
+ conf->XOverFreq);
+
+ const ALuint order{(conf->ChanMask > AMBI_2ORDER_MASK) ? 3u :
+ (conf->ChanMask > AMBI_1ORDER_MASK) ? 2u : 1u};
+ device->mAmbiOrder = order;
+
+ ALuint count;
+ if((conf->ChanMask&AMBI_PERIPHONIC_MASK))
+ {
+ count = static_cast<ALuint>(AmbiChannelsFromOrder(order));
+ std::transform(AmbiIndex::From3D.begin(), AmbiIndex::From3D.begin()+count,
+ std::begin(device->Dry.AmbiMap),
+ [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; }
+ );
+ }
+ else
+ {
+ count = static_cast<ALuint>(Ambi2DChannelsFromOrder(order));
+ std::transform(AmbiIndex::From2D.begin(), AmbiIndex::From2D.begin()+count,
+ std::begin(device->Dry.AmbiMap),
+ [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; }
+ );
+ }
+ AllocChannels(device, count, device->channelsFromFmt());
+
+ TRACE("Enabling %s-band %s-order%s ambisonic decoder\n",
+ (!hqdec || conf->FreqBands == 1) ? "single" : "dual",
+ (conf->ChanMask > AMBI_2ORDER_MASK) ? "third" :
+ (conf->ChanMask > AMBI_1ORDER_MASK) ? "second" : "first",
+ (conf->ChanMask&AMBI_PERIPHONIC_MASK) ? " periphonic" : ""
+ );
+ device->AmbiDecoder = al::make_unique<BFormatDec>(conf, hqdec, count, device->Frequency,
+ speakermap);
+
+ auto accum_spkr_dist = std::bind(std::plus<float>{}, _1,
+ std::bind(std::mem_fn(&AmbDecConf::SpeakerConf::Distance), _2));
+ const ALfloat avg_dist{
+ std::accumulate(conf->Speakers.begin(), conf->Speakers.end(), 0.0f, accum_spkr_dist) /
+ static_cast<ALfloat>(conf->Speakers.size())};
+ InitNearFieldCtrl(device, avg_dist, order,
+ (conf->ChanMask&AMBI_PERIPHONIC_MASK) ? chans_per_order3d : chans_per_order2d);
+
+ InitDistanceComp(device, conf, speakermap);
+}
+
+void InitHrtfPanning(ALCdevice *device)
+{
+ constexpr float PI{al::MathDefs<float>::Pi()};
+ constexpr float PI_2{al::MathDefs<float>::Pi() / 2.0f};
+ constexpr float PI_4{al::MathDefs<float>::Pi() / 4.0f};
+ constexpr float PI3_4{al::MathDefs<float>::Pi() * 3.0f / 4.0f};
+ const float CornerElev{static_cast<float>(std::atan2(1.0, std::sqrt(2.0)))};
+ static const AngularPoint AmbiPoints1O[]{
+ { ElevRadius{ CornerElev}, AzimRadius{ -PI_4} },
+ { ElevRadius{ CornerElev}, AzimRadius{-PI3_4} },
+ { ElevRadius{ CornerElev}, AzimRadius{ PI_4} },
+ { ElevRadius{ CornerElev}, AzimRadius{ PI3_4} },
+ { ElevRadius{-CornerElev}, AzimRadius{ -PI_4} },
+ { ElevRadius{-CornerElev}, AzimRadius{-PI3_4} },
+ { ElevRadius{-CornerElev}, AzimRadius{ PI_4} },
+ { ElevRadius{-CornerElev}, AzimRadius{ PI3_4} },
+ }, AmbiPoints2O[]{
+ { ElevRadius{ 0.0f}, AzimRadius{ 0.0f} },
+ { ElevRadius{ 0.0f}, AzimRadius{ PI} },
+ { ElevRadius{ 0.0f}, AzimRadius{ -PI_2} },
+ { ElevRadius{ 0.0f}, AzimRadius{ PI_2} },
+ { ElevRadius{ PI_2}, AzimRadius{ 0.0f} },
+ { ElevRadius{ -PI_2}, AzimRadius{ 0.0f} },
+ { ElevRadius{ PI_4}, AzimRadius{ -PI_2} },
+ { ElevRadius{ PI_4}, AzimRadius{ PI_2} },
+ { ElevRadius{ -PI_4}, AzimRadius{ -PI_2} },
+ { ElevRadius{ -PI_4}, AzimRadius{ PI_2} },
+ { ElevRadius{ PI_4}, AzimRadius{ 0.0f} },
+ { ElevRadius{ PI_4}, AzimRadius{ PI} },
+ { ElevRadius{ -PI_4}, AzimRadius{ 0.0f} },
+ { ElevRadius{ -PI_4}, AzimRadius{ PI} },
+ { ElevRadius{ 0.0f}, AzimRadius{ -PI_4} },
+ { ElevRadius{ 0.0f}, AzimRadius{-PI3_4} },
+ { ElevRadius{ 0.0f}, AzimRadius{ PI_4} },
+ { ElevRadius{ 0.0f}, AzimRadius{ PI3_4} },
+ { ElevRadius{ CornerElev}, AzimRadius{ -PI_4} },
+ { ElevRadius{ CornerElev}, AzimRadius{-PI3_4} },
+ { ElevRadius{ CornerElev}, AzimRadius{ PI_4} },
+ { ElevRadius{ CornerElev}, AzimRadius{ PI3_4} },
+ { ElevRadius{-CornerElev}, AzimRadius{ -PI_4} },
+ { ElevRadius{-CornerElev}, AzimRadius{-PI3_4} },
+ { ElevRadius{-CornerElev}, AzimRadius{ PI_4} },
+ { ElevRadius{-CornerElev}, AzimRadius{ PI3_4} },
+ };
+ static const float AmbiMatrix1O[][MAX_AMBI_CHANNELS]{
+ { 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f },
+ { 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f },
+ { 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f },
+ { 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f },
+ { 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f },
+ { 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f },
+ { 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f },
+ { 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f },
+ }, AmbiMatrix2O[][MAX_AMBI_CHANNELS]{
+ { 3.846153846e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 8.606629658e-02f },
+ { 3.846153846e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 8.606629658e-02f },
+ { 3.846153846e-02f, 6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, -8.606629658e-02f },
+ { 3.846153846e-02f, -6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, -8.606629658e-02f },
+ { 3.846153846e-02f, 0.000000000e+00f, 6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 9.938079900e-02f, 0.000000000e+00f, 0.000000000e+00f },
+ { 3.846153846e-02f, 0.000000000e+00f, -6.661733875e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 9.938079900e-02f, 0.000000000e+00f, 0.000000000e+00f },
+ { 3.846153846e-02f, 4.710557198e-02f, 4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.834676493e-02f, 2.484519975e-02f, 0.000000000e+00f, -4.303314829e-02f },
+ { 3.846153846e-02f, -4.710557198e-02f, 4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.834676493e-02f, 2.484519975e-02f, 0.000000000e+00f, -4.303314829e-02f },
+ { 3.846153846e-02f, 4.710557198e-02f, -4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.834676493e-02f, 2.484519975e-02f, 0.000000000e+00f, -4.303314829e-02f },
+ { 3.846153846e-02f, -4.710557198e-02f, -4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.834676493e-02f, 2.484519975e-02f, 0.000000000e+00f, -4.303314829e-02f },
+ { 3.846153846e-02f, 0.000000000e+00f, 4.710557198e-02f, 4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 2.484519975e-02f, 6.834676493e-02f, 4.303314829e-02f },
+ { 3.846153846e-02f, 0.000000000e+00f, 4.710557198e-02f, -4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 2.484519975e-02f, -6.834676493e-02f, 4.303314829e-02f },
+ { 3.846153846e-02f, 0.000000000e+00f, -4.710557198e-02f, 4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 2.484519975e-02f, -6.834676493e-02f, 4.303314829e-02f },
+ { 3.846153846e-02f, 0.000000000e+00f, -4.710557198e-02f, -4.710557198e-02f, 0.000000000e+00f, 0.000000000e+00f, 2.484519975e-02f, 6.834676493e-02f, 4.303314829e-02f },
+ { 3.846153846e-02f, 4.710557198e-02f, 0.000000000e+00f, 4.710557198e-02f, 6.834676493e-02f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 0.000000000e+00f },
+ { 3.846153846e-02f, 4.710557198e-02f, 0.000000000e+00f, -4.710557198e-02f, -6.834676493e-02f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 0.000000000e+00f },
+ { 3.846153846e-02f, -4.710557198e-02f, 0.000000000e+00f, 4.710557198e-02f, -6.834676493e-02f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 0.000000000e+00f },
+ { 3.846153846e-02f, -4.710557198e-02f, 0.000000000e+00f, -4.710557198e-02f, 6.834676493e-02f, 0.000000000e+00f, -4.969039950e-02f, 0.000000000e+00f, 0.000000000e+00f },
+ { 3.846153846e-02f, 3.846153846e-02f, 3.846153846e-02f, 3.846153846e-02f, 4.556450996e-02f, 4.556450996e-02f, 0.000000000e+00f, 4.556450996e-02f, 0.000000000e+00f },
+ { 3.846153846e-02f, 3.846153846e-02f, 3.846153846e-02f, -3.846153846e-02f, -4.556450996e-02f, 4.556450996e-02f, 0.000000000e+00f, -4.556450996e-02f, 0.000000000e+00f },
+ { 3.846153846e-02f, -3.846153846e-02f, 3.846153846e-02f, 3.846153846e-02f, -4.556450996e-02f, -4.556450996e-02f, 0.000000000e+00f, 4.556450996e-02f, 0.000000000e+00f },
+ { 3.846153846e-02f, -3.846153846e-02f, 3.846153846e-02f, -3.846153846e-02f, 4.556450996e-02f, -4.556450996e-02f, 0.000000000e+00f, -4.556450996e-02f, 0.000000000e+00f },
+ { 3.846153846e-02f, 3.846153846e-02f, -3.846153846e-02f, 3.846153846e-02f, 4.556450996e-02f, -4.556450996e-02f, 0.000000000e+00f, -4.556450996e-02f, 0.000000000e+00f },
+ { 3.846153846e-02f, 3.846153846e-02f, -3.846153846e-02f, -3.846153846e-02f, -4.556450996e-02f, -4.556450996e-02f, 0.000000000e+00f, 4.556450996e-02f, 0.000000000e+00f },
+ { 3.846153846e-02f, -3.846153846e-02f, -3.846153846e-02f, 3.846153846e-02f, -4.556450996e-02f, 4.556450996e-02f, 0.000000000e+00f, -4.556450996e-02f, 0.000000000e+00f },
+ { 3.846153846e-02f, -3.846153846e-02f, -3.846153846e-02f, -3.846153846e-02f, 4.556450996e-02f, 4.556450996e-02f, 0.000000000e+00f, 4.556450996e-02f, 0.000000000e+00f },
+ };
+ static const float AmbiOrderHFGain1O[MAX_AMBI_ORDER+1]{
+ 2.000000000e+00f, 1.154700538e+00f
+ }, AmbiOrderHFGain2O[MAX_AMBI_ORDER+1]{
+ 2.687419249e+00f, 2.081665999e+00f, 1.074967700e+00f
+ };
+ static const ALuint ChansPerOrder[MAX_AMBI_ORDER+1]{ 1, 3, 5, 7 };
+
+ static_assert(al::size(AmbiPoints1O) == al::size(AmbiMatrix1O), "First-Order Ambisonic HRTF mismatch");
+ static_assert(al::size(AmbiPoints2O) == al::size(AmbiMatrix2O), "Second-Order Ambisonic HRTF mismatch");
+
+ /* Don't bother with HOA when using full HRTF rendering. Nothing needs it,
+ * and it eases the CPU/memory load.
+ */
+ device->mRenderMode = HrtfRender;
+ ALuint ambi_order{1};
+ if(auto modeopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "hrtf-mode"))
+ {
+ struct HrtfModeEntry {
+ char name[8];
+ RenderMode mode;
+ ALuint order;
+ };
+ static const HrtfModeEntry hrtf_modes[]{
+ { "full", HrtfRender, 1 },
+ { "ambi1", NormalRender, 1 },
+ { "ambi2", NormalRender, 2 },
+ };
+
+ const char *mode{modeopt->c_str()};
+ if(al::strcasecmp(mode, "basic") == 0 || al::strcasecmp(mode, "ambi3") == 0)
+ {
+ ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", mode, "ambi2");
+ mode = "ambi2";
+ }
+
+ auto match_entry = [mode](const HrtfModeEntry &entry) -> bool
+ { return al::strcasecmp(mode, entry.name) == 0; };
+ auto iter = std::find_if(std::begin(hrtf_modes), std::end(hrtf_modes), match_entry);
+ if(iter == std::end(hrtf_modes))
+ ERR("Unexpected hrtf-mode: %s\n", mode);
+ else
+ {
+ device->mRenderMode = iter->mode;
+ ambi_order = iter->order;
+ }
+ }
+ TRACE("%u%s order %sHRTF rendering enabled, using \"%s\"\n", ambi_order,
+ (((ambi_order%100)/10) == 1) ? "th" :
+ ((ambi_order%10) == 1) ? "st" :
+ ((ambi_order%10) == 2) ? "nd" :
+ ((ambi_order%10) == 3) ? "rd" : "th",
+ (device->mRenderMode == HrtfRender) ? "+ Full " : "",
+ device->HrtfName.c_str());
+
+ al::span<const AngularPoint> AmbiPoints{};
+ const float (*AmbiMatrix)[MAX_AMBI_CHANNELS]{};
+ const float *AmbiOrderHFGain{};
+ if(ambi_order >= 2)
+ {
+ AmbiPoints = AmbiPoints2O;
+ AmbiMatrix = AmbiMatrix2O;
+ AmbiOrderHFGain = AmbiOrderHFGain2O;
+ }
+ else /*if(ambi_order == 1)*/
+ {
+ AmbiPoints = AmbiPoints1O;
+ AmbiMatrix = AmbiMatrix1O;
+ AmbiOrderHFGain = AmbiOrderHFGain1O;
+ }
+ device->mAmbiOrder = ambi_order;
+
+ const size_t count{AmbiChannelsFromOrder(ambi_order)};
+ device->mHrtfState = DirectHrtfState::Create(count);
+
+ std::transform(AmbiIndex::From3D.begin(), AmbiIndex::From3D.begin()+count,
+ std::begin(device->Dry.AmbiMap),
+ [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; }
+ );
+ AllocChannels(device, static_cast<ALuint>(count), device->channelsFromFmt());
+
+ BuildBFormatHrtf(device->mHrtf, device->mHrtfState.get(), AmbiPoints, AmbiMatrix,
+ AmbiOrderHFGain);
+
+ HrtfEntry *Hrtf{device->mHrtf};
+ InitNearFieldCtrl(device, Hrtf->field[0].distance, ambi_order, ChansPerOrder);
+}
+
+void InitUhjPanning(ALCdevice *device)
+{
+ /* UHJ is always 2D first-order. */
+ constexpr size_t count{Ambi2DChannelsFromOrder(1)};
+
+ device->mAmbiOrder = 1;
+
+ auto acnmap_end = AmbiIndex::FromFuMa.begin() + count;
+ std::transform(AmbiIndex::FromFuMa.begin(), acnmap_end, std::begin(device->Dry.AmbiMap),
+ [](const uint8_t &acn) noexcept -> BFChannelConfig
+ { return BFChannelConfig{1.0f/AmbiScale::FromFuMa[acn], acn}; }
+ );
+ AllocChannels(device, ALuint{count}, device->channelsFromFmt());
+}
+
+} // namespace
+
+void aluInitRenderer(ALCdevice *device, ALint hrtf_id, HrtfRequestMode hrtf_appreq, HrtfRequestMode hrtf_userreq)
+{
+ /* Hold the HRTF the device last used, in case it's used again. */
+ HrtfEntry *old_hrtf{device->mHrtf};
+
+ device->mHrtfState = nullptr;
+ device->mHrtf = nullptr;
+ device->HrtfName.clear();
+ device->mRenderMode = NormalRender;
+
+ if(device->FmtChans != DevFmtStereo)
+ {
+ if(old_hrtf)
+ old_hrtf->DecRef();
+ old_hrtf = nullptr;
+ if(hrtf_appreq == Hrtf_Enable)
+ device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT;
+
+ const char *layout{nullptr};
+ switch(device->FmtChans)
+ {
+ case DevFmtQuad: layout = "quad"; break;
+ case DevFmtX51: /* fall-through */
+ case DevFmtX51Rear: layout = "surround51"; break;
+ case DevFmtX61: layout = "surround61"; break;
+ case DevFmtX71: layout = "surround71"; break;
+ /* Mono, Stereo, and Ambisonics output don't use custom decoders. */
+ case DevFmtMono:
+ case DevFmtStereo:
+ case DevFmtAmbi3D:
+ break;
+ }
+
+ const char *devname{device->DeviceName.c_str()};
+ ALuint speakermap[MAX_OUTPUT_CHANNELS];
+ AmbDecConf *pconf{nullptr};
+ AmbDecConf conf{};
+ if(layout)
+ {
+ if(auto decopt = ConfigValueStr(devname, "decoder", layout))
+ {
+ if(!conf.load(decopt->c_str()))
+ ERR("Failed to load layout file %s\n", decopt->c_str());
+ else if(conf.Speakers.size() > MAX_OUTPUT_CHANNELS)
+ ERR("Unsupported speaker count %zu (max %d)\n", conf.Speakers.size(),
+ MAX_OUTPUT_CHANNELS);
+ else if(conf.ChanMask > AMBI_3ORDER_MASK)
+ ERR("Unsupported channel mask 0x%04x (max 0x%x)\n", conf.ChanMask,
+ AMBI_3ORDER_MASK);
+ else if(MakeSpeakerMap(device, &conf, speakermap))
+ pconf = &conf;
+ }
+ }
+
+ if(!pconf)
+ InitPanning(device);
+ else
+ {
+ int hqdec{GetConfigValueBool(devname, "decoder", "hq-mode", 1)};
+ InitCustomPanning(device, !!hqdec, pconf, speakermap);
+ }
+ if(device->AmbiDecoder)
+ device->PostProcess = &ALCdevice::ProcessAmbiDec;
+ return;
+ }
+
+ bool headphones{device->IsHeadphones != AL_FALSE};
+ if(device->Type != Loopback)
+ {
+ if(auto modeopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "stereo-mode"))
+ {
+ const char *mode{modeopt->c_str()};
+ if(al::strcasecmp(mode, "headphones") == 0)
+ headphones = true;
+ else if(al::strcasecmp(mode, "speakers") == 0)
+ headphones = false;
+ else if(al::strcasecmp(mode, "auto") != 0)
+ ERR("Unexpected stereo-mode: %s\n", mode);
+ }
+ }
+
+ if(hrtf_userreq == Hrtf_Default)
+ {
+ bool usehrtf = (headphones && hrtf_appreq != Hrtf_Disable) ||
+ (hrtf_appreq == Hrtf_Enable);
+ if(!usehrtf) goto no_hrtf;
+
+ device->HrtfStatus = ALC_HRTF_ENABLED_SOFT;
+ if(headphones && hrtf_appreq != Hrtf_Disable)
+ device->HrtfStatus = ALC_HRTF_HEADPHONES_DETECTED_SOFT;
+ }
+ else
+ {
+ if(hrtf_userreq != Hrtf_Enable)
+ {
+ if(hrtf_appreq == Hrtf_Enable)
+ device->HrtfStatus = ALC_HRTF_DENIED_SOFT;
+ goto no_hrtf;
+ }
+ device->HrtfStatus = ALC_HRTF_REQUIRED_SOFT;
+ }
+
+ if(device->HrtfList.empty())
+ device->HrtfList = EnumerateHrtf(device->DeviceName.c_str());
+
+ if(hrtf_id >= 0 && static_cast<ALuint>(hrtf_id) < device->HrtfList.size())
+ {
+ const EnumeratedHrtf &entry = device->HrtfList[static_cast<ALuint>(hrtf_id)];
+ HrtfEntry *hrtf{GetLoadedHrtf(entry.hrtf)};
+ if(hrtf && hrtf->sampleRate == device->Frequency)
+ {
+ device->mHrtf = hrtf;
+ device->HrtfName = entry.name;
+ }
+ else if(hrtf)
+ hrtf->DecRef();
+ }
+
+ if(!device->mHrtf)
+ {
+ auto find_hrtf = [device](const EnumeratedHrtf &entry) -> bool
+ {
+ HrtfEntry *hrtf{GetLoadedHrtf(entry.hrtf)};
+ if(!hrtf) return false;
+ if(hrtf->sampleRate != device->Frequency)
+ {
+ hrtf->DecRef();
+ return false;
+ }
+ device->mHrtf = hrtf;
+ device->HrtfName = entry.name;
+ return true;
+ };
+ std::find_if(device->HrtfList.cbegin(), device->HrtfList.cend(), find_hrtf);
+ }
+
+ if(device->mHrtf)
+ {
+ if(old_hrtf)
+ old_hrtf->DecRef();
+ old_hrtf = nullptr;
+
+ InitHrtfPanning(device);
+ device->PostProcess = &ALCdevice::ProcessHrtf;
+ return;
+ }
+ device->HrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT;
+
+no_hrtf:
+ if(old_hrtf)
+ old_hrtf->DecRef();
+ old_hrtf = nullptr;
+
+ device->mRenderMode = StereoPair;
+
+ if(device->Type != Loopback)
+ {
+ if(auto cflevopt = ConfigValueInt(device->DeviceName.c_str(), nullptr, "cf_level"))
+ {
+ if(*cflevopt > 0 && *cflevopt <= 6)
+ {
+ device->Bs2b = al::make_unique<bs2b>();
+ bs2b_set_params(device->Bs2b.get(), *cflevopt,
+ static_cast<int>(device->Frequency));
+ TRACE("BS2B enabled\n");
+ InitPanning(device);
+ device->PostProcess = &ALCdevice::ProcessBs2b;
+ return;
+ }
+ }
+ }
+
+ if(auto encopt = ConfigValueStr(device->DeviceName.c_str(), nullptr, "stereo-encoding"))
+ {
+ const char *mode{encopt->c_str()};
+ if(al::strcasecmp(mode, "uhj") == 0)
+ device->mRenderMode = NormalRender;
+ else if(al::strcasecmp(mode, "panpot") != 0)
+ ERR("Unexpected stereo-encoding: %s\n", mode);
+ }
+ if(device->mRenderMode == NormalRender)
+ {
+ device->Uhj_Encoder = al::make_unique<Uhj2Encoder>();
+ TRACE("UHJ enabled\n");
+ InitUhjPanning(device);
+ device->PostProcess = &ALCdevice::ProcessUhj;
+ return;
+ }
+
+ TRACE("Stereo rendering\n");
+ InitPanning(device);
+ device->PostProcess = &ALCdevice::ProcessAmbiDec;
+}
+
+
+void aluInitEffectPanning(ALeffectslot *slot, ALCdevice *device)
+{
+ const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)};
+ slot->MixBuffer.resize(count);
+ slot->MixBuffer.shrink_to_fit();
+
+ auto acnmap_end = AmbiIndex::From3D.begin() + count;
+ auto iter = std::transform(AmbiIndex::From3D.begin(), acnmap_end, slot->Wet.AmbiMap.begin(),
+ [](const uint8_t &acn) noexcept -> BFChannelConfig
+ { return BFChannelConfig{1.0f, acn}; }
+ );
+ std::fill(iter, slot->Wet.AmbiMap.end(), BFChannelConfig{});
+ slot->Wet.Buffer = {slot->MixBuffer.data(), slot->MixBuffer.size()};
+}
+
+
+void CalcAmbiCoeffs(const float y, const float z, const float x, const float spread,
+ const al::span<float,MAX_AMBI_CHANNELS> coeffs)
+{
+ /* Zeroth-order */
+ coeffs[0] = 1.0f; /* ACN 0 = 1 */
+ /* First-order */
+ coeffs[1] = 1.732050808f * y; /* ACN 1 = sqrt(3) * Y */
+ coeffs[2] = 1.732050808f * z; /* ACN 2 = sqrt(3) * Z */
+ coeffs[3] = 1.732050808f * x; /* ACN 3 = sqrt(3) * X */
+ /* Second-order */
+ coeffs[4] = 3.872983346f * x * y; /* ACN 4 = sqrt(15) * X * Y */
+ coeffs[5] = 3.872983346f * y * z; /* ACN 5 = sqrt(15) * Y * Z */
+ coeffs[6] = 1.118033989f * (z*z*3.0f - 1.0f); /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */
+ coeffs[7] = 3.872983346f * x * z; /* ACN 7 = sqrt(15) * X * Z */
+ coeffs[8] = 1.936491673f * (x*x - y*y); /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */
+ /* Third-order */
+ coeffs[9] = 2.091650066f * y * (x*x*3.0f - y*y); /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */
+ coeffs[10] = 10.246950766f * z * x * y; /* ACN 10 = sqrt(105) * Z * X * Y */
+ coeffs[11] = 1.620185175f * y * (z*z*5.0f - 1.0f); /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */
+ coeffs[12] = 1.322875656f * z * (z*z*5.0f - 3.0f); /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */
+ coeffs[13] = 1.620185175f * x * (z*z*5.0f - 1.0f); /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */
+ coeffs[14] = 5.123475383f * z * (x*x - y*y); /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */
+ coeffs[15] = 2.091650066f * x * (x*x - y*y*3.0f); /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */
+ /* Fourth-order */
+ /* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */
+ /* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */
+ /* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */
+ /* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */
+ /* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */
+ /* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */
+ /* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */
+ /* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */
+ /* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */
+
+ if(spread > 0.0f)
+ {
+ /* Implement the spread by using a spherical source that subtends the
+ * angle spread. See:
+ * http://www.ppsloan.org/publications/StupidSH36.pdf - Appendix A3
+ *
+ * When adjusted for N3D normalization instead of SN3D, these
+ * calculations are:
+ *
+ * ZH0 = -sqrt(pi) * (-1+ca);
+ * ZH1 = 0.5*sqrt(pi) * sa*sa;
+ * ZH2 = -0.5*sqrt(pi) * ca*(-1+ca)*(ca+1);
+ * ZH3 = -0.125*sqrt(pi) * (-1+ca)*(ca+1)*(5*ca*ca - 1);
+ * ZH4 = -0.125*sqrt(pi) * ca*(-1+ca)*(ca+1)*(7*ca*ca - 3);
+ * ZH5 = -0.0625*sqrt(pi) * (-1+ca)*(ca+1)*(21*ca*ca*ca*ca - 14*ca*ca + 1);
+ *
+ * The gain of the source is compensated for size, so that the
+ * loudness doesn't depend on the spread. Thus:
+ *
+ * ZH0 = 1.0f;
+ * ZH1 = 0.5f * (ca+1.0f);
+ * ZH2 = 0.5f * (ca+1.0f)*ca;
+ * ZH3 = 0.125f * (ca+1.0f)*(5.0f*ca*ca - 1.0f);
+ * ZH4 = 0.125f * (ca+1.0f)*(7.0f*ca*ca - 3.0f)*ca;
+ * ZH5 = 0.0625f * (ca+1.0f)*(21.0f*ca*ca*ca*ca - 14.0f*ca*ca + 1.0f);
+ */
+ const float ca{std::cos(spread * 0.5f)};
+ /* Increase the source volume by up to +3dB for a full spread. */
+ const float scale{std::sqrt(1.0f + spread/al::MathDefs<float>::Tau())};
+
+ const float ZH0_norm{scale};
+ const float ZH1_norm{scale * 0.5f * (ca+1.f)};
+ const float ZH2_norm{scale * 0.5f * (ca+1.f)*ca};
+ const float ZH3_norm{scale * 0.125f * (ca+1.f)*(5.f*ca*ca-1.f)};
+
+ /* Zeroth-order */
+ coeffs[0] *= ZH0_norm;
+ /* First-order */
+ coeffs[1] *= ZH1_norm;
+ coeffs[2] *= ZH1_norm;
+ coeffs[3] *= ZH1_norm;
+ /* Second-order */
+ coeffs[4] *= ZH2_norm;
+ coeffs[5] *= ZH2_norm;
+ coeffs[6] *= ZH2_norm;
+ coeffs[7] *= ZH2_norm;
+ coeffs[8] *= ZH2_norm;
+ /* Third-order */
+ coeffs[9] *= ZH3_norm;
+ coeffs[10] *= ZH3_norm;
+ coeffs[11] *= ZH3_norm;
+ coeffs[12] *= ZH3_norm;
+ coeffs[13] *= ZH3_norm;
+ coeffs[14] *= ZH3_norm;
+ coeffs[15] *= ZH3_norm;
+ }
+}
+
+void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain,
+ const al::span<float,MAX_OUTPUT_CHANNELS> gains)
+{
+ auto ambimap = mix->AmbiMap.cbegin();
+
+ auto iter = std::transform(ambimap, ambimap+mix->Buffer.size(), gains.begin(),
+ [coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> float
+ { return chanmap.Scale * coeffs[chanmap.Index] * ingain; }
+ );
+ std::fill(iter, gains.end(), 0.0f);
+}
diff --git a/alc/ringbuffer.cpp b/alc/ringbuffer.cpp
new file mode 100644
index 00000000..1f72f4b1
--- /dev/null
+++ b/alc/ringbuffer.cpp
@@ -0,0 +1,251 @@
+/**
+ * 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 "ringbuffer.h"
+
+#include <algorithm>
+#include <climits>
+#include <cstdint>
+#include <stdexcept>
+
+#include "almalloc.h"
+
+
+RingBufferPtr CreateRingBuffer(size_t sz, size_t elem_sz, int limit_writes)
+{
+ size_t power_of_two{0u};
+ if(sz > 0)
+ {
+ power_of_two = sz;
+ power_of_two |= power_of_two>>1;
+ power_of_two |= power_of_two>>2;
+ power_of_two |= power_of_two>>4;
+ power_of_two |= power_of_two>>8;
+ power_of_two |= power_of_two>>16;
+#if SIZE_MAX > UINT_MAX
+ power_of_two |= power_of_two>>32;
+#endif
+ }
+ ++power_of_two;
+ if(power_of_two <= sz || power_of_two > std::numeric_limits<size_t>::max()/elem_sz)
+ throw std::overflow_error{"Ring buffer size overflow"};
+
+ const size_t bufbytes{power_of_two * elem_sz};
+ RingBufferPtr rb{new (FamCount{bufbytes}) RingBuffer{bufbytes}};
+ rb->mWriteSize = limit_writes ? sz : (power_of_two-1);
+ rb->mSizeMask = power_of_two - 1;
+ rb->mElemSize = elem_sz;
+
+ return rb;
+}
+
+void RingBuffer::reset() noexcept
+{
+ mWritePtr.store(0, std::memory_order_relaxed);
+ mReadPtr.store(0, std::memory_order_relaxed);
+ std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, al::byte{});
+}
+
+
+size_t RingBuffer::readSpace() const noexcept
+{
+ size_t w = mWritePtr.load(std::memory_order_acquire);
+ size_t r = mReadPtr.load(std::memory_order_acquire);
+ return (w-r) & mSizeMask;
+}
+
+size_t RingBuffer::writeSpace() const noexcept
+{
+ size_t w = mWritePtr.load(std::memory_order_acquire);
+ size_t r = mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask;
+ return (r-w-1) & mSizeMask;
+}
+
+
+size_t RingBuffer::read(void *dest, size_t cnt) noexcept
+{
+ const size_t free_cnt{readSpace()};
+ if(free_cnt == 0) return 0;
+
+ const size_t to_read{std::min(cnt, free_cnt)};
+ size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask};
+
+ size_t n1, n2;
+ const size_t cnt2{read_ptr + to_read};
+ if(cnt2 > mSizeMask+1)
+ {
+ n1 = mSizeMask+1 - read_ptr;
+ n2 = cnt2 & mSizeMask;
+ }
+ else
+ {
+ n1 = to_read;
+ n2 = 0;
+ }
+
+ auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize,
+ static_cast<al::byte*>(dest));
+ read_ptr += n1;
+ if(n2 > 0)
+ {
+ std::copy_n(mBuffer.begin(), n2*mElemSize, outiter);
+ read_ptr += n2;
+ }
+ mReadPtr.store(read_ptr, std::memory_order_release);
+ return to_read;
+}
+
+size_t RingBuffer::peek(void *dest, size_t cnt) const noexcept
+{
+ const size_t free_cnt{readSpace()};
+ if(free_cnt == 0) return 0;
+
+ const size_t to_read{std::min(cnt, free_cnt)};
+ size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask};
+
+ size_t n1, n2;
+ const size_t cnt2{read_ptr + to_read};
+ if(cnt2 > mSizeMask+1)
+ {
+ n1 = mSizeMask+1 - read_ptr;
+ n2 = cnt2 & mSizeMask;
+ }
+ else
+ {
+ n1 = to_read;
+ n2 = 0;
+ }
+
+ auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize,
+ static_cast<al::byte*>(dest));
+ if(n2 > 0)
+ std::copy_n(mBuffer.begin(), n2*mElemSize, outiter);
+ return to_read;
+}
+
+size_t RingBuffer::write(const void *src, size_t cnt) noexcept
+{
+ const size_t free_cnt{writeSpace()};
+ if(free_cnt == 0) return 0;
+
+ const size_t to_write{std::min(cnt, free_cnt)};
+ size_t write_ptr{mWritePtr.load(std::memory_order_relaxed) & mSizeMask};
+
+ size_t n1, n2;
+ const size_t cnt2{write_ptr + to_write};
+ if(cnt2 > mSizeMask+1)
+ {
+ n1 = mSizeMask+1 - write_ptr;
+ n2 = cnt2 & mSizeMask;
+ }
+ else
+ {
+ n1 = to_write;
+ n2 = 0;
+ }
+
+ auto srcbytes = static_cast<const al::byte*>(src);
+ std::copy_n(srcbytes, n1*mElemSize, mBuffer.begin() + write_ptr*mElemSize);
+ write_ptr += n1;
+ if(n2 > 0)
+ {
+ std::copy_n(srcbytes + n1*mElemSize, n2*mElemSize, mBuffer.begin());
+ write_ptr += n2;
+ }
+ mWritePtr.store(write_ptr, std::memory_order_release);
+ return to_write;
+}
+
+
+void RingBuffer::readAdvance(size_t cnt) noexcept
+{
+ mReadPtr.fetch_add(cnt, std::memory_order_acq_rel);
+}
+
+void RingBuffer::writeAdvance(size_t cnt) noexcept
+{
+ mWritePtr.fetch_add(cnt, std::memory_order_acq_rel);
+}
+
+
+ll_ringbuffer_data_pair RingBuffer::getReadVector() const noexcept
+{
+ ll_ringbuffer_data_pair ret;
+
+ size_t w{mWritePtr.load(std::memory_order_acquire)};
+ size_t r{mReadPtr.load(std::memory_order_acquire)};
+ w &= mSizeMask;
+ r &= mSizeMask;
+ const size_t free_cnt{(w-r) & mSizeMask};
+
+ const size_t cnt2{r + free_cnt};
+ if(cnt2 > mSizeMask+1)
+ {
+ /* Two part vector: the rest of the buffer after the current read ptr,
+ * plus some from the start of the buffer. */
+ ret.first.buf = const_cast<al::byte*>(mBuffer.data() + r*mElemSize);
+ ret.first.len = mSizeMask+1 - r;
+ ret.second.buf = const_cast<al::byte*>(mBuffer.data());
+ ret.second.len = cnt2 & mSizeMask;
+ }
+ else
+ {
+ /* Single part vector: just the rest of the buffer */
+ ret.first.buf = const_cast<al::byte*>(mBuffer.data() + r*mElemSize);
+ ret.first.len = free_cnt;
+ ret.second.buf = nullptr;
+ ret.second.len = 0;
+ }
+
+ return ret;
+}
+
+ll_ringbuffer_data_pair RingBuffer::getWriteVector() const noexcept
+{
+ ll_ringbuffer_data_pair ret;
+
+ size_t w{mWritePtr.load(std::memory_order_acquire)};
+ size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask};
+ w &= mSizeMask;
+ r &= mSizeMask;
+ const size_t free_cnt{(r-w-1) & mSizeMask};
+
+ const size_t cnt2{w + free_cnt};
+ if(cnt2 > mSizeMask+1)
+ {
+ /* Two part vector: the rest of the buffer after the current write ptr,
+ * plus some from the start of the buffer. */
+ ret.first.buf = const_cast<al::byte*>(mBuffer.data() + w*mElemSize);
+ ret.first.len = mSizeMask+1 - w;
+ ret.second.buf = const_cast<al::byte*>(mBuffer.data());
+ ret.second.len = cnt2 & mSizeMask;
+ }
+ else
+ {
+ ret.first.buf = const_cast<al::byte*>(mBuffer.data() + w*mElemSize);
+ ret.first.len = free_cnt;
+ ret.second.buf = nullptr;
+ ret.second.len = 0;
+ }
+
+ return ret;
+}
diff --git a/alc/ringbuffer.h b/alc/ringbuffer.h
new file mode 100644
index 00000000..3151fdcb
--- /dev/null
+++ b/alc/ringbuffer.h
@@ -0,0 +1,97 @@
+#ifndef RINGBUFFER_H
+#define RINGBUFFER_H
+
+#include <stddef.h>
+
+#include <atomic>
+#include <memory>
+#include <utility>
+
+#include "albyte.h"
+#include "almalloc.h"
+
+
+/* NOTE: This lockless ringbuffer implementation is copied from JACK, extended
+ * to include an element size. Consequently, parameters and return values for a
+ * size or count is in 'elements', not bytes. Additionally, it only supports
+ * single-consumer/single-provider operation.
+ */
+
+struct ll_ringbuffer_data {
+ al::byte *buf;
+ size_t len;
+};
+using ll_ringbuffer_data_pair = std::pair<ll_ringbuffer_data,ll_ringbuffer_data>;
+
+
+struct RingBuffer {
+ std::atomic<size_t> mWritePtr{0u};
+ std::atomic<size_t> mReadPtr{0u};
+ size_t mWriteSize{0u};
+ size_t mSizeMask{0u};
+ size_t mElemSize{0u};
+
+ al::FlexArray<al::byte, 16> mBuffer;
+
+ RingBuffer(const size_t count) : mBuffer{count} { }
+
+ /** Reset the read and write pointers to zero. This is not thread safe. */
+ void reset() noexcept;
+
+ /**
+ * The non-copying data reader. Returns two ringbuffer data pointers that
+ * hold the current readable data. If the readable data is in one segment
+ * the second segment has zero length.
+ */
+ ll_ringbuffer_data_pair getReadVector() const noexcept;
+ /**
+ * The non-copying data writer. Returns two ringbuffer data pointers that
+ * hold the current writeable data. If the writeable data is in one segment
+ * the second segment has zero length.
+ */
+ ll_ringbuffer_data_pair getWriteVector() const noexcept;
+
+ /**
+ * Return the number of elements available for reading. This is the number
+ * of elements in front of the read pointer and behind the write pointer.
+ */
+ size_t readSpace() const noexcept;
+ /**
+ * The copying data reader. Copy at most `cnt' elements into `dest'.
+ * Returns the actual number of elements copied.
+ */
+ size_t read(void *dest, size_t cnt) noexcept;
+ /**
+ * The copying data reader w/o read pointer advance. Copy at most `cnt'
+ * elements into `dest'. Returns the actual number of elements copied.
+ */
+ size_t peek(void *dest, size_t cnt) const noexcept;
+ /** Advance the read pointer `cnt' places. */
+ void readAdvance(size_t cnt) noexcept;
+
+ /**
+ * Return the number of elements available for writing. This is the number
+ * of elements in front of the write pointer and behind the read pointer.
+ */
+ size_t writeSpace() const noexcept;
+ /**
+ * The copying data writer. Copy at most `cnt' elements from `src'. Returns
+ * the actual number of elements copied.
+ */
+ size_t write(const void *src, size_t cnt) noexcept;
+ /** Advance the write pointer `cnt' places. */
+ void writeAdvance(size_t cnt) noexcept;
+
+ DEF_FAM_NEWDEL(RingBuffer, mBuffer)
+};
+using RingBufferPtr = std::unique_ptr<RingBuffer>;
+
+
+/**
+ * Create a new ringbuffer to hold at least `sz' elements of `elem_sz' bytes.
+ * The number of elements is rounded up to the next power of two (even if it is
+ * already a power of two, to ensure the requested amount can be written).
+ */
+RingBufferPtr CreateRingBuffer(size_t sz, size_t elem_sz, int limit_writes);
+
+#endif /* RINGBUFFER_H */
diff --git a/alc/uhjfilter.cpp b/alc/uhjfilter.cpp
new file mode 100644
index 00000000..7d01a91f
--- /dev/null
+++ b/alc/uhjfilter.cpp
@@ -0,0 +1,138 @@
+
+#include "config.h"
+
+#include "uhjfilter.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "AL/al.h"
+
+#include "alnumeric.h"
+#include "opthelpers.h"
+
+
+namespace {
+
+/* This is the maximum number of samples processed for each inner loop
+ * iteration. */
+#define MAX_UPDATE_SAMPLES 128
+
+
+constexpr ALfloat Filter1CoeffSqr[4] = {
+ 0.479400865589f, 0.876218493539f, 0.976597589508f, 0.997499255936f
+};
+constexpr ALfloat Filter2CoeffSqr[4] = {
+ 0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156685f
+};
+
+void allpass_process(AllPassState *state, ALfloat *dst, const ALfloat *src, const ALfloat aa,
+ const size_t todo)
+{
+ ALfloat z1{state->z[0]};
+ ALfloat z2{state->z[1]};
+ auto proc_sample = [aa,&z1,&z2](const ALfloat input) noexcept -> ALfloat
+ {
+ const ALfloat output{input*aa + z1};
+ z1 = z2; z2 = output*aa - input;
+ return output;
+ };
+ std::transform(src, src+todo, dst, proc_sample);
+ state->z[0] = z1;
+ state->z[1] = z2;
+}
+
+} // namespace
+
+
+/* NOTE: There seems to be a bit of an inconsistency in how this encoding is
+ * supposed to work. Some references, such as
+ *
+ * http://members.tripod.com/martin_leese/Ambisonic/UHJ_file_format.html
+ *
+ * specify a pre-scaling of sqrt(2) on the W channel input, while other
+ * references, such as
+ *
+ * https://en.wikipedia.org/wiki/Ambisonic_UHJ_format#Encoding.5B1.5D
+ * and
+ * https://wiki.xiph.org/Ambisonics#UHJ_format
+ *
+ * do not. The sqrt(2) scaling is in line with B-Format decoder coefficients
+ * which include such a scaling for the W channel input, however the original
+ * source for this equation is a 1985 paper by Michael Gerzon, which does not
+ * apparently include the scaling. Applying the extra scaling creates a louder
+ * result with a narrower stereo image compared to not scaling, and I don't
+ * know which is the intended result.
+ */
+
+void Uhj2Encoder::encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
+ FloatBufferLine *InSamples, const size_t SamplesToDo)
+{
+ alignas(16) ALfloat D[MAX_UPDATE_SAMPLES], S[MAX_UPDATE_SAMPLES];
+ alignas(16) ALfloat temp[MAX_UPDATE_SAMPLES];
+
+ ASSUME(SamplesToDo > 0);
+
+ auto winput = InSamples[0].cbegin();
+ auto xinput = InSamples[1].cbegin();
+ auto yinput = InSamples[2].cbegin();
+ for(size_t base{0};base < SamplesToDo;)
+ {
+ const size_t todo{minz(SamplesToDo - base, MAX_UPDATE_SAMPLES)};
+ ASSUME(todo > 0);
+
+ /* D = 0.6554516*Y */
+ std::transform(yinput, yinput+todo, std::begin(temp),
+ [](const float y) noexcept -> float { return 0.6554516f*y; });
+ allpass_process(&mFilter1_Y[0], temp, temp, Filter1CoeffSqr[0], todo);
+ allpass_process(&mFilter1_Y[1], temp, temp, Filter1CoeffSqr[1], todo);
+ allpass_process(&mFilter1_Y[2], temp, temp, Filter1CoeffSqr[2], todo);
+ allpass_process(&mFilter1_Y[3], temp, temp, Filter1CoeffSqr[3], todo);
+ /* NOTE: Filter1 requires a 1 sample delay for the final output, so
+ * take the last processed sample from the previous run as the first
+ * output sample.
+ */
+ D[0] = mLastY;
+ for(size_t i{1};i < todo;i++)
+ D[i] = temp[i-1];
+ mLastY = temp[todo-1];
+
+ /* D += j(-0.3420201*W + 0.5098604*X) */
+ std::transform(winput, winput+todo, xinput, std::begin(temp),
+ [](const float w, const float x) noexcept -> float
+ { return -0.3420201f*w + 0.5098604f*x; });
+ allpass_process(&mFilter2_WX[0], temp, temp, Filter2CoeffSqr[0], todo);
+ allpass_process(&mFilter2_WX[1], temp, temp, Filter2CoeffSqr[1], todo);
+ allpass_process(&mFilter2_WX[2], temp, temp, Filter2CoeffSqr[2], todo);
+ allpass_process(&mFilter2_WX[3], temp, temp, Filter2CoeffSqr[3], todo);
+ for(size_t i{0};i < todo;i++)
+ D[i] += temp[i];
+
+ /* S = 0.9396926*W + 0.1855740*X */
+ std::transform(winput, winput+todo, xinput, std::begin(temp),
+ [](const float w, const float x) noexcept -> float
+ { return 0.9396926f*w + 0.1855740f*x; });
+ allpass_process(&mFilter1_WX[0], temp, temp, Filter1CoeffSqr[0], todo);
+ allpass_process(&mFilter1_WX[1], temp, temp, Filter1CoeffSqr[1], todo);
+ allpass_process(&mFilter1_WX[2], temp, temp, Filter1CoeffSqr[2], todo);
+ allpass_process(&mFilter1_WX[3], temp, temp, Filter1CoeffSqr[3], todo);
+ S[0] = mLastWX;
+ for(size_t i{1};i < todo;i++)
+ S[i] = temp[i-1];
+ mLastWX = temp[todo-1];
+
+ /* Left = (S + D)/2.0 */
+ ALfloat *RESTRICT left = al::assume_aligned<16>(LeftOut.data()+base);
+ for(size_t i{0};i < todo;i++)
+ left[i] += (S[i] + D[i]) * 0.5f;
+ /* Right = (S - D)/2.0 */
+ ALfloat *RESTRICT right = al::assume_aligned<16>(RightOut.data()+base);
+ for(size_t i{0};i < todo;i++)
+ right[i] += (S[i] - D[i]) * 0.5f;
+
+ winput += todo;
+ xinput += todo;
+ yinput += todo;
+ base += todo;
+ }
+}
diff --git a/Alc/uhjfilter.h b/alc/uhjfilter.h
index e773e0a7..88d30351 100644
--- a/Alc/uhjfilter.h
+++ b/alc/uhjfilter.h
@@ -3,11 +3,13 @@
#include "AL/al.h"
-#include "alMain.h"
+#include "alcmain.h"
+#include "almalloc.h"
-typedef struct AllPassState {
- ALfloat z[2];
-} AllPassState;
+
+struct AllPassState {
+ ALfloat z[2]{0.0f, 0.0f};
+};
/* Encoding 2-channel UHJ from B-Format is done as:
*
@@ -34,16 +36,19 @@ typedef struct AllPassState {
* other inputs.
*/
-typedef struct Uhj2Encoder {
- AllPassState Filter1_Y[4];
- AllPassState Filter2_WX[4];
- AllPassState Filter1_WX[4];
- ALfloat LastY, LastWX;
-} Uhj2Encoder;
+struct Uhj2Encoder {
+ AllPassState mFilter1_Y[4];
+ AllPassState mFilter2_WX[4];
+ AllPassState mFilter1_WX[4];
+ ALfloat mLastY{0.0f}, mLastWX{0.0f};
-/* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
- * signal. The input must use FuMa channel ordering and scaling.
- */
-void EncodeUhj2(Uhj2Encoder *enc, ALfloat *restrict LeftOut, ALfloat *restrict RightOut, ALfloat (*restrict InSamples)[BUFFERSIZE], ALsizei SamplesToDo);
+ /* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
+ * signal. The input must use FuMa channel ordering and scaling.
+ */
+ void encode(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, FloatBufferLine *InSamples,
+ const size_t SamplesToDo);
+
+ DEF_NEWDEL(Uhj2Encoder)
+};
#endif /* UHJFILTER_H */
diff --git a/alc/uiddefs.cpp b/alc/uiddefs.cpp
new file mode 100644
index 00000000..244c01a5
--- /dev/null
+++ b/alc/uiddefs.cpp
@@ -0,0 +1,37 @@
+
+#include "config.h"
+
+
+#ifndef AL_NO_UID_DEFS
+
+#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H)
+#define INITGUID
+#include <windows.h>
+#ifdef HAVE_GUIDDEF_H
+#include <guiddef.h>
+#else
+#include <initguid.h>
+#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_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x00,0xa0,0xc9,0x25,0xcd,0x16);
+
+DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e);
+DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6);
+DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1,0x78, 0xc2,0xf5,0x68,0xa7,0x03,0xb2);
+DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7,0xbf, 0xad,0xdc,0xa7,0xc2,0x60,0xe2);
+DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4,0xde, 0x18,0x5c,0x39,0x5c,0xd3,0x17);
+
+#ifdef HAVE_WASAPI
+#include <wtypes.h>
+#include <devpropdef.h>
+#include <propkeydef.h>
+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 );
+#endif
+#endif
+
+#endif /* AL_NO_UID_DEFS */
diff --git a/alc/voice.cpp b/alc/voice.cpp
new file mode 100644
index 00000000..1c38f36f
--- /dev/null
+++ b/alc/voice.cpp
@@ -0,0 +1,837 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 1999-2007 by authors.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include "voice.h"
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <cassert>
+#include <climits>
+#include <cstddef>
+#include <cstdint>
+#include <iterator>
+#include <memory>
+#include <new>
+#include <utility>
+
+#include "AL/al.h"
+#include "AL/alc.h"
+
+#include "al/buffer.h"
+#include "al/event.h"
+#include "al/source.h"
+#include "alcmain.h"
+#include "albyte.h"
+#include "alconfig.h"
+#include "alcontext.h"
+#include "alnumeric.h"
+#include "aloptional.h"
+#include "alspan.h"
+#include "alstring.h"
+#include "alu.h"
+#include "cpu_caps.h"
+#include "devformat.h"
+#include "filters/biquad.h"
+#include "filters/nfc.h"
+#include "filters/splitter.h"
+#include "hrtf.h"
+#include "inprogext.h"
+#include "logging.h"
+#include "mixer/defs.h"
+#include "opthelpers.h"
+#include "ringbuffer.h"
+#include "threads.h"
+#include "vector.h"
+
+
+static_assert((INT_MAX>>FRACTIONBITS)/MAX_PITCH > BUFFERSIZE,
+ "MAX_PITCH and/or BUFFERSIZE are too large for FRACTIONBITS!");
+
+
+Resampler ResamplerDefault{Resampler::Linear};
+
+namespace {
+
+using HrtfMixerFunc = void(*)(const ALfloat *InSamples, float2 *AccumSamples, const ALuint IrSize,
+ MixHrtfFilter *hrtfparams, const size_t BufferSize);
+using HrtfMixerBlendFunc = void(*)(const ALfloat *InSamples, float2 *AccumSamples,
+ const ALuint IrSize, const HrtfFilter *oldparams, MixHrtfFilter *newparams,
+ const size_t BufferSize);
+
+HrtfMixerFunc MixHrtfSamples = MixHrtf_<CTag>;
+HrtfMixerBlendFunc MixHrtfBlendSamples = MixHrtfBlend_<CTag>;
+
+inline HrtfMixerFunc SelectHrtfMixer()
+{
+#ifdef HAVE_NEON
+ if((CPUCapFlags&CPU_CAP_NEON))
+ return MixHrtf_<NEONTag>;
+#endif
+#ifdef HAVE_SSE
+ if((CPUCapFlags&CPU_CAP_SSE))
+ return MixHrtf_<SSETag>;
+#endif
+ return MixHrtf_<CTag>;
+}
+
+inline HrtfMixerBlendFunc SelectHrtfBlendMixer()
+{
+#ifdef HAVE_NEON
+ if((CPUCapFlags&CPU_CAP_NEON))
+ return MixHrtfBlend_<NEONTag>;
+#endif
+#ifdef HAVE_SSE
+ if((CPUCapFlags&CPU_CAP_SSE))
+ return MixHrtfBlend_<SSETag>;
+#endif
+ return MixHrtfBlend_<CTag>;
+}
+
+} // namespace
+
+
+void aluInitMixer()
+{
+ if(auto resopt = ConfigValueStr(nullptr, nullptr, "resampler"))
+ {
+ struct ResamplerEntry {
+ const char name[16];
+ const Resampler resampler;
+ };
+ constexpr ResamplerEntry ResamplerList[]{
+ { "none", Resampler::Point },
+ { "point", Resampler::Point },
+ { "cubic", Resampler::Cubic },
+ { "bsinc12", Resampler::BSinc12 },
+ { "fast_bsinc12", Resampler::FastBSinc12 },
+ { "bsinc24", Resampler::BSinc24 },
+ { "fast_bsinc24", Resampler::FastBSinc24 },
+ };
+
+ const char *str{resopt->c_str()};
+ if(al::strcasecmp(str, "bsinc") == 0)
+ {
+ WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str);
+ str = "bsinc12";
+ }
+ else if(al::strcasecmp(str, "sinc4") == 0 || al::strcasecmp(str, "sinc8") == 0)
+ {
+ WARN("Resampler option \"%s\" is deprecated, using cubic\n", str);
+ str = "cubic";
+ }
+
+ auto iter = std::find_if(std::begin(ResamplerList), std::end(ResamplerList),
+ [str](const ResamplerEntry &entry) -> bool
+ { return al::strcasecmp(str, entry.name) == 0; });
+ if(iter == std::end(ResamplerList))
+ ERR("Invalid resampler: %s\n", str);
+ else
+ ResamplerDefault = iter->resampler;
+ }
+
+ MixHrtfBlendSamples = SelectHrtfBlendMixer();
+ MixHrtfSamples = SelectHrtfMixer();
+}
+
+
+namespace {
+
+/* A quick'n'dirty lookup table to decode a muLaw-encoded byte sample into a
+ * signed 16-bit sample */
+constexpr ALshort muLawDecompressionTable[256] = {
+ -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
+ -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
+ -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
+ -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
+ -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
+ -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
+ -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
+ -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
+ -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
+ -1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
+ -876, -844, -812, -780, -748, -716, -684, -652,
+ -620, -588, -556, -524, -492, -460, -428, -396,
+ -372, -356, -340, -324, -308, -292, -276, -260,
+ -244, -228, -212, -196, -180, -164, -148, -132,
+ -120, -112, -104, -96, -88, -80, -72, -64,
+ -56, -48, -40, -32, -24, -16, -8, 0,
+ 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
+ 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
+ 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
+ 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
+ 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
+ 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
+ 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
+ 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
+ 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
+ 1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
+ 876, 844, 812, 780, 748, 716, 684, 652,
+ 620, 588, 556, 524, 492, 460, 428, 396,
+ 372, 356, 340, 324, 308, 292, 276, 260,
+ 244, 228, 212, 196, 180, 164, 148, 132,
+ 120, 112, 104, 96, 88, 80, 72, 64,
+ 56, 48, 40, 32, 24, 16, 8, 0
+};
+
+/* A quick'n'dirty lookup table to decode an aLaw-encoded byte sample into a
+ * signed 16-bit sample */
+constexpr ALshort aLawDecompressionTable[256] = {
+ -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
+ -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
+ -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
+ -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
+ -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
+ -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
+ -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472,
+ -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
+ -344, -328, -376, -360, -280, -264, -312, -296,
+ -472, -456, -504, -488, -408, -392, -440, -424,
+ -88, -72, -120, -104, -24, -8, -56, -40,
+ -216, -200, -248, -232, -152, -136, -184, -168,
+ -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
+ -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
+ -688, -656, -752, -720, -560, -528, -624, -592,
+ -944, -912, -1008, -976, -816, -784, -880, -848,
+ 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
+ 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
+ 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
+ 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
+ 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
+ 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
+ 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
+ 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
+ 344, 328, 376, 360, 280, 264, 312, 296,
+ 472, 456, 504, 488, 408, 392, 440, 424,
+ 88, 72, 120, 104, 24, 8, 56, 40,
+ 216, 200, 248, 232, 152, 136, 184, 168,
+ 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
+ 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
+ 688, 656, 752, 720, 560, 528, 624, 592,
+ 944, 912, 1008, 976, 816, 784, 880, 848
+};
+
+template<FmtType T>
+struct FmtTypeTraits { };
+
+template<>
+struct FmtTypeTraits<FmtUByte> {
+ using Type = ALubyte;
+ static constexpr inline float to_float(const Type val) noexcept
+ { return val*(1.0f/128.0f) - 1.0f; }
+};
+template<>
+struct FmtTypeTraits<FmtShort> {
+ using Type = ALshort;
+ static constexpr inline float to_float(const Type val) noexcept { return val*(1.0f/32768.0f); }
+};
+template<>
+struct FmtTypeTraits<FmtFloat> {
+ using Type = ALfloat;
+ static constexpr inline float to_float(const Type val) noexcept { return val; }
+};
+template<>
+struct FmtTypeTraits<FmtDouble> {
+ using Type = ALdouble;
+ static constexpr inline float to_float(const Type val) noexcept
+ { return static_cast<ALfloat>(val); }
+};
+template<>
+struct FmtTypeTraits<FmtMulaw> {
+ using Type = ALubyte;
+ static constexpr inline float to_float(const Type val) noexcept
+ { return muLawDecompressionTable[val] * (1.0f/32768.0f); }
+};
+template<>
+struct FmtTypeTraits<FmtAlaw> {
+ using Type = ALubyte;
+ static constexpr inline float to_float(const Type val) noexcept
+ { return aLawDecompressionTable[val] * (1.0f/32768.0f); }
+};
+
+
+void SendSourceStoppedEvent(ALCcontext *context, ALuint id)
+{
+ RingBuffer *ring{context->mAsyncEvents.get()};
+ auto evt_vec = ring->getWriteVector();
+ if(evt_vec.first.len < 1) return;
+
+ AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_SourceStateChange}};
+ evt->u.srcstate.id = id;
+ evt->u.srcstate.state = AL_STOPPED;
+
+ ring->writeAdvance(1);
+ context->mEventSem.post();
+}
+
+
+const ALfloat *DoFilters(BiquadFilter *lpfilter, BiquadFilter *hpfilter, ALfloat *dst,
+ const ALfloat *src, const size_t numsamples, int type)
+{
+ switch(type)
+ {
+ case AF_None:
+ lpfilter->clear();
+ hpfilter->clear();
+ break;
+
+ case AF_LowPass:
+ lpfilter->process(dst, src, numsamples);
+ hpfilter->clear();
+ return dst;
+ case AF_HighPass:
+ lpfilter->clear();
+ hpfilter->process(dst, src, numsamples);
+ return dst;
+
+ case AF_BandPass:
+ lpfilter->process(dst, src, numsamples);
+ hpfilter->process(dst, dst, numsamples);
+ return dst;
+ }
+ return src;
+}
+
+
+template<FmtType T>
+inline void LoadSampleArray(ALfloat *RESTRICT dst, const al::byte *src, const size_t srcstep,
+ const size_t samples) noexcept
+{
+ using SampleType = typename FmtTypeTraits<T>::Type;
+
+ const SampleType *RESTRICT ssrc{reinterpret_cast<const SampleType*>(src)};
+ for(size_t i{0u};i < samples;i++)
+ dst[i] = FmtTypeTraits<T>::to_float(ssrc[i*srcstep]);
+}
+
+void LoadSamples(ALfloat *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype,
+ const size_t samples) noexcept
+{
+#define HANDLE_FMT(T) case T: 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
+}
+
+ALfloat *LoadBufferStatic(ALbufferlistitem *BufferListItem, ALbufferlistitem *&BufferLoopItem,
+ const size_t NumChannels, const size_t SampleSize, const size_t chan, size_t DataPosInt,
+ al::span<ALfloat> SrcBuffer)
+{
+ const ALbuffer *Buffer{BufferListItem->mBuffer};
+ const ALuint LoopStart{Buffer->LoopStart};
+ const ALuint LoopEnd{Buffer->LoopEnd};
+ ASSUME(LoopEnd > LoopStart);
+
+ /* If current pos is beyond the loop range, do not loop */
+ if(!BufferLoopItem || DataPosInt >= LoopEnd)
+ {
+ BufferLoopItem = nullptr;
+
+ /* Load what's left to play from the buffer */
+ const size_t DataRem{minz(SrcBuffer.size(), Buffer->SampleLen-DataPosInt)};
+
+ const al::byte *Data{Buffer->mData.data()};
+ Data += (DataPosInt*NumChannels + chan)*SampleSize;
+
+ LoadSamples(SrcBuffer.data(), Data, NumChannels, Buffer->mFmtType, DataRem);
+ SrcBuffer = SrcBuffer.subspan(DataRem);
+ }
+ else
+ {
+ /* Load what's left of this loop iteration */
+ const size_t DataRem{minz(SrcBuffer.size(), LoopEnd-DataPosInt)};
+
+ const al::byte *Data{Buffer->mData.data()};
+ Data += (DataPosInt*NumChannels + chan)*SampleSize;
+
+ LoadSamples(SrcBuffer.data(), Data, NumChannels, Buffer->mFmtType, DataRem);
+ SrcBuffer = SrcBuffer.subspan(DataRem);
+
+ /* Load any repeats of the loop we can to fill the buffer. */
+ const auto LoopSize = static_cast<size_t>(LoopEnd - LoopStart);
+ while(!SrcBuffer.empty())
+ {
+ const size_t DataSize{minz(SrcBuffer.size(), LoopSize)};
+
+ Data = Buffer->mData.data() + (LoopStart*NumChannels + chan)*SampleSize;
+
+ LoadSamples(SrcBuffer.data(), Data, NumChannels, Buffer->mFmtType, DataSize);
+ SrcBuffer = SrcBuffer.subspan(DataSize);
+ }
+ }
+ return SrcBuffer.begin();
+}
+
+ALfloat *LoadBufferQueue(ALbufferlistitem *BufferListItem, ALbufferlistitem *BufferLoopItem,
+ const size_t NumChannels, const size_t SampleSize, const size_t chan, size_t DataPosInt,
+ al::span<ALfloat> SrcBuffer)
+{
+ /* Crawl the buffer queue to fill in the temp buffer */
+ while(BufferListItem && !SrcBuffer.empty())
+ {
+ ALbuffer *Buffer{BufferListItem->mBuffer};
+ if(!(Buffer && DataPosInt < Buffer->SampleLen))
+ {
+ if(Buffer) DataPosInt -= Buffer->SampleLen;
+ BufferListItem = BufferListItem->mNext.load(std::memory_order_acquire);
+ if(!BufferListItem) BufferListItem = BufferLoopItem;
+ continue;
+ }
+
+ const size_t DataSize{minz(SrcBuffer.size(), Buffer->SampleLen-DataPosInt)};
+
+ const al::byte *Data{Buffer->mData.data()};
+ Data += (DataPosInt*NumChannels + chan)*SampleSize;
+
+ LoadSamples(SrcBuffer.data(), Data, NumChannels, Buffer->mFmtType, DataSize);
+ SrcBuffer = SrcBuffer.subspan(DataSize);
+ if(SrcBuffer.empty()) break;
+
+ DataPosInt = 0;
+ BufferListItem = BufferListItem->mNext.load(std::memory_order_acquire);
+ if(!BufferListItem) BufferListItem = BufferLoopItem;
+ }
+
+ return SrcBuffer.begin();
+}
+
+
+void DoHrtfMix(const float TargetGain, DirectParams &parms, const float *samples,
+ const ALuint DstBufferSize, const ALuint Counter, ALuint OutPos, const ALuint IrSize,
+ ALCdevice *Device)
+{
+ auto &HrtfSamples = Device->HrtfSourceData;
+ auto &AccumSamples = Device->HrtfAccumData;
+
+ /* Copy the HRTF history and new input samples into a temp buffer. */
+ auto src_iter = std::copy(parms.Hrtf.State.History.begin(), parms.Hrtf.State.History.end(),
+ std::begin(HrtfSamples));
+ std::copy_n(samples, DstBufferSize, src_iter);
+ /* Copy the last used samples back into the history buffer for later. */
+ std::copy_n(std::begin(HrtfSamples) + DstBufferSize, parms.Hrtf.State.History.size(),
+ parms.Hrtf.State.History.begin());
+
+ /* If fading, the old gain is not silence, and this is the first mixing
+ * pass, fade between the IRs.
+ */
+ ALuint fademix{0u};
+ if(Counter && parms.Hrtf.Old.Gain > GAIN_SILENCE_THRESHOLD && OutPos == 0)
+ {
+ fademix = minu(DstBufferSize, 128);
+
+ float gain{TargetGain};
+
+ /* The new coefficients need to fade in completely since they're
+ * replacing the old ones. To keep the gain fading consistent,
+ * interpolate between the old and new target gains given how much of
+ * the fade time this mix handles.
+ */
+ if LIKELY(Counter > fademix)
+ {
+ const ALfloat a{static_cast<float>(fademix) / static_cast<float>(Counter)};
+ gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a);
+ }
+ MixHrtfFilter hrtfparams;
+ hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs;
+ hrtfparams.Delay[0] = parms.Hrtf.Target.Delay[0];
+ hrtfparams.Delay[1] = parms.Hrtf.Target.Delay[1];
+ hrtfparams.Gain = 0.0f;
+ hrtfparams.GainStep = gain / static_cast<float>(fademix);
+
+ MixHrtfBlendSamples(HrtfSamples, AccumSamples+OutPos, IrSize, &parms.Hrtf.Old, &hrtfparams,
+ fademix);
+ /* Update the old parameters with the result. */
+ parms.Hrtf.Old = parms.Hrtf.Target;
+ if(fademix < Counter)
+ parms.Hrtf.Old.Gain = hrtfparams.Gain;
+ else
+ parms.Hrtf.Old.Gain = TargetGain;
+ OutPos += fademix;
+ }
+
+ if LIKELY(fademix < DstBufferSize)
+ {
+ const ALuint todo{DstBufferSize - fademix};
+ float gain{TargetGain};
+
+ /* Interpolate the target gain if the gain fading lasts longer than
+ * this mix.
+ */
+ if(Counter > DstBufferSize)
+ {
+ const float a{static_cast<float>(todo) / static_cast<float>(Counter-fademix)};
+ gain = lerp(parms.Hrtf.Old.Gain, TargetGain, a);
+ }
+
+ MixHrtfFilter hrtfparams;
+ hrtfparams.Coeffs = &parms.Hrtf.Target.Coeffs;
+ hrtfparams.Delay[0] = parms.Hrtf.Target.Delay[0];
+ hrtfparams.Delay[1] = parms.Hrtf.Target.Delay[1];
+ hrtfparams.Gain = parms.Hrtf.Old.Gain;
+ hrtfparams.GainStep = (gain - parms.Hrtf.Old.Gain) / static_cast<float>(todo);
+ MixHrtfSamples(HrtfSamples+fademix, AccumSamples+OutPos, IrSize, &hrtfparams, todo);
+ /* Store the interpolated gain or the final target gain depending if
+ * the fade is done.
+ */
+ if(DstBufferSize < Counter)
+ parms.Hrtf.Old.Gain = gain;
+ else
+ parms.Hrtf.Old.Gain = TargetGain;
+ }
+}
+
+void DoNfcMix(ALvoice::TargetData &Direct, const float *TargetGains, DirectParams &parms,
+ const float *samples, const ALuint DstBufferSize, const ALuint Counter, const ALuint OutPos,
+ ALCdevice *Device)
+{
+ const size_t outcount{Device->NumChannelsPerOrder[0]};
+ MixSamples({samples, DstBufferSize}, Direct.Buffer.first(outcount),
+ parms.Gains.Current.data(), TargetGains, Counter, OutPos);
+
+ const al::span<float> nfcsamples{Device->NfcSampleData, DstBufferSize};
+ size_t chanoffset{outcount};
+ using FilterProc = void (NfcFilter::*)(float*,const float*,const size_t);
+ auto apply_nfc = [&Direct,&parms,samples,TargetGains,Counter,OutPos,&chanoffset,nfcsamples](
+ const FilterProc process, const size_t chancount) -> void
+ {
+ if(chancount < 1) return;
+ (parms.NFCtrlFilter.*process)(nfcsamples.data(), samples, nfcsamples.size());
+ MixSamples(nfcsamples, Direct.Buffer.subspan(chanoffset, chancount),
+ &parms.Gains.Current[chanoffset], &TargetGains[chanoffset], Counter, OutPos);
+ chanoffset += chancount;
+ };
+ apply_nfc(&NfcFilter::process1, Device->NumChannelsPerOrder[1]);
+ apply_nfc(&NfcFilter::process2, Device->NumChannelsPerOrder[2]);
+ apply_nfc(&NfcFilter::process3, Device->NumChannelsPerOrder[3]);
+}
+
+} // namespace
+
+void ALvoice::mix(const State vstate, ALCcontext *Context, const ALuint SamplesToDo)
+{
+ static constexpr std::array<float,MAX_OUTPUT_CHANNELS> SilentTarget{};
+
+ ASSUME(SamplesToDo > 0);
+
+ /* Get voice info */
+ const bool isstatic{(mFlags&VOICE_IS_STATIC) != 0};
+ ALuint DataPosInt{mPosition.load(std::memory_order_relaxed)};
+ ALuint DataPosFrac{mPositionFrac.load(std::memory_order_relaxed)};
+ ALbufferlistitem *BufferListItem{mCurrentBuffer.load(std::memory_order_relaxed)};
+ ALbufferlistitem *BufferLoopItem{mLoopBuffer.load(std::memory_order_relaxed)};
+ const ALuint NumChannels{mNumChannels};
+ const ALuint SampleSize{mSampleSize};
+ const ALuint increment{mStep};
+ if(increment < 1) return;
+
+ ASSUME(NumChannels > 0);
+ ASSUME(SampleSize > 0);
+ ASSUME(increment > 0);
+
+ ALCdevice *Device{Context->mDevice.get()};
+ const ALuint NumSends{Device->NumAuxSends};
+ const ALuint IrSize{Device->mHrtf ? Device->mHrtf->irSize : 0};
+
+ ResamplerFunc Resample{(increment == FRACTIONONE && DataPosFrac == 0) ?
+ Resample_<CopyTag,CTag> : mResampler};
+
+ ALuint Counter{(mFlags&VOICE_IS_FADING) ? SamplesToDo : 0};
+ if(!Counter)
+ {
+ /* No fading, just overwrite the old/current params. */
+ for(ALuint chan{0};chan < NumChannels;chan++)
+ {
+ ChannelData &chandata = mChans[chan];
+ {
+ DirectParams &parms = chandata.mDryParams;
+ if(!(mFlags&VOICE_HAS_HRTF))
+ parms.Gains.Current = parms.Gains.Target;
+ else
+ parms.Hrtf.Old = parms.Hrtf.Target;
+ }
+ for(ALuint send{0};send < NumSends;++send)
+ {
+ if(mSend[send].Buffer.empty())
+ continue;
+
+ SendParams &parms = chandata.mWetParams[send];
+ parms.Gains.Current = parms.Gains.Target;
+ }
+ }
+ }
+ else if((mFlags&VOICE_HAS_HRTF))
+ {
+ for(ALuint chan{0};chan < NumChannels;chan++)
+ {
+ DirectParams &parms = mChans[chan].mDryParams;
+ if(!(parms.Hrtf.Old.Gain > GAIN_SILENCE_THRESHOLD))
+ {
+ /* The old HRTF params are silent, so overwrite the old
+ * coefficients with the new, and reset the old gain to 0. The
+ * future mix will then fade from silence.
+ */
+ parms.Hrtf.Old = parms.Hrtf.Target;
+ parms.Hrtf.Old.Gain = 0.0f;
+ }
+ }
+ }
+
+ ALuint buffers_done{0u};
+ ALuint OutPos{0u};
+ do {
+ /* Figure out how many buffer samples will be needed */
+ ALuint DstBufferSize{SamplesToDo - OutPos};
+
+ /* Calculate the last written dst sample pos. */
+ uint64_t DataSize64{DstBufferSize - 1};
+ /* Calculate the last read src sample pos. */
+ DataSize64 = (DataSize64*increment + DataPosFrac) >> FRACTIONBITS;
+ /* +1 to get the src sample count, include padding. */
+ DataSize64 += 1 + MAX_RESAMPLER_PADDING;
+
+ auto SrcBufferSize = static_cast<ALuint>(
+ minu64(DataSize64, BUFFERSIZE + MAX_RESAMPLER_PADDING + 1));
+ if(SrcBufferSize > BUFFERSIZE + MAX_RESAMPLER_PADDING)
+ {
+ SrcBufferSize = BUFFERSIZE + MAX_RESAMPLER_PADDING;
+ /* If the source buffer got saturated, we can't fill the desired
+ * dst size. Figure out how many samples we can actually mix from
+ * this.
+ */
+ DataSize64 = SrcBufferSize - MAX_RESAMPLER_PADDING;
+ DataSize64 = ((DataSize64<<FRACTIONBITS) - DataPosFrac + increment-1) / increment;
+ DstBufferSize = static_cast<ALuint>(minu64(DataSize64, DstBufferSize));
+
+ /* Some mixers like having a multiple of 4, so try to give that
+ * unless this is the last update.
+ */
+ if(DstBufferSize < SamplesToDo-OutPos)
+ DstBufferSize &= ~3u;
+ }
+
+ ASSUME(DstBufferSize > 0);
+ for(ALuint chan{0};chan < NumChannels;chan++)
+ {
+ ChannelData &chandata = mChans[chan];
+ const al::span<ALfloat> SrcData{Device->SourceData, SrcBufferSize};
+
+ /* Load the previous samples into the source data first, then load
+ * what we can from the buffer queue.
+ */
+ auto srciter = std::copy_n(chandata.mPrevSamples.begin(), MAX_RESAMPLER_PADDING>>1,
+ SrcData.begin());
+
+ if UNLIKELY(!BufferListItem)
+ srciter = std::copy(chandata.mPrevSamples.begin()+(MAX_RESAMPLER_PADDING>>1),
+ chandata.mPrevSamples.end(), srciter);
+ else if(isstatic)
+ srciter = LoadBufferStatic(BufferListItem, BufferLoopItem, NumChannels,
+ SampleSize, chan, DataPosInt, {srciter, SrcData.end()});
+ else
+ srciter = LoadBufferQueue(BufferListItem, BufferLoopItem, NumChannels,
+ SampleSize, chan, DataPosInt, {srciter, SrcData.end()});
+
+ if UNLIKELY(srciter != SrcData.end())
+ {
+ /* If the source buffer wasn't filled, copy the last sample for
+ * the remaining buffer. Ideally it should have ended with
+ * silence, but if not the gain fading should help avoid clicks
+ * from sudden amplitude changes.
+ */
+ const ALfloat sample{*(srciter-1)};
+ std::fill(srciter, SrcData.end(), sample);
+ }
+
+ /* Store the last source samples used for next time. */
+ std::copy_n(&SrcData[(increment*DstBufferSize + DataPosFrac)>>FRACTIONBITS],
+ chandata.mPrevSamples.size(), chandata.mPrevSamples.begin());
+
+ /* Resample, then apply ambisonic upsampling as needed. */
+ const ALfloat *ResampledData{Resample(&mResampleState,
+ &SrcData[MAX_RESAMPLER_PADDING>>1], DataPosFrac, increment,
+ {Device->ResampledData, DstBufferSize})};
+ if((mFlags&VOICE_IS_AMBISONIC))
+ {
+ const ALfloat hfscale{chandata.mAmbiScale};
+ /* Beware the evil const_cast. It's safe since it's pointing to
+ * either SourceData or ResampledData (both non-const), but the
+ * resample method takes the source as const float* and may
+ * return it without copying to output, making it currently
+ * unavoidable.
+ */
+ chandata.mAmbiSplitter.applyHfScale(const_cast<ALfloat*>(ResampledData), hfscale,
+ DstBufferSize);
+ }
+
+ /* Now filter and mix to the appropriate outputs. */
+ ALfloat (&FilterBuf)[BUFFERSIZE] = Device->FilteredData;
+ {
+ DirectParams &parms = chandata.mDryParams;
+ const ALfloat *samples{DoFilters(&parms.LowPass, &parms.HighPass, FilterBuf,
+ ResampledData, DstBufferSize, mDirect.FilterType)};
+
+ if((mFlags&VOICE_HAS_HRTF))
+ {
+ const ALfloat TargetGain{UNLIKELY(vstate == ALvoice::Stopping) ? 0.0f :
+ parms.Hrtf.Target.Gain};
+ DoHrtfMix(TargetGain, parms, samples, DstBufferSize, Counter, OutPos, IrSize,
+ Device);
+ }
+ else if((mFlags&VOICE_HAS_NFC))
+ {
+ const float *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ?
+ SilentTarget.data() : parms.Gains.Target.data()};
+ DoNfcMix(mDirect, TargetGains, parms, samples, DstBufferSize, Counter, OutPos,
+ Device);
+ }
+ else
+ {
+ const float *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ?
+ SilentTarget.data() : parms.Gains.Target.data()};
+ MixSamples({samples, DstBufferSize}, mDirect.Buffer,
+ parms.Gains.Current.data(), TargetGains, Counter, OutPos);
+ }
+ }
+
+ for(ALuint send{0};send < NumSends;++send)
+ {
+ if(mSend[send].Buffer.empty())
+ continue;
+
+ SendParams &parms = chandata.mWetParams[send];
+ const ALfloat *samples{DoFilters(&parms.LowPass, &parms.HighPass, FilterBuf,
+ ResampledData, DstBufferSize, mSend[send].FilterType)};
+
+ const float *TargetGains{UNLIKELY(vstate == ALvoice::Stopping) ?
+ SilentTarget.data() : parms.Gains.Target.data()};
+ MixSamples({samples, DstBufferSize}, mSend[send].Buffer,
+ parms.Gains.Current.data(), TargetGains, Counter, OutPos);
+ }
+ }
+ /* Update positions */
+ DataPosFrac += increment*DstBufferSize;
+ DataPosInt += DataPosFrac>>FRACTIONBITS;
+ DataPosFrac &= FRACTIONMASK;
+
+ OutPos += DstBufferSize;
+ Counter = maxu(DstBufferSize, Counter) - DstBufferSize;
+
+ if UNLIKELY(!BufferListItem)
+ {
+ /* Do nothing extra when there's no buffers. */
+ }
+ else if(isstatic)
+ {
+ if(BufferLoopItem)
+ {
+ /* Handle looping static source */
+ const ALbuffer *Buffer{BufferListItem->mBuffer};
+ const ALuint LoopStart{Buffer->LoopStart};
+ const ALuint LoopEnd{Buffer->LoopEnd};
+ if(DataPosInt >= LoopEnd)
+ {
+ assert(LoopEnd > LoopStart);
+ DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart;
+ }
+ }
+ else
+ {
+ /* Handle non-looping static source */
+ if(DataPosInt >= BufferListItem->mSampleLen)
+ {
+ BufferListItem = nullptr;
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* Handle streaming source */
+ do {
+ if(BufferListItem->mSampleLen > DataPosInt)
+ break;
+
+ DataPosInt -= BufferListItem->mSampleLen;
+
+ ++buffers_done;
+ BufferListItem = BufferListItem->mNext.load(std::memory_order_relaxed);
+ if(!BufferListItem) BufferListItem = BufferLoopItem;
+ } while(BufferListItem);
+ }
+ } while(OutPos < SamplesToDo);
+
+ mFlags |= VOICE_IS_FADING;
+
+ /* Don't update positions and buffers if we were stopping. */
+ if UNLIKELY(vstate == ALvoice::Stopping)
+ {
+ mPlayState.store(ALvoice::Stopped, std::memory_order_release);
+ return;
+ }
+
+ /* Capture the source ID in case it's reset for stopping. */
+ const ALuint SourceID{mSourceID.load(std::memory_order_relaxed)};
+
+ /* Update voice info */
+ mPosition.store(DataPosInt, std::memory_order_relaxed);
+ mPositionFrac.store(DataPosFrac, std::memory_order_relaxed);
+ mCurrentBuffer.store(BufferListItem, std::memory_order_relaxed);
+ if(!BufferListItem)
+ {
+ mLoopBuffer.store(nullptr, std::memory_order_relaxed);
+ mSourceID.store(0u, std::memory_order_relaxed);
+ }
+ std::atomic_thread_fence(std::memory_order_release);
+
+ /* Send any events now, after the position/buffer info was updated. */
+ const ALbitfieldSOFT enabledevt{Context->mEnabledEvts.load(std::memory_order_acquire)};
+ if(buffers_done > 0 && (enabledevt&EventType_BufferCompleted))
+ {
+ RingBuffer *ring{Context->mAsyncEvents.get()};
+ auto evt_vec = ring->getWriteVector();
+ if(evt_vec.first.len > 0)
+ {
+ AsyncEvent *evt{new (evt_vec.first.buf) AsyncEvent{EventType_BufferCompleted}};
+ evt->u.bufcomp.id = SourceID;
+ evt->u.bufcomp.count = buffers_done;
+ ring->writeAdvance(1);
+ Context->mEventSem.post();
+ }
+ }
+
+ if(!BufferListItem)
+ {
+ /* If the voice just ended, set it to Stopping so the next render
+ * ensures any residual noise fades to 0 amplitude.
+ */
+ mPlayState.store(ALvoice::Stopping, std::memory_order_release);
+ if((enabledevt&EventType_SourceStateChange))
+ SendSourceStoppedEvent(Context, SourceID);
+ }
+}
diff --git a/alc/voice.h b/alc/voice.h
new file mode 100644
index 00000000..d6b624f9
--- /dev/null
+++ b/alc/voice.h
@@ -0,0 +1,293 @@
+#ifndef VOICE_H
+#define VOICE_H
+
+#include <array>
+
+#include "AL/al.h"
+#include "AL/alext.h"
+
+#include "al/buffer.h"
+#include "alspan.h"
+#include "alu.h"
+#include "filters/biquad.h"
+#include "filters/nfc.h"
+#include "filters/splitter.h"
+#include "hrtf.h"
+
+enum class DistanceModel;
+
+
+enum SpatializeMode {
+ SpatializeOff = AL_FALSE,
+ SpatializeOn = AL_TRUE,
+ SpatializeAuto = AL_AUTO_SOFT
+};
+
+enum class Resampler {
+ Point,
+ Linear,
+ Cubic,
+ FastBSinc12,
+ BSinc12,
+ FastBSinc24,
+ BSinc24,
+
+ Max = BSinc24
+};
+extern Resampler ResamplerDefault;
+
+/* The number of distinct scale and phase intervals within the bsinc filter
+ * table.
+ */
+#define BSINC_SCALE_BITS 4
+#define BSINC_SCALE_COUNT (1<<BSINC_SCALE_BITS)
+#define BSINC_PHASE_BITS 5
+#define BSINC_PHASE_COUNT (1<<BSINC_PHASE_BITS)
+
+/* Interpolator state. Kind of a misnomer since the interpolator itself is
+ * stateless. This just keeps it from having to recompute scale-related
+ * mappings for every sample.
+ */
+struct BsincState {
+ float sf; /* Scale interpolation factor. */
+ ALuint m; /* Coefficient count. */
+ ALuint l; /* Left coefficient offset. */
+ /* Filter coefficients, followed by the phase, scale, and scale-phase
+ * delta coefficients. Starting at phase index 0, each subsequent phase
+ * index follows contiguously.
+ */
+ const float *filter;
+};
+
+union InterpState {
+ BsincState bsinc;
+};
+
+using ResamplerFunc = const float*(*)(const InterpState *state, const float *RESTRICT src,
+ ALuint frac, ALuint increment, const al::span<float> dst);
+
+ResamplerFunc PrepareResampler(Resampler resampler, ALuint increment, InterpState *state);
+
+
+enum {
+ AF_None = 0,
+ AF_LowPass = 1,
+ AF_HighPass = 2,
+ AF_BandPass = AF_LowPass | AF_HighPass
+};
+
+
+struct MixHrtfFilter {
+ const HrirArray *Coeffs;
+ ALsizei Delay[2];
+ float Gain;
+ float GainStep;
+};
+
+
+struct DirectParams {
+ BiquadFilter LowPass;
+ BiquadFilter HighPass;
+
+ NfcFilter NFCtrlFilter;
+
+ struct {
+ HrtfFilter Old;
+ HrtfFilter Target;
+ HrtfState State;
+ } Hrtf;
+
+ struct {
+ std::array<float,MAX_OUTPUT_CHANNELS> Current;
+ std::array<float,MAX_OUTPUT_CHANNELS> Target;
+ } Gains;
+};
+
+struct SendParams {
+ BiquadFilter LowPass;
+ BiquadFilter HighPass;
+
+ struct {
+ std::array<float,MAX_OUTPUT_CHANNELS> Current;
+ std::array<float,MAX_OUTPUT_CHANNELS> Target;
+ } Gains;
+};
+
+
+struct ALvoicePropsBase {
+ float Pitch;
+ float Gain;
+ float OuterGain;
+ float MinGain;
+ float MaxGain;
+ float InnerAngle;
+ float OuterAngle;
+ float RefDistance;
+ float MaxDistance;
+ float RolloffFactor;
+ std::array<float,3> Position;
+ std::array<float,3> Velocity;
+ std::array<float,3> Direction;
+ std::array<float,3> OrientAt;
+ std::array<float,3> OrientUp;
+ bool HeadRelative;
+ DistanceModel mDistanceModel;
+ Resampler mResampler;
+ bool DirectChannels;
+ SpatializeMode mSpatializeMode;
+
+ bool DryGainHFAuto;
+ bool WetGainAuto;
+ bool WetGainHFAuto;
+ float OuterGainHF;
+
+ float AirAbsorptionFactor;
+ float RoomRolloffFactor;
+ float DopplerFactor;
+
+ std::array<float,2> StereoPan;
+
+ float Radius;
+
+ /** Direct filter and auxiliary send info. */
+ struct {
+ float Gain;
+ float GainHF;
+ float HFReference;
+ float GainLF;
+ float LFReference;
+ } Direct;
+ struct SendData {
+ ALeffectslot *Slot;
+ float Gain;
+ float GainHF;
+ float HFReference;
+ float GainLF;
+ float LFReference;
+ } Send[MAX_SENDS];
+};
+
+struct ALvoiceProps : public ALvoicePropsBase {
+ std::atomic<ALvoiceProps*> next{nullptr};
+
+ DEF_NEWDEL(ALvoiceProps)
+};
+
+#define VOICE_IS_STATIC (1u<<0)
+#define VOICE_IS_FADING (1u<<1) /* Fading sources use gain stepping for smooth transitions. */
+#define VOICE_IS_AMBISONIC (1u<<2) /* Voice needs HF scaling for ambisonic upsampling. */
+#define VOICE_HAS_HRTF (1u<<3)
+#define VOICE_HAS_NFC (1u<<4)
+
+struct ALvoice {
+ enum State {
+ Stopped = 0,
+ Playing = 1,
+ Stopping = 2
+ };
+
+ std::atomic<ALvoiceProps*> mUpdate{nullptr};
+
+ std::atomic<ALuint> mSourceID{0u};
+ std::atomic<State> mPlayState{Stopped};
+
+ ALvoicePropsBase mProps;
+
+ /**
+ * Source offset in samples, relative to the currently playing buffer, NOT
+ * the whole queue.
+ */
+ std::atomic<ALuint> mPosition;
+ /** Fractional (fixed-point) offset to the next sample. */
+ std::atomic<ALuint> mPositionFrac;
+
+ /* Current buffer queue item being played. */
+ std::atomic<ALbufferlistitem*> mCurrentBuffer;
+
+ /* Buffer queue item to loop to at end of queue (will be NULL for non-
+ * looping voices).
+ */
+ std::atomic<ALbufferlistitem*> mLoopBuffer;
+
+ /* Properties for the attached buffer(s). */
+ FmtChannels mFmtChannels;
+ ALuint mFrequency;
+ ALuint mNumChannels;
+ ALuint mSampleSize;
+
+ /** Current target parameters used for mixing. */
+ ALuint mStep;
+
+ ResamplerFunc mResampler;
+
+ InterpState mResampleState;
+
+ ALuint mFlags;
+
+ struct TargetData {
+ int FilterType;
+ al::span<FloatBufferLine> Buffer;
+ };
+ TargetData mDirect;
+ std::array<TargetData,MAX_SENDS> mSend;
+
+ struct ChannelData {
+ alignas(16) std::array<float,MAX_RESAMPLER_PADDING> mPrevSamples;
+
+ float mAmbiScale;
+ BandSplitter mAmbiSplitter;
+
+ DirectParams mDryParams;
+ std::array<SendParams,MAX_SENDS> mWetParams;
+ };
+ std::array<ChannelData,MAX_INPUT_CHANNELS> mChans;
+
+ ALvoice() = default;
+ ALvoice(const ALvoice&) = delete;
+ ALvoice(ALvoice&& rhs) noexcept { *this = std::move(rhs); }
+ ~ALvoice() { delete mUpdate.exchange(nullptr, std::memory_order_acq_rel); }
+ ALvoice& operator=(const ALvoice&) = delete;
+ ALvoice& operator=(ALvoice&& rhs) noexcept
+ {
+ ALvoiceProps *old_update{mUpdate.load(std::memory_order_relaxed)};
+ mUpdate.store(rhs.mUpdate.exchange(old_update, std::memory_order_relaxed),
+ std::memory_order_relaxed);
+
+ mSourceID.store(rhs.mSourceID.load(std::memory_order_relaxed), std::memory_order_relaxed);
+ mPlayState.store(rhs.mPlayState.load(std::memory_order_relaxed),
+ std::memory_order_relaxed);
+
+ mProps = rhs.mProps;
+
+ mPosition.store(rhs.mPosition.load(std::memory_order_relaxed), std::memory_order_relaxed);
+ mPositionFrac.store(rhs.mPositionFrac.load(std::memory_order_relaxed),
+ std::memory_order_relaxed);
+
+ mCurrentBuffer.store(rhs.mCurrentBuffer.load(std::memory_order_relaxed),
+ std::memory_order_relaxed);
+ mLoopBuffer.store(rhs.mLoopBuffer.load(std::memory_order_relaxed),
+ std::memory_order_relaxed);
+
+ mFmtChannels = rhs.mFmtChannels;
+ mFrequency = rhs.mFrequency;
+ mNumChannels = rhs.mNumChannels;
+ mSampleSize = rhs.mSampleSize;
+
+ mStep = rhs.mStep;
+ mResampler = rhs.mResampler;
+
+ mResampleState = rhs.mResampleState;
+
+ mFlags = rhs.mFlags;
+
+ mDirect = rhs.mDirect;
+ mSend = rhs.mSend;
+ mChans = rhs.mChans;
+
+ return *this;
+ }
+
+ void mix(const State vstate, ALCcontext *Context, const ALuint SamplesToDo);
+};
+
+#endif /* VOICE_H */
diff --git a/alsoftrc.sample b/alsoftrc.sample
index 8061ed1c..de3ab6f3 100644
--- a/alsoftrc.sample
+++ b/alsoftrc.sample
@@ -72,9 +72,11 @@
#frequency =
## period_size:
-# Sets the update period size, in frames. This is the number of frames needed
-# for each mixing update. Acceptable values range between 64 and 8192.
-#period_size = 1024
+# Sets the update period size, in sample frames. This is the number of frames
+# needed for each mixing update. Acceptable values range between 64 and 8192.
+# If left unspecified it will default to 1/50th of the frequency (20ms, or 882
+# for 44100, 960 for 48000, etc).
+#period_size =
## periods:
# Sets the number of update periods. Higher values create a larger mix ahead,
@@ -100,8 +102,8 @@
## ambi-format:
# Specifies the channel order and normalization for the "ambi*" set of channel
-# configurations. Valid settings are: fuma, acn+sn3d, acn+n3d
-#ambi-format = acn+sn3d
+# configurations. Valid settings are: fuma, ambix (or acn+sn3d), acn+n3d
+#ambi-format = ambix
## hrtf:
# Controls HRTF processing. These filters provide better spatialization of
@@ -113,6 +115,19 @@
# respectively.
#hrtf = auto
+## hrtf-mode:
+# Specifies the rendering mode for HRTF processing. Setting the mode to full
+# (default) applies a unique HRIR filter to each source given its relative
+# location, providing the clearest directional response at the cost of the
+# highest CPU usage. Setting the mode to ambi1, ambi2, or ambi3 will instead
+# mix to a first-, second-, or third-order ambisonic buffer respectively, then
+# decode that buffer with HRTF filters. Ambi1 has the lowest CPU usage,
+# replacing the per-source HRIR filter for a simple 4-channel panning mix, but
+# retains full 3D placement at the cost of a more diffuse response. Ambi2 and
+# ambi3 increasingly improve the directional clarity, at the cost of more CPU
+# usage (still less than "full", given some number of active sources).
+#hrtf-mode = full
+
## default-hrtf:
# Specifies the default HRTF to use. When multiple HRTFs are available, this
# determines the preferred one to use if none are specifically requested. Note
@@ -146,14 +161,18 @@
#cf_level = 0
## resampler: (global)
-# Selects the resampler used when mixing sources. Valid values are:
+# Selects the default resampler used when mixing sources. Valid values are:
# point - nearest sample, no interpolation
# linear - extrapolates samples using a linear slope between samples
# cubic - extrapolates samples using a Catmull-Rom spline
# bsinc12 - extrapolates samples using a band-limited Sinc filter (varying
# between 12 and 24 points, with anti-aliasing)
+# fast_bsinc12 - same as bsinc12, except without interpolation between down-
+# sampling scales
# bsinc24 - extrapolates samples using a band-limited Sinc filter (varying
# between 24 and 48 points, with anti-aliasing)
+# fast_bsinc24 - same as bsinc24, except without interpolation between down-
+# sampling scales
#resampler = linear
## rt-prio: (global)
@@ -221,7 +240,7 @@
# help for apps that try to use effects which are too CPU intensive for the
# system to handle. Available effects are: eaxreverb,reverb,autowah,chorus,
# compressor,distortion,echo,equalizer,flanger,modulator,dedicated,pshifter,
-# fshifter
+# fshifter,vmorpher.
#excludefx =
## default-reverb: (global)
@@ -257,7 +276,7 @@
# configuration files for the appropriate speaker configuration you intend to
# use (see the quad, surround51, etc options below). Currently, up to third-
# order decoding is supported.
-hq-mode = false
+#hq-mode = true
## distance-comp:
# Enables compensation for the speakers' relative distances to the listener.
@@ -265,7 +284,7 @@ hq-mode = false
# behave as though they are all equidistant, which is important for proper
# playback of 3D sound rendering. Requires the proper distances to be
# specified in the decoder configuration file.
-distance-comp = true
+#distance-comp = true
## nfc:
# Enables near-field control filters. This simulates and compensates for low-
@@ -273,40 +292,41 @@ distance-comp = true
# creates a more realistic perception of sound distance. Note that the effect
# may be stronger or weaker than intended if the application doesn't use or
# specify an appropriate unit scale, or if incorrect speaker distances are set
-# in the decoder configuration file. Requires hq-mode to be enabled.
-nfc = true
+# in the decoder configuration file.
+#nfc = false
## nfc-ref-delay
-# Specifies the reference delay value for ambisonic output. When channels is
-# set to one of the ambi* formats, this option enables NFC-HOA output with the
-# specified Reference Delay parameter. The specified value can then be shared
-# with an appropriate NFC-HOA decoder to reproduce correct near-field effects.
-# Keep in mind that despite being designed for higher-order ambisonics, this
-# applies to first-order output all the same. When left unset, normal output
-# is created with no near-field simulation.
-nfc-ref-delay =
+# Specifies the reference delay value for ambisonic output when NFC filters
+# are enabled. If channels is set to one of the ambi* formats, this option
+# enables NFC-HOA output with the specified Reference Delay parameter. The
+# specified value can then be shared with an appropriate NFC-HOA decoder to
+# reproduce correct near-field effects. Keep in mind that despite being
+# designed for higher-order ambisonics, this also applies to first-order
+# output. When left unset, normal output is created with no near-field
+# simulation. Requires the nfc option to also be enabled.
+#nfc-ref-delay =
## quad:
# Decoder configuration file for Quadraphonic channel output. See
# docs/ambdec.txt for a description of the file format.
-quad =
+#quad =
## surround51:
# Decoder configuration file for 5.1 Surround (Side and Rear) channel output.
# See docs/ambdec.txt for a description of the file format.
-surround51 =
+#surround51 =
## surround61:
# Decoder configuration file for 6.1 Surround channel output. See
# docs/ambdec.txt for a description of the file format.
-surround61 =
+#surround61 =
## surround71:
# Decoder configuration file for 7.1 Surround channel output. See
# docs/ambdec.txt for a description of the file format. Note: This can be used
# to enable 3D7.1 with the appropriate configuration and speaker placement,
# see docs/3D7.1.txt.
-surround71 =
+#surround71 =
##
## Reverb effect stuff (includes EAX reverb)
@@ -336,7 +356,7 @@ surround71 =
# device specifier (seen by applications) will not be updated when this
# occurs, and neither will the AL device configuration (sample rate, format,
# etc).
-#allow-moves = false
+#allow-moves = true
## fix-rate:
# Specifies whether to match the playback stream's sample rate to the device's
@@ -345,6 +365,14 @@ surround71 =
# PulseAudio server.
#fix-rate = false
+## adjust-latency:
+# Attempts to adjust the overall latency of device playback. Note that this
+# may have adverse effects on the resulting internal buffer sizes and mixing
+# updates, leading to performance problems and drop-outs. However, if the
+# PulseAudio server is creating a lot of latency, enabling this may help make
+# it more manageable.
+#adjust-latency = false
+
##
## ALSA backend stuff
##
diff --git a/appveyor.yml b/appveyor.yml
index 6d826eed..81080828 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,10 +1,18 @@
-version: 1.19.0.{build}
+version: 1.20.0.{build}
environment:
matrix:
- - GEN: "Visual Studio 14 2015"
+ - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+ GEN: "Visual Studio 14 2015"
+ ARCH: Win32
CFG: Release
- - GEN: "Visual Studio 14 2015 Win64"
+ - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+ GEN: "Visual Studio 14 2015"
+ ARCH: x64
+ CFG: Release
+ - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+ GEN: "Visual Studio 15 2017"
+ ARCH: x64
CFG: Release
install:
@@ -14,6 +22,6 @@ install:
build_script:
- cd build
- - cmake -G"%GEN%" -DALSOFT_REQUIRE_WINMM=ON -DALSOFT_REQUIRE_DSOUND=ON -DALSOFT_REQUIRE_WASAPI=ON -DALSOFT_EMBED_HRTF_DATA=YES ..
+ - cmake -G "%GEN%" -A %ARCH% -DALSOFT_BUILD_ROUTER=ON -DALSOFT_REQUIRE_WINMM=ON -DALSOFT_REQUIRE_DSOUND=ON -DALSOFT_REQUIRE_WASAPI=ON -DALSOFT_EMBED_HRTF_DATA=YES ..
- cmake --build . --config %CFG% --clean-first
diff --git a/cmake/CheckFileOffsetBits.c b/cmake/CheckFileOffsetBits.c
deleted file mode 100644
index de98296e..00000000
--- a/cmake/CheckFileOffsetBits.c
+++ /dev/null
@@ -1,9 +0,0 @@
-#include <sys/types.h>
-
-#define KB ((off_t)(1024))
-#define MB ((off_t)(KB*1024))
-#define GB ((off_t)(MB*1024))
-int tb[((GB+GB+GB) > GB) ? 1 : -1];
-
-int main()
-{ return 0; }
diff --git a/cmake/CheckFileOffsetBits.cmake b/cmake/CheckFileOffsetBits.cmake
deleted file mode 100644
index 1dc154e4..00000000
--- a/cmake/CheckFileOffsetBits.cmake
+++ /dev/null
@@ -1,39 +0,0 @@
-# - Check if the _FILE_OFFSET_BITS macro is needed for large files
-# CHECK_FILE_OFFSET_BITS()
-#
-# The following variables may be set before calling this macro to
-# modify the way the check is run:
-#
-# CMAKE_REQUIRED_FLAGS = string of compile command line flags
-# CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar)
-# CMAKE_REQUIRED_INCLUDES = list of include directories
-# Copyright (c) 2009, Chris Robinson
-#
-# Redistribution and use is allowed according to the terms of the LGPL license.
-
-
-MACRO(CHECK_FILE_OFFSET_BITS)
-
- IF(NOT DEFINED _FILE_OFFSET_BITS)
- MESSAGE(STATUS "Checking _FILE_OFFSET_BITS for large files")
- TRY_COMPILE(__WITHOUT_FILE_OFFSET_BITS_64
- ${CMAKE_CURRENT_BINARY_DIR}
- ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CheckFileOffsetBits.c
- COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS})
- IF(NOT __WITHOUT_FILE_OFFSET_BITS_64)
- TRY_COMPILE(__WITH_FILE_OFFSET_BITS_64
- ${CMAKE_CURRENT_BINARY_DIR}
- ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CheckFileOffsetBits.c
- COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -D_FILE_OFFSET_BITS=64)
- ENDIF(NOT __WITHOUT_FILE_OFFSET_BITS_64)
-
- IF(NOT __WITHOUT_FILE_OFFSET_BITS_64 AND __WITH_FILE_OFFSET_BITS_64)
- SET(_FILE_OFFSET_BITS 64 CACHE INTERNAL "_FILE_OFFSET_BITS macro needed for large files")
- MESSAGE(STATUS "Checking _FILE_OFFSET_BITS for large files - 64")
- ELSE(NOT __WITHOUT_FILE_OFFSET_BITS_64 AND __WITH_FILE_OFFSET_BITS_64)
- SET(_FILE_OFFSET_BITS "" CACHE INTERNAL "_FILE_OFFSET_BITS macro needed for large files")
- MESSAGE(STATUS "Checking _FILE_OFFSET_BITS for large files - not needed")
- ENDIF(NOT __WITHOUT_FILE_OFFSET_BITS_64 AND __WITH_FILE_OFFSET_BITS_64)
- ENDIF(NOT DEFINED _FILE_OFFSET_BITS)
-
-ENDMACRO(CHECK_FILE_OFFSET_BITS) \ No newline at end of file
diff --git a/cmake/CheckSharedFunctionExists.cmake b/cmake/CheckSharedFunctionExists.cmake
deleted file mode 100644
index c691fa9c..00000000
--- a/cmake/CheckSharedFunctionExists.cmake
+++ /dev/null
@@ -1,92 +0,0 @@
-# - Check if a symbol exists as a function, variable, or macro
-# CHECK_SYMBOL_EXISTS(<symbol> <files> <variable>)
-#
-# Check that the <symbol> is available after including given header
-# <files> and store the result in a <variable>. Specify the list
-# of files in one argument as a semicolon-separated list.
-#
-# If the header files define the symbol as a macro it is considered
-# available and assumed to work. If the header files declare the
-# symbol as a function or variable then the symbol must also be
-# available for linking. If the symbol is a type or enum value
-# it will not be recognized (consider using CheckTypeSize or
-# CheckCSourceCompiles).
-#
-# The following variables may be set before calling this macro to
-# modify the way the check is run:
-#
-# CMAKE_REQUIRED_FLAGS = string of compile command line flags
-# CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar)
-# CMAKE_REQUIRED_INCLUDES = list of include directories
-# CMAKE_REQUIRED_LIBRARIES = list of libraries to link
-
-#=============================================================================
-# Copyright 2003-2011 Kitware, Inc.
-#
-# Distributed under the OSI-approved BSD License (the "License");
-# see accompanying file Copyright.txt for details.
-#
-# This software is distributed WITHOUT ANY WARRANTY; without even the
-# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-# See the License for more information.
-#=============================================================================
-# (To distribute this file outside of CMake, substitute the full
-# License text for the above reference.)
-
-MACRO(CHECK_SHARED_FUNCTION_EXISTS SYMBOL FILES LIBRARY LOCATION VARIABLE)
- IF(NOT DEFINED "${VARIABLE}" OR "x${${VARIABLE}}" STREQUAL "x${VARIABLE}")
- SET(CMAKE_CONFIGURABLE_FILE_CONTENT "/* */\n")
- SET(MACRO_CHECK_SYMBOL_EXISTS_FLAGS ${CMAKE_REQUIRED_FLAGS})
- IF(CMAKE_REQUIRED_LIBRARIES)
- SET(CHECK_SYMBOL_EXISTS_LIBS
- "-DLINK_LIBRARIES:STRING=${CMAKE_REQUIRED_LIBRARIES};${LIBRARY}")
- ELSE(CMAKE_REQUIRED_LIBRARIES)
- SET(CHECK_SYMBOL_EXISTS_LIBS
- "-DLINK_LIBRARIES:STRING=${LIBRARY}")
- ENDIF(CMAKE_REQUIRED_LIBRARIES)
- IF(CMAKE_REQUIRED_INCLUDES)
- SET(CMAKE_SYMBOL_EXISTS_INCLUDES
- "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_REQUIRED_INCLUDES}")
- ELSE(CMAKE_REQUIRED_INCLUDES)
- SET(CMAKE_SYMBOL_EXISTS_INCLUDES)
- ENDIF(CMAKE_REQUIRED_INCLUDES)
- FOREACH(FILE ${FILES})
- SET(CMAKE_CONFIGURABLE_FILE_CONTENT
- "${CMAKE_CONFIGURABLE_FILE_CONTENT}#include <${FILE}>\n")
- ENDFOREACH(FILE)
- SET(CMAKE_CONFIGURABLE_FILE_CONTENT
- "${CMAKE_CONFIGURABLE_FILE_CONTENT}\nvoid cmakeRequireSymbol(int dummy,...){(void)dummy;}\nint main()\n{\n cmakeRequireSymbol(0,&${SYMBOL});\n return 0;\n}\n")
-
- CONFIGURE_FILE("${CMAKE_ROOT}/Modules/CMakeConfigurableFile.in"
- "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckSymbolExists.c" @ONLY)
-
- MESSAGE(STATUS "Looking for ${SYMBOL} in ${LIBRARY}")
- TRY_COMPILE(${VARIABLE}
- ${CMAKE_CURRENT_BINARY_DIR}
- ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckSymbolExists.c
- COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}
- CMAKE_FLAGS
- -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_SYMBOL_EXISTS_FLAGS}
- -DLINK_DIRECTORIES:STRING=${LOCATION}
- "${CHECK_SYMBOL_EXISTS_LIBS}"
- "${CMAKE_SYMBOL_EXISTS_INCLUDES}"
- OUTPUT_VARIABLE OUTPUT)
- IF(${VARIABLE})
- MESSAGE(STATUS "Looking for ${SYMBOL} in ${LIBRARY} - found")
- SET(${VARIABLE} 1 CACHE INTERNAL "Have symbol ${SYMBOL} in ${LIBRARY}")
- FILE(APPEND ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
- "Determining if the ${SYMBOL} "
- "exist in ${LIBRARY} passed with the following output:\n"
- "${OUTPUT}\nFile ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckSymbolExists.c:\n"
- "${CMAKE_CONFIGURABLE_FILE_CONTENT}\n")
- ELSE(${VARIABLE})
- MESSAGE(STATUS "Looking for ${SYMBOL} in ${LIBRARY} - not found.")
- SET(${VARIABLE} "" CACHE INTERNAL "Have symbol ${SYMBOL} in ${LIBRARY}")
- FILE(APPEND ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
- "Determining if the ${SYMBOL} "
- "exist in ${LIBRARY} failed with the following output:\n"
- "${OUTPUT}\nFile ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckSymbolExists.c:\n"
- "${CMAKE_CONFIGURABLE_FILE_CONTENT}\n")
- ENDIF(${VARIABLE})
- ENDIF(NOT DEFINED "${VARIABLE}" OR "x${${VARIABLE}}" STREQUAL "x${VARIABLE}")
-ENDMACRO(CHECK_SHARED_FUNCTION_EXISTS)
diff --git a/cmake/FindMySOFA.cmake b/cmake/FindMySOFA.cmake
new file mode 100644
index 00000000..a1d57446
--- /dev/null
+++ b/cmake/FindMySOFA.cmake
@@ -0,0 +1,81 @@
+# - Find MySOFA
+# Find the MySOFA libraries
+#
+# This module defines the following variables:
+# MYSOFA_FOUND - True if MYSOFA_INCLUDE_DIR & MYSOFA_LIBRARY are found
+# MYSOFA_INCLUDE_DIRS - where to find mysofa.h, etc.
+# MYSOFA_LIBRARIES - the MySOFA library
+#
+
+#=============================================================================
+# Copyright 2009-2011 Kitware, Inc.
+# Copyright 2009-2011 Philip Lowman <[email protected]>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# * The names of Kitware, Inc., the Insight Consortium, or the names of
+# any consortium members, or of any contributors, may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS''
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#=============================================================================
+
+find_package(ZLIB)
+
+find_path(MYSOFA_INCLUDE_DIR NAMES mysofa.h
+ DOC "The MySOFA include directory"
+)
+
+find_library(MYSOFA_LIBRARY NAMES mysofa
+ DOC "The MySOFA library"
+)
+
+find_library(MYSOFA_M_LIBRARY NAMES m
+ DOC "The math library for MySOFA"
+)
+
+# handle the QUIETLY and REQUIRED arguments and set MYSOFA_FOUND to TRUE if
+# all listed variables are TRUE
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(MYSOFA REQUIRED_VARS MYSOFA_LIBRARY MYSOFA_INCLUDE_DIR ZLIB_FOUND)
+
+if(MYSOFA_FOUND)
+ set(MYSOFA_INCLUDE_DIRS ${MYSOFA_INCLUDE_DIR})
+ set(MYSOFA_LIBRARIES ${MYSOFA_LIBRARY})
+ set(MYSOFA_LIBRARIES ${MYSOFA_LIBRARIES} ZLIB::ZLIB)
+ if(MYSOFA_M_LIBRARY)
+ set(MYSOFA_LIBRARIES ${MYSOFA_LIBRARIES} ${MYSOFA_M_LIBRARY})
+ endif()
+
+ add_library(MySOFA::MySOFA UNKNOWN IMPORTED)
+ set_property(TARGET MySOFA::MySOFA PROPERTY
+ IMPORTED_LOCATION ${MYSOFA_LIBRARY})
+ set_target_properties(MySOFA::MySOFA PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${MYSOFA_INCLUDE_DIRS}
+ INTERFACE_LINK_LIBRARIES ZLIB::ZLIB)
+ if(MYSOFA_M_LIBRARY)
+ set_property(TARGET MySOFA::MySOFA APPEND PROPERTY
+ INTERFACE_LINK_LIBRARIES ${MYSOFA_M_LIBRARY})
+ endif()
+endif()
+
+mark_as_advanced(MYSOFA_INCLUDE_DIR MYSOFA_LIBRARY)
diff --git a/cmake/FindOpenSL.cmake b/cmake/FindOpenSL.cmake
new file mode 100644
index 00000000..41c68fde
--- /dev/null
+++ b/cmake/FindOpenSL.cmake
@@ -0,0 +1,64 @@
+# - Find OpenSL
+# Find the OpenSL libraries
+#
+# This module defines the following variables:
+# OPENSL_FOUND - True if OPENSL_INCLUDE_DIR & OPENSL_LIBRARY are set
+# OPENSL_INCLUDE_DIRS - where to find SLES/OpenSLES.h, etc.
+# OPENSL_LIBRARIES - the OpenSL library
+#
+
+#=============================================================================
+# Copyright 2009-2011 Kitware, Inc.
+# Copyright 2009-2011 Philip Lowman <[email protected]>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# * The names of Kitware, Inc., the Insight Consortium, or the names of
+# any consortium members, or of any contributors, may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS''
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#=============================================================================
+
+find_path(OPENSL_INCLUDE_DIR NAMES SLES/OpenSLES.h
+ DOC "The OpenSL include directory"
+)
+find_path(OPENSL_ANDROID_INCLUDE_DIR NAMES SLES/OpenSLES_Android.h
+ DOC "The OpenSL Android include directory"
+)
+
+find_library(OPENSL_LIBRARY NAMES OpenSLES
+ DOC "The OpenSL library"
+)
+
+# handle the QUIETLY and REQUIRED arguments and set OPENSL_FOUND to TRUE if
+# all listed variables are TRUE
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OPENSL REQUIRED_VARS OPENSL_LIBRARY OPENSL_INCLUDE_DIR
+ OPENSL_ANDROID_INCLUDE_DIR)
+
+if(OPENSL_FOUND)
+ set(OPENSL_LIBRARIES ${OPENSL_LIBRARY})
+ set(OPENSL_INCLUDE_DIRS ${OPENSL_INCLUDE_DIR} ${OPENSL_ANDROID_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(OPENSL_INCLUDE_DIR OPENSL_ANDROID_INCLUDE_DIR OPENSL_LIBRARY)
diff --git a/cmake/FindWindowsSDK.cmake b/cmake/FindWindowsSDK.cmake
index e136b897..3fcc3bf4 100644
--- a/cmake/FindWindowsSDK.cmake
+++ b/cmake/FindWindowsSDK.cmake
@@ -73,6 +73,11 @@ macro(_winsdk_announce)
endmacro()
set(_winsdk_win10vers
+ 10.0.18362.0 # Windows 10 Version 1903
+ 10.0.17763.0 # Windows 10 Version 1809
+ 10.0.17134.0 # Windows 10 Version 1803 (April 2018 Update)
+ 10.0.16299.0 # Windows 10 Version 1709 (Fall Creators Update)
+ 10.0.15063.0 # Windows 10 Version 1703 (Creators Update)
10.0.14393.0 # Redstone aka Win10 1607 "Anniversary Update"
10.0.10586.0 # TH2 aka Win10 1511
10.0.10240.0 # Win10 RTM
@@ -527,11 +532,11 @@ if(WINDOWSSDK_FOUND)
winv6.3 # Win 8.1 min requirement
)
- list(APPEND _suffixes
- "lib/${_ver}/${_winsdk_arch}"
- "lib/${_ver}/um/${_winsdk_arch8}"
- "lib/${_ver}/km/${_winsdk_arch8}"
- )
+ list(APPEND _suffixes
+ "lib/${_ver}/${_winsdk_arch}"
+ "lib/${_ver}/um/${_winsdk_arch8}"
+ "lib/${_ver}/km/${_winsdk_arch8}"
+ )
endforeach()
# Look for WDF libraries in Win10+ SDK
diff --git a/common/albyte.h b/common/albyte.h
new file mode 100644
index 00000000..23864636
--- /dev/null
+++ b/common/albyte.h
@@ -0,0 +1,99 @@
+#ifndef AL_BYTE_H
+#define AL_BYTE_H
+
+#include <cstddef>
+#include <limits>
+#include <type_traits>
+
+namespace al {
+
+/* The "canonical" way to store raw byte data. Like C++17's std::byte, it's not
+ * treated as a character type and does not work with arithmatic ops. Only
+ * bitwise ops are allowed.
+ */
+enum class byte : unsigned char { };
+
+#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__),bool>::type = true
+
+template<typename T, REQUIRES(std::is_integral<T>::value)>
+inline constexpr T to_integer(al::byte b) noexcept { return T(b); }
+
+
+template<typename T, REQUIRES(std::is_integral<T>::value)>
+inline constexpr al::byte operator<<(al::byte lhs, T rhs) noexcept
+{ return al::byte(to_integer<unsigned int>(lhs) << rhs); }
+
+template<typename T, REQUIRES(std::is_integral<T>::value)>
+inline constexpr al::byte operator>>(al::byte lhs, T rhs) noexcept
+{ return al::byte(to_integer<unsigned int>(lhs) >> rhs); }
+
+template<typename T, REQUIRES(std::is_integral<T>::value)>
+inline al::byte& operator<<=(al::byte &lhs, T rhs) noexcept
+{ lhs = lhs << rhs; return lhs; }
+
+template<typename T, REQUIRES(std::is_integral<T>::value)>
+inline al::byte& operator>>=(al::byte &lhs, T rhs) noexcept
+{ lhs = lhs >> rhs; return lhs; }
+
+#define AL_DECL_OP(op) \
+template<typename T, REQUIRES(std::is_integral<T>::value)> \
+inline constexpr al::byte operator op (al::byte lhs, T rhs) noexcept \
+{ return al::byte(to_integer<unsigned int>(lhs) op static_cast<unsigned int>(rhs)); } \
+template<typename T, REQUIRES(std::is_integral<T>::value)> \
+inline al::byte& operator op##= (al::byte &lhs, T rhs) noexcept \
+{ lhs = lhs op rhs; return lhs; } \
+inline constexpr al::byte operator op (al::byte lhs, al::byte rhs) noexcept \
+{ return al::byte(lhs op to_integer<unsigned int>(rhs)); } \
+inline al::byte& operator op##= (al::byte &lhs, al::byte rhs) noexcept \
+{ lhs = lhs op rhs; return lhs; }
+
+AL_DECL_OP(|)
+AL_DECL_OP(&)
+AL_DECL_OP(^)
+
+#undef AL_DECL_OP
+
+inline constexpr al::byte operator~(al::byte b) noexcept
+{ return al::byte(~to_integer<unsigned int>(b)); }
+
+
+template<size_t N>
+class bitfield {
+ static constexpr size_t bits_per_byte{std::numeric_limits<unsigned char>::digits};
+ static constexpr size_t NumElems{(N+bits_per_byte-1) / bits_per_byte};
+
+ byte vals[NumElems]{};
+
+public:
+ template<size_t b>
+ inline void set() noexcept
+ {
+ static_assert(b < N, "Bit index out of range");
+ vals[b/bits_per_byte] |= 1 << (b%bits_per_byte);
+ }
+ template<size_t b>
+ inline void unset() noexcept
+ {
+ static_assert(b < N, "Bit index out of range");
+ vals[b/bits_per_byte] &= ~(1 << (b%bits_per_byte));
+ }
+ template<size_t b>
+ inline bool get() const noexcept
+ {
+ static_assert(b < N, "Bit index out of range");
+ return (vals[b/bits_per_byte] & (1 << (b%bits_per_byte))) != byte{};
+ }
+
+ template<size_t b, size_t ...args, REQUIRES(sizeof...(args) > 0)>
+ void set() noexcept
+ {
+ set<b>();
+ set<args...>();
+ }
+};
+
+#undef REQUIRES
+
+} // namespace al
+
+#endif /* AL_BYTE_H */
diff --git a/common/alcomplex.c b/common/alcomplex.c
deleted file mode 100644
index d4045aeb..00000000
--- a/common/alcomplex.c
+++ /dev/null
@@ -1,92 +0,0 @@
-
-#include "config.h"
-
-#include "alcomplex.h"
-#include "math_defs.h"
-
-
-extern inline ALcomplex complex_add(ALcomplex a, ALcomplex b);
-extern inline ALcomplex complex_sub(ALcomplex a, ALcomplex b);
-extern inline ALcomplex complex_mult(ALcomplex a, ALcomplex b);
-
-void complex_fft(ALcomplex *FFTBuffer, ALsizei FFTSize, ALdouble Sign)
-{
- ALsizei i, j, k, mask, step, step2;
- ALcomplex temp, u, w;
- ALdouble arg;
-
- /* Bit-reversal permutation applied to a sequence of FFTSize items */
- for(i = 1;i < FFTSize-1;i++)
- {
- for(mask = 0x1, j = 0;mask < FFTSize;mask <<= 1)
- {
- if((i&mask) != 0)
- j++;
- j <<= 1;
- }
- j >>= 1;
-
- if(i < j)
- {
- temp = FFTBuffer[i];
- FFTBuffer[i] = FFTBuffer[j];
- FFTBuffer[j] = temp;
- }
- }
-
- /* Iterative form of Danielson–Lanczos lemma */
- for(i = 1, step = 2;i < FFTSize;i<<=1, step<<=1)
- {
- step2 = step >> 1;
- arg = M_PI / step2;
-
- w.Real = cos(arg);
- w.Imag = sin(arg) * Sign;
-
- u.Real = 1.0;
- u.Imag = 0.0;
-
- for(j = 0;j < step2;j++)
- {
- for(k = j;k < FFTSize;k+=step)
- {
- temp = complex_mult(FFTBuffer[k+step2], u);
- FFTBuffer[k+step2] = complex_sub(FFTBuffer[k], temp);
- FFTBuffer[k] = complex_add(FFTBuffer[k], temp);
- }
-
- u = complex_mult(u, w);
- }
- }
-}
-
-void complex_hilbert(ALcomplex *Buffer, ALsizei size)
-{
- const ALdouble inverse_size = 1.0/(ALdouble)size;
- ALsizei todo, i;
-
- for(i = 0;i < size;i++)
- Buffer[i].Imag = 0.0;
-
- complex_fft(Buffer, size, 1.0);
-
- todo = size >> 1;
- Buffer[0].Real *= inverse_size;
- Buffer[0].Imag *= inverse_size;
- for(i = 1;i < todo;i++)
- {
- Buffer[i].Real *= 2.0*inverse_size;
- Buffer[i].Imag *= 2.0*inverse_size;
- }
- Buffer[i].Real *= inverse_size;
- Buffer[i].Imag *= inverse_size;
- i++;
-
- for(;i < size;i++)
- {
- Buffer[i].Real = 0.0;
- Buffer[i].Imag = 0.0;
- }
-
- complex_fft(Buffer, size, -1.0);
-}
diff --git a/common/alcomplex.cpp b/common/alcomplex.cpp
new file mode 100644
index 00000000..e5cbe8a0
--- /dev/null
+++ b/common/alcomplex.cpp
@@ -0,0 +1,73 @@
+
+#include "config.h"
+
+#include "alcomplex.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <utility>
+
+#include "math_defs.h"
+
+
+void complex_fft(const al::span<std::complex<double>> buffer, const double sign)
+{
+ const size_t fftsize{buffer.size()};
+ /* Bit-reversal permutation applied to a sequence of FFTSize items */
+ for(size_t i{1u};i < fftsize-1;i++)
+ {
+ size_t j{0u};
+ for(size_t mask{1u};mask < fftsize;mask <<= 1)
+ {
+ if((i&mask) != 0)
+ j++;
+ j <<= 1;
+ }
+ j >>= 1;
+
+ if(i < j)
+ std::swap(buffer[i], buffer[j]);
+ }
+
+ /* Iterative form of Danielson–Lanczos lemma */
+ size_t step{2u};
+ for(size_t i{1u};i < fftsize;i<<=1, step<<=1)
+ {
+ const size_t step2{step >> 1};
+ double arg{al::MathDefs<double>::Pi() / static_cast<double>(step2)};
+
+ std::complex<double> w{std::cos(arg), std::sin(arg)*sign};
+ std::complex<double> u{1.0, 0.0};
+ for(size_t j{0};j < step2;j++)
+ {
+ for(size_t k{j};k < fftsize;k+=step)
+ {
+ std::complex<double> temp{buffer[k+step2] * u};
+ buffer[k+step2] = buffer[k] - temp;
+ buffer[k] += temp;
+ }
+
+ u *= w;
+ }
+ }
+}
+
+void complex_hilbert(const al::span<std::complex<double>> buffer)
+{
+ complex_fft(buffer, 1.0);
+
+ const double inverse_size = 1.0/static_cast<double>(buffer.size());
+ auto bufiter = buffer.begin();
+ const auto halfiter = bufiter + (buffer.size()>>1);
+
+ *bufiter *= inverse_size; ++bufiter;
+ bufiter = std::transform(bufiter, halfiter, bufiter,
+ [inverse_size](const std::complex<double> &c) -> std::complex<double>
+ { return c * (2.0*inverse_size); });
+ *bufiter *= inverse_size; ++bufiter;
+
+ std::fill(bufiter, buffer.end(), std::complex<double>{});
+
+ complex_fft(buffer, -1.0);
+}
diff --git a/common/alcomplex.h b/common/alcomplex.h
index 2418ce78..12b86436 100644
--- a/common/alcomplex.h
+++ b/common/alcomplex.h
@@ -1,71 +1,25 @@
#ifndef ALCOMPLEX_H
#define ALCOMPLEX_H
-#include "AL/al.h"
+#include <complex>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct ALcomplex {
- ALdouble Real;
- ALdouble Imag;
-} ALcomplex;
-
-/** Addition of two complex numbers. */
-inline ALcomplex complex_add(ALcomplex a, ALcomplex b)
-{
- ALcomplex result;
-
- result.Real = a.Real + b.Real;
- result.Imag = a.Imag + b.Imag;
-
- return result;
-}
-
-/** Subtraction of two complex numbers. */
-inline ALcomplex complex_sub(ALcomplex a, ALcomplex b)
-{
- ALcomplex result;
-
- result.Real = a.Real - b.Real;
- result.Imag = a.Imag - b.Imag;
-
- return result;
-}
-
-/** Multiplication of two complex numbers. */
-inline ALcomplex complex_mult(ALcomplex a, ALcomplex b)
-{
- ALcomplex result;
-
- result.Real = a.Real*b.Real - a.Imag*b.Imag;
- result.Imag = a.Imag*b.Real + a.Real*b.Imag;
-
- return result;
-}
+#include "alspan.h"
/**
* Iterative implementation of 2-radix FFT (In-place algorithm). Sign = -1 is
- * FFT and 1 is iFFT (inverse). Fills FFTBuffer[0...FFTSize-1] with the
- * Discrete Fourier Transform (DFT) of the time domain data stored in
- * FFTBuffer[0...FFTSize-1]. FFTBuffer is an array of complex numbers, FFTSize
- * MUST BE power of two.
+ * FFT and 1 is iFFT (inverse). Fills the buffer with the Discrete Fourier
+ * Transform (DFT) of the time domain data stored in the buffer. The buffer is
+ * an array of complex numbers, and MUST BE power of two.
*/
-void complex_fft(ALcomplex *FFTBuffer, ALsizei FFTSize, ALdouble Sign);
+void complex_fft(const al::span<std::complex<double>> buffer, const double sign);
/**
* Calculate the complex helical sequence (discrete-time analytical signal) of
* the given input using the discrete Hilbert transform (In-place algorithm).
- * Fills Buffer[0...size-1] with the discrete-time analytical signal stored in
- * Buffer[0...size-1]. Buffer is an array of complex numbers, size MUST BE
- * power of two.
+ * Fills the buffer with the discrete-time analytical signal stored in the
+ * buffer. The buffer is an array of complex numbers and MUST BE power of two,
+ * and the imaginary components should be cleared to 0.
*/
-void complex_hilbert(ALcomplex *Buffer, ALsizei size);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif
+void complex_hilbert(const al::span<std::complex<double>> buffer);
#endif /* ALCOMPLEX_H */
diff --git a/common/alexcpt.cpp b/common/alexcpt.cpp
new file mode 100644
index 00000000..5055e34f
--- /dev/null
+++ b/common/alexcpt.cpp
@@ -0,0 +1,30 @@
+
+#include "config.h"
+
+#include "alexcpt.h"
+
+#include <cstdio>
+#include <cstdarg>
+
+#include "opthelpers.h"
+
+
+namespace al {
+
+backend_exception::backend_exception(ALCenum code, const char *msg, ...) : mErrorCode{code}
+{
+ va_list args, args2;
+ va_start(args, msg);
+ va_copy(args2, args);
+ int msglen{std::vsnprintf(nullptr, 0, msg, args)};
+ if LIKELY(msglen > 0)
+ {
+ mMessage.resize(static_cast<size_t>(msglen)+1);
+ std::vsnprintf(&mMessage[0], mMessage.length(), msg, args2);
+ mMessage.pop_back();
+ }
+ va_end(args2);
+ va_end(args);
+}
+
+} // namespace al
diff --git a/common/alexcpt.h b/common/alexcpt.h
new file mode 100644
index 00000000..ff09bab2
--- /dev/null
+++ b/common/alexcpt.h
@@ -0,0 +1,36 @@
+#ifndef ALEXCPT_H
+#define ALEXCPT_H
+
+#include <exception>
+#include <string>
+
+#include "AL/alc.h"
+
+
+#ifdef __GNUC__
+#define ALEXCPT_FORMAT(x, y, z) __attribute__((format(x, (y), (z))))
+#else
+#define ALEXCPT_FORMAT(x, y, z)
+#endif
+
+
+namespace al {
+
+class backend_exception final : public std::exception {
+ std::string mMessage;
+ ALCenum mErrorCode;
+
+public:
+ backend_exception(ALCenum code, const char *msg, ...) ALEXCPT_FORMAT(printf, 3,4);
+
+ const char *what() const noexcept override { return mMessage.c_str(); }
+ ALCenum errorCode() const noexcept { return mErrorCode; }
+};
+
+} // namespace al
+
+#define START_API_FUNC try
+
+#define END_API_FUNC catch(...) { std::terminate(); }
+
+#endif /* ALEXCPT_H */
diff --git a/common/alfstream.cpp b/common/alfstream.cpp
new file mode 100644
index 00000000..7378c678
--- /dev/null
+++ b/common/alfstream.cpp
@@ -0,0 +1,147 @@
+
+#include "config.h"
+
+#include "alfstream.h"
+
+#include "strutils.h"
+
+#ifdef _WIN32
+
+namespace al {
+
+auto filebuf::underflow() -> int_type
+{
+ if(mFile != INVALID_HANDLE_VALUE && gptr() == egptr())
+ {
+ // Read in the next chunk of data, and set the pointers on success
+ DWORD got{};
+ if(ReadFile(mFile, mBuffer.data(), static_cast<DWORD>(mBuffer.size()), &got, nullptr))
+ setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got);
+ }
+ if(gptr() == egptr())
+ return traits_type::eof();
+ return traits_type::to_int_type(*gptr());
+}
+
+auto filebuf::seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) -> pos_type
+{
+ if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
+ return traits_type::eof();
+
+ LARGE_INTEGER fpos{};
+ switch(whence)
+ {
+ case std::ios_base::beg:
+ fpos.QuadPart = offset;
+ if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
+ return traits_type::eof();
+ break;
+
+ case std::ios_base::cur:
+ // If the offset remains in the current buffer range, just
+ // update the pointer.
+ if((offset >= 0 && offset < off_type(egptr()-gptr())) ||
+ (offset < 0 && -offset <= off_type(gptr()-eback())))
+ {
+ // Get the current file offset to report the correct read
+ // offset.
+ fpos.QuadPart = 0;
+ if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
+ return traits_type::eof();
+ setg(eback(), gptr()+offset, egptr());
+ return fpos.QuadPart - off_type(egptr()-gptr());
+ }
+ // Need to offset for the file offset being at egptr() while
+ // the requested offset is relative to gptr().
+ offset -= off_type(egptr()-gptr());
+ fpos.QuadPart = offset;
+ if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
+ return traits_type::eof();
+ break;
+
+ case std::ios_base::end:
+ fpos.QuadPart = offset;
+ if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_END))
+ return traits_type::eof();
+ break;
+
+ default:
+ return traits_type::eof();
+ }
+ setg(nullptr, nullptr, nullptr);
+ return fpos.QuadPart;
+}
+
+auto filebuf::seekpos(pos_type pos, std::ios_base::openmode mode) -> pos_type
+{
+ // Simplified version of seekoff
+ if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
+ return traits_type::eof();
+
+ LARGE_INTEGER fpos{};
+ fpos.QuadPart = pos;
+ if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
+ return traits_type::eof();
+
+ setg(nullptr, nullptr, nullptr);
+ return fpos.QuadPart;
+}
+
+filebuf::~filebuf()
+{
+ if(mFile != INVALID_HANDLE_VALUE)
+ CloseHandle(mFile);
+ mFile = INVALID_HANDLE_VALUE;
+}
+
+bool filebuf::open(const wchar_t *filename, std::ios_base::openmode mode)
+{
+ if((mode&std::ios_base::out) || !(mode&std::ios_base::in))
+ return false;
+ HANDLE f{CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, nullptr)};
+ if(f == INVALID_HANDLE_VALUE) return false;
+
+ if(mFile != INVALID_HANDLE_VALUE)
+ CloseHandle(mFile);
+ mFile = f;
+
+ setg(nullptr, nullptr, nullptr);
+ return true;
+}
+bool filebuf::open(const char *filename, std::ios_base::openmode mode)
+{
+ std::wstring wname{utf8_to_wstr(filename)};
+ return open(wname.c_str(), mode);
+}
+
+
+ifstream::ifstream(const wchar_t *filename, std::ios_base::openmode mode)
+ : std::istream{nullptr}
+{
+ init(&mStreamBuf);
+
+ // Set the failbit if the file failed to open.
+ if((mode&std::ios_base::out) || !mStreamBuf.open(filename, mode|std::ios_base::in))
+ clear(failbit);
+}
+
+ifstream::ifstream(const char *filename, std::ios_base::openmode mode)
+ : std::istream{nullptr}
+{
+ init(&mStreamBuf);
+
+ // Set the failbit if the file failed to open.
+ if((mode&std::ios_base::out) || !mStreamBuf.open(filename, mode|std::ios_base::in))
+ clear(failbit);
+}
+
+/* This is only here to ensure the compiler doesn't define an implicit
+ * destructor, which it tries to automatically inline and subsequently complain
+ * it can't inline without excessive code growth.
+ */
+ifstream::~ifstream() { }
+
+} // namespace al
+
+#endif
diff --git a/common/alfstream.h b/common/alfstream.h
new file mode 100644
index 00000000..046a6e2a
--- /dev/null
+++ b/common/alfstream.h
@@ -0,0 +1,70 @@
+#ifndef AL_FSTREAM_H
+#define AL_FSTREAM_H
+
+#ifdef _WIN32
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <array>
+#include <string>
+#include <fstream>
+
+
+namespace al {
+
+// Windows' std::ifstream fails with non-ANSI paths since the standard only
+// specifies names using const char* (or std::string). MSVC has a non-standard
+// extension using const wchar_t* (or std::wstring?) to handle Unicode paths,
+// but not all Windows compilers support it. So we have to make our own istream
+// that accepts UTF-8 paths and forwards to Unicode-aware I/O functions.
+class filebuf final : public std::streambuf {
+ std::array<char_type,4096> mBuffer;
+ HANDLE mFile{INVALID_HANDLE_VALUE};
+
+ int_type underflow() override;
+ pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override;
+ pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override;
+
+public:
+ filebuf() = default;
+ ~filebuf() override;
+
+ bool open(const wchar_t *filename, std::ios_base::openmode mode);
+ bool open(const char *filename, std::ios_base::openmode mode);
+
+ bool is_open() const noexcept { return mFile != INVALID_HANDLE_VALUE; }
+};
+
+// Inherit from std::istream to use our custom streambuf
+class ifstream final : public std::istream {
+ filebuf mStreamBuf;
+
+public:
+ ifstream(const wchar_t *filename, std::ios_base::openmode mode = std::ios_base::in);
+ ifstream(const std::wstring &filename, std::ios_base::openmode mode = std::ios_base::in)
+ : ifstream(filename.c_str(), mode) { }
+ ifstream(const char *filename, std::ios_base::openmode mode = std::ios_base::in);
+ ifstream(const std::string &filename, std::ios_base::openmode mode = std::ios_base::in)
+ : ifstream(filename.c_str(), mode) { }
+ ~ifstream() override;
+
+ bool is_open() const noexcept { return mStreamBuf.is_open(); }
+};
+
+} // namespace al
+
+#else /* _WIN32 */
+
+#include <fstream>
+
+namespace al {
+
+using filebuf = std::filebuf;
+using ifstream = std::ifstream;
+
+} // namespace al
+
+#endif /* _WIN32 */
+
+#endif /* AL_FSTREAM_H */
diff --git a/common/align.h b/common/align.h
deleted file mode 100644
index e2dc81df..00000000
--- a/common/align.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef AL_ALIGN_H
-#define AL_ALIGN_H
-
-#if defined(HAVE_STDALIGN_H) && defined(HAVE_C11_ALIGNAS)
-#include <stdalign.h>
-#endif
-
-#ifndef alignas
-#if defined(IN_IDE_PARSER)
-/* KDevelop has problems with our align macro, so just use nothing for parsing. */
-#define alignas(x)
-#elif defined(HAVE_C11_ALIGNAS)
-#define alignas _Alignas
-#else
-/* NOTE: Our custom ALIGN macro can't take a type name like alignas can. For
- * maximum compatibility, only provide constant integer values to alignas. */
-#define alignas(_x) ALIGN(_x)
-#endif
-#endif
-
-#endif /* AL_ALIGN_H */
diff --git a/common/almalloc.c b/common/almalloc.c
deleted file mode 100644
index 0d982ca1..00000000
--- a/common/almalloc.c
+++ /dev/null
@@ -1,110 +0,0 @@
-
-#include "config.h"
-
-#include "almalloc.h"
-
-#include <stdlib.h>
-#include <string.h>
-#ifdef HAVE_MALLOC_H
-#include <malloc.h>
-#endif
-#ifdef HAVE_WINDOWS_H
-#include <windows.h>
-#else
-#include <unistd.h>
-#endif
-
-
-#ifdef __GNUC__
-#define LIKELY(x) __builtin_expect(!!(x), !0)
-#define UNLIKELY(x) __builtin_expect(!!(x), 0)
-#else
-#define LIKELY(x) (!!(x))
-#define UNLIKELY(x) (!!(x))
-#endif
-
-
-void *al_malloc(size_t alignment, size_t size)
-{
-#if defined(HAVE_ALIGNED_ALLOC)
- size = (size+(alignment-1))&~(alignment-1);
- return aligned_alloc(alignment, size);
-#elif defined(HAVE_POSIX_MEMALIGN)
- void *ret;
- if(posix_memalign(&ret, alignment, size) == 0)
- return ret;
- return NULL;
-#elif defined(HAVE__ALIGNED_MALLOC)
- return _aligned_malloc(size, alignment);
-#else
- char *ret = malloc(size+alignment);
- if(ret != NULL)
- {
- *(ret++) = 0x00;
- while(((ptrdiff_t)ret&(alignment-1)) != 0)
- *(ret++) = 0x55;
- }
- return ret;
-#endif
-}
-
-void *al_calloc(size_t alignment, size_t size)
-{
- void *ret = al_malloc(alignment, size);
- if(ret) memset(ret, 0, size);
- return ret;
-}
-
-void al_free(void *ptr)
-{
-#if defined(HAVE_ALIGNED_ALLOC) || defined(HAVE_POSIX_MEMALIGN)
- free(ptr);
-#elif defined(HAVE__ALIGNED_MALLOC)
- _aligned_free(ptr);
-#else
- if(ptr != NULL)
- {
- char *finder = ptr;
- do {
- --finder;
- } while(*finder == 0x55);
- free(finder);
- }
-#endif
-}
-
-size_t al_get_page_size(void)
-{
- static size_t psize = 0;
- if(UNLIKELY(!psize))
- {
-#ifdef HAVE_SYSCONF
-#if defined(_SC_PAGESIZE)
- if(!psize) psize = sysconf(_SC_PAGESIZE);
-#elif defined(_SC_PAGE_SIZE)
- if(!psize) psize = sysconf(_SC_PAGE_SIZE);
-#endif
-#endif /* HAVE_SYSCONF */
-#ifdef _WIN32
- if(!psize)
- {
- SYSTEM_INFO sysinfo;
- memset(&sysinfo, 0, sizeof(sysinfo));
-
- GetSystemInfo(&sysinfo);
- psize = sysinfo.dwPageSize;
- }
-#endif
- if(!psize) psize = DEF_ALIGN;
- }
- return psize;
-}
-
-int al_is_sane_alignment_allocator(void)
-{
-#if defined(HAVE_ALIGNED_ALLOC) || defined(HAVE_POSIX_MEMALIGN) || defined(HAVE__ALIGNED_MALLOC)
- return 1;
-#else
- return 0;
-#endif
-}
diff --git a/common/almalloc.cpp b/common/almalloc.cpp
new file mode 100644
index 00000000..842fb400
--- /dev/null
+++ b/common/almalloc.cpp
@@ -0,0 +1,67 @@
+
+#include "config.h"
+
+#include "almalloc.h"
+
+#include <cassert>
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+
+
+#define ALIGNED_ALLOC_AVAILABLE (__STDC_VERSION__ >= 201112L || __cplusplus >= 201703L)
+
+void *al_malloc(size_t alignment, size_t size)
+{
+ assert((alignment & (alignment-1)) == 0);
+ alignment = std::max(alignment, alignof(std::max_align_t));
+
+#if ALIGNED_ALLOC_AVAILABLE
+ size = (size+(alignment-1))&~(alignment-1);
+ return aligned_alloc(alignment, size);
+#elif defined(HAVE_POSIX_MEMALIGN)
+ void *ret;
+ if(posix_memalign(&ret, alignment, size) == 0)
+ return ret;
+ return nullptr;
+#elif defined(HAVE__ALIGNED_MALLOC)
+ return _aligned_malloc(size, alignment);
+#else
+ auto *ret = static_cast<char*>(malloc(size+alignment));
+ if(ret != nullptr)
+ {
+ *(ret++) = 0x00;
+ while((reinterpret_cast<uintptr_t>(ret)&(alignment-1)) != 0)
+ *(ret++) = 0x55;
+ }
+ return ret;
+#endif
+}
+
+void *al_calloc(size_t alignment, size_t size)
+{
+ void *ret = al_malloc(alignment, size);
+ if(ret) memset(ret, 0, size);
+ return ret;
+}
+
+void al_free(void *ptr) noexcept
+{
+#if ALIGNED_ALLOC_AVAILABLE || defined(HAVE_POSIX_MEMALIGN)
+ free(ptr);
+#elif defined(HAVE__ALIGNED_MALLOC)
+ _aligned_free(ptr);
+#else
+ if(ptr != nullptr)
+ {
+ auto *finder = static_cast<char*>(ptr);
+ do {
+ --finder;
+ } while(*finder == 0x55);
+ free(finder);
+ }
+#endif
+}
diff --git a/common/almalloc.h b/common/almalloc.h
index a4297cf5..15ec600d 100644
--- a/common/almalloc.h
+++ b/common/almalloc.h
@@ -1,30 +1,310 @@
#ifndef AL_MALLOC_H
#define AL_MALLOC_H
-#include <stddef.h>
+#include <algorithm>
+#include <cstddef>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <new>
+#include <type_traits>
+#include <utility>
-#ifdef __cplusplus
-extern "C" {
-#endif
+#include "pragmadefs.h"
-/* Minimum alignment required by posix_memalign. */
-#define DEF_ALIGN sizeof(void*)
void *al_malloc(size_t alignment, size_t size);
void *al_calloc(size_t alignment, size_t size);
-void al_free(void *ptr);
+void al_free(void *ptr) noexcept;
+
+
+#define DEF_NEWDEL(T) \
+ void *operator new(size_t size) \
+ { \
+ void *ret = al_malloc(alignof(T), size); \
+ if(!ret) throw std::bad_alloc(); \
+ return ret; \
+ } \
+ void operator delete(void *block) noexcept { al_free(block); }
+
+#define DEF_PLACE_NEWDEL() \
+ void *operator new(size_t /*size*/, void *ptr) noexcept { return ptr; } \
+ void operator delete(void *block, void*) noexcept { al_free(block); } \
+ void operator delete(void *block) noexcept { al_free(block); }
+
+struct FamCount { size_t mCount; };
+
+#define DEF_FAM_NEWDEL(T, FamMem) \
+ static constexpr size_t Sizeof(size_t count) noexcept \
+ { return decltype(FamMem)::Sizeof(count, offsetof(T, FamMem)); } \
+ \
+ void *operator new(size_t /*size*/, FamCount fam) \
+ { \
+ if(void *ret{al_malloc(alignof(T), T::Sizeof(fam.mCount))}) \
+ return ret; \
+ throw std::bad_alloc(); \
+ } \
+ void operator delete(void *block, FamCount) { al_free(block); } \
+ void operator delete(void *block) noexcept { al_free(block); }
+
+
+namespace al {
+
+#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__),int>::type = 0
+
+template<typename T, std::size_t alignment=alignof(T)>
+struct allocator {
+ using value_type = T;
+ using reference = T&;
+ using const_reference = const T&;
+ using pointer = T*;
+ using const_pointer = const T*;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using is_always_equal = std::true_type;
+
+ template<typename U>
+ struct rebind {
+ using other = allocator<U, (alignment<alignof(U))?alignof(U):alignment>;
+ };
+
+ allocator() = default;
+ template<typename U, std::size_t N>
+ constexpr allocator(const allocator<U,N>&) noexcept { }
+
+ T *allocate(std::size_t n)
+ {
+ if(n > std::numeric_limits<std::size_t>::max()/sizeof(T)) throw std::bad_alloc();
+ if(auto p = static_cast<T*>(al_malloc(alignment, n*sizeof(T)))) return p;
+ throw std::bad_alloc();
+ }
+ void deallocate(T *p, std::size_t) noexcept { al_free(p); }
+};
+template<typename T, std::size_t N, typename U, std::size_t M>
+bool operator==(const allocator<T,N>&, const allocator<U,M>&) noexcept { return true; }
+template<typename T, std::size_t N, typename U, std::size_t M>
+bool operator!=(const allocator<T,N>&, const allocator<U,M>&) noexcept { return false; }
-size_t al_get_page_size(void);
+template<size_t alignment, typename T>
+inline T* assume_aligned(T *ptr) noexcept
+{
+ static_assert((alignment & (alignment-1)) == 0, "alignment must be a power of 2");
+#ifdef __GNUC__
+ return static_cast<T*>(__builtin_assume_aligned(ptr, alignment));
+#elif defined(_MSC_VER)
+ auto ptrval = reinterpret_cast<uintptr_t>(ptr);
+ if((ptrval&(alignment-1)) != 0) __assume(0);
+ return reinterpret_cast<T*>(ptrval);
+#else
+ return ptr;
+#endif
+}
-/**
- * Returns non-0 if the allocation function has direct alignment handling.
- * Otherwise, the standard malloc is used with an over-allocation and pointer
- * offset strategy.
+/* At least VS 2015 complains that 'ptr' is unused when the given type's
+ * destructor is trivial (a no-op). So disable that warning for this call.
*/
-int al_is_sane_alignment_allocator(void);
+DIAGNOSTIC_PUSH
+msc_pragma(warning(disable : 4100))
+template<typename T>
+inline void destroy_at(T *ptr) { ptr->~T(); }
+DIAGNOSTIC_POP
-#ifdef __cplusplus
+template<typename T>
+inline void destroy(T first, const T end)
+{
+ while(first != end)
+ {
+ al::destroy_at(std::addressof(*first));
+ ++first;
+ }
}
-#endif
+
+template<typename T, typename N, REQUIRES(std::is_integral<N>::value)>
+inline T destroy_n(T first, N count)
+{
+ if(count != 0)
+ {
+ do {
+ al::destroy_at(std::addressof(*first));
+ ++first;
+ } while(--count);
+ }
+ return first;
+}
+
+
+template<typename T>
+inline void uninitialized_default_construct(T first, const T last)
+{
+ using ValueT = typename std::iterator_traits<T>::value_type;
+ T current{first};
+ try {
+ while(current != last)
+ {
+ ::new (static_cast<void*>(std::addressof(*current))) ValueT;
+ ++current;
+ }
+ }
+ catch(...) {
+ destroy(first, current);
+ throw;
+ }
+}
+
+template<typename T, typename N, REQUIRES(std::is_integral<N>::value)>
+inline T uninitialized_default_construct_n(T first, N count)
+{
+ using ValueT = typename std::iterator_traits<T>::value_type;
+ T current{first};
+ if(count != 0)
+ {
+ try {
+ do {
+ ::new (static_cast<void*>(std::addressof(*current))) ValueT;
+ ++current;
+ } while(--count);
+ }
+ catch(...) {
+ destroy(first, current);
+ throw;
+ }
+ }
+ return current;
+}
+
+
+template<typename T0, typename T1>
+inline T1 uninitialized_move(T0 first, const T0 last, const T1 output)
+{
+ using ValueT = typename std::iterator_traits<T1>::value_type;
+ T1 current{output};
+ try {
+ while(first != last)
+ {
+ ::new (static_cast<void*>(std::addressof(*current))) ValueT{std::move(*first)};
+ ++current;
+ ++first;
+ }
+ }
+ catch(...) {
+ destroy(output, current);
+ throw;
+ }
+ return current;
+}
+
+template<typename T0, typename N, typename T1, REQUIRES(std::is_integral<N>::value)>
+inline T1 uninitialized_move_n(T0 first, N count, const T1 output)
+{
+ using ValueT = typename std::iterator_traits<T1>::value_type;
+ T1 current{output};
+ if(count != 0)
+ {
+ try {
+ do {
+ ::new (static_cast<void*>(std::addressof(*current))) ValueT{std::move(*first)};
+ ++current;
+ ++first;
+ } while(--count);
+ }
+ catch(...) {
+ destroy(output, current);
+ throw;
+ }
+ }
+ return current;
+}
+
+
+/* std::make_unique was added with C++14, so until we rely on that, make our
+ * own version.
+ */
+template<typename T, typename ...ArgsT>
+std::unique_ptr<T> make_unique(ArgsT&&...args)
+{ return std::unique_ptr<T>{new T{std::forward<ArgsT>(args)...}}; }
+
+
+/* A flexible array type. Used either standalone or at the end of a parent
+ * struct, with placement new, to have a run-time-sized array that's embedded
+ * with its size.
+ */
+template<typename T, size_t alignment=alignof(T)>
+struct FlexArray {
+ using element_type = T;
+ using value_type = typename std::remove_cv<T>::type;
+ using index_type = size_t;
+ using difference_type = ptrdiff_t;
+
+ using pointer = T*;
+ using const_pointer = const T*;
+ using reference = T&;
+ using const_reference = const T&;
+
+ using iterator = pointer;
+ using const_iterator = const_pointer;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+
+ const index_type mSize;
+DIAGNOSTIC_PUSH
+std_pragma("GCC diagnostic ignored \"-Wpedantic\"")
+msc_pragma(warning(disable : 4200))
+ alignas(alignment) element_type mArray[0];
+DIAGNOSTIC_POP
+
+ static std::unique_ptr<FlexArray> Create(index_type count)
+ {
+ void *ptr{al_calloc(alignof(FlexArray), Sizeof(count))};
+ return std::unique_ptr<FlexArray>{new (ptr) FlexArray{count}};
+ }
+ static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept
+ {
+ return base +
+ std::max<index_type>(offsetof(FlexArray, mArray) + sizeof(T)*count, sizeof(FlexArray));
+ }
+
+ FlexArray(index_type size) : mSize{size}
+ { uninitialized_default_construct_n(mArray, mSize); }
+ ~FlexArray() { destroy_n(mArray, mSize); }
+
+ FlexArray(const FlexArray&) = delete;
+ FlexArray& operator=(const FlexArray&) = delete;
+
+ index_type size() const noexcept { return mSize; }
+ bool empty() const noexcept { return mSize == 0; }
+
+ pointer data() noexcept { return mArray; }
+ const_pointer data() const noexcept { return mArray; }
+
+ reference operator[](index_type i) noexcept { return mArray[i]; }
+ const_reference operator[](index_type i) const noexcept { return mArray[i]; }
+
+ reference front() noexcept { return mArray[0]; }
+ const_reference front() const noexcept { return mArray[0]; }
+
+ reference back() noexcept { return mArray[mSize-1]; }
+ const_reference back() const noexcept { return mArray[mSize-1]; }
+
+ iterator begin() noexcept { return mArray; }
+ const_iterator begin() const noexcept { return mArray; }
+ const_iterator cbegin() const noexcept { return mArray; }
+ iterator end() noexcept { return mArray + mSize; }
+ const_iterator end() const noexcept { return mArray + mSize; }
+ const_iterator cend() const noexcept { return mArray + mSize; }
+
+ reverse_iterator rbegin() noexcept { return end(); }
+ const_reverse_iterator rbegin() const noexcept { return end(); }
+ const_reverse_iterator crbegin() const noexcept { return cend(); }
+ reverse_iterator rend() noexcept { return begin(); }
+ const_reverse_iterator rend() const noexcept { return begin(); }
+ const_reverse_iterator crend() const noexcept { return cbegin(); }
+
+ DEF_PLACE_NEWDEL()
+};
+
+#undef REQUIRES
+
+} // namespace al
#endif /* AL_MALLOC_H */
diff --git a/common/alnumeric.h b/common/alnumeric.h
new file mode 100644
index 00000000..9158b764
--- /dev/null
+++ b/common/alnumeric.h
@@ -0,0 +1,347 @@
+#ifndef AL_NUMERIC_H
+#define AL_NUMERIC_H
+
+#include <cstddef>
+#include <cstdint>
+#ifdef HAVE_INTRIN_H
+#include <intrin.h>
+#endif
+#ifdef HAVE_SSE_INTRINSICS
+#include <xmmintrin.h>
+#endif
+
+#include "opthelpers.h"
+
+
+inline constexpr int64_t operator "" _i64(unsigned long long int n) noexcept { return static_cast<int64_t>(n); }
+inline constexpr uint64_t operator "" _u64(unsigned long long int n) noexcept { return static_cast<uint64_t>(n); }
+
+
+constexpr inline float minf(float a, float b) noexcept
+{ return ((a > b) ? b : a); }
+constexpr inline float maxf(float a, float b) noexcept
+{ return ((a > b) ? a : b); }
+constexpr inline float clampf(float val, float min, float max) noexcept
+{ return minf(max, maxf(min, val)); }
+
+constexpr inline double mind(double a, double b) noexcept
+{ return ((a > b) ? b : a); }
+constexpr inline double maxd(double a, double b) noexcept
+{ return ((a > b) ? a : b); }
+constexpr inline double clampd(double val, double min, double max) noexcept
+{ return mind(max, maxd(min, val)); }
+
+constexpr inline unsigned int minu(unsigned int a, unsigned int b) noexcept
+{ return ((a > b) ? b : a); }
+constexpr inline unsigned int maxu(unsigned int a, unsigned int b) noexcept
+{ return ((a > b) ? a : b); }
+constexpr inline unsigned int clampu(unsigned int val, unsigned int min, unsigned int max) noexcept
+{ return minu(max, maxu(min, val)); }
+
+constexpr inline int mini(int a, int b) noexcept
+{ return ((a > b) ? b : a); }
+constexpr inline int maxi(int a, int b) noexcept
+{ return ((a > b) ? a : b); }
+constexpr inline int clampi(int val, int min, int max) noexcept
+{ return mini(max, maxi(min, val)); }
+
+constexpr inline int64_t mini64(int64_t a, int64_t b) noexcept
+{ return ((a > b) ? b : a); }
+constexpr inline int64_t maxi64(int64_t a, int64_t b) noexcept
+{ return ((a > b) ? a : b); }
+constexpr inline int64_t clampi64(int64_t val, int64_t min, int64_t max) noexcept
+{ return mini64(max, maxi64(min, val)); }
+
+constexpr inline uint64_t minu64(uint64_t a, uint64_t b) noexcept
+{ return ((a > b) ? b : a); }
+constexpr inline uint64_t maxu64(uint64_t a, uint64_t b) noexcept
+{ return ((a > b) ? a : b); }
+constexpr inline uint64_t clampu64(uint64_t val, uint64_t min, uint64_t max) noexcept
+{ return minu64(max, maxu64(min, val)); }
+
+constexpr inline size_t minz(size_t a, size_t b) noexcept
+{ return ((a > b) ? b : a); }
+constexpr inline size_t maxz(size_t a, size_t b) noexcept
+{ return ((a > b) ? a : b); }
+constexpr inline size_t clampz(size_t val, size_t min, size_t max) noexcept
+{ return minz(max, maxz(min, val)); }
+
+
+/** Find the next power-of-2 for non-power-of-2 numbers. */
+inline uint32_t NextPowerOf2(uint32_t value) noexcept
+{
+ if(value > 0)
+ {
+ value--;
+ value |= value>>1;
+ value |= value>>2;
+ value |= value>>4;
+ value |= value>>8;
+ value |= value>>16;
+ }
+ return value+1;
+}
+
+/** Round up a value to the next multiple. */
+inline size_t RoundUp(size_t value, size_t r) noexcept
+{
+ value += r-1;
+ return value - (value%r);
+}
+
+
+/* Define CTZ macros (count trailing zeros), and POPCNT macros (population
+ * count/count 1 bits), for 32- and 64-bit integers. The CTZ macros' results
+ * are *UNDEFINED* if the value is 0.
+ */
+#ifdef __GNUC__
+
+#define POPCNT32 __builtin_popcount
+#define CTZ32 __builtin_ctz
+#if SIZEOF_LONG == 8
+#define POPCNT64 __builtin_popcountl
+#define CTZ64 __builtin_ctzl
+#else
+#define POPCNT64 __builtin_popcountll
+#define CTZ64 __builtin_ctzll
+#endif
+
+#else
+
+/* There be black magics here. The popcnt method is derived from
+ * https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
+ * while the ctz-utilizing-popcnt algorithm is shown here
+ * http://www.hackersdelight.org/hdcodetxt/ntz.c.txt
+ * as the ntz2 variant. These likely aren't the most efficient methods, but
+ * they're good enough if the GCC built-ins aren't available.
+ */
+inline int fallback_popcnt32(uint32_t v)
+{
+ v = v - ((v >> 1) & 0x55555555u);
+ v = (v & 0x33333333u) + ((v >> 2) & 0x33333333u);
+ v = (v + (v >> 4)) & 0x0f0f0f0fu;
+ return (int)((v * 0x01010101u) >> 24);
+}
+#define POPCNT32 fallback_popcnt32
+inline int fallback_popcnt64(uint64_t v)
+{
+ v = v - ((v >> 1) & 0x5555555555555555_u64);
+ v = (v & 0x3333333333333333_u64) + ((v >> 2) & 0x3333333333333333_u64);
+ v = (v + (v >> 4)) & 0x0f0f0f0f0f0f0f0f_u64;
+ return (int)((v * 0x0101010101010101_u64) >> 56);
+}
+#define POPCNT64 fallback_popcnt64
+
+#if defined(HAVE_BITSCANFORWARD64_INTRINSIC)
+
+inline int msvc64_ctz32(uint32_t v)
+{
+ unsigned long idx = 32;
+ _BitScanForward(&idx, v);
+ return (int)idx;
+}
+#define CTZ32 msvc64_ctz32
+inline int msvc64_ctz64(uint64_t v)
+{
+ unsigned long idx = 64;
+ _BitScanForward64(&idx, v);
+ return (int)idx;
+}
+#define CTZ64 msvc64_ctz64
+
+#elif defined(HAVE_BITSCANFORWARD_INTRINSIC)
+
+inline int msvc_ctz32(uint32_t v)
+{
+ unsigned long idx = 32;
+ _BitScanForward(&idx, v);
+ return (int)idx;
+}
+#define CTZ32 msvc_ctz32
+inline int msvc_ctz64(uint64_t v)
+{
+ unsigned long idx = 64;
+ if(!_BitScanForward(&idx, (uint32_t)(v&0xffffffff)))
+ {
+ if(_BitScanForward(&idx, (uint32_t)(v>>32)))
+ idx += 32;
+ }
+ return (int)idx;
+}
+#define CTZ64 msvc_ctz64
+
+#else
+
+inline int fallback_ctz32(uint32_t value)
+{ return POPCNT32(~value & (value - 1)); }
+#define CTZ32 fallback_ctz32
+inline int fallback_ctz64(uint64_t value)
+{ return POPCNT64(~value & (value - 1)); }
+#define CTZ64 fallback_ctz64
+
+#endif
+#endif
+
+
+/**
+ * Fast float-to-int conversion. No particular rounding mode is assumed; the
+ * IEEE-754 default is round-to-nearest with ties-to-even, though an app could
+ * change it on its own threads. On some systems, a truncating conversion may
+ * always be the fastest method.
+ */
+inline int fastf2i(float f) noexcept
+{
+#if defined(HAVE_SSE_INTRINSICS)
+ return _mm_cvt_ss2si(_mm_set_ss(f));
+
+#elif defined(_MSC_VER) && defined(_M_IX86_FP)
+
+ int i;
+ __asm fld f
+ __asm fistp i
+ return i;
+
+#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
+
+ int i;
+#ifdef __SSE_MATH__
+ __asm__("cvtss2si %1, %0" : "=r"(i) : "x"(f));
+#else
+ __asm__ __volatile__("fistpl %0" : "=m"(i) : "t"(f) : "st");
+#endif
+ return i;
+
+#else
+
+ return static_cast<int>(f);
+#endif
+}
+inline unsigned int fastf2u(float f) noexcept
+{ return static_cast<unsigned int>(fastf2i(f)); }
+
+/** Converts float-to-int using standard behavior (truncation). */
+inline int float2int(float f) noexcept
+{
+#if defined(HAVE_SSE_INTRINSICS)
+ return _mm_cvtt_ss2si(_mm_set_ss(f));
+
+#elif ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \
+ !defined(__SSE_MATH__)) || (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0)
+ int sign, shift, mant;
+ union {
+ float f;
+ int i;
+ } conv;
+
+ conv.f = f;
+ sign = (conv.i>>31) | 1;
+ shift = ((conv.i>>23)&0xff) - (127+23);
+
+ /* Over/underflow */
+ if UNLIKELY(shift >= 31 || shift < -23)
+ return 0;
+
+ mant = (conv.i&0x7fffff) | 0x800000;
+ if LIKELY(shift < 0)
+ return (mant >> -shift) * sign;
+ return (mant << shift) * sign;
+
+#else
+
+ return static_cast<int>(f);
+#endif
+}
+inline unsigned int float2uint(float f) noexcept
+{ return static_cast<unsigned int>(float2int(f)); }
+
+/** Converts double-to-int using standard behavior (truncation). */
+inline int double2int(double d) noexcept
+{
+#if defined(HAVE_SSE_INTRINSICS)
+ return _mm_cvttsd_si32(_mm_set_sd(d));
+
+#elif ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \
+ !defined(__SSE2_MATH__)) || (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2)
+
+ int sign, shift;
+ int64_t mant;
+ union {
+ double d;
+ int64_t i64;
+ } conv;
+
+ conv.d = d;
+ sign = (conv.i64 >> 63) | 1;
+ shift = ((conv.i64 >> 52) & 0x7ff) - (1023 + 52);
+
+ /* Over/underflow */
+ if UNLIKELY(shift >= 63 || shift < -52)
+ return 0;
+
+ mant = (conv.i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64;
+ if LIKELY(shift < 0)
+ return (int)(mant >> -shift) * sign;
+ return (int)(mant << shift) * sign;
+
+#else
+
+ return static_cast<int>(d);
+#endif
+}
+
+/**
+ * Rounds a float to the nearest integral value, according to the current
+ * rounding mode. This is essentially an inlined version of rintf, although
+ * makes fewer promises (e.g. -0 or -0.25 rounded to 0 may result in +0).
+ */
+inline float fast_roundf(float f) noexcept
+{
+#if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) && \
+ !defined(__SSE_MATH__)
+
+ float out;
+ __asm__ __volatile__("frndint" : "=t"(out) : "0"(f));
+ return out;
+
+#else
+
+ /* Integral limit, where sub-integral precision is not available for
+ * floats.
+ */
+ static const float ilim[2]{
+ 8388608.0f /* 0x1.0p+23 */,
+ -8388608.0f /* -0x1.0p+23 */
+ };
+ unsigned int sign, expo;
+ union {
+ float f;
+ unsigned int i;
+ } conv;
+
+ conv.f = f;
+ sign = (conv.i>>31)&0x01;
+ expo = (conv.i>>23)&0xff;
+
+ if UNLIKELY(expo >= 150/*+23*/)
+ {
+ /* An exponent (base-2) of 23 or higher is incapable of sub-integral
+ * precision, so it's already an integral value. We don't need to worry
+ * about infinity or NaN here.
+ */
+ return f;
+ }
+ /* Adding the integral limit to the value (with a matching sign) forces a
+ * result that has no sub-integral precision, and is consequently forced to
+ * round to an integral value. Removing the integral limit then restores
+ * the initial value rounded to the integral. The compiler should not
+ * optimize this out because of non-associative rules on floating-point
+ * math (as long as you don't use -fassociative-math,
+ * -funsafe-math-optimizations, -ffast-math, or -Ofast, in which case this
+ * may break).
+ */
+ f += ilim[sign];
+ return f - ilim[sign];
+#endif
+}
+
+#endif /* AL_NUMERIC_H */
diff --git a/common/aloptional.h b/common/aloptional.h
new file mode 100644
index 00000000..269cba0e
--- /dev/null
+++ b/common/aloptional.h
@@ -0,0 +1,149 @@
+#ifndef AL_OPTIONAL_H
+#define AL_OPTIONAL_H
+
+#include <initializer_list>
+#include <type_traits>
+#include <utility>
+
+#include "almalloc.h"
+
+namespace al {
+
+#define REQUIRES(...) bool rt_=true, typename std::enable_if<rt_ && (__VA_ARGS__),bool>::type = true
+
+struct nullopt_t { };
+struct in_place_t { };
+
+constexpr nullopt_t nullopt{};
+constexpr in_place_t in_place{};
+
+template<typename T>
+class optional {
+ bool mHasValue{false};
+ union {
+ char mDummy[sizeof(T)]{};
+ T mValue;
+ };
+
+ template<typename... Args>
+ void DoConstruct(Args&& ...args)
+ {
+ ::new (std::addressof(mValue)) T{std::forward<Args>(args)...};
+ mHasValue = true;
+ }
+
+public:
+ using value_type = T;
+
+ optional() noexcept = default;
+ optional(nullopt_t) noexcept { }
+ template<REQUIRES(std::is_copy_constructible<T>::value)>
+ optional(const optional &rhs) { if(rhs) DoConstruct(*rhs); }
+ template<REQUIRES(std::is_move_constructible<T>::value)>
+ optional(optional&& rhs) { if(rhs) DoConstruct(std::move(*rhs)); }
+ template<typename... Args, REQUIRES(std::is_constructible<T, Args...>::value)>
+ explicit optional(in_place_t, Args&& ...args) : mHasValue{true}
+ , mValue{std::forward<Args>(args)...}
+ { }
+ template<typename U, typename... Args, REQUIRES(std::is_constructible<T, std::initializer_list<U>&, Args...>::value)>
+ explicit optional(in_place_t, std::initializer_list<U> il, Args&& ...args)
+ : mHasValue{true}, mValue{il, std::forward<Args>(args)...}
+ { }
+ template<typename U=value_type, REQUIRES(std::is_constructible<T, U&&>::value &&
+ !std::is_same<typename std::decay<U>::type, in_place_t>::value &&
+ !std::is_same<typename std::decay<U>::type, optional<T>>::value &&
+ std::is_constructible<U&&, T>::value)>
+ constexpr explicit optional(U&& value) : mHasValue{true}, mValue{std::forward<U>(value)}
+ { }
+ template<typename U=value_type, REQUIRES(std::is_constructible<T, U&&>::value &&
+ !std::is_same<typename std::decay<U>::type, in_place_t>::value &&
+ !std::is_same<typename std::decay<U>::type, optional<T>>::value &&
+ !std::is_constructible<U&&, T>::value)>
+ constexpr optional(U&& value) : mHasValue{true}, mValue{std::forward<U>(value)}
+ { }
+ ~optional() { if(mHasValue) al::destroy_at(std::addressof(mValue)); }
+
+ optional& operator=(nullopt_t) noexcept { reset(); return *this; }
+ template<REQUIRES(std::is_copy_constructible<T>::value && std::is_copy_assignable<T>::value)>
+ optional& operator=(const optional &rhs)
+ {
+ if(!rhs)
+ reset();
+ else if(*this)
+ mValue = *rhs;
+ else
+ DoConstruct(*rhs);
+ return *this;
+ }
+ template<REQUIRES(std::is_move_constructible<T>::value && std::is_move_assignable<T>::value)>
+ optional& operator=(optional&& rhs)
+ {
+ if(!rhs)
+ reset();
+ else if(*this)
+ mValue = std::move(*rhs);
+ else
+ DoConstruct(std::move(*rhs));
+ return *this;
+ }
+ template<typename U=T, REQUIRES(std::is_constructible<T, U>::value &&
+ std::is_assignable<T&, U>::value &&
+ !std::is_same<typename std::decay<U>::type, optional<T>>::value &&
+ (!std::is_same<typename std::decay<U>::type, T>::value ||
+ !std::is_scalar<U>::value))>
+ optional& operator=(U&& rhs)
+ {
+ if(*this)
+ mValue = std::forward<U>(rhs);
+ else
+ DoConstruct(std::forward<U>(rhs));
+ return *this;
+ }
+
+ const T* operator->() const { return std::addressof(mValue); }
+ T* operator->() { return std::addressof(mValue); }
+ const T& operator*() const& { return mValue; }
+ T& operator*() & { return mValue; }
+ const T&& operator*() const&& { return std::move(mValue); }
+ T&& operator*() && { return std::move(mValue); }
+
+ operator bool() const noexcept { return mHasValue; }
+ bool has_value() const noexcept { return mHasValue; }
+
+ T& value() & { return mValue; }
+ const T& value() const& { return mValue; }
+ T&& value() && { return std::move(mValue); }
+ const T&& value() const&& { return std::move(mValue); }
+
+ template<typename U>
+ T value_or(U&& defval) const&
+ { return bool{*this} ? **this : static_cast<T>(std::forward<U>(defval)); }
+ template<typename U>
+ T value_or(U&& defval) &&
+ { return bool{*this} ? std::move(**this) : static_cast<T>(std::forward<U>(defval)); }
+
+ void reset() noexcept
+ {
+ if(mHasValue)
+ al::destroy_at(std::addressof(mValue));
+ mHasValue = false;
+ }
+};
+
+template<typename T>
+inline optional<typename std::decay<T>::type> make_optional(T&& arg)
+{ return optional<typename std::decay<T>::type>{in_place, std::forward<T>(arg)}; }
+
+template<typename T, typename... Args>
+inline optional<T> make_optional(Args&& ...args)
+{ return optional<T>{in_place, std::forward<Args>(args)...}; }
+
+template<typename T, typename U, typename... Args>
+inline optional<T> make_optional(std::initializer_list<U> il, Args&& ...args)
+{ return optional<T>{in_place, il, std::forward<Args>(args)...}; }
+
+#undef REQUIRES
+
+} // namespace al
+
+#endif /* AL_SPAN_H */
diff --git a/common/alspan.h b/common/alspan.h
new file mode 100644
index 00000000..cb34d410
--- /dev/null
+++ b/common/alspan.h
@@ -0,0 +1,297 @@
+#ifndef AL_SPAN_H
+#define AL_SPAN_H
+
+#include <array>
+#include <cstddef>
+#include <initializer_list>
+#include <iterator>
+#include <type_traits>
+
+namespace al {
+
+template<typename T>
+constexpr auto size(T &cont) -> decltype(cont.size())
+{ return cont.size(); }
+
+template<typename T>
+constexpr auto size(const T &cont) -> decltype(cont.size())
+{ return cont.size(); }
+
+template<typename T, size_t N>
+constexpr size_t size(T (&)[N]) noexcept
+{ return N; }
+
+template<typename T>
+constexpr size_t size(std::initializer_list<T> list) noexcept
+{ return list.size(); }
+
+
+template<typename T>
+constexpr auto data(T &cont) -> decltype(cont.data())
+{ return cont.data(); }
+
+template<typename T>
+constexpr auto data(const T &cont) -> decltype(cont.data())
+{ return cont.data(); }
+
+template<typename T, size_t N>
+constexpr T* data(T (&arr)[N]) noexcept
+{ return arr; }
+
+template<typename T>
+constexpr const T* data(std::initializer_list<T> list) noexcept
+{ return list.begin(); }
+
+
+template<typename T, size_t E=static_cast<size_t>(-1)>
+class span;
+
+namespace detail_ {
+ template<typename... Ts>
+ struct make_void { using type = void; };
+ template<typename... Ts>
+ using void_t = typename make_void<Ts...>::type;
+
+ template<typename T>
+ struct is_span_ : std::false_type { };
+ template<typename T, size_t E>
+ struct is_span_<span<T,E>> : std::true_type { };
+ template<typename T>
+ using is_span = is_span_<typename std::remove_cv<T>::type>;
+
+ template<typename T>
+ struct is_std_array_ : std::false_type { };
+ template<typename T, size_t N>
+ struct is_std_array_<std::array<T,N>> : std::true_type { };
+ template<typename T>
+ using is_std_array = is_std_array_<typename std::remove_cv<T>::type>;
+
+ template<typename T, typename = void>
+ struct has_size_and_data : std::false_type { };
+ template<typename T>
+ struct has_size_and_data<T,
+ void_t<decltype(al::size(std::declval<T>())), decltype(al::data(std::declval<T>()))>>
+ : std::true_type { };
+
+ template<typename T>
+ using remove_pointer_t = typename std::remove_pointer<T>::type;
+} // namespace detail_
+
+#define REQUIRES(...) bool rt_=true, typename std::enable_if<rt_ && (__VA_ARGS__),bool>::type = true
+#define IS_VALID_CONTAINER(C) \
+ !detail_::is_span<C>::value && !detail_::is_std_array<C>::value && \
+ !std::is_array<C>::value && detail_::has_size_and_data<C>::value && \
+ std::is_convertible<detail_::remove_pointer_t<decltype(al::data(std::declval<C&>()))>(*)[],element_type(*)[]>::value
+
+template<typename T, size_t E>
+class span {
+ static constexpr size_t dynamic_extent{static_cast<size_t>(-1)};
+
+public:
+ using element_type = T;
+ using value_type = typename std::remove_cv<T>::type;
+ using index_type = size_t;
+ using difference_type = ptrdiff_t;
+
+ using pointer = T*;
+ using const_pointer = const T*;
+ using reference = T&;
+ using const_reference = const T&;
+
+ using iterator = pointer;
+ using const_iterator = const_pointer;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ static constexpr size_t extent{E};
+
+ template<REQUIRES(extent==0)>
+ constexpr span() noexcept { }
+ constexpr span(pointer ptr, index_type /*count*/) : mData{ptr} { }
+ constexpr span(pointer first, pointer /*last*/) : mData{first} { }
+ constexpr span(element_type (&arr)[E]) noexcept : span{al::data(arr), al::size(arr)} { }
+ constexpr span(std::array<value_type,E> &arr) noexcept : span{al::data(arr), al::size(arr)} { }
+ template<REQUIRES(std::is_const<element_type>::value)>
+ constexpr span(const std::array<value_type,E> &arr) noexcept : span{al::data(arr), al::size(arr)} { }
+ template<typename U, REQUIRES(IS_VALID_CONTAINER(U))>
+ constexpr span(U &cont) : span{al::data(cont), al::size(cont)} { }
+ template<typename U, REQUIRES(IS_VALID_CONTAINER(const U))>
+ constexpr span(const U &cont) : span{al::data(cont), al::size(cont)} { }
+ template<typename U, REQUIRES(!std::is_same<element_type,U>::value && std::is_convertible<U(*)[],element_type(*)[]>::value)>
+ constexpr span(const span<U,E> &span_) noexcept : span{al::data(span_), al::size(span_)} { }
+ constexpr span(const span&) noexcept = default;
+
+ span& operator=(const span &rhs) noexcept = default;
+
+ constexpr reference front() const { return *mData; }
+ constexpr reference back() const { return *(mData+E-1); }
+ constexpr reference operator[](index_type idx) const { return mData[idx]; }
+ constexpr pointer data() const noexcept { return mData; }
+
+ constexpr index_type size() const noexcept { return E; }
+ constexpr index_type size_bytes() const noexcept { return E * sizeof(value_type); }
+ constexpr bool empty() const noexcept { return E == 0; }
+
+ constexpr iterator begin() const noexcept { return mData; }
+ constexpr iterator end() const noexcept { return mData+E; }
+ constexpr const_iterator cbegin() const noexcept { return mData; }
+ constexpr const_iterator cend() const noexcept { return mData+E; }
+
+ constexpr reverse_iterator rbegin() const noexcept { return end(); }
+ constexpr reverse_iterator rend() const noexcept { return begin(); }
+ constexpr const_reverse_iterator crbegin() const noexcept { return cend(); }
+ constexpr const_reverse_iterator crend() const noexcept { return cbegin(); }
+
+ template<size_t C>
+ constexpr span<element_type,C> first() const
+ {
+ static_assert(E >= C, "New size exceeds original capacity");
+ return span<element_type,C>{mData, C};
+ }
+
+ template<size_t C>
+ constexpr span<element_type,C> last() const
+ {
+ static_assert(E >= C, "New size exceeds original capacity");
+ return span<element_type,C>{mData+(E-C), C};
+ }
+
+ template<size_t O, size_t C, size_t RealC=((C==dynamic_extent) ? E-O : C)>
+ constexpr span<element_type,RealC> subspan() const
+ {
+ static_assert(E >= O, "Offset exceeds extent");
+ static_assert(E-O >= RealC, "New size exceeds original capacity");
+ return span<element_type,RealC>{mData+O, RealC};
+ }
+
+ /* NOTE: Can't declare objects of a specialized template class prior to
+ * defining the specialization. As a result, these methods need to be
+ * defined later.
+ */
+ constexpr span<element_type,dynamic_extent> first(size_t count) const;
+ constexpr span<element_type,dynamic_extent> last(size_t count) const;
+ constexpr span<element_type,dynamic_extent> subspan(size_t offset, size_t count=dynamic_extent) const;
+
+private:
+ pointer mData{nullptr};
+};
+
+template<typename T>
+class span<T,static_cast<size_t>(-1)> {
+ static constexpr size_t dynamic_extent{static_cast<size_t>(-1)};
+
+public:
+ using element_type = T;
+ using value_type = typename std::remove_cv<T>::type;
+ using index_type = size_t;
+ using difference_type = ptrdiff_t;
+
+ using pointer = T*;
+ using const_pointer = const T*;
+ using reference = T&;
+ using const_reference = const T&;
+
+ using iterator = pointer;
+ using const_iterator = const_pointer;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ static constexpr size_t extent{static_cast<size_t>(-1)};
+
+ constexpr span() noexcept = default;
+ constexpr span(pointer ptr, index_type count) : mData{ptr}, mDataEnd{ptr+count} { }
+ constexpr span(pointer first, pointer last) : mData{first}, mDataEnd{last} { }
+ template<size_t N>
+ constexpr span(element_type (&arr)[N]) noexcept : span{al::data(arr), al::size(arr)} { }
+ template<size_t N>
+ constexpr span(std::array<value_type,N> &arr) noexcept : span{al::data(arr), al::size(arr)} { }
+ template<size_t N, REQUIRES(std::is_const<element_type>::value)>
+ constexpr span(const std::array<value_type,N> &arr) noexcept : span{al::data(arr), al::size(arr)} { }
+ template<typename U, REQUIRES(IS_VALID_CONTAINER(U))>
+ constexpr span(U &cont) : span{al::data(cont), al::size(cont)} { }
+ template<typename U, REQUIRES(IS_VALID_CONTAINER(const U))>
+ constexpr span(const U &cont) : span{al::data(cont), al::size(cont)} { }
+ template<typename U, size_t N, REQUIRES((!std::is_same<element_type,U>::value || extent != N) && std::is_convertible<U(*)[],element_type(*)[]>::value)>
+ constexpr span(const span<U,N> &span_) noexcept : span{al::data(span_), al::size(span_)} { }
+ constexpr span(const span&) noexcept = default;
+
+ span& operator=(const span &rhs) noexcept = default;
+
+ constexpr reference front() const { return *mData; }
+ constexpr reference back() const { return *(mDataEnd-1); }
+ constexpr reference operator[](index_type idx) const { return mData[idx]; }
+ constexpr pointer data() const noexcept { return mData; }
+
+ constexpr index_type size() const noexcept { return static_cast<index_type>(mDataEnd-mData); }
+ constexpr index_type size_bytes() const noexcept
+ { return static_cast<index_type>(mDataEnd-mData) * sizeof(value_type); }
+ constexpr bool empty() const noexcept { return mData == mDataEnd; }
+
+ constexpr iterator begin() const noexcept { return mData; }
+ constexpr iterator end() const noexcept { return mDataEnd; }
+ constexpr const_iterator cbegin() const noexcept { return mData; }
+ constexpr const_iterator cend() const noexcept { return mDataEnd; }
+
+ constexpr reverse_iterator rbegin() const noexcept { return end(); }
+ constexpr reverse_iterator rend() const noexcept { return begin(); }
+ constexpr const_reverse_iterator crbegin() const noexcept { return cend(); }
+ constexpr const_reverse_iterator crend() const noexcept { return cbegin(); }
+
+ template<size_t C>
+ constexpr span<element_type,C> first() const
+ { return span<element_type,C>{mData, C}; }
+
+ constexpr span first(size_t count) const
+ { return (count >= size()) ? *this : span{mData, mData+count}; }
+
+ template<size_t C>
+ constexpr span<element_type,C> last() const
+ { return span<element_type,C>{mDataEnd-C, C}; }
+
+ constexpr span last(size_t count) const
+ { return (count >= size()) ? *this : span{mDataEnd-count, mDataEnd}; }
+
+ template<size_t O, size_t C=dynamic_extent>
+ constexpr span<element_type,C> subspan() const
+ { return span<element_type,C>{mData+O, (C!=dynamic_extent) ? mData+C : mDataEnd}; }
+
+ constexpr span subspan(size_t offset, size_t count=dynamic_extent) const
+ {
+ return (offset > size()) ? span{} :
+ (count >= size()-offset) ? span{mData+offset, mDataEnd} :
+ span{mData+offset, mData+offset+count};
+ }
+
+private:
+ pointer mData{nullptr};
+ pointer mDataEnd{nullptr};
+};
+
+template<typename T, size_t E>
+constexpr inline auto span<T,E>::first(size_t count) const -> span<element_type,dynamic_extent>
+{
+ return (count >= size()) ? span<element_type>{mData, extent} :
+ span<element_type>{mData, count};
+}
+
+template<typename T, size_t E>
+constexpr inline auto span<T,E>::last(size_t count) const -> span<element_type,dynamic_extent>
+{
+ return (count >= size()) ? span<element_type>{mData, extent} :
+ span<element_type>{mData+extent-count, count};
+}
+
+template<typename T, size_t E>
+constexpr inline auto span<T,E>::subspan(size_t offset, size_t count) const -> span<element_type,dynamic_extent>
+{
+ return (offset > size()) ? span<element_type>{} :
+ (count >= size()-offset) ? span<element_type>{mData+offset, mData+extent} :
+ span<element_type>{mData+offset, mData+offset+count};
+}
+
+#undef IS_VALID_CONTAINER
+#undef REQUIRES
+
+} // namespace al
+
+#endif /* AL_SPAN_H */
diff --git a/common/alstring.cpp b/common/alstring.cpp
new file mode 100644
index 00000000..4a84be1d
--- /dev/null
+++ b/common/alstring.cpp
@@ -0,0 +1,45 @@
+
+#include "config.h"
+
+#include "alstring.h"
+
+#include <cctype>
+#include <string>
+
+
+namespace {
+
+int to_upper(const char ch)
+{
+ using char8_traits = std::char_traits<char>;
+ return std::toupper(char8_traits::to_int_type(ch));
+}
+
+} // namespace
+
+namespace al {
+
+int strcasecmp(const char *str0, const char *str1) noexcept
+{
+ do {
+ const int diff{to_upper(*str0) - to_upper(*str1)};
+ if(diff < 0) return -1;
+ if(diff > 0) return 1;
+ } while(*(str0++) && *(str1++));
+ return 0;
+}
+
+int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept
+{
+ if(len > 0)
+ {
+ do {
+ const int diff{to_upper(*str0) - to_upper(*str1)};
+ if(diff < 0) return -1;
+ if(diff > 0) return 1;
+ } while(--len && *(str0++) && *(str1++));
+ }
+ return 0;
+}
+
+} // namespace al
diff --git a/common/alstring.h b/common/alstring.h
new file mode 100644
index 00000000..194e54a1
--- /dev/null
+++ b/common/alstring.h
@@ -0,0 +1,29 @@
+#ifndef AL_STRING_H
+#define AL_STRING_H
+
+#include <cstddef>
+#include <string>
+
+#include "almalloc.h"
+
+
+namespace al {
+
+template<typename T, typename Tr=std::char_traits<T>>
+using basic_string = std::basic_string<T, Tr, al::allocator<T>>;
+
+using string = basic_string<char>;
+using wstring = basic_string<wchar_t>;
+using u16string = basic_string<char16_t>;
+using u32string = basic_string<char32_t>;
+
+
+/* These would be better served by using a string_view-like span/view with
+ * case-insensitive char traits.
+ */
+int strcasecmp(const char *str0, const char *str1) noexcept;
+int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept;
+
+} // namespace al
+
+#endif /* AL_STRING_H */
diff --git a/common/atomic.c b/common/atomic.c
deleted file mode 100644
index 7a8fe6d8..00000000
--- a/common/atomic.c
+++ /dev/null
@@ -1,10 +0,0 @@
-
-#include "config.h"
-
-#include "atomic.h"
-
-
-extern inline void InitRef(RefCount *ptr, uint value);
-extern inline uint ReadRef(RefCount *ptr);
-extern inline uint IncrementRef(RefCount *ptr);
-extern inline uint DecrementRef(RefCount *ptr);
diff --git a/common/atomic.h b/common/atomic.h
index 39daa1dc..5e9b04c6 100644
--- a/common/atomic.h
+++ b/common/atomic.h
@@ -1,439 +1,33 @@
#ifndef AL_ATOMIC_H
#define AL_ATOMIC_H
-#include "static_assert.h"
-#include "bool.h"
+#include <atomic>
-#ifdef __GNUC__
-/* This helps cast away the const-ness of a pointer without accidentally
- * changing the pointer type. This is necessary due to Clang's inability to use
- * atomic_load on a const _Atomic variable.
- */
-#define CONST_CAST(T, V) __extension__({ \
- const T _tmp = (V); \
- (T)_tmp; \
-})
-#else
-#define CONST_CAST(T, V) ((T)(V))
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* Atomics using C11 */
-#ifdef HAVE_C11_ATOMIC
-
-#include <stdatomic.h>
-
-#define almemory_order memory_order
-#define almemory_order_relaxed memory_order_relaxed
-#define almemory_order_consume memory_order_consume
-#define almemory_order_acquire memory_order_acquire
-#define almemory_order_release memory_order_release
-#define almemory_order_acq_rel memory_order_acq_rel
-#define almemory_order_seq_cst memory_order_seq_cst
-
-#define ATOMIC(T) T _Atomic
-#define ATOMIC_FLAG atomic_flag
-
-#define ATOMIC_INIT atomic_init
-#define ATOMIC_INIT_STATIC ATOMIC_VAR_INIT
-/*#define ATOMIC_FLAG_INIT ATOMIC_FLAG_INIT*/
-
-#define ATOMIC_LOAD atomic_load_explicit
-#define ATOMIC_STORE atomic_store_explicit
-
-#define ATOMIC_ADD atomic_fetch_add_explicit
-#define ATOMIC_SUB atomic_fetch_sub_explicit
-
-#define ATOMIC_EXCHANGE atomic_exchange_explicit
-#define ATOMIC_COMPARE_EXCHANGE_STRONG atomic_compare_exchange_strong_explicit
-#define ATOMIC_COMPARE_EXCHANGE_WEAK atomic_compare_exchange_weak_explicit
-
-#define ATOMIC_FLAG_TEST_AND_SET atomic_flag_test_and_set_explicit
-#define ATOMIC_FLAG_CLEAR atomic_flag_clear_explicit
-
-#define ATOMIC_THREAD_FENCE atomic_thread_fence
-
-/* Atomics using GCC intrinsics */
-#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) && !defined(__QNXNTO__)
-
-enum almemory_order {
- almemory_order_relaxed,
- almemory_order_consume,
- almemory_order_acquire,
- almemory_order_release,
- almemory_order_acq_rel,
- almemory_order_seq_cst
-};
-
-#define ATOMIC(T) struct { T volatile value; }
-#define ATOMIC_FLAG ATOMIC(int)
-
-#define ATOMIC_INIT(_val, _newval) do { (_val)->value = (_newval); } while(0)
-#define ATOMIC_INIT_STATIC(_newval) {(_newval)}
-#define ATOMIC_FLAG_INIT ATOMIC_INIT_STATIC(0)
-
-#define ATOMIC_LOAD(_val, _MO) __extension__({ \
- __typeof((_val)->value) _r = (_val)->value; \
- __asm__ __volatile__("" ::: "memory"); \
- _r; \
-})
-#define ATOMIC_STORE(_val, _newval, _MO) do { \
- __asm__ __volatile__("" ::: "memory"); \
- (_val)->value = (_newval); \
-} while(0)
-
-#define ATOMIC_ADD(_val, _incr, _MO) __sync_fetch_and_add(&(_val)->value, (_incr))
-#define ATOMIC_SUB(_val, _decr, _MO) __sync_fetch_and_sub(&(_val)->value, (_decr))
-
-#define ATOMIC_EXCHANGE(_val, _newval, _MO) __extension__({ \
- __asm__ __volatile__("" ::: "memory"); \
- __sync_lock_test_and_set(&(_val)->value, (_newval)); \
-})
-#define ATOMIC_COMPARE_EXCHANGE_STRONG(_val, _oldval, _newval, _MO1, _MO2) __extension__({ \
- __typeof(*(_oldval)) _o = *(_oldval); \
- *(_oldval) = __sync_val_compare_and_swap(&(_val)->value, _o, (_newval)); \
- *(_oldval) == _o; \
-})
-
-#define ATOMIC_FLAG_TEST_AND_SET(_val, _MO) __extension__({ \
- __asm__ __volatile__("" ::: "memory"); \
- __sync_lock_test_and_set(&(_val)->value, 1); \
-})
-#define ATOMIC_FLAG_CLEAR(_val, _MO) __extension__({ \
- __sync_lock_release(&(_val)->value); \
- __asm__ __volatile__("" ::: "memory"); \
-})
-
-
-#define ATOMIC_THREAD_FENCE(order) do { \
- enum { must_be_constant = (order) }; \
- const int _o = must_be_constant; \
- if(_o > almemory_order_relaxed) \
- __asm__ __volatile__("" ::: "memory"); \
-} while(0)
-
-/* Atomics using x86/x86-64 GCC inline assembly */
-#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
-
-#define WRAP_ADD(S, ret, dest, incr) __asm__ __volatile__( \
- "lock; xadd"S" %0,(%1)" \
- : "=r" (ret) \
- : "r" (dest), "0" (incr) \
- : "memory" \
-)
-#define WRAP_SUB(S, ret, dest, decr) __asm__ __volatile__( \
- "lock; xadd"S" %0,(%1)" \
- : "=r" (ret) \
- : "r" (dest), "0" (-(decr)) \
- : "memory" \
-)
-
-#define WRAP_XCHG(S, ret, dest, newval) __asm__ __volatile__( \
- "lock; xchg"S" %0,(%1)" \
- : "=r" (ret) \
- : "r" (dest), "0" (newval) \
- : "memory" \
-)
-#define WRAP_CMPXCHG(S, ret, dest, oldval, newval) __asm__ __volatile__( \
- "lock; cmpxchg"S" %2,(%1)" \
- : "=a" (ret) \
- : "r" (dest), "r" (newval), "0" (oldval) \
- : "memory" \
-)
-
-
-enum almemory_order {
- almemory_order_relaxed,
- almemory_order_consume,
- almemory_order_acquire,
- almemory_order_release,
- almemory_order_acq_rel,
- almemory_order_seq_cst
-};
-
-#define ATOMIC(T) struct { T volatile value; }
-
-#define ATOMIC_INIT(_val, _newval) do { (_val)->value = (_newval); } while(0)
-#define ATOMIC_INIT_STATIC(_newval) {(_newval)}
-
-#define ATOMIC_LOAD(_val, _MO) __extension__({ \
- __typeof((_val)->value) _r = (_val)->value; \
- __asm__ __volatile__("" ::: "memory"); \
- _r; \
-})
-#define ATOMIC_STORE(_val, _newval, _MO) do { \
- __asm__ __volatile__("" ::: "memory"); \
- (_val)->value = (_newval); \
-} while(0)
-
-#define ATOMIC_ADD(_val, _incr, _MO) __extension__({ \
- static_assert(sizeof((_val)->value)==4 || sizeof((_val)->value)==8, "Unsupported size!"); \
- __typeof((_val)->value) _r; \
- if(sizeof((_val)->value) == 4) WRAP_ADD("l", _r, &(_val)->value, _incr); \
- else if(sizeof((_val)->value) == 8) WRAP_ADD("q", _r, &(_val)->value, _incr); \
- _r; \
-})
-#define ATOMIC_SUB(_val, _decr, _MO) __extension__({ \
- static_assert(sizeof((_val)->value)==4 || sizeof((_val)->value)==8, "Unsupported size!"); \
- __typeof((_val)->value) _r; \
- if(sizeof((_val)->value) == 4) WRAP_SUB("l", _r, &(_val)->value, _decr); \
- else if(sizeof((_val)->value) == 8) WRAP_SUB("q", _r, &(_val)->value, _decr); \
- _r; \
-})
-
-#define ATOMIC_EXCHANGE(_val, _newval, _MO) __extension__({ \
- __typeof((_val)->value) _r; \
- if(sizeof((_val)->value) == 4) WRAP_XCHG("l", _r, &(_val)->value, (_newval)); \
- else if(sizeof((_val)->value) == 8) WRAP_XCHG("q", _r, &(_val)->value, (_newval)); \
- _r; \
-})
-#define ATOMIC_COMPARE_EXCHANGE_STRONG(_val, _oldval, _newval, _MO1, _MO2) __extension__({ \
- __typeof(*(_oldval)) _old = *(_oldval); \
- if(sizeof((_val)->value) == 4) WRAP_CMPXCHG("l", *(_oldval), &(_val)->value, _old, (_newval)); \
- else if(sizeof((_val)->value) == 8) WRAP_CMPXCHG("q", *(_oldval), &(_val)->value, _old, (_newval)); \
- *(_oldval) == _old; \
-})
-
-#define ATOMIC_EXCHANGE_PTR(_val, _newval, _MO) __extension__({ \
- void *_r; \
- if(sizeof(void*) == 4) WRAP_XCHG("l", _r, &(_val)->value, (_newval)); \
- else if(sizeof(void*) == 8) WRAP_XCHG("q", _r, &(_val)->value, (_newval));\
- _r; \
-})
-#define ATOMIC_COMPARE_EXCHANGE_PTR_STRONG(_val, _oldval, _newval, _MO1, _MO2) __extension__({ \
- void *_old = *(_oldval); \
- if(sizeof(void*) == 4) WRAP_CMPXCHG("l", *(_oldval), &(_val)->value, _old, (_newval)); \
- else if(sizeof(void*) == 8) WRAP_CMPXCHG("q", *(_oldval), &(_val)->value, _old, (_newval)); \
- *(_oldval) == _old; \
-})
-
-#define ATOMIC_THREAD_FENCE(order) do { \
- enum { must_be_constant = (order) }; \
- const int _o = must_be_constant; \
- if(_o > almemory_order_relaxed) \
- __asm__ __volatile__("" ::: "memory"); \
-} while(0)
-
-/* Atomics using Windows methods */
-#elif defined(_WIN32)
-
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-
-/* NOTE: This mess is *extremely* touchy. It lacks quite a bit of safety
- * checking due to the lack of multi-statement expressions, typeof(), and C99
- * compound literals. It is incapable of properly exchanging floats, which get
- * casted to LONG/int, and could cast away potential warnings.
- *
- * Unfortunately, it's the only semi-safe way that doesn't rely on C99 (because
- * MSVC).
- */
-
-inline LONG AtomicAdd32(volatile LONG *dest, LONG incr)
-{
- return InterlockedExchangeAdd(dest, incr);
-}
-inline LONGLONG AtomicAdd64(volatile LONGLONG *dest, LONGLONG incr)
-{
- return InterlockedExchangeAdd64(dest, incr);
-}
-inline LONG AtomicSub32(volatile LONG *dest, LONG decr)
-{
- return InterlockedExchangeAdd(dest, -decr);
-}
-inline LONGLONG AtomicSub64(volatile LONGLONG *dest, LONGLONG decr)
-{
- return InterlockedExchangeAdd64(dest, -decr);
-}
-
-inline LONG AtomicSwap32(volatile LONG *dest, LONG newval)
-{
- return InterlockedExchange(dest, newval);
-}
-inline LONGLONG AtomicSwap64(volatile LONGLONG *dest, LONGLONG newval)
-{
- return InterlockedExchange64(dest, newval);
-}
-inline void *AtomicSwapPtr(void *volatile *dest, void *newval)
-{
- return InterlockedExchangePointer(dest, newval);
-}
-
-inline bool CompareAndSwap32(volatile LONG *dest, LONG newval, LONG *oldval)
-{
- LONG old = *oldval;
- *oldval = InterlockedCompareExchange(dest, newval, *oldval);
- return old == *oldval;
-}
-inline bool CompareAndSwap64(volatile LONGLONG *dest, LONGLONG newval, LONGLONG *oldval)
-{
- LONGLONG old = *oldval;
- *oldval = InterlockedCompareExchange64(dest, newval, *oldval);
- return old == *oldval;
-}
-inline bool CompareAndSwapPtr(void *volatile *dest, void *newval, void **oldval)
-{
- void *old = *oldval;
- *oldval = InterlockedCompareExchangePointer(dest, newval, *oldval);
- return old == *oldval;
-}
-
-#define WRAP_ADDSUB(T, _func, _ptr, _amnt) _func((T volatile*)(_ptr), (_amnt))
-#define WRAP_XCHG(T, _func, _ptr, _newval) _func((T volatile*)(_ptr), (_newval))
-#define WRAP_CMPXCHG(T, _func, _ptr, _newval, _oldval) _func((T volatile*)(_ptr), (_newval), (T*)(_oldval))
-
-
-enum almemory_order {
- almemory_order_relaxed,
- almemory_order_consume,
- almemory_order_acquire,
- almemory_order_release,
- almemory_order_acq_rel,
- almemory_order_seq_cst
-};
-
-#define ATOMIC(T) struct { T volatile value; }
-
-#define ATOMIC_INIT(_val, _newval) do { (_val)->value = (_newval); } while(0)
-#define ATOMIC_INIT_STATIC(_newval) {(_newval)}
-
-#define ATOMIC_LOAD(_val, _MO) ((_val)->value)
-#define ATOMIC_STORE(_val, _newval, _MO) do { \
- (_val)->value = (_newval); \
-} while(0)
-
-int _al_invalid_atomic_size(); /* not defined */
-void *_al_invalid_atomic_ptr_size(); /* not defined */
-
-#define ATOMIC_ADD(_val, _incr, _MO) \
- ((sizeof((_val)->value)==4) ? WRAP_ADDSUB(LONG, AtomicAdd32, &(_val)->value, (_incr)) : \
- (sizeof((_val)->value)==8) ? WRAP_ADDSUB(LONGLONG, AtomicAdd64, &(_val)->value, (_incr)) : \
- _al_invalid_atomic_size())
-#define ATOMIC_SUB(_val, _decr, _MO) \
- ((sizeof((_val)->value)==4) ? WRAP_ADDSUB(LONG, AtomicSub32, &(_val)->value, (_decr)) : \
- (sizeof((_val)->value)==8) ? WRAP_ADDSUB(LONGLONG, AtomicSub64, &(_val)->value, (_decr)) : \
- _al_invalid_atomic_size())
-#define ATOMIC_EXCHANGE(_val, _newval, _MO) \
- ((sizeof((_val)->value)==4) ? WRAP_XCHG(LONG, AtomicSwap32, &(_val)->value, (_newval)) : \
- (sizeof((_val)->value)==8) ? WRAP_XCHG(LONGLONG, AtomicSwap64, &(_val)->value, (_newval)) : \
- (LONG)_al_invalid_atomic_size())
-#define ATOMIC_COMPARE_EXCHANGE_STRONG(_val, _oldval, _newval, _MO1, _MO2) \
- ((sizeof((_val)->value)==4) ? WRAP_CMPXCHG(LONG, CompareAndSwap32, &(_val)->value, (_newval), (_oldval)) : \
- (sizeof((_val)->value)==8) ? WRAP_CMPXCHG(LONGLONG, CompareAndSwap64, &(_val)->value, (_newval), (_oldval)) : \
- (bool)_al_invalid_atomic_size())
+using RefCount = std::atomic<unsigned int>;
-#define ATOMIC_EXCHANGE_PTR(_val, _newval, _MO) \
- ((sizeof((_val)->value)==sizeof(void*)) ? AtomicSwapPtr((void*volatile*)&(_val)->value, (_newval)) : \
- _al_invalid_atomic_ptr_size())
-#define ATOMIC_COMPARE_EXCHANGE_PTR_STRONG(_val, _oldval, _newval, _MO1, _MO2)\
- ((sizeof((_val)->value)==sizeof(void*)) ? CompareAndSwapPtr((void*volatile*)&(_val)->value, (_newval), (void**)(_oldval)) : \
- (bool)_al_invalid_atomic_size())
-
-#define ATOMIC_THREAD_FENCE(order) do { \
- enum { must_be_constant = (order) }; \
- const int _o = must_be_constant; \
- if(_o > almemory_order_relaxed) \
- _ReadWriteBarrier(); \
-} while(0)
-
-#else
-
-#error "No atomic functions available on this platform!"
-
-#define ATOMIC(T) T
-
-#define ATOMIC_INIT(_val, _newval) ((void)0)
-#define ATOMIC_INIT_STATIC(_newval) (0)
-
-#define ATOMIC_LOAD(...) (0)
-#define ATOMIC_STORE(...) ((void)0)
-
-#define ATOMIC_ADD(...) (0)
-#define ATOMIC_SUB(...) (0)
-
-#define ATOMIC_EXCHANGE(...) (0)
-#define ATOMIC_COMPARE_EXCHANGE_STRONG(...) (0)
-
-#define ATOMIC_THREAD_FENCE(...) ((void)0)
-#endif
-
-/* If no PTR xchg variants are provided, the normal ones can handle it. */
-#ifndef ATOMIC_EXCHANGE_PTR
-#define ATOMIC_EXCHANGE_PTR ATOMIC_EXCHANGE
-#define ATOMIC_COMPARE_EXCHANGE_PTR_STRONG ATOMIC_COMPARE_EXCHANGE_STRONG
-#define ATOMIC_COMPARE_EXCHANGE_PTR_WEAK ATOMIC_COMPARE_EXCHANGE_WEAK
-#endif
-
-/* If no weak cmpxchg is provided (not all systems will have one), substitute a
- * strong cmpxchg. */
-#ifndef ATOMIC_COMPARE_EXCHANGE_WEAK
-#define ATOMIC_COMPARE_EXCHANGE_WEAK ATOMIC_COMPARE_EXCHANGE_STRONG
-#endif
-#ifndef ATOMIC_COMPARE_EXCHANGE_PTR_WEAK
-#define ATOMIC_COMPARE_EXCHANGE_PTR_WEAK ATOMIC_COMPARE_EXCHANGE_PTR_STRONG
-#endif
-
-/* If no ATOMIC_FLAG is defined, simulate one with an atomic int using exchange
- * and store ops.
- */
-#ifndef ATOMIC_FLAG
-#define ATOMIC_FLAG ATOMIC(int)
-#define ATOMIC_FLAG_INIT ATOMIC_INIT_STATIC(0)
-#define ATOMIC_FLAG_TEST_AND_SET(_val, _MO) ATOMIC_EXCHANGE(_val, 1, _MO)
-#define ATOMIC_FLAG_CLEAR(_val, _MO) ATOMIC_STORE(_val, 0, _MO)
-#endif
-
-
-#define ATOMIC_LOAD_SEQ(_val) ATOMIC_LOAD(_val, almemory_order_seq_cst)
-#define ATOMIC_STORE_SEQ(_val, _newval) ATOMIC_STORE(_val, _newval, almemory_order_seq_cst)
-
-#define ATOMIC_ADD_SEQ(_val, _incr) ATOMIC_ADD(_val, _incr, almemory_order_seq_cst)
-#define ATOMIC_SUB_SEQ(_val, _decr) ATOMIC_SUB(_val, _decr, almemory_order_seq_cst)
-
-#define ATOMIC_EXCHANGE_SEQ(_val, _newval) ATOMIC_EXCHANGE(_val, _newval, almemory_order_seq_cst)
-#define ATOMIC_COMPARE_EXCHANGE_STRONG_SEQ(_val, _oldval, _newval) \
- ATOMIC_COMPARE_EXCHANGE_STRONG(_val, _oldval, _newval, almemory_order_seq_cst, almemory_order_seq_cst)
-#define ATOMIC_COMPARE_EXCHANGE_WEAK_SEQ(_val, _oldval, _newval) \
- ATOMIC_COMPARE_EXCHANGE_WEAK(_val, _oldval, _newval, almemory_order_seq_cst, almemory_order_seq_cst)
-
-#define ATOMIC_EXCHANGE_PTR_SEQ(_val, _newval) ATOMIC_EXCHANGE_PTR(_val, _newval, almemory_order_seq_cst)
-#define ATOMIC_COMPARE_EXCHANGE_PTR_STRONG_SEQ(_val, _oldval, _newval) \
- ATOMIC_COMPARE_EXCHANGE_PTR_STRONG(_val, _oldval, _newval, almemory_order_seq_cst, almemory_order_seq_cst)
-#define ATOMIC_COMPARE_EXCHANGE_PTR_WEAK_SEQ(_val, _oldval, _newval) \
- ATOMIC_COMPARE_EXCHANGE_PTR_WEAK(_val, _oldval, _newval, almemory_order_seq_cst, almemory_order_seq_cst)
-
-
-typedef unsigned int uint;
-typedef ATOMIC(uint) RefCount;
-
-inline void InitRef(RefCount *ptr, uint value)
-{ ATOMIC_INIT(ptr, value); }
-inline uint ReadRef(RefCount *ptr)
-{ return ATOMIC_LOAD(ptr, almemory_order_acquire); }
-inline uint IncrementRef(RefCount *ptr)
-{ return ATOMIC_ADD(ptr, 1, almemory_order_acq_rel)+1; }
-inline uint DecrementRef(RefCount *ptr)
-{ return ATOMIC_SUB(ptr, 1, almemory_order_acq_rel)-1; }
+inline void InitRef(RefCount &ref, unsigned int value)
+{ ref.store(value, std::memory_order_relaxed); }
+inline unsigned int ReadRef(RefCount &ref)
+{ return ref.load(std::memory_order_acquire); }
+inline unsigned int IncrementRef(RefCount &ref)
+{ return ref.fetch_add(1u, std::memory_order_acq_rel)+1u; }
+inline unsigned int DecrementRef(RefCount &ref)
+{ return ref.fetch_sub(1u, std::memory_order_acq_rel)-1u; }
/* WARNING: A livelock is theoretically possible if another thread keeps
* changing the head without giving this a chance to actually swap in the new
* one (practically impossible with this little code, but...).
*/
-#define ATOMIC_REPLACE_HEAD(T, _head, _entry) do { \
- T _first = ATOMIC_LOAD(_head, almemory_order_acquire); \
- do { \
- ATOMIC_STORE(&(_entry)->next, _first, almemory_order_relaxed); \
- } while(ATOMIC_COMPARE_EXCHANGE_PTR_WEAK(_head, &_first, _entry, \
- almemory_order_acq_rel, almemory_order_acquire) == 0); \
-} while(0)
-
-#ifdef __cplusplus
+template<typename T>
+inline void AtomicReplaceHead(std::atomic<T> &head, T newhead)
+{
+ T first_ = head.load(std::memory_order_acquire);
+ do {
+ newhead->next.store(first_, std::memory_order_relaxed);
+ } while(!head.compare_exchange_weak(first_, newhead,
+ std::memory_order_acq_rel, std::memory_order_acquire));
}
-#endif
#endif /* AL_ATOMIC_H */
diff --git a/common/bool.h b/common/bool.h
deleted file mode 100644
index 6f714d09..00000000
--- a/common/bool.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef AL_BOOL_H
-#define AL_BOOL_H
-
-#ifdef HAVE_STDBOOL_H
-#include <stdbool.h>
-#endif
-
-#ifndef bool
-#ifdef HAVE_C99_BOOL
-#define bool _Bool
-#else
-#define bool int
-#endif
-#define false 0
-#define true 1
-#endif
-
-#endif /* AL_BOOL_H */
diff --git a/common/dynload.cpp b/common/dynload.cpp
new file mode 100644
index 00000000..f1c2a7eb
--- /dev/null
+++ b/common/dynload.cpp
@@ -0,0 +1,44 @@
+
+#include "config.h"
+
+#include "dynload.h"
+
+#include "strutils.h"
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+void *LoadLib(const char *name)
+{
+ std::wstring wname{utf8_to_wstr(name)};
+ return LoadLibraryW(wname.c_str());
+}
+void CloseLib(void *handle)
+{ FreeLibrary(static_cast<HMODULE>(handle)); }
+void *GetSymbol(void *handle, const char *name)
+{ return reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(handle), name)); }
+
+#elif defined(HAVE_DLFCN_H)
+
+#include <dlfcn.h>
+
+void *LoadLib(const char *name)
+{
+ dlerror();
+ void *handle{dlopen(name, RTLD_NOW)};
+ const char *err{dlerror()};
+ if(err) handle = nullptr;
+ return handle;
+}
+void CloseLib(void *handle)
+{ dlclose(handle); }
+void *GetSymbol(void *handle, const char *name)
+{
+ dlerror();
+ void *sym{dlsym(handle, name)};
+ const char *err{dlerror()};
+ if(err) sym = nullptr;
+ return sym;
+}
+#endif
diff --git a/common/dynload.h b/common/dynload.h
new file mode 100644
index 00000000..bd9e86f7
--- /dev/null
+++ b/common/dynload.h
@@ -0,0 +1,14 @@
+#ifndef AL_DYNLOAD_H
+#define AL_DYNLOAD_H
+
+#if defined(_WIN32) || defined(HAVE_DLFCN_H)
+
+#define HAVE_DYNLOAD
+
+void *LoadLib(const char *name);
+void CloseLib(void *handle);
+void *GetSymbol(void *handle, const char *name);
+
+#endif
+
+#endif /* AL_DYNLOAD_H */
diff --git a/common/endiantest.h b/common/endiantest.h
new file mode 100644
index 00000000..0a20eb91
--- /dev/null
+++ b/common/endiantest.h
@@ -0,0 +1,14 @@
+#ifndef AL_ENDIANTEST_H
+#define AL_ENDIANTEST_H
+
+#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__)
+#define IS_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+#else
+static const union {
+ unsigned int u;
+ unsigned char b[sizeof(unsigned int)];
+} EndianTest = { 1 };
+#define IS_LITTLE_ENDIAN (EndianTest.b[0] == 1)
+#endif
+
+#endif /* AL_ENDIANTEST_H */
diff --git a/common/intrusive_ptr.h b/common/intrusive_ptr.h
new file mode 100644
index 00000000..595c831d
--- /dev/null
+++ b/common/intrusive_ptr.h
@@ -0,0 +1,120 @@
+#ifndef INTRUSIVE_PTR_H
+#define INTRUSIVE_PTR_H
+
+#include "atomic.h"
+#include "opthelpers.h"
+
+
+namespace al {
+
+template<typename T>
+class intrusive_ref {
+ RefCount mRef{1u};
+
+public:
+ unsigned int add_ref() noexcept { return IncrementRef(mRef); }
+ unsigned int release() noexcept
+ {
+ auto ref = DecrementRef(mRef);
+ if UNLIKELY(ref == 0)
+ delete static_cast<T*>(this);
+ return ref;
+ }
+
+ /**
+ * Release only if doing so would not bring the object to 0 references and
+ * delete it. Returns false if the object could not be released.
+ *
+ * NOTE: The caller is responsible for handling a failed release, as it
+ * means the object has no other references and needs to be be deleted
+ * somehow.
+ */
+ bool releaseIfNoDelete() noexcept
+ {
+ auto val = mRef.load(std::memory_order_acquire);
+ while(val > 1 && !mRef.compare_exchange_strong(val, val-1, std::memory_order_acq_rel))
+ {
+ /* val was updated with the current value on failure, so just try
+ * again.
+ */
+ }
+
+ return val >= 2;
+ }
+};
+
+
+template<typename T>
+class intrusive_ptr {
+ T *mPtr{nullptr};
+
+public:
+ intrusive_ptr() noexcept = default;
+ intrusive_ptr(const intrusive_ptr &rhs) noexcept : mPtr{rhs.mPtr}
+ { if(mPtr) mPtr->add_ref(); }
+ intrusive_ptr(intrusive_ptr&& rhs) noexcept : mPtr{rhs.mPtr}
+ { rhs.mPtr = nullptr; }
+ intrusive_ptr(std::nullptr_t) noexcept { }
+ explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { }
+ ~intrusive_ptr() { if(mPtr) mPtr->release(); }
+
+ intrusive_ptr& operator=(const intrusive_ptr &rhs) noexcept
+ {
+ if(rhs.mPtr) rhs.mPtr->add_ref();
+ if(mPtr) mPtr->release();
+ mPtr = rhs.mPtr;
+ return *this;
+ }
+ intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept
+ {
+ 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; }
+
+ void reset() noexcept
+ {
+ if(mPtr)
+ mPtr->release();
+ mPtr = nullptr;
+ }
+
+ T* release() noexcept
+ {
+ T *ret{mPtr};
+ mPtr = nullptr;
+ return ret;
+ }
+
+ void swap(intrusive_ptr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
+ void swap(intrusive_ptr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
+};
+
+#define AL_DECL_OP(op) \
+template<typename T> \
+inline bool operator op(const intrusive_ptr<T> &lhs, const T *rhs) noexcept \
+{ return lhs.get() op rhs; } \
+template<typename T> \
+inline bool operator op(const T *lhs, const intrusive_ptr<T> &rhs) noexcept \
+{ return lhs op rhs.get(); }
+
+AL_DECL_OP(==)
+AL_DECL_OP(!=)
+AL_DECL_OP(<=)
+AL_DECL_OP(>=)
+AL_DECL_OP(<)
+AL_DECL_OP(>)
+
+#undef AL_DECL_OP
+
+} // namespace al
+
+#endif /* INTRUSIVE_PTR_H */
diff --git a/common/math_defs.h b/common/math_defs.h
index aa79b695..0362a956 100644
--- a/common/math_defs.h
+++ b/common/math_defs.h
@@ -2,64 +2,31 @@
#define AL_MATH_DEFS_H
#include <math.h>
-#ifdef HAVE_FLOAT_H
-#include <float.h>
-#endif
#ifndef M_PI
-#define M_PI (3.14159265358979323846)
+#define M_PI 3.14159265358979323846
#endif
-#define F_PI (3.14159265358979323846f)
-#define F_PI_2 (1.57079632679489661923f)
-#define F_TAU (6.28318530717958647692f)
+constexpr inline float Deg2Rad(float x) noexcept { return x * static_cast<float>(M_PI/180.0); }
+constexpr inline float Rad2Deg(float x) noexcept { return x * static_cast<float>(180.0/M_PI); }
-#ifndef FLT_EPSILON
-#define FLT_EPSILON (1.19209290e-07f)
-#endif
+namespace al {
-#define SQRT_2 1.41421356237309504880
-#define SQRT_3 1.73205080756887719318
+template<typename Real>
+struct MathDefs { };
-#define SQRTF_2 1.41421356237309504880f
-#define SQRTF_3 1.73205080756887719318f
+template<>
+struct MathDefs<float> {
+ static constexpr inline float Pi() noexcept { return static_cast<float>(M_PI); }
+ static constexpr inline float Tau() noexcept { return static_cast<float>(M_PI * 2.0); }
+};
-#ifndef HUGE_VALF
-static const union msvc_inf_hack {
- unsigned char b[4];
- float f;
-} msvc_inf_union = {{ 0x00, 0x00, 0x80, 0x7F }};
-#define HUGE_VALF (msvc_inf_union.f)
-#endif
-
-#ifndef HAVE_LOG2F
-static inline float log2f(float f)
-{
- return logf(f) / logf(2.0f);
-}
-#endif
-
-#ifndef HAVE_CBRTF
-static inline float cbrtf(float f)
-{
- return powf(f, 1.0f/3.0f);
-}
-#endif
-
-#ifndef HAVE_COPYSIGNF
-static inline float copysignf(float x, float y)
-{
- union {
- float f;
- unsigned int u;
- } ux = { x }, uy = { y };
- ux.u &= 0x7fffffffu;
- ux.u |= (uy.u&0x80000000u);
- return ux.f;
-}
-#endif
+template<>
+struct MathDefs<double> {
+ static constexpr inline double Pi() noexcept { return M_PI; }
+ static constexpr inline double Tau() noexcept { return M_PI * 2.0; }
+};
-#define DEG2RAD(x) ((float)(x) * (float)(M_PI/180.0))
-#define RAD2DEG(x) ((float)(x) * (float)(180.0/M_PI))
+} // namespace al
#endif /* AL_MATH_DEFS_H */
diff --git a/common/opthelpers.h b/common/opthelpers.h
new file mode 100644
index 00000000..58578c15
--- /dev/null
+++ b/common/opthelpers.h
@@ -0,0 +1,39 @@
+#ifndef OPTHELPERS_H
+#define OPTHELPERS_H
+
+#ifdef __has_builtin
+#define HAS_BUILTIN __has_builtin
+#else
+#define HAS_BUILTIN(x) (0)
+#endif
+
+#ifdef __GNUC__
+/* LIKELY optimizes the case where the condition is true. The condition is not
+ * required to be true, but it can result in more optimal code for the true
+ * path at the expense of a less optimal false path.
+ */
+#define LIKELY(x) (__builtin_expect(!!(x), !false))
+/* The opposite of LIKELY, optimizing the case where the condition is false. */
+#define UNLIKELY(x) (__builtin_expect(!!(x), false))
+/* Unlike LIKELY, ASSUME requires the condition to be true or else it invokes
+ * undefined behavior. It's essentially an assert without actually checking the
+ * condition at run-time, allowing for stronger optimizations than LIKELY.
+ */
+#if HAS_BUILTIN(__builtin_assume)
+#define ASSUME __builtin_assume
+#else
+#define ASSUME(x) do { if(!(x)) __builtin_unreachable(); } while(0)
+#endif
+
+#else /* __GNUC__ */
+
+#define LIKELY(x) (!!(x))
+#define UNLIKELY(x) (!!(x))
+#ifdef _MSC_VER
+#define ASSUME __assume
+#else
+#define ASSUME(x) ((void)0)
+#endif /* _MSC_VER */
+#endif /* __GNUC__ */
+
+#endif /* OPTHELPERS_H */
diff --git a/common/pragmadefs.h b/common/pragmadefs.h
new file mode 100644
index 00000000..1d0af066
--- /dev/null
+++ b/common/pragmadefs.h
@@ -0,0 +1,21 @@
+#ifndef PRAGMADEFS_H
+#define PRAGMADEFS_H
+
+#if defined(_MSC_VER)
+#define DIAGNOSTIC_PUSH __pragma(warning(push))
+#define DIAGNOSTIC_POP __pragma(warning(pop))
+#define std_pragma(...)
+#define msc_pragma __pragma
+#else
+#if defined(__GNUC__) || defined(__clang__)
+#define DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push")
+#define DIAGNOSTIC_POP _Pragma("GCC diagnostic push")
+#else
+#define DIAGNOSTIC_PUSH
+#define DIAGNOSTIC_POP
+#endif
+#define std_pragma _Pragma
+#define msc_pragma(...)
+#endif
+
+#endif /* PRAGMADEFS_H */
diff --git a/common/rwlock.c b/common/rwlock.c
deleted file mode 100644
index 67cf3acf..00000000
--- a/common/rwlock.c
+++ /dev/null
@@ -1,59 +0,0 @@
-
-#include "config.h"
-
-#include "rwlock.h"
-
-#include "bool.h"
-#include "atomic.h"
-#include "threads.h"
-
-
-/* A simple spinlock. Yield the thread while the given integer is set by
- * another. Could probably be improved... */
-#define LOCK(l) do { \
- while(ATOMIC_FLAG_TEST_AND_SET(&(l), almemory_order_acq_rel) == true) \
- althrd_yield(); \
-} while(0)
-#define UNLOCK(l) ATOMIC_FLAG_CLEAR(&(l), almemory_order_release)
-
-
-void RWLockInit(RWLock *lock)
-{
- InitRef(&lock->read_count, 0);
- InitRef(&lock->write_count, 0);
- ATOMIC_FLAG_CLEAR(&lock->read_lock, almemory_order_relaxed);
- ATOMIC_FLAG_CLEAR(&lock->read_entry_lock, almemory_order_relaxed);
- ATOMIC_FLAG_CLEAR(&lock->write_lock, almemory_order_relaxed);
-}
-
-void ReadLock(RWLock *lock)
-{
- LOCK(lock->read_entry_lock);
- LOCK(lock->read_lock);
- /* NOTE: ATOMIC_ADD returns the *old* value! */
- if(ATOMIC_ADD(&lock->read_count, 1, almemory_order_acq_rel) == 0)
- LOCK(lock->write_lock);
- UNLOCK(lock->read_lock);
- UNLOCK(lock->read_entry_lock);
-}
-
-void ReadUnlock(RWLock *lock)
-{
- /* NOTE: ATOMIC_SUB returns the *old* value! */
- if(ATOMIC_SUB(&lock->read_count, 1, almemory_order_acq_rel) == 1)
- UNLOCK(lock->write_lock);
-}
-
-void WriteLock(RWLock *lock)
-{
- if(ATOMIC_ADD(&lock->write_count, 1, almemory_order_acq_rel) == 0)
- LOCK(lock->read_lock);
- LOCK(lock->write_lock);
-}
-
-void WriteUnlock(RWLock *lock)
-{
- UNLOCK(lock->write_lock);
- if(ATOMIC_SUB(&lock->write_count, 1, almemory_order_acq_rel) == 1)
- UNLOCK(lock->read_lock);
-}
diff --git a/common/rwlock.h b/common/rwlock.h
deleted file mode 100644
index 8e36fa1a..00000000
--- a/common/rwlock.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#ifndef AL_RWLOCK_H
-#define AL_RWLOCK_H
-
-#include "bool.h"
-#include "atomic.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct {
- RefCount read_count;
- RefCount write_count;
- ATOMIC_FLAG read_lock;
- ATOMIC_FLAG read_entry_lock;
- ATOMIC_FLAG write_lock;
-} RWLock;
-#define RWLOCK_STATIC_INITIALIZE { ATOMIC_INIT_STATIC(0), ATOMIC_INIT_STATIC(0), \
- ATOMIC_FLAG_INIT, ATOMIC_FLAG_INIT, ATOMIC_FLAG_INIT }
-
-void RWLockInit(RWLock *lock);
-void ReadLock(RWLock *lock);
-void ReadUnlock(RWLock *lock);
-void WriteLock(RWLock *lock);
-void WriteUnlock(RWLock *lock);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* AL_RWLOCK_H */
diff --git a/common/static_assert.h b/common/static_assert.h
deleted file mode 100644
index bf0ce065..00000000
--- a/common/static_assert.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef AL_STATIC_ASSERT_H
-#define AL_STATIC_ASSERT_H
-
-#include <assert.h>
-
-
-#ifndef static_assert
-#ifdef HAVE_C11_STATIC_ASSERT
-#define static_assert _Static_assert
-#else
-#define CTASTR2(_pre,_post) _pre##_post
-#define CTASTR(_pre,_post) CTASTR2(_pre,_post)
-#if defined(__COUNTER__)
-#define static_assert(_cond, _msg) typedef struct { int CTASTR(static_assert_failed_at_line_,__LINE__) : !!(_cond); } CTASTR(static_assertion_,__COUNTER__)
-#else
-#define static_assert(_cond, _msg) struct { int CTASTR(static_assert_failed_at_line_,__LINE__) : !!(_cond); }
-#endif
-#endif
-#endif
-
-#endif /* AL_STATIC_ASSERT_H */
diff --git a/common/strutils.cpp b/common/strutils.cpp
new file mode 100644
index 00000000..870a0ed3
--- /dev/null
+++ b/common/strutils.cpp
@@ -0,0 +1,62 @@
+
+#include "config.h"
+
+#include "strutils.h"
+
+#include <cstdlib>
+
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+std::string wstr_to_utf8(const WCHAR *wstr)
+{
+ std::string ret;
+
+ int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
+ if(len > 0)
+ {
+ ret.resize(len);
+ WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &ret[0], len, nullptr, nullptr);
+ ret.pop_back();
+ }
+
+ return ret;
+}
+
+std::wstring utf8_to_wstr(const char *str)
+{
+ std::wstring ret;
+
+ int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
+ if(len > 0)
+ {
+ ret.resize(len);
+ MultiByteToWideChar(CP_UTF8, 0, str, -1, &ret[0], len);
+ ret.pop_back();
+ }
+
+ return ret;
+}
+#endif
+
+namespace al {
+
+al::optional<std::string> getenv(const char *envname)
+{
+ const char *str{std::getenv(envname)};
+ if(str && str[0] != '\0') return str;
+ return al::nullopt;
+}
+
+#ifdef _WIN32
+al::optional<std::wstring> getenv(const WCHAR *envname)
+{
+ const WCHAR *str{_wgetenv(envname)};
+ if(str && str[0] != L'\0') return str;
+ return al::nullopt;
+}
+#endif
+
+} // namespace al
diff --git a/common/strutils.h b/common/strutils.h
new file mode 100644
index 00000000..0c7a0e22
--- /dev/null
+++ b/common/strutils.h
@@ -0,0 +1,24 @@
+#ifndef AL_STRUTILS_H
+#define AL_STRUTILS_H
+
+#include <string>
+
+#include "aloptional.h"
+
+#ifdef _WIN32
+#include <wchar.h>
+
+std::string wstr_to_utf8(const wchar_t *wstr);
+std::wstring utf8_to_wstr(const char *str);
+#endif
+
+namespace al {
+
+al::optional<std::string> getenv(const char *envname);
+#ifdef _WIN32
+al::optional<std::wstring> getenv(const wchar_t *envname);
+#endif
+
+} // namespace al
+
+#endif /* AL_STRUTILS_H */
diff --git a/common/threads.c b/common/threads.c
deleted file mode 100644
index e8301297..00000000
--- a/common/threads.c
+++ /dev/null
@@ -1,751 +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 "threads.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#include "uintmap.h"
-
-
-extern inline althrd_t althrd_current(void);
-extern inline int althrd_equal(althrd_t thr0, althrd_t thr1);
-extern inline void althrd_exit(int res);
-extern inline void althrd_yield(void);
-
-extern inline int almtx_lock(almtx_t *mtx);
-extern inline int almtx_unlock(almtx_t *mtx);
-extern inline int almtx_trylock(almtx_t *mtx);
-
-extern inline void *altss_get(altss_t tss_id);
-extern inline int altss_set(altss_t tss_id, void *val);
-
-
-#ifndef UNUSED
-#if defined(__cplusplus)
-#define UNUSED(x)
-#elif defined(__GNUC__)
-#define UNUSED(x) UNUSED_##x __attribute__((unused))
-#elif defined(__LCLINT__)
-#define UNUSED(x) /*@unused@*/ x
-#else
-#define UNUSED(x) x
-#endif
-#endif
-
-
-#define THREAD_STACK_SIZE (2*1024*1024) /* 2MB */
-
-#ifdef _WIN32
-
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-#include <mmsystem.h>
-
-
-/* An associative map of uint:void* pairs. The key is the unique Thread ID and
- * the value is the thread HANDLE. The thread ID is passed around as the
- * althrd_t since there is only one ID per thread, whereas a thread may be
- * referenced by multiple different HANDLEs. This map allows retrieving the
- * original handle which is needed to join the thread and get its return value.
- */
-static UIntMap ThrdIdHandle = UINTMAP_STATIC_INITIALIZE;
-
-/* An associative map of uint:void* pairs. The key is the TLS index (given by
- * TlsAlloc), and the value is the altss_dtor_t callback. When a thread exits,
- * we iterate over the TLS indices for their thread-local value and call the
- * destructor function with it if they're both not NULL.
- */
-static UIntMap TlsDestructors = UINTMAP_STATIC_INITIALIZE;
-
-
-void althrd_setname(althrd_t thr, const char *name)
-{
-#if defined(_MSC_VER)
-#define MS_VC_EXCEPTION 0x406D1388
-#pragma pack(push,8)
- struct {
- DWORD dwType; // Must be 0x1000.
- LPCSTR szName; // Pointer to name (in user addr space).
- DWORD dwThreadID; // Thread ID (-1=caller thread).
- DWORD dwFlags; // Reserved for future use, must be zero.
- } info;
-#pragma pack(pop)
- info.dwType = 0x1000;
- info.szName = name;
- info.dwThreadID = thr;
- info.dwFlags = 0;
-
- __try {
- RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info);
- }
- __except(EXCEPTION_CONTINUE_EXECUTION) {
- }
-#undef MS_VC_EXCEPTION
-#else
- (void)thr;
- (void)name;
-#endif
-}
-
-
-typedef struct thread_cntr {
- althrd_start_t func;
- void *arg;
-} thread_cntr;
-
-static DWORD WINAPI althrd_starter(void *arg)
-{
- thread_cntr cntr;
- memcpy(&cntr, arg, sizeof(cntr));
- free(arg);
-
- return (DWORD)((*cntr.func)(cntr.arg));
-}
-
-
-int althrd_create(althrd_t *thr, althrd_start_t func, void *arg)
-{
- thread_cntr *cntr;
- DWORD thrid;
- HANDLE hdl;
-
- cntr = malloc(sizeof(*cntr));
- if(!cntr) return althrd_nomem;
-
- cntr->func = func;
- cntr->arg = arg;
-
- hdl = CreateThread(NULL, THREAD_STACK_SIZE, althrd_starter, cntr, 0, &thrid);
- if(!hdl)
- {
- free(cntr);
- return althrd_error;
- }
- InsertUIntMapEntry(&ThrdIdHandle, thrid, hdl);
-
- *thr = thrid;
- return althrd_success;
-}
-
-int althrd_detach(althrd_t thr)
-{
- HANDLE hdl = RemoveUIntMapKey(&ThrdIdHandle, thr);
- if(!hdl) return althrd_error;
-
- CloseHandle(hdl);
- return althrd_success;
-}
-
-int althrd_join(althrd_t thr, int *res)
-{
- DWORD code;
-
- HANDLE hdl = RemoveUIntMapKey(&ThrdIdHandle, thr);
- if(!hdl) return althrd_error;
-
- WaitForSingleObject(hdl, INFINITE);
- GetExitCodeThread(hdl, &code);
- CloseHandle(hdl);
-
- if(res != NULL)
- *res = (int)code;
- return althrd_success;
-}
-
-int althrd_sleep(const struct timespec *ts, struct timespec* UNUSED(rem))
-{
- DWORD msec;
-
- if(ts->tv_sec < 0 || ts->tv_sec >= (0x7fffffff / 1000) ||
- ts->tv_nsec < 0 || ts->tv_nsec >= 1000000000)
- return -2;
-
- msec = (DWORD)(ts->tv_sec * 1000);
- msec += (DWORD)((ts->tv_nsec+999999) / 1000000);
- Sleep(msec);
-
- return 0;
-}
-
-
-int almtx_init(almtx_t *mtx, int type)
-{
- if(!mtx) return althrd_error;
-
- type &= ~almtx_recursive;
- if(type != almtx_plain)
- return althrd_error;
-
- InitializeCriticalSection(mtx);
- return althrd_success;
-}
-
-void almtx_destroy(almtx_t *mtx)
-{
- DeleteCriticalSection(mtx);
-}
-
-#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0600
-int alcnd_init(alcnd_t *cond)
-{
- InitializeConditionVariable(cond);
- return althrd_success;
-}
-
-int alcnd_signal(alcnd_t *cond)
-{
- WakeConditionVariable(cond);
- return althrd_success;
-}
-
-int alcnd_broadcast(alcnd_t *cond)
-{
- WakeAllConditionVariable(cond);
- return althrd_success;
-}
-
-int alcnd_wait(alcnd_t *cond, almtx_t *mtx)
-{
- if(SleepConditionVariableCS(cond, mtx, INFINITE) != 0)
- return althrd_success;
- return althrd_error;
-}
-
-void alcnd_destroy(alcnd_t* UNUSED(cond))
-{
- /* Nothing to delete? */
-}
-
-#else
-
-/* WARNING: This is a rather poor implementation of condition variables, with
- * known problems. However, it's simple, efficient, and good enough for now to
- * not require Vista. Based on "Strategies for Implementing POSIX Condition
- * Variables" by Douglas C. Schmidt and Irfan Pyarali:
- * http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
- */
-/* A better solution may be using Wine's implementation. It requires internals
- * (NtCreateKeyedEvent, NtReleaseKeyedEvent, and NtWaitForKeyedEvent) from
- * ntdll, and implemention of exchange and compare-exchange for RefCounts.
- */
-
-typedef struct {
- RefCount wait_count;
-
- HANDLE events[2];
-} _int_alcnd_t;
-enum {
- SIGNAL = 0,
- BROADCAST = 1
-};
-
-int alcnd_init(alcnd_t *cond)
-{
- _int_alcnd_t *icond = calloc(1, sizeof(*icond));
- if(!icond) return althrd_nomem;
-
- InitRef(&icond->wait_count, 0);
-
- icond->events[SIGNAL] = CreateEventW(NULL, FALSE, FALSE, NULL);
- icond->events[BROADCAST] = CreateEventW(NULL, TRUE, FALSE, NULL);
- if(!icond->events[SIGNAL] || !icond->events[BROADCAST])
- {
- if(icond->events[SIGNAL])
- CloseHandle(icond->events[SIGNAL]);
- if(icond->events[BROADCAST])
- CloseHandle(icond->events[BROADCAST]);
- free(icond);
- return althrd_error;
- }
-
- cond->Ptr = icond;
- return althrd_success;
-}
-
-int alcnd_signal(alcnd_t *cond)
-{
- _int_alcnd_t *icond = cond->Ptr;
- if(ReadRef(&icond->wait_count) > 0)
- SetEvent(icond->events[SIGNAL]);
- return althrd_success;
-}
-
-int alcnd_broadcast(alcnd_t *cond)
-{
- _int_alcnd_t *icond = cond->Ptr;
- if(ReadRef(&icond->wait_count) > 0)
- SetEvent(icond->events[BROADCAST]);
- return althrd_success;
-}
-
-int alcnd_wait(alcnd_t *cond, almtx_t *mtx)
-{
- _int_alcnd_t *icond = cond->Ptr;
- int res;
-
- IncrementRef(&icond->wait_count);
- LeaveCriticalSection(mtx);
-
- res = WaitForMultipleObjects(2, icond->events, FALSE, INFINITE);
-
- if(DecrementRef(&icond->wait_count) == 0 && res == WAIT_OBJECT_0+BROADCAST)
- ResetEvent(icond->events[BROADCAST]);
- EnterCriticalSection(mtx);
-
- return althrd_success;
-}
-
-void alcnd_destroy(alcnd_t *cond)
-{
- _int_alcnd_t *icond = cond->Ptr;
- CloseHandle(icond->events[SIGNAL]);
- CloseHandle(icond->events[BROADCAST]);
- free(icond);
-}
-#endif /* defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0600 */
-
-
-int alsem_init(alsem_t *sem, unsigned int initial)
-{
- *sem = CreateSemaphore(NULL, initial, INT_MAX, NULL);
- if(*sem != NULL) return althrd_success;
- return althrd_error;
-}
-
-void alsem_destroy(alsem_t *sem)
-{
- CloseHandle(*sem);
-}
-
-int alsem_post(alsem_t *sem)
-{
- DWORD ret = ReleaseSemaphore(*sem, 1, NULL);
- if(ret) return althrd_success;
- return althrd_error;
-}
-
-int alsem_wait(alsem_t *sem)
-{
- DWORD ret = WaitForSingleObject(*sem, INFINITE);
- if(ret == WAIT_OBJECT_0) return althrd_success;
- return althrd_error;
-}
-
-int alsem_trywait(alsem_t *sem)
-{
- DWORD ret = WaitForSingleObject(*sem, 0);
- if(ret == WAIT_OBJECT_0) return althrd_success;
- if(ret == WAIT_TIMEOUT) return althrd_busy;
- return althrd_error;
-}
-
-
-int altss_create(altss_t *tss_id, altss_dtor_t callback)
-{
- DWORD key = TlsAlloc();
- if(key == TLS_OUT_OF_INDEXES)
- return althrd_error;
-
- *tss_id = key;
- if(callback != NULL)
- InsertUIntMapEntry(&TlsDestructors, key, callback);
- return althrd_success;
-}
-
-void altss_delete(altss_t tss_id)
-{
- RemoveUIntMapKey(&TlsDestructors, tss_id);
- TlsFree(tss_id);
-}
-
-
-int altimespec_get(struct timespec *ts, int base)
-{
- static_assert(sizeof(FILETIME) == sizeof(ULARGE_INTEGER),
- "Size of FILETIME does not match ULARGE_INTEGER");
- if(base == AL_TIME_UTC)
- {
- union {
- FILETIME ftime;
- ULARGE_INTEGER ulint;
- } systime;
- GetSystemTimeAsFileTime(&systime.ftime);
- /* FILETIME is in 100-nanosecond units, or 1/10th of a microsecond. */
- ts->tv_sec = systime.ulint.QuadPart/10000000;
- ts->tv_nsec = (systime.ulint.QuadPart%10000000) * 100;
- return base;
- }
-
- return 0;
-}
-
-
-void alcall_once(alonce_flag *once, void (*callback)(void))
-{
- LONG ret;
- while((ret=InterlockedExchange(once, 1)) == 1)
- althrd_yield();
- if(ret == 0)
- (*callback)();
- InterlockedExchange(once, 2);
-}
-
-
-void althrd_deinit(void)
-{
- ResetUIntMap(&ThrdIdHandle);
- ResetUIntMap(&TlsDestructors);
-}
-
-void althrd_thread_detach(void)
-{
- ALsizei i;
-
- LockUIntMapRead(&TlsDestructors);
- for(i = 0;i < TlsDestructors.size;i++)
- {
- void *ptr = altss_get(TlsDestructors.keys[i]);
- altss_dtor_t callback = (altss_dtor_t)TlsDestructors.values[i];
- if(ptr)
- {
- if(callback) callback(ptr);
- altss_set(TlsDestructors.keys[i], NULL);
- }
- }
- UnlockUIntMapRead(&TlsDestructors);
-}
-
-#else
-
-#include <sys/time.h>
-#include <unistd.h>
-#include <pthread.h>
-#ifdef HAVE_PTHREAD_NP_H
-#include <pthread_np.h>
-#endif
-
-
-extern inline int althrd_sleep(const struct timespec *ts, struct timespec *rem);
-extern inline void alcall_once(alonce_flag *once, void (*callback)(void));
-
-extern inline void althrd_deinit(void);
-extern inline void althrd_thread_detach(void);
-
-void althrd_setname(althrd_t thr, const char *name)
-{
-#if defined(HAVE_PTHREAD_SETNAME_NP)
-#if defined(PTHREAD_SETNAME_NP_ONE_PARAM)
- if(althrd_equal(thr, althrd_current()))
- pthread_setname_np(name);
-#elif defined(PTHREAD_SETNAME_NP_THREE_PARAMS)
- pthread_setname_np(thr, "%s", (void*)name);
-#else
- pthread_setname_np(thr, name);
-#endif
-#elif defined(HAVE_PTHREAD_SET_NAME_NP)
- pthread_set_name_np(thr, name);
-#else
- (void)thr;
- (void)name;
-#endif
-}
-
-
-typedef struct thread_cntr {
- althrd_start_t func;
- void *arg;
-} thread_cntr;
-
-static void *althrd_starter(void *arg)
-{
- thread_cntr cntr;
- memcpy(&cntr, arg, sizeof(cntr));
- free(arg);
-
- return (void*)(intptr_t)((*cntr.func)(cntr.arg));
-}
-
-
-int althrd_create(althrd_t *thr, althrd_start_t func, void *arg)
-{
- thread_cntr *cntr;
- pthread_attr_t attr;
- size_t stackmult = 1;
- int err;
-
- cntr = malloc(sizeof(*cntr));
- if(!cntr) return althrd_nomem;
-
- if(pthread_attr_init(&attr) != 0)
- {
- free(cntr);
- return althrd_error;
- }
-retry_stacksize:
- if(pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE*stackmult) != 0)
- {
- pthread_attr_destroy(&attr);
- free(cntr);
- return althrd_error;
- }
-
- cntr->func = func;
- cntr->arg = arg;
- if((err=pthread_create(thr, &attr, althrd_starter, cntr)) == 0)
- {
- pthread_attr_destroy(&attr);
- return althrd_success;
- }
-
- if(err == EINVAL)
- {
- /* If an invalid stack size, try increasing it (limit x4, 8MB). */
- if(stackmult < 4)
- {
- stackmult *= 2;
- goto retry_stacksize;
- }
- /* If still nothing, try defaults and hope they're good enough. */
- if(pthread_create(thr, NULL, althrd_starter, cntr) == 0)
- {
- pthread_attr_destroy(&attr);
- return althrd_success;
- }
- }
- pthread_attr_destroy(&attr);
- free(cntr);
- return althrd_error;
-}
-
-int althrd_detach(althrd_t thr)
-{
- if(pthread_detach(thr) != 0)
- return althrd_error;
- return althrd_success;
-}
-
-int althrd_join(althrd_t thr, int *res)
-{
- void *code;
-
- if(pthread_join(thr, &code) != 0)
- return althrd_error;
- if(res != NULL)
- *res = (int)(intptr_t)code;
- return althrd_success;
-}
-
-
-int almtx_init(almtx_t *mtx, int type)
-{
- int ret;
-
- if(!mtx) return althrd_error;
- if((type&~almtx_recursive) != 0)
- return althrd_error;
-
- if(type == almtx_plain)
- ret = pthread_mutex_init(mtx, NULL);
- else
- {
- pthread_mutexattr_t attr;
-
- ret = pthread_mutexattr_init(&attr);
- if(ret) return althrd_error;
-
- if(type == almtx_recursive)
- {
- ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
-#ifdef HAVE_PTHREAD_MUTEXATTR_SETKIND_NP
- if(ret != 0)
- ret = pthread_mutexattr_setkind_np(&attr, PTHREAD_MUTEX_RECURSIVE);
-#endif
- }
- else
- ret = 1;
- if(ret == 0)
- ret = pthread_mutex_init(mtx, &attr);
- pthread_mutexattr_destroy(&attr);
- }
- return ret ? althrd_error : althrd_success;
-}
-
-void almtx_destroy(almtx_t *mtx)
-{
- pthread_mutex_destroy(mtx);
-}
-
-int alcnd_init(alcnd_t *cond)
-{
- if(pthread_cond_init(cond, NULL) == 0)
- return althrd_success;
- return althrd_error;
-}
-
-int alcnd_signal(alcnd_t *cond)
-{
- if(pthread_cond_signal(cond) == 0)
- return althrd_success;
- return althrd_error;
-}
-
-int alcnd_broadcast(alcnd_t *cond)
-{
- if(pthread_cond_broadcast(cond) == 0)
- return althrd_success;
- return althrd_error;
-}
-
-int alcnd_wait(alcnd_t *cond, almtx_t *mtx)
-{
- if(pthread_cond_wait(cond, mtx) == 0)
- return althrd_success;
- return althrd_error;
-}
-
-void alcnd_destroy(alcnd_t *cond)
-{
- pthread_cond_destroy(cond);
-}
-
-
-#ifdef __APPLE__
-
-int alsem_init(alsem_t *sem, unsigned int initial)
-{
- *sem = dispatch_semaphore_create(initial);
- return *sem ? althrd_success : althrd_error;
-}
-
-void alsem_destroy(alsem_t *sem)
-{
- dispatch_release(*sem);
-}
-
-int alsem_post(alsem_t *sem)
-{
- dispatch_semaphore_signal(*sem);
- return althrd_success;
-}
-
-int alsem_wait(alsem_t *sem)
-{
- dispatch_semaphore_wait(*sem, DISPATCH_TIME_FOREVER);
- return althrd_success;
-}
-
-int alsem_trywait(alsem_t *sem)
-{
- long value = dispatch_semaphore_wait(*sem, DISPATCH_TIME_NOW);
- return value == 0 ? althrd_success : althrd_busy;
-}
-
-#else /* !__APPLE__ */
-
-int alsem_init(alsem_t *sem, unsigned int initial)
-{
- if(sem_init(sem, 0, initial) == 0)
- return althrd_success;
- return althrd_error;
-}
-
-void alsem_destroy(alsem_t *sem)
-{
- sem_destroy(sem);
-}
-
-int alsem_post(alsem_t *sem)
-{
- if(sem_post(sem) == 0)
- return althrd_success;
- return althrd_error;
-}
-
-int alsem_wait(alsem_t *sem)
-{
- if(sem_wait(sem) == 0) return althrd_success;
- if(errno == EINTR) return -2;
- return althrd_error;
-}
-
-int alsem_trywait(alsem_t *sem)
-{
- if(sem_trywait(sem) == 0) return althrd_success;
- if(errno == EWOULDBLOCK) return althrd_busy;
- if(errno == EINTR) return -2;
- return althrd_error;
-}
-
-#endif /* __APPLE__ */
-
-
-int altss_create(altss_t *tss_id, altss_dtor_t callback)
-{
- if(pthread_key_create(tss_id, callback) != 0)
- return althrd_error;
- return althrd_success;
-}
-
-void altss_delete(altss_t tss_id)
-{
- pthread_key_delete(tss_id);
-}
-
-
-int altimespec_get(struct timespec *ts, int base)
-{
- if(base == AL_TIME_UTC)
- {
- int ret;
-#if _POSIX_TIMERS > 0
- ret = clock_gettime(CLOCK_REALTIME, ts);
- if(ret == 0) return base;
-#else /* _POSIX_TIMERS > 0 */
- struct timeval tv;
- ret = gettimeofday(&tv, NULL);
- if(ret == 0)
- {
- ts->tv_sec = tv.tv_sec;
- ts->tv_nsec = tv.tv_usec * 1000;
- return base;
- }
-#endif
- }
-
- return 0;
-}
-
-#endif
-
-
-void al_nssleep(unsigned long nsec)
-{
- struct timespec ts, rem;
- ts.tv_sec = nsec / 1000000000ul;
- ts.tv_nsec = nsec % 1000000000ul;
-
- while(althrd_sleep(&ts, &rem) == -1)
- ts = rem;
-}
diff --git a/common/threads.cpp b/common/threads.cpp
new file mode 100644
index 00000000..ff01de42
--- /dev/null
+++ b/common/threads.cpp
@@ -0,0 +1,169 @@
+/**
+ * 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 "threads.h"
+
+#include <system_error>
+
+
+#ifdef _WIN32
+
+#include <limits>
+
+void althrd_setname(const char *name)
+{
+#if defined(_MSC_VER)
+#define MS_VC_EXCEPTION 0x406D1388
+#pragma pack(push,8)
+ struct {
+ DWORD dwType; // Must be 0x1000.
+ LPCSTR szName; // Pointer to name (in user addr space).
+ DWORD dwThreadID; // Thread ID (-1=caller thread).
+ DWORD dwFlags; // Reserved for future use, must be zero.
+ } info;
+#pragma pack(pop)
+ info.dwType = 0x1000;
+ info.szName = name;
+ info.dwThreadID = ~DWORD{0};
+ info.dwFlags = 0;
+
+ __try {
+ RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info);
+ }
+ __except(EXCEPTION_CONTINUE_EXECUTION) {
+ }
+#undef MS_VC_EXCEPTION
+#else
+ (void)name;
+#endif
+}
+
+namespace al {
+
+semaphore::semaphore(unsigned int initial)
+{
+ if(initial > static_cast<unsigned int>(std::numeric_limits<int>::max()))
+ throw std::system_error(std::make_error_code(std::errc::value_too_large));
+ mSem = CreateSemaphore(nullptr, initial, std::numeric_limits<int>::max(), nullptr);
+ if(mSem == nullptr)
+ throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again));
+}
+
+semaphore::~semaphore()
+{ CloseHandle(mSem); }
+
+void semaphore::post()
+{
+ if(!ReleaseSemaphore(mSem, 1, nullptr))
+ throw std::system_error(std::make_error_code(std::errc::value_too_large));
+}
+
+void semaphore::wait() noexcept
+{ WaitForSingleObject(mSem, INFINITE); }
+
+bool semaphore::try_wait() noexcept
+{ return WaitForSingleObject(mSem, 0) == WAIT_OBJECT_0; }
+
+} // namespace al
+
+#else
+
+#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP)
+#include <pthread.h>
+#ifdef HAVE_PTHREAD_NP_H
+#include <pthread_np.h>
+#endif
+
+void althrd_setname(const char *name)
+{
+#if defined(HAVE_PTHREAD_SET_NAME_NP)
+ pthread_set_name_np(pthread_self(), name);
+#elif defined(PTHREAD_SETNAME_NP_ONE_PARAM)
+ pthread_setname_np(name);
+#elif defined(PTHREAD_SETNAME_NP_THREE_PARAMS)
+ pthread_setname_np(pthread_self(), "%s", (void*)name);
+#else
+ pthread_setname_np(pthread_self(), name);
+#endif
+}
+
+#else
+
+void althrd_setname(const char*) { }
+#endif
+
+namespace al {
+
+#ifdef __APPLE__
+
+semaphore::semaphore(unsigned int initial)
+{
+ mSem = dispatch_semaphore_create(initial);
+ if(!mSem)
+ throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again));
+}
+
+semaphore::~semaphore()
+{ dispatch_release(mSem); }
+
+void semaphore::post()
+{ dispatch_semaphore_signal(mSem); }
+
+void semaphore::wait() noexcept
+{ dispatch_semaphore_wait(mSem, DISPATCH_TIME_FOREVER); }
+
+bool semaphore::try_wait() noexcept
+{ return dispatch_semaphore_wait(mSem, DISPATCH_TIME_NOW) == 0; }
+
+#else /* !__APPLE__ */
+
+#include <cerrno>
+
+semaphore::semaphore(unsigned int initial)
+{
+ if(sem_init(&mSem, 0, initial) != 0)
+ throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again));
+}
+
+semaphore::~semaphore()
+{ sem_destroy(&mSem); }
+
+void semaphore::post()
+{
+ if(sem_post(&mSem) != 0)
+ throw std::system_error(std::make_error_code(std::errc::value_too_large));
+}
+
+void semaphore::wait() noexcept
+{
+ while(sem_wait(&mSem) == -1 && errno == EINTR) {
+ }
+}
+
+bool semaphore::try_wait() noexcept
+{ return sem_trywait(&mSem) == 0; }
+
+#endif /* __APPLE__ */
+
+} // namespace al
+
+#endif /* _WIN32 */
diff --git a/common/threads.h b/common/threads.h
index 2d1b4e7f..ff571a66 100644
--- a/common/threads.h
+++ b/common/threads.h
@@ -1,8 +1,6 @@
#ifndef AL_THREADS_H
#define AL_THREADS_H
-#include <time.h>
-
#if defined(__GNUC__) && defined(__i386__)
/* force_align_arg_pointer is required for proper function arguments aligning
* when SSE code is used. Some systems (Windows, QNX) do not guarantee our
@@ -13,256 +11,41 @@
#define FORCE_ALIGN
#endif
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-enum {
- althrd_success = 0,
- althrd_error,
- althrd_nomem,
- althrd_timedout,
- althrd_busy
-};
-
-enum {
- almtx_plain = 0,
- almtx_recursive = 1,
-};
-
-typedef int (*althrd_start_t)(void*);
-typedef void (*altss_dtor_t)(void*);
-
-
-#define AL_TIME_UTC 1
-
-
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
-
-
-#ifndef HAVE_STRUCT_TIMESPEC
-struct timespec {
- time_t tv_sec;
- long tv_nsec;
-};
-#endif
-
-typedef DWORD althrd_t;
-typedef CRITICAL_SECTION almtx_t;
-#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0600
-typedef CONDITION_VARIABLE alcnd_t;
+#elif defined(__APPLE__)
+#include <dispatch/dispatch.h>
#else
-typedef struct { void *Ptr; } alcnd_t;
+#include <semaphore.h>
#endif
-typedef HANDLE alsem_t;
-typedef DWORD altss_t;
-typedef LONG alonce_flag;
-
-#define AL_ONCE_FLAG_INIT 0
-
-int althrd_sleep(const struct timespec *ts, struct timespec *rem);
-void alcall_once(alonce_flag *once, void (*callback)(void));
-
-void althrd_deinit(void);
-void althrd_thread_detach(void);
-
-
-inline althrd_t althrd_current(void)
-{
- return GetCurrentThreadId();
-}
-
-inline int althrd_equal(althrd_t thr0, althrd_t thr1)
-{
- return thr0 == thr1;
-}
-inline void althrd_exit(int res)
-{
- ExitThread(res);
-}
+void althrd_setname(const char *name);
-inline void althrd_yield(void)
-{
- SwitchToThread();
-}
-
-
-inline int almtx_lock(almtx_t *mtx)
-{
- if(!mtx) return althrd_error;
- EnterCriticalSection(mtx);
- return althrd_success;
-}
-
-inline int almtx_unlock(almtx_t *mtx)
-{
- if(!mtx) return althrd_error;
- LeaveCriticalSection(mtx);
- return althrd_success;
-}
-
-inline int almtx_trylock(almtx_t *mtx)
-{
- if(!mtx) return althrd_error;
- if(!TryEnterCriticalSection(mtx))
- return althrd_busy;
- return althrd_success;
-}
-
-
-inline void *altss_get(altss_t tss_id)
-{
- return TlsGetValue(tss_id);
-}
-
-inline int altss_set(altss_t tss_id, void *val)
-{
- if(TlsSetValue(tss_id, val) == 0)
- return althrd_error;
- return althrd_success;
-}
+namespace al {
+class semaphore {
+#ifdef _WIN32
+ using native_type = HANDLE;
+#elif defined(__APPLE__)
+ using native_type = dispatch_semaphore_t;
#else
-
-#include <stdint.h>
-#include <errno.h>
-#include <pthread.h>
-#ifdef __APPLE__
-#include <dispatch/dispatch.h>
-#else /* !__APPLE__ */
-#include <semaphore.h>
-#endif /* __APPLE__ */
-
-
-typedef pthread_t althrd_t;
-typedef pthread_mutex_t almtx_t;
-typedef pthread_cond_t alcnd_t;
-#ifdef __APPLE__
-typedef dispatch_semaphore_t alsem_t;
-#else /* !__APPLE__ */
-typedef sem_t alsem_t;
-#endif /* __APPLE__ */
-typedef pthread_key_t altss_t;
-typedef pthread_once_t alonce_flag;
-
-#define AL_ONCE_FLAG_INIT PTHREAD_ONCE_INIT
-
-
-inline althrd_t althrd_current(void)
-{
- return pthread_self();
-}
-
-inline int althrd_equal(althrd_t thr0, althrd_t thr1)
-{
- return pthread_equal(thr0, thr1);
-}
-
-inline void althrd_exit(int res)
-{
- pthread_exit((void*)(intptr_t)res);
-}
-
-inline void althrd_yield(void)
-{
- sched_yield();
-}
-
-inline int althrd_sleep(const struct timespec *ts, struct timespec *rem)
-{
- int ret = nanosleep(ts, rem);
- if(ret != 0)
- {
- ret = ((errno==EINTR) ? -1 : -2);
- errno = 0;
- }
- return ret;
-}
-
-
-inline int almtx_lock(almtx_t *mtx)
-{
- if(pthread_mutex_lock(mtx) != 0)
- return althrd_error;
- return althrd_success;
-}
-
-inline int almtx_unlock(almtx_t *mtx)
-{
- if(pthread_mutex_unlock(mtx) != 0)
- return althrd_error;
- return althrd_success;
-}
-
-inline int almtx_trylock(almtx_t *mtx)
-{
- int ret = pthread_mutex_trylock(mtx);
- switch(ret)
- {
- case 0: return althrd_success;
- case EBUSY: return althrd_busy;
- }
- return althrd_error;
-}
-
-
-inline void *altss_get(altss_t tss_id)
-{
- return pthread_getspecific(tss_id);
-}
-
-inline int altss_set(altss_t tss_id, void *val)
-{
- if(pthread_setspecific(tss_id, val) != 0)
- return althrd_error;
- return althrd_success;
-}
-
-
-inline void alcall_once(alonce_flag *once, void (*callback)(void))
-{
- pthread_once(once, callback);
-}
-
-
-inline void althrd_deinit(void) { }
-inline void althrd_thread_detach(void) { }
-
+ using native_type = sem_t;
#endif
+ native_type mSem;
+public:
+ semaphore(unsigned int initial=0);
+ semaphore(const semaphore&) = delete;
+ ~semaphore();
-int althrd_create(althrd_t *thr, althrd_start_t func, void *arg);
-int althrd_detach(althrd_t thr);
-int althrd_join(althrd_t thr, int *res);
-void althrd_setname(althrd_t thr, const char *name);
+ semaphore& operator=(const semaphore&) = delete;
-int almtx_init(almtx_t *mtx, int type);
-void almtx_destroy(almtx_t *mtx);
-
-int alcnd_init(alcnd_t *cond);
-int alcnd_signal(alcnd_t *cond);
-int alcnd_broadcast(alcnd_t *cond);
-int alcnd_wait(alcnd_t *cond, almtx_t *mtx);
-void alcnd_destroy(alcnd_t *cond);
-
-int alsem_init(alsem_t *sem, unsigned int initial);
-void alsem_destroy(alsem_t *sem);
-int alsem_post(alsem_t *sem);
-int alsem_wait(alsem_t *sem);
-int alsem_trywait(alsem_t *sem);
-
-int altss_create(altss_t *tss_id, altss_dtor_t callback);
-void altss_delete(altss_t tss_id);
-
-int altimespec_get(struct timespec *ts, int base);
-
-void al_nssleep(unsigned long nsec);
+ void post();
+ void wait() noexcept;
+ bool try_wait() noexcept;
+};
-#ifdef __cplusplus
-}
-#endif
+} // namespace al
#endif /* AL_THREADS_H */
diff --git a/common/uintmap.c b/common/uintmap.c
deleted file mode 100644
index 18d52d64..00000000
--- a/common/uintmap.c
+++ /dev/null
@@ -1,182 +0,0 @@
-
-#include "config.h"
-
-#include "uintmap.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-#include "almalloc.h"
-
-
-extern inline void LockUIntMapRead(UIntMap *map);
-extern inline void UnlockUIntMapRead(UIntMap *map);
-extern inline void LockUIntMapWrite(UIntMap *map);
-extern inline void UnlockUIntMapWrite(UIntMap *map);
-
-
-void InitUIntMap(UIntMap *map, ALsizei limit)
-{
- map->keys = NULL;
- map->values = NULL;
- map->size = 0;
- map->capacity = 0;
- map->limit = limit;
- RWLockInit(&map->lock);
-}
-
-void ResetUIntMap(UIntMap *map)
-{
- WriteLock(&map->lock);
- al_free(map->keys);
- map->keys = NULL;
- map->values = NULL;
- map->size = 0;
- map->capacity = 0;
- WriteUnlock(&map->lock);
-}
-
-ALenum InsertUIntMapEntry(UIntMap *map, ALuint key, ALvoid *value)
-{
- ALsizei pos = 0;
-
- WriteLock(&map->lock);
- if(map->size > 0)
- {
- ALsizei count = map->size;
- do {
- ALsizei step = count>>1;
- ALsizei i = pos+step;
- if(!(map->keys[i] < key))
- count = step;
- else
- {
- pos = i+1;
- count -= step+1;
- }
- } while(count > 0);
- }
-
- if(pos == map->size || map->keys[pos] != key)
- {
- if(map->size >= map->limit)
- {
- WriteUnlock(&map->lock);
- return AL_OUT_OF_MEMORY;
- }
-
- if(map->size == map->capacity)
- {
- ALuint *keys = NULL;
- ALvoid **values;
- ALsizei newcap, keylen;
-
- newcap = (map->capacity ? (map->capacity<<1) : 4);
- if(map->limit > 0 && newcap > map->limit)
- newcap = map->limit;
- if(newcap > map->capacity)
- {
- /* Round the memory size for keys up to a multiple of the
- * pointer size.
- */
- keylen = newcap * sizeof(map->keys[0]);
- keylen += sizeof(map->values[0]) - 1;
- keylen -= keylen%sizeof(map->values[0]);
-
- keys = al_malloc(16, keylen + newcap*sizeof(map->values[0]));
- }
- if(!keys)
- {
- WriteUnlock(&map->lock);
- return AL_OUT_OF_MEMORY;
- }
- values = (ALvoid**)((ALbyte*)keys + keylen);
-
- if(map->keys)
- {
- memcpy(keys, map->keys, map->size*sizeof(map->keys[0]));
- memcpy(values, map->values, map->size*sizeof(map->values[0]));
- }
- al_free(map->keys);
- map->keys = keys;
- map->values = values;
- map->capacity = newcap;
- }
-
- if(pos < map->size)
- {
- memmove(&map->keys[pos+1], &map->keys[pos],
- (map->size-pos)*sizeof(map->keys[0]));
- memmove(&map->values[pos+1], &map->values[pos],
- (map->size-pos)*sizeof(map->values[0]));
- }
- map->size++;
- }
- map->keys[pos] = key;
- map->values[pos] = value;
- WriteUnlock(&map->lock);
-
- return AL_NO_ERROR;
-}
-
-ALvoid *RemoveUIntMapKey(UIntMap *map, ALuint key)
-{
- ALvoid *ptr = NULL;
- WriteLock(&map->lock);
- if(map->size > 0)
- {
- ALsizei pos = 0;
- ALsizei count = map->size;
- do {
- ALsizei step = count>>1;
- ALsizei i = pos+step;
- if(!(map->keys[i] < key))
- count = step;
- else
- {
- pos = i+1;
- count -= step+1;
- }
- } while(count > 0);
- if(pos < map->size && map->keys[pos] == key)
- {
- ptr = map->values[pos];
- if(pos < map->size-1)
- {
- memmove(&map->keys[pos], &map->keys[pos+1],
- (map->size-1-pos)*sizeof(map->keys[0]));
- memmove(&map->values[pos], &map->values[pos+1],
- (map->size-1-pos)*sizeof(map->values[0]));
- }
- map->size--;
- }
- }
- WriteUnlock(&map->lock);
- return ptr;
-}
-
-ALvoid *LookupUIntMapKey(UIntMap *map, ALuint key)
-{
- ALvoid *ptr = NULL;
- ReadLock(&map->lock);
- if(map->size > 0)
- {
- ALsizei pos = 0;
- ALsizei count = map->size;
- do {
- ALsizei step = count>>1;
- ALsizei i = pos+step;
- if(!(map->keys[i] < key))
- count = step;
- else
- {
- pos = i+1;
- count -= step+1;
- }
- } while(count > 0);
- if(pos < map->size && map->keys[pos] == key)
- ptr = map->values[pos];
- }
- ReadUnlock(&map->lock);
- return ptr;
-}
diff --git a/common/uintmap.h b/common/uintmap.h
deleted file mode 100644
index 32868653..00000000
--- a/common/uintmap.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef AL_UINTMAP_H
-#define AL_UINTMAP_H
-
-#include <limits.h>
-
-#include "AL/al.h"
-#include "rwlock.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct UIntMap {
- ALuint *keys;
- /* Shares memory with keys. */
- ALvoid **values;
-
- ALsizei size;
- ALsizei capacity;
- ALsizei limit;
- RWLock lock;
-} UIntMap;
-#define UINTMAP_STATIC_INITIALIZE_N(_n) { NULL, NULL, 0, 0, (_n), RWLOCK_STATIC_INITIALIZE }
-#define UINTMAP_STATIC_INITIALIZE UINTMAP_STATIC_INITIALIZE_N(INT_MAX)
-
-void InitUIntMap(UIntMap *map, ALsizei limit);
-void ResetUIntMap(UIntMap *map);
-ALenum InsertUIntMapEntry(UIntMap *map, ALuint key, ALvoid *value);
-ALvoid *RemoveUIntMapKey(UIntMap *map, ALuint key);
-ALvoid *LookupUIntMapKey(UIntMap *map, ALuint key);
-
-inline void LockUIntMapRead(UIntMap *map) { ReadLock(&map->lock); }
-inline void UnlockUIntMapRead(UIntMap *map) { ReadUnlock(&map->lock); }
-inline void LockUIntMapWrite(UIntMap *map) { WriteLock(&map->lock); }
-inline void UnlockUIntMapWrite(UIntMap *map) { WriteUnlock(&map->lock); }
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* AL_UINTMAP_H */
diff --git a/common/vecmat.h b/common/vecmat.h
new file mode 100644
index 00000000..7186ec6c
--- /dev/null
+++ b/common/vecmat.h
@@ -0,0 +1,86 @@
+#ifndef COMMON_VECMAT_H
+#define COMMON_VECMAT_H
+
+#include <array>
+#include <cmath>
+#include <cstddef>
+#include <limits>
+
+
+namespace alu {
+
+class Vector {
+ alignas(16) std::array<float,4> mVals;
+
+public:
+ Vector() noexcept = default;
+ constexpr Vector(float a, float b, float c, float d) noexcept
+ : mVals{{a, b, c, d}}
+ { }
+
+ float& operator[](size_t idx) noexcept { return mVals[idx]; }
+ constexpr const float& operator[](size_t idx) const noexcept { return mVals[idx]; }
+
+ Vector& operator+=(const Vector &rhs) noexcept
+ {
+ mVals[0] += rhs.mVals[0];
+ mVals[1] += rhs.mVals[1];
+ mVals[2] += rhs.mVals[2];
+ mVals[3] += rhs.mVals[3];
+ return *this;
+ }
+
+ float normalize()
+ {
+ const float length{std::sqrt(mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2])};
+ if(length > std::numeric_limits<float>::epsilon())
+ {
+ float inv_length = 1.0f/length;
+ mVals[0] *= inv_length;
+ mVals[1] *= inv_length;
+ mVals[2] *= inv_length;
+ return length;
+ }
+ mVals[0] = mVals[1] = mVals[2] = 0.0f;
+ return 0.0f;
+ }
+};
+
+class Matrix {
+ alignas(16) std::array<std::array<float,4>,4> mVals;
+
+public:
+ Matrix() noexcept = default;
+ constexpr Matrix(float aa, float ab, float ac, float ad,
+ float ba, float bb, float bc, float bd,
+ float ca, float cb, float cc, float cd,
+ float da, float db, float dc, float dd) noexcept
+ : mVals{{{{aa, ab, ac, ad}}, {{ba, bb, bc, bd}}, {{ca, cb, cc, cd}}, {{da, db, dc, dd}}}}
+ { }
+
+ std::array<float,4>& operator[](size_t idx) noexcept { return mVals[idx]; }
+ constexpr const std::array<float,4>& operator[](size_t idx) const noexcept { return mVals[idx]; }
+
+ void setRow(size_t idx, float a, float b, float c, float d) noexcept
+ {
+ mVals[idx][0] = a;
+ mVals[idx][1] = b;
+ mVals[idx][2] = c;
+ mVals[idx][3] = d;
+ }
+
+ static const Matrix &Identity() noexcept
+ {
+ static constexpr Matrix identity{
+ 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
+ };
+ return identity;
+ }
+};
+
+} // namespace alu
+
+#endif /* COMMON_VECMAT_H */
diff --git a/common/vector.h b/common/vector.h
new file mode 100644
index 00000000..1b69d6a7
--- /dev/null
+++ b/common/vector.h
@@ -0,0 +1,15 @@
+#ifndef AL_VECTOR_H
+#define AL_VECTOR_H
+
+#include <vector>
+
+#include "almalloc.h"
+
+namespace al {
+
+template<typename T, size_t alignment=alignof(T)>
+using vector = std::vector<T, al::allocator<T, alignment>>;
+
+} // namespace al
+
+#endif /* AL_VECTOR_H */
diff --git a/common/win_main_utf8.h b/common/win_main_utf8.h
index faddc257..242d3b8a 100644
--- a/common/win_main_utf8.h
+++ b/common/win_main_utf8.h
@@ -30,7 +30,7 @@ static FILE *my_fopen(const char *fname, const char *mode)
return NULL;
}
- wname = calloc(sizeof(WCHAR), namelen+modelen);
+ wname = (WCHAR*)calloc(sizeof(WCHAR), namelen+modelen);
wmode = wname + namelen;
MultiByteToWideChar(CP_UTF8, 0, fname, -1, wname, namelen);
MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, modelen);
@@ -73,7 +73,7 @@ static void GetUnicodeArgs(int *argc, char ***argv)
total += WideCharToMultiByte(CP_UTF8, 0, args[i], -1, NULL, 0, NULL, NULL);
atexit(cleanup_arglist);
- arglist = *argv = calloc(1, total);
+ arglist = *argv = (char**)calloc(1, total);
(*argv)[0] = (char*)(*argv + nargs);
for(i = 0;i < nargs-1;i++)
{
diff --git a/config.h.in b/config.h.in
index 9cc6c16b..4a1e2b00 100644
--- a/config.h.in
+++ b/config.h.in
@@ -2,21 +2,12 @@
#define AL_API ${EXPORT_DECL}
#define ALC_API ${EXPORT_DECL}
-/* Define any available alignment declaration */
-#define ALIGN(x) ${ALIGN_DECL}
-
-/* Define a built-in call indicating an aligned data pointer */
-#define ASSUME_ALIGNED(x, y) ${ASSUME_ALIGNED_DECL}
+/* Define a restrict macro for non-aliased pointers */
+#define RESTRICT ${RESTRICT_DECL}
/* Define if HRTF data is embedded in the library */
#cmakedefine ALSOFT_EMBED_HRTF_DATA
-/* Define if we have the sysconf function */
-#cmakedefine HAVE_SYSCONF
-
-/* Define if we have the C11 aligned_alloc function */
-#cmakedefine HAVE_ALIGNED_ALLOC
-
/* Define if we have the posix_memalign function */
#cmakedefine HAVE_POSIX_MEMALIGN
@@ -86,66 +77,12 @@
/* Define if we have the stat function */
#cmakedefine HAVE_STAT
-/* Define if we have the lrintf function */
-#cmakedefine HAVE_LRINTF
-
-/* Define if we have the modff function */
-#cmakedefine HAVE_MODFF
-
-/* Define if we have the log2f function */
-#cmakedefine HAVE_LOG2F
-
-/* Define if we have the cbrtf function */
-#cmakedefine HAVE_CBRTF
-
-/* Define if we have the copysignf function */
-#cmakedefine HAVE_COPYSIGNF
-
-/* Define if we have the strtof function */
-#cmakedefine HAVE_STRTOF
-
-/* Define if we have the strnlen function */
-#cmakedefine HAVE_STRNLEN
-
-/* Define if we have the __int64 type */
-#cmakedefine HAVE___INT64
-
/* Define to the size of a long int type */
#cmakedefine SIZEOF_LONG ${SIZEOF_LONG}
-/* Define to the size of a long long int type */
-#cmakedefine SIZEOF_LONG_LONG ${SIZEOF_LONG_LONG}
-
-/* Define if we have C99 _Bool support */
-#cmakedefine HAVE_C99_BOOL
-
-/* Define if we have C11 _Static_assert support */
-#cmakedefine HAVE_C11_STATIC_ASSERT
-
-/* Define if we have C11 _Alignas support */
-#cmakedefine HAVE_C11_ALIGNAS
-
-/* Define if we have C11 _Atomic support */
-#cmakedefine HAVE_C11_ATOMIC
-
-/* Define if we have GCC's destructor attribute */
-#cmakedefine HAVE_GCC_DESTRUCTOR
-
/* Define if we have GCC's format attribute */
#cmakedefine HAVE_GCC_FORMAT
-/* Define if we have stdint.h */
-#cmakedefine HAVE_STDINT_H
-
-/* Define if we have stdbool.h */
-#cmakedefine HAVE_STDBOOL_H
-
-/* Define if we have stdalign.h */
-#cmakedefine HAVE_STDALIGN_H
-
-/* Define if we have windows.h */
-#cmakedefine HAVE_WINDOWS_H
-
/* Define if we have dlfcn.h */
#cmakedefine HAVE_DLFCN_H
@@ -158,9 +95,6 @@
/* Define if we have dirent.h */
#cmakedefine HAVE_DIRENT_H
-/* Define if we have strings.h */
-#cmakedefine HAVE_STRINGS_H
-
/* Define if we have cpuid.h */
#cmakedefine HAVE_CPUID_H
@@ -176,15 +110,6 @@
/* Define if we have initguid.h */
#cmakedefine HAVE_INITGUID_H
-/* Define if we have ieeefp.h */
-#cmakedefine HAVE_IEEEFP_H
-
-/* Define if we have float.h */
-#cmakedefine HAVE_FLOAT_H
-
-/* Define if we have fenv.h */
-#cmakedefine HAVE_FENV_H
-
/* Define if we have GCC's __get_cpuid() */
#cmakedefine HAVE_GCC_GET_CPUID
@@ -197,11 +122,8 @@
/* Define if we have the _BitScanForward() intrinsic */
#cmakedefine HAVE_BITSCANFORWARD_INTRINSIC
-/* Define if we have _controlfp() */
-#cmakedefine HAVE__CONTROLFP
-
-/* Define if we have __control87_2() */
-#cmakedefine HAVE___CONTROL87_2
+/* Define if we have SSE intrinsics */
+#cmakedefine HAVE_SSE_INTRINSICS
/* Define if we have pthread_setschedparam() */
#cmakedefine HAVE_PTHREAD_SETSCHEDPARAM
@@ -217,9 +139,3 @@
/* Define if we have pthread_set_name_np() */
#cmakedefine HAVE_PTHREAD_SET_NAME_NP
-
-/* Define if we have pthread_mutexattr_setkind_np() */
-#cmakedefine HAVE_PTHREAD_MUTEXATTR_SETKIND_NP
-
-/* Define if we have pthread_mutex_timedlock() */
-#cmakedefine HAVE_PTHREAD_MUTEX_TIMEDLOCK
diff --git a/docs/ambisonics.txt b/docs/ambisonics.txt
index 2d94427e..b03e3bed 100644
--- a/docs/ambisonics.txt
+++ b/docs/ambisonics.txt
@@ -79,30 +79,23 @@ Soft (or any other OpenAL implementation that wishes to) can render using
Ambisonics and decode the ambisonic mix for a high level of accuracy over what
simple pan-pot could provide.
-This is effectively what the high-quality mode option does, when given an
-appropriate decoder configuation for the playback channel layout. 3D rendering
-and effect mixing is done to an ambisonic buffer, which is later decoded for
-output utilizing the benefits available to ambisonic processing.
-
-The basic, non-high-quality, renderer uses similar principles, however it skips
-the frequency-dependent processing (so low frequency sounds are treated the
-same as high frequency sounds) and does some creative manipulation of the
-involved math to skip the intermediate ambisonic buffer, rendering more
-directly to the output while still taking advantage of all the available
-speakers to reconstruct the sound wave. This method trades away some playback
-quality for less memory and processor usage.
-
-In addition to providing good support for surround sound playback, Ambisonics
-also has benefits with stereo output. 2-channel UHJ is a stereo-compatible
-format that encodes some surround sound information using a wide-band 90-degree
-phase shift filter. It works by taking a B-Format signal, and deriving a
-frontal stereo mix with the rear sounds attenuated and filtered in with it.
-Although the result is not as good as 3-channel (2D) B-Format, it has the
-distinct advantage of only using 2 channels and being compatible with stereo
-output. This means it will sound just fine when played as-is through a normal
-stereo device, or it may optionally be fed to a properly configured surround
-sound receiver which can extract the encoded information and restore some of
-the original surround sound signal.
+When given an appropriate decoder configuration for the channel layout, the
+ambisonic mix can be decoded utilizing the benefits available to ambisonic
+processing, including frequency-dependent processing and near-field effects.
+Without a decoder configuration, the ambisonic mix can still be decoded for
+good stereo or surround sound output, although without near-field effects as
+there's no speaker distance information.
+
+In addition to surround sound output, Ambisonics also has benefits with stereo
+output. 2-channel UHJ is a stereo-compatible format that encodes some surround
+sound information using a wide-band 90-degree phase shift filter. This is
+generated by taking the ambisonic mix and deriving a front-stereo mix with
+with the rear sounds filtered in with it. Although the result is not as good as
+3-channel (2D) B-Format, it has the distinct advantage of only using 2 channels
+and being compatible with stereo output. This means it will sound just fine
+when played as-is through a normal stereo device, or it may optionally be fed
+to a properly configured surround sound receiver which can extract the encoded
+information and restore some of the original surround sound signal.
What Are Its Limitations?
diff --git a/docs/env-vars.txt b/docs/env-vars.txt
index a973ee81..fee9ffb0 100644
--- a/docs/env-vars.txt
+++ b/docs/env-vars.txt
@@ -11,9 +11,6 @@ Specifies the amount of logging OpenAL Soft will write out:
1 - Prints out errors only
2 - Prints out warnings and errors
3 - Prints out additional information, as well as warnings and errors
-4 - Same as 3, but also device and context reference count changes. This will
- print out *a lot* of info, and is generally not useful unless you're trying
- to track a reference leak within the library.
ALSOFT_LOGFILE
Specifies a filename that logged output will be written to. Note that the file
@@ -79,13 +76,3 @@ which some applications make use of to protect against partial updates. In an
attempt to standardize on that behavior, OpenAL Soft has changed those methods
accordingly. Setting this to "ignore" restores the previous no-op behavior for
applications that interact poorly with the new behavior.
-
-__ALSOFT_REVERB_IGNORES_SOUND_SPEED
-Older versions of OpenAL Soft ignored the app-specified speed of sound when
-calculating distance-related reverb decays and always assumed the default
-343.3m/s. Now, both of the AL_SPEED_OF_SOUND and AL_METERS_PER_UNIT properties
-are taken into account for speed of sound adjustments to have an appropriate
-affect on the reverb decay. Consequently, applications that use reverb but
-don't set these properties properly may find the reverb decay too strong.
-Setting this to "true" or "1" will revert to the old behavior for those apps
-and assume the default speed of sound for reverb.
diff --git a/examples/alffplay.cpp b/examples/alffplay.cpp
index 27520a6d..655ffc96 100644
--- a/examples/alffplay.cpp
+++ b/examples/alffplay.cpp
@@ -8,29 +8,44 @@
#include <functional>
#include <algorithm>
#include <iostream>
+#include <utility>
#include <iomanip>
+#include <cstdint>
#include <cstring>
-#include <limits>
-#include <thread>
-#include <chrono>
+#include <cstdlib>
#include <atomic>
+#include <cerrno>
+#include <chrono>
+#include <cstdio>
+#include <memory>
+#include <string>
+#include <thread>
#include <vector>
-#include <mutex>
-#include <deque>
#include <array>
#include <cmath>
-#include <string>
+#include <deque>
+#include <mutex>
+#include <ratio>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
-#include "libavutil/time.h"
+#include "libavformat/version.h"
+#include "libavutil/avutil.h"
+#include "libavutil/error.h"
+#include "libavutil/frame.h"
+#include "libavutil/mem.h"
#include "libavutil/pixfmt.h"
-#include "libavutil/avstring.h"
+#include "libavutil/rational.h"
+#include "libavutil/samplefmt.h"
+#include "libavutil/time.h"
+#include "libavutil/version.h"
#include "libavutil/channel_layout.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
+
+struct SwsContext;
}
#include "SDL.h"
@@ -85,20 +100,24 @@ typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values);
namespace {
+inline constexpr int64_t operator "" _i64(unsigned long long int n) noexcept { return static_cast<int64_t>(n); }
+
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif
+using fixed32 = std::chrono::duration<int64_t,std::ratio<1,(1_i64<<32)>>;
using nanoseconds = std::chrono::nanoseconds;
using microseconds = std::chrono::microseconds;
using milliseconds = std::chrono::milliseconds;
using seconds = std::chrono::seconds;
using seconds_d64 = std::chrono::duration<double>;
-const std::string AppName("alffplay");
+const std::string AppName{"alffplay"};
-bool EnableDirectOut = false;
-bool EnableWideStereo = false;
+bool EnableDirectOut{false};
+bool EnableWideStereo{false};
+bool DisableVideo{false};
LPALGETSOURCEI64VSOFT alGetSourcei64vSOFT;
LPALCGETINTEGER64VSOFT alcGetInteger64vSOFT;
@@ -113,27 +132,23 @@ LPALEVENTCONTROLSOFT alEventControlSOFT;
LPALEVENTCALLBACKSOFT alEventCallbackSOFT;
#endif
-const seconds AVNoSyncThreshold(10);
+const seconds AVNoSyncThreshold{10};
-const milliseconds VideoSyncThreshold(10);
-#define VIDEO_PICTURE_QUEUE_SIZE 16
+const milliseconds VideoSyncThreshold{10};
+#define VIDEO_PICTURE_QUEUE_SIZE 24
-const seconds_d64 AudioSyncThreshold(0.03);
-const milliseconds AudioSampleCorrectionMax(50);
+const seconds_d64 AudioSyncThreshold{0.03};
+const milliseconds AudioSampleCorrectionMax{50};
/* Averaging filter coefficient for audio sync. */
#define AUDIO_DIFF_AVG_NB 20
-const double AudioAvgFilterCoeff = std::pow(0.01, 1.0/AUDIO_DIFF_AVG_NB);
+const double AudioAvgFilterCoeff{std::pow(0.01, 1.0/AUDIO_DIFF_AVG_NB)};
/* Per-buffer size, in time */
-const milliseconds AudioBufferTime(20);
+const milliseconds AudioBufferTime{20};
/* Buffer total size, in time (should be divisible by the buffer time) */
-const milliseconds AudioBufferTotalTime(800);
-
-#define MAX_QUEUE_SIZE (15 * 1024 * 1024) /* Bytes of compressed data to keep queued */
+const milliseconds AudioBufferTotalTime{800};
enum {
- FF_UPDATE_EVENT = SDL_USEREVENT,
- FF_REFRESH_EVENT,
- FF_MOVIE_DONE_EVENT
+ FF_MOVIE_DONE_EVENT = SDL_USEREVENT
};
enum class SyncMaster {
@@ -146,7 +161,7 @@ enum class SyncMaster {
inline microseconds get_avtime()
-{ return microseconds(av_gettime()); }
+{ return microseconds{av_gettime()}; }
/* Define unique_ptrs to auto-cleanup associated ffmpeg objects. */
struct AVIOContextDeleter {
@@ -180,43 +195,83 @@ struct SwsContextDeleter {
using SwsContextPtr = std::unique_ptr<SwsContext,SwsContextDeleter>;
+template<size_t SizeLimit>
class PacketQueue {
+ std::mutex mMutex;
+ std::condition_variable mCondVar;
std::deque<AVPacket> mPackets;
size_t mTotalSize{0};
+ bool mFinished{false};
-public:
- ~PacketQueue() { clear(); }
-
- bool empty() const noexcept { return mPackets.empty(); }
- size_t totalSize() const noexcept { return mTotalSize; }
-
- void put(const AVPacket *pkt)
+ AVPacket *getPacket(std::unique_lock<std::mutex> &lock)
{
- mPackets.push_back(AVPacket{});
- if(av_packet_ref(&mPackets.back(), pkt) != 0)
- mPackets.pop_back();
- else
- mTotalSize += mPackets.back().size;
+ while(mPackets.empty() && !mFinished)
+ mCondVar.wait(lock);
+ return mPackets.empty() ? nullptr : &mPackets.front();
}
- AVPacket *front() noexcept
- { return &mPackets.front(); }
-
void pop()
{
AVPacket *pkt = &mPackets.front();
- mTotalSize -= pkt->size;
+ mTotalSize -= static_cast<unsigned int>(pkt->size);
av_packet_unref(pkt);
mPackets.pop_front();
}
- void clear()
+public:
+ ~PacketQueue()
{
for(AVPacket &pkt : mPackets)
av_packet_unref(&pkt);
mPackets.clear();
mTotalSize = 0;
}
+
+ int sendTo(AVCodecContext *codecctx)
+ {
+ std::unique_lock<std::mutex> lock{mMutex};
+
+ AVPacket *pkt{getPacket(lock)};
+ if(!pkt) return avcodec_send_packet(codecctx, nullptr);
+
+ const int ret{avcodec_send_packet(codecctx, pkt)};
+ if(ret != AVERROR(EAGAIN))
+ {
+ if(ret < 0)
+ std::cerr<< "Failed to send packet: "<<ret <<std::endl;
+ pop();
+ }
+ return ret;
+ }
+
+ void setFinished()
+ {
+ {
+ std::lock_guard<std::mutex> _{mMutex};
+ mFinished = true;
+ }
+ mCondVar.notify_one();
+ }
+
+ bool put(const AVPacket *pkt)
+ {
+ {
+ std::unique_lock<std::mutex> lock{mMutex};
+ if(mTotalSize >= SizeLimit)
+ return false;
+
+ mPackets.push_back(AVPacket{});
+ if(av_packet_ref(&mPackets.back(), pkt) != 0)
+ {
+ mPackets.pop_back();
+ return true;
+ }
+
+ mTotalSize += static_cast<unsigned int>(mPackets.back().size);
+ }
+ mCondVar.notify_one();
+ return true;
+ }
};
@@ -228,8 +283,7 @@ struct AudioState {
AVStream *mStream{nullptr};
AVCodecCtxPtr mCodecCtx;
- std::mutex mQueueMtx;
- std::condition_variable mQueueCond;
+ PacketQueue<2*1024*1024> mPackets;
/* Used for clock difference average computation */
seconds_d64 mClockDiffAvg{0};
@@ -245,7 +299,7 @@ struct AudioState {
SwrContextPtr mSwresCtx;
/* Conversion format, for what gets fed to OpenAL */
- int mDstChanLayout{0};
+ uint64_t mDstChanLayout{0};
AVSampleFormat mDstSampleFmt{AV_SAMPLE_FMT_NONE};
/* Storage of converted samples */
@@ -256,14 +310,14 @@ struct AudioState {
/* OpenAL format */
ALenum mFormat{AL_NONE};
- ALsizei mFrameSize{0};
+ ALuint mFrameSize{0};
std::mutex mSrcMutex;
std::condition_variable mSrcCond;
std::atomic_flag mConnected;
ALuint mSource{0};
std::vector<ALuint> mBuffers;
- ALsizei mBufferIdx{0};
+ ALuint mBufferIdx{0};
AudioState(MovieState &movie) : mMovie(movie)
{ mConnected.test_and_set(std::memory_order_relaxed); }
@@ -272,7 +326,7 @@ struct AudioState {
if(mSource)
alDeleteSources(1, &mSource);
if(!mBuffers.empty())
- alDeleteBuffers(mBuffers.size(), mBuffers.data());
+ alDeleteBuffers(static_cast<ALsizei>(mBuffers.size()), mBuffers.data());
av_freep(&mSamples);
}
@@ -286,16 +340,15 @@ struct AudioState {
nanoseconds getClockNoLock();
nanoseconds getClock()
{
- std::lock_guard<std::mutex> lock(mSrcMutex);
+ std::lock_guard<std::mutex> lock{mSrcMutex};
return getClockNoLock();
}
- bool isBufferFilled();
void startPlayback();
int getSync();
int decodeFrame();
- bool readAudio(uint8_t *samples, int length);
+ bool readAudio(uint8_t *samples, unsigned int length);
int handler();
};
@@ -306,53 +359,46 @@ struct VideoState {
AVStream *mStream{nullptr};
AVCodecCtxPtr mCodecCtx;
- std::mutex mQueueMtx;
- std::condition_variable mQueueCond;
+ PacketQueue<14*1024*1024> mPackets;
- nanoseconds mClock{0};
- nanoseconds mFrameTimer{0};
- nanoseconds mFrameLastPts{0};
- nanoseconds mFrameLastDelay{0};
- nanoseconds mCurrentPts{0};
- /* time (av_gettime) at which we updated mCurrentPts - used to have running video pts */
- microseconds mCurrentPtsTime{0};
+ /* The pts of the currently displayed frame, and the time (av_gettime) it
+ * was last updated - used to have running video pts
+ */
+ nanoseconds mDisplayPts{0};
+ microseconds mDisplayPtsTime{microseconds::min()};
+ std::mutex mDispPtsMutex;
- /* Decompressed video frame, and swscale context for conversion */
- AVFramePtr mDecodedFrame;
+ /* Swscale context for format conversion */
SwsContextPtr mSwscaleCtx;
struct Picture {
- SDL_Texture *mImage{nullptr};
- int mWidth{0}, mHeight{0}; /* Logical image size (actual size may be larger) */
- std::atomic<bool> mUpdated{false};
- nanoseconds mPts{0};
-
- ~Picture()
- {
- if(mImage)
- SDL_DestroyTexture(mImage);
- mImage = nullptr;
- }
+ AVFramePtr mFrame{};
+ nanoseconds mPts{nanoseconds::min()};
};
std::array<Picture,VIDEO_PICTURE_QUEUE_SIZE> mPictQ;
- size_t mPictQSize{0}, mPictQRead{0}, mPictQWrite{0};
+ std::atomic<size_t> mPictQRead{0u}, mPictQWrite{1u};
std::mutex mPictQMutex;
std::condition_variable mPictQCond;
+
+ SDL_Texture *mImage{nullptr};
+ int mWidth{0}, mHeight{0}; /* Logical image size (actual size may be larger) */
bool mFirstUpdate{true};
+
std::atomic<bool> mEOS{false};
std::atomic<bool> mFinalUpdate{false};
VideoState(MovieState &movie) : mMovie(movie) { }
+ ~VideoState()
+ {
+ if(mImage)
+ SDL_DestroyTexture(mImage);
+ mImage = nullptr;
+ }
nanoseconds getClock();
- bool isBufferFilled();
- static Uint32 SDLCALL sdl_refresh_timer_cb(Uint32 interval, void *opaque);
- void schedRefresh(milliseconds delay);
void display(SDL_Window *screen, SDL_Renderer *renderer);
- void refreshTimer(SDL_Window *screen, SDL_Renderer *renderer);
- void updatePicture(SDL_Window *screen, SDL_Renderer *renderer);
- int queuePicture(nanoseconds pts);
+ void updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw);
int handler();
};
@@ -362,13 +408,7 @@ struct MovieState {
SyncMaster mAVSyncType{SyncMaster::Default};
- microseconds mClockBase{0};
- std::atomic<bool> mPlaying{false};
-
- std::mutex mSendMtx;
- std::condition_variable mSendCond;
- /* NOTE: false/clear = need data, true/set = no data needed */
- std::atomic_flag mSendDataGood;
+ microseconds mClockBase{microseconds::min()};
std::atomic<bool> mQuit{false};
@@ -401,7 +441,7 @@ struct MovieState {
nanoseconds getDuration();
- int streamComponentOpen(int stream_index);
+ int streamComponentOpen(unsigned int stream_index);
int parse_handler();
};
@@ -417,10 +457,10 @@ nanoseconds AudioState::getClockNoLock()
// Get the current device clock time and latency.
auto device = alcGetContextsDevice(alcGetCurrentContext());
- ALCint64SOFT devtimes[2] = {0,0};
+ ALCint64SOFT devtimes[2]{0,0};
alcGetInteger64vSOFT(device, ALC_DEVICE_CLOCK_LATENCY_SOFT, 2, devtimes);
- auto latency = nanoseconds(devtimes[1]);
- auto device_time = nanoseconds(devtimes[0]);
+ auto latency = nanoseconds{devtimes[1]};
+ auto device_time = nanoseconds{devtimes[0]};
// The clock is simply the current device time relative to the recorded
// start time. We can also subtract the latency to get more a accurate
@@ -443,12 +483,10 @@ nanoseconds AudioState::getClockNoLock()
* sample at OpenAL's current position, and subtracting the source latency
* from that gives the timestamp of the sample currently at the DAC.
*/
- nanoseconds pts = mCurrentPts;
+ nanoseconds pts{mCurrentPts};
if(mSource)
{
ALint64SOFT offset[2];
- ALint queued;
- ALint status;
/* NOTE: The source state must be checked last, in case an underrun
* occurs and the source stops between retrieving the offset+latency
@@ -459,9 +497,10 @@ nanoseconds AudioState::getClockNoLock()
{
ALint ioffset;
alGetSourcei(mSource, AL_SAMPLE_OFFSET, &ioffset);
- offset[0] = (ALint64SOFT)ioffset << 32;
+ offset[0] = ALint64SOFT{ioffset} << 32;
offset[1] = 0;
}
+ ALint queued, status;
alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued);
alGetSourcei(mSource, AL_SOURCE_STATE, &status);
@@ -471,46 +510,31 @@ nanoseconds AudioState::getClockNoLock()
* when it starts recovery. */
if(status != AL_STOPPED)
{
- using fixed32 = std::chrono::duration<int64_t,std::ratio<1,(1ll<<32)>>;
-
pts -= AudioBufferTime*queued;
pts += std::chrono::duration_cast<nanoseconds>(
- fixed32(offset[0] / mCodecCtx->sample_rate)
- );
+ fixed32{offset[0] / mCodecCtx->sample_rate});
}
/* Don't offset by the latency if the source isn't playing. */
if(status == AL_PLAYING)
- pts -= nanoseconds(offset[1]);
+ pts -= nanoseconds{offset[1]};
}
return std::max(pts, nanoseconds::zero());
}
-bool AudioState::isBufferFilled()
-{
- /* All of OpenAL's buffer queueing happens under the mSrcMutex lock, as
- * does the source gen. So when we're able to grab the lock and the source
- * is valid, the queue must be full.
- */
- std::lock_guard<std::mutex> lock(mSrcMutex);
- return mSource != 0;
-}
-
void AudioState::startPlayback()
{
alSourcePlay(mSource);
if(alcGetInteger64vSOFT)
{
- using fixed32 = std::chrono::duration<int64_t,std::ratio<1,(1ll<<32)>>;
-
// Subtract the total buffer queue time from the current pts to get the
// pts of the start of the queue.
- nanoseconds startpts = mCurrentPts - AudioBufferTotalTime;
- int64_t srctimes[2]={0,0};
+ nanoseconds startpts{mCurrentPts - AudioBufferTotalTime};
+ int64_t srctimes[2]{0,0};
alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_CLOCK_SOFT, srctimes);
- auto device_time = nanoseconds(srctimes[1]);
- auto src_offset = std::chrono::duration_cast<nanoseconds>(fixed32(srctimes[0])) /
- mCodecCtx->sample_rate;
+ auto device_time = nanoseconds{srctimes[1]};
+ auto src_offset = std::chrono::duration_cast<nanoseconds>(fixed32{srctimes[0]}) /
+ mCodecCtx->sample_rate;
// The mixer may have ticked and incremented the device time and sample
// offset, so subtract the source offset from the device time to get
@@ -543,47 +567,31 @@ int AudioState::getSync()
return 0;
/* Constrain the per-update difference to avoid exceedingly large skips */
- diff = std::min<nanoseconds>(std::max<nanoseconds>(diff, -AudioSampleCorrectionMax),
- AudioSampleCorrectionMax);
- return (int)std::chrono::duration_cast<seconds>(diff*mCodecCtx->sample_rate).count();
+ diff = std::min<nanoseconds>(diff, AudioSampleCorrectionMax);
+ return static_cast<int>(std::chrono::duration_cast<seconds>(diff*mCodecCtx->sample_rate).count());
}
int AudioState::decodeFrame()
{
while(!mMovie.mQuit.load(std::memory_order_relaxed))
{
- std::unique_lock<std::mutex> lock(mQueueMtx);
- int ret = avcodec_receive_frame(mCodecCtx.get(), mDecodedFrame.get());
- if(ret == AVERROR(EAGAIN))
- {
- mMovie.mSendDataGood.clear(std::memory_order_relaxed);
- std::unique_lock<std::mutex>(mMovie.mSendMtx).unlock();
- mMovie.mSendCond.notify_one();
- do {
- mQueueCond.wait(lock);
- ret = avcodec_receive_frame(mCodecCtx.get(), mDecodedFrame.get());
- } while(ret == AVERROR(EAGAIN));
- }
- lock.unlock();
- if(ret == AVERROR_EOF) break;
- mMovie.mSendDataGood.clear(std::memory_order_relaxed);
- mMovie.mSendCond.notify_one();
- if(ret < 0)
+ int ret;
+ while((ret=avcodec_receive_frame(mCodecCtx.get(), mDecodedFrame.get())) == AVERROR(EAGAIN))
+ mPackets.sendTo(mCodecCtx.get());
+ if(ret != 0)
{
- std::cerr<< "Failed to decode frame: "<<ret <<std::endl;
- return 0;
+ if(ret == AVERROR_EOF) break;
+ std::cerr<< "Failed to receive frame: "<<ret <<std::endl;
+ continue;
}
if(mDecodedFrame->nb_samples <= 0)
- {
- av_frame_unref(mDecodedFrame.get());
continue;
- }
/* If provided, update w/ pts */
if(mDecodedFrame->best_effort_timestamp != AV_NOPTS_VALUE)
mCurrentPts = std::chrono::duration_cast<nanoseconds>(
- seconds_d64(av_q2d(mStream->time_base)*mDecodedFrame->best_effort_timestamp)
+ seconds_d64{av_q2d(mStream->time_base)*mDecodedFrame->best_effort_timestamp}
);
if(mDecodedFrame->nb_samples > mSamplesMax)
@@ -596,9 +604,8 @@ int AudioState::decodeFrame()
mSamplesMax = mDecodedFrame->nb_samples;
}
/* Return the amount of sample frames converted */
- int data_size = swr_convert(mSwresCtx.get(), &mSamples, mDecodedFrame->nb_samples,
- (const uint8_t**)mDecodedFrame->data, mDecodedFrame->nb_samples
- );
+ int data_size{swr_convert(mSwresCtx.get(), &mSamples, mDecodedFrame->nb_samples,
+ const_cast<const uint8_t**>(mDecodedFrame->data), mDecodedFrame->nb_samples)};
av_frame_unref(mDecodedFrame.get());
return data_size;
@@ -611,17 +618,17 @@ int AudioState::decodeFrame()
* multiple of the template type size.
*/
template<typename T>
-static void sample_dup(uint8_t *out, const uint8_t *in, int count, int frame_size)
+static void sample_dup(uint8_t *out, const uint8_t *in, unsigned int count, size_t frame_size)
{
- const T *sample = reinterpret_cast<const T*>(in);
- T *dst = reinterpret_cast<T*>(out);
+ auto *sample = reinterpret_cast<const T*>(in);
+ auto *dst = reinterpret_cast<T*>(out);
if(frame_size == sizeof(T))
std::fill_n(dst, count, *sample);
else
{
/* NOTE: frame_size is a multiple of sizeof(T). */
- int type_mult = frame_size / sizeof(T);
- int i = 0;
+ size_t type_mult{frame_size / sizeof(T)};
+ size_t i{0};
std::generate_n(dst, count*type_mult,
[sample,type_mult,&i]() -> T
{
@@ -634,10 +641,10 @@ static void sample_dup(uint8_t *out, const uint8_t *in, int count, int frame_siz
}
-bool AudioState::readAudio(uint8_t *samples, int length)
+bool AudioState::readAudio(uint8_t *samples, unsigned int length)
{
- int sample_skip = getSync();
- int audio_size = 0;
+ int sample_skip{getSync()};
+ unsigned int audio_size{0};
/* Read the next chunk of data, refill the buffer, and queue it
* on the source */
@@ -656,22 +663,23 @@ bool AudioState::readAudio(uint8_t *samples, int length)
// Adjust the device start time and current pts by the amount we're
// skipping/duplicating, so that the clock remains correct for the
// current stream position.
- auto skip = nanoseconds(seconds(mSamplesPos)) / mCodecCtx->sample_rate;
+ auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate;
mDeviceStartTime -= skip;
mCurrentPts += skip;
continue;
}
- int rem = length - audio_size;
+ unsigned int rem{length - audio_size};
if(mSamplesPos >= 0)
{
- int len = mSamplesLen - mSamplesPos;
+ const auto len = static_cast<unsigned int>(mSamplesLen - mSamplesPos);
if(rem > len) rem = len;
- memcpy(samples, mSamples + mSamplesPos*mFrameSize, rem*mFrameSize);
+ std::copy_n(mSamples + static_cast<unsigned int>(mSamplesPos)*mFrameSize,
+ rem*mFrameSize, samples);
}
else
{
- rem = std::min(rem, -mSamplesPos);
+ rem = std::min(rem, static_cast<unsigned int>(-mSamplesPos));
/* Add samples by copying the first sample */
if((mFrameSize&7) == 0)
@@ -685,7 +693,7 @@ bool AudioState::readAudio(uint8_t *samples, int length)
}
mSamplesPos += rem;
- mCurrentPts += nanoseconds(seconds(rem)) / mCodecCtx->sample_rate;
+ mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate;
samples += rem*mFrameSize;
audio_size += rem;
}
@@ -694,10 +702,10 @@ bool AudioState::readAudio(uint8_t *samples, int length)
if(audio_size < length)
{
- int rem = length - audio_size;
+ const unsigned int rem{length - audio_size};
std::fill_n(samples, rem*mFrameSize,
(mDstSampleFmt == AV_SAMPLE_FMT_U8) ? 0x80 : 0x00);
- mCurrentPts += nanoseconds(seconds(rem)) / mCodecCtx->sample_rate;
+ mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate;
audio_size += rem;
}
return true;
@@ -709,14 +717,14 @@ void AL_APIENTRY AudioState::EventCallback(ALenum eventType, ALuint object, ALui
ALsizei length, const ALchar *message,
void *userParam)
{
- AudioState *self = reinterpret_cast<AudioState*>(userParam);
+ auto self = static_cast<AudioState*>(userParam);
if(eventType == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT)
{
/* Temporarily lock the source mutex to ensure it's not between
* checking the processed count and going to sleep.
*/
- std::unique_lock<std::mutex>(self->mSrcMutex).unlock();
+ std::unique_lock<std::mutex>{self->mSrcMutex}.unlock();
self->mSrcCond.notify_one();
return;
}
@@ -736,15 +744,15 @@ void AL_APIENTRY AudioState::EventCallback(ALenum eventType, ALuint object, ALui
std::cout<< "\n"
"Object ID: "<<object<<"\n"
"Parameter: "<<param<<"\n"
- "Message: "<<std::string(message, length)<<"\n----"<<
+ "Message: "<<std::string{message, static_cast<ALuint>(length)}<<"\n----"<<
std::endl;
if(eventType == AL_EVENT_TYPE_DISCONNECTED_SOFT)
{
- { std::lock_guard<std::mutex> lock(self->mSrcMutex);
+ {
+ std::lock_guard<std::mutex> lock{self->mSrcMutex};
self->mConnected.clear(std::memory_order_release);
}
- std::unique_lock<std::mutex>(self->mSrcMutex).unlock();
self->mSrcCond.notify_one();
}
}
@@ -752,8 +760,8 @@ void AL_APIENTRY AudioState::EventCallback(ALenum eventType, ALuint object, ALui
int AudioState::handler()
{
- std::unique_lock<std::mutex> lock(mSrcMutex);
- milliseconds sleep_time = AudioBufferTime / 3;
+ std::unique_lock<std::mutex> srclock{mSrcMutex, std::defer_lock};
+ milliseconds sleep_time{AudioBufferTime / 3};
ALenum fmt;
#ifdef AL_SOFT_events
@@ -772,13 +780,15 @@ int AudioState::handler()
/* Find a suitable format for OpenAL. */
mDstChanLayout = 0;
- if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8 || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P)
+ mFormat = AL_NONE;
+ if((mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) &&
+ alIsExtensionPresent("AL_EXT_FLOAT32"))
{
- mDstSampleFmt = AV_SAMPLE_FMT_U8;
- mFrameSize = 1;
+ mDstSampleFmt = AV_SAMPLE_FMT_FLT;
+ mFrameSize = 4;
if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1 &&
alIsExtensionPresent("AL_EXT_MCFORMATS") &&
- (fmt=alGetEnumValue("AL_FORMAT_71CHN8")) != AL_NONE && fmt != -1)
+ (fmt=alGetEnumValue("AL_FORMAT_71CHN32")) != AL_NONE && fmt != -1)
{
mDstChanLayout = mCodecCtx->channel_layout;
mFrameSize *= 8;
@@ -787,7 +797,7 @@ int AudioState::handler()
if((mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 ||
mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) &&
alIsExtensionPresent("AL_EXT_MCFORMATS") &&
- (fmt=alGetEnumValue("AL_FORMAT_51CHN8")) != AL_NONE && fmt != -1)
+ (fmt=alGetEnumValue("AL_FORMAT_51CHN32")) != AL_NONE && fmt != -1)
{
mDstChanLayout = mCodecCtx->channel_layout;
mFrameSize *= 6;
@@ -797,23 +807,42 @@ int AudioState::handler()
{
mDstChanLayout = mCodecCtx->channel_layout;
mFrameSize *= 1;
- mFormat = AL_FORMAT_MONO8;
+ mFormat = AL_FORMAT_MONO_FLOAT32;
}
- if(!mDstChanLayout)
+ /* Assume 3D B-Format (ambisonics) if the channel layout is blank and
+ * there's 4 or more channels. FFmpeg/libavcodec otherwise seems to
+ * have no way to specify if the source is actually B-Format (let alone
+ * if it's 2D or 3D).
+ */
+ if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 &&
+ alIsExtensionPresent("AL_EXT_BFORMAT") &&
+ (fmt=alGetEnumValue("AL_FORMAT_BFORMAT3D_FLOAT32")) != AL_NONE && fmt != -1)
+ {
+ int order{static_cast<int>(std::sqrt(mCodecCtx->channels)) - 1};
+ if((order+1)*(order+1) == mCodecCtx->channels ||
+ (order+1)*(order+1) + 2 == mCodecCtx->channels)
+ {
+ /* OpenAL only supports first-order with AL_EXT_BFORMAT, which
+ * is 4 channels for 3D buffers.
+ */
+ mFrameSize *= 4;
+ mFormat = fmt;
+ }
+ }
+ if(!mFormat)
{
mDstChanLayout = AV_CH_LAYOUT_STEREO;
mFrameSize *= 2;
- mFormat = AL_FORMAT_STEREO8;
+ mFormat = AL_FORMAT_STEREO_FLOAT32;
}
}
- if((mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) &&
- alIsExtensionPresent("AL_EXT_FLOAT32"))
+ if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8 || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P)
{
- mDstSampleFmt = AV_SAMPLE_FMT_FLT;
- mFrameSize = 4;
+ mDstSampleFmt = AV_SAMPLE_FMT_U8;
+ mFrameSize = 1;
if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1 &&
alIsExtensionPresent("AL_EXT_MCFORMATS") &&
- (fmt=alGetEnumValue("AL_FORMAT_71CHN32")) != AL_NONE && fmt != -1)
+ (fmt=alGetEnumValue("AL_FORMAT_71CHN8")) != AL_NONE && fmt != -1)
{
mDstChanLayout = mCodecCtx->channel_layout;
mFrameSize *= 8;
@@ -822,7 +851,7 @@ int AudioState::handler()
if((mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 ||
mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) &&
alIsExtensionPresent("AL_EXT_MCFORMATS") &&
- (fmt=alGetEnumValue("AL_FORMAT_51CHN32")) != AL_NONE && fmt != -1)
+ (fmt=alGetEnumValue("AL_FORMAT_51CHN8")) != AL_NONE && fmt != -1)
{
mDstChanLayout = mCodecCtx->channel_layout;
mFrameSize *= 6;
@@ -832,16 +861,28 @@ int AudioState::handler()
{
mDstChanLayout = mCodecCtx->channel_layout;
mFrameSize *= 1;
- mFormat = AL_FORMAT_MONO_FLOAT32;
+ mFormat = AL_FORMAT_MONO8;
}
- if(!mDstChanLayout)
+ if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 &&
+ alIsExtensionPresent("AL_EXT_BFORMAT") &&
+ (fmt=alGetEnumValue("AL_FORMAT_BFORMAT3D8")) != AL_NONE && fmt != -1)
+ {
+ int order{static_cast<int>(std::sqrt(mCodecCtx->channels)) - 1};
+ if((order+1)*(order+1) == mCodecCtx->channels ||
+ (order+1)*(order+1) + 2 == mCodecCtx->channels)
+ {
+ mFrameSize *= 4;
+ mFormat = fmt;
+ }
+ }
+ if(!mFormat)
{
mDstChanLayout = AV_CH_LAYOUT_STEREO;
mFrameSize *= 2;
- mFormat = AL_FORMAT_STEREO_FLOAT32;
+ mFormat = AL_FORMAT_STEREO8;
}
}
- if(!mDstChanLayout)
+ if(!mFormat)
{
mDstSampleFmt = AV_SAMPLE_FMT_S16;
mFrameSize = 2;
@@ -868,18 +909,30 @@ int AudioState::handler()
mFrameSize *= 1;
mFormat = AL_FORMAT_MONO16;
}
- if(!mDstChanLayout)
+ if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 &&
+ alIsExtensionPresent("AL_EXT_BFORMAT") &&
+ (fmt=alGetEnumValue("AL_FORMAT_BFORMAT3D16")) != AL_NONE && fmt != -1)
+ {
+ int order{static_cast<int>(std::sqrt(mCodecCtx->channels)) - 1};
+ if((order+1)*(order+1) == mCodecCtx->channels ||
+ (order+1)*(order+1) + 2 == mCodecCtx->channels)
+ {
+ mFrameSize *= 4;
+ mFormat = fmt;
+ }
+ }
+ if(!mFormat)
{
mDstChanLayout = AV_CH_LAYOUT_STEREO;
mFrameSize *= 2;
mFormat = AL_FORMAT_STEREO16;
}
}
- void *samples = nullptr;
- ALsizei buffer_len = std::chrono::duration_cast<std::chrono::duration<int>>(
- mCodecCtx->sample_rate * AudioBufferTime).count() * mFrameSize;
+ void *samples{nullptr};
+ ALsizei buffer_len = static_cast<int>(std::chrono::duration_cast<seconds>(
+ mCodecCtx->sample_rate * AudioBufferTime).count() * mFrameSize);
- mSamples = NULL;
+ mSamples = nullptr;
mSamplesMax = 0;
mSamplesPos = 0;
mSamplesLen = 0;
@@ -891,13 +944,36 @@ int AudioState::handler()
goto finish;
}
- mSwresCtx.reset(swr_alloc_set_opts(nullptr,
- mDstChanLayout, mDstSampleFmt, mCodecCtx->sample_rate,
- mCodecCtx->channel_layout ? mCodecCtx->channel_layout :
- (uint64_t)av_get_default_channel_layout(mCodecCtx->channels),
- mCodecCtx->sample_fmt, mCodecCtx->sample_rate,
- 0, nullptr
- ));
+ if(!mDstChanLayout)
+ {
+ /* OpenAL only supports first-order ambisonics with AL_EXT_BFORMAT, so
+ * we have to drop any extra channels. It also only supports FuMa
+ * channel ordering and normalization, so a custom matrix is needed to
+ * scale and reorder the source from AmbiX.
+ */
+ mSwresCtx.reset(swr_alloc_set_opts(nullptr,
+ (1_i64<<4)-1, mDstSampleFmt, mCodecCtx->sample_rate,
+ (1_i64<<mCodecCtx->channels)-1, mCodecCtx->sample_fmt, mCodecCtx->sample_rate,
+ 0, nullptr));
+
+ /* Note that ffmpeg/libavcodec has no method to check the ambisonic
+ * channel order and normalization, so we can only assume AmbiX as the
+ * defacto-standard. This is not true for .amb files, which use FuMa.
+ */
+ std::vector<double> mtx(64*64, 0.0);
+ mtx[0 + 0*64] = std::sqrt(0.5);
+ mtx[3 + 1*64] = 1.0;
+ mtx[1 + 2*64] = 1.0;
+ mtx[2 + 3*64] = 1.0;
+ swr_set_matrix(mSwresCtx.get(), mtx.data(), 64);
+ }
+ else
+ mSwresCtx.reset(swr_alloc_set_opts(nullptr,
+ static_cast<int64_t>(mDstChanLayout), mDstSampleFmt, mCodecCtx->sample_rate,
+ mCodecCtx->channel_layout ? static_cast<int64_t>(mCodecCtx->channel_layout) :
+ av_get_default_channel_layout(mCodecCtx->channels),
+ mCodecCtx->sample_fmt, mCodecCtx->sample_rate,
+ 0, nullptr));
if(!mSwresCtx || swr_init(mSwresCtx.get()) != 0)
{
std::cerr<< "Failed to initialize audio converter" <<std::endl;
@@ -905,14 +981,14 @@ int AudioState::handler()
}
mBuffers.assign(AudioBufferTotalTime / AudioBufferTime, 0);
- alGenBuffers(mBuffers.size(), mBuffers.data());
+ alGenBuffers(static_cast<ALsizei>(mBuffers.size()), mBuffers.data());
alGenSources(1, &mSource);
if(EnableDirectOut)
alSourcei(mSource, AL_DIRECT_CHANNELS_SOFT, AL_TRUE);
- if(EnableWideStereo)
- {
- ALfloat angles[2] = { (ALfloat)(M_PI/3.0), (ALfloat)(-M_PI/3.0) };
+ if (EnableWideStereo) {
+ ALfloat angles[2] = {static_cast<ALfloat>(M_PI / 3.0),
+ static_cast<ALfloat>(-M_PI / 3.0)};
alSourcefv(mSource, AL_STEREO_ANGLES, angles);
}
@@ -928,13 +1004,28 @@ int AudioState::handler()
if(alGetError() != AL_NO_ERROR)
{
fprintf(stderr, "Failed to use mapped buffers\n");
- samples = av_malloc(buffer_len);
+ samples = av_malloc(static_cast<ALuint>(buffer_len));
}
}
else
#endif
- samples = av_malloc(buffer_len);
+ samples = av_malloc(static_cast<ALuint>(buffer_len));
+ /* Prefill the codec buffer. */
+ do {
+ const int ret{mPackets.sendTo(mCodecCtx.get())};
+ if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
+ break;
+ } while(1);
+
+ srclock.lock();
+ if(alcGetInteger64vSOFT)
+ {
+ int64_t devtime{};
+ alcGetInteger64vSOFT(alcGetContextsDevice(alcGetCurrentContext()), ALC_DEVICE_CLOCK_SOFT,
+ 1, &devtime);
+ mDeviceStartTime = nanoseconds{devtime} - mCurrentPts;
+ }
while(alGetError() == AL_NO_ERROR && !mMovie.mQuit.load(std::memory_order_relaxed) &&
mConnected.test_and_set(std::memory_order_relaxed))
{
@@ -944,35 +1035,37 @@ int AudioState::handler()
while(processed > 0)
{
std::array<ALuint,4> bids;
- alSourceUnqueueBuffers(mSource, std::min<ALsizei>(bids.size(), processed),
- bids.data());
- processed -= std::min<ALsizei>(bids.size(), processed);
+ const ALsizei todq{std::min<ALsizei>(bids.size(), processed)};
+ alSourceUnqueueBuffers(mSource, todq, bids.data());
+ processed -= todq;
}
/* Refill the buffer queue. */
ALint queued;
alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued);
- while((ALuint)queued < mBuffers.size())
+ while(static_cast<ALuint>(queued) < mBuffers.size())
{
- ALuint bufid = mBuffers[mBufferIdx];
-
- uint8_t *ptr = reinterpret_cast<uint8_t*>(samples
-#ifdef AL_SOFT_map_buffer
- ? samples : alMapBufferSOFT(bufid, 0, buffer_len, AL_MAP_WRITE_BIT_SOFT)
-#endif
- );
- if(!ptr) break;
-
+ const ALuint bufid{mBuffers[mBufferIdx]};
/* Read the next chunk of data, filling the buffer, and queue it on
- * the source */
- bool got_audio = readAudio(ptr, buffer_len);
+ * the source.
+ */
#ifdef AL_SOFT_map_buffer
- if(!samples) alUnmapBufferSOFT(bufid);
+ if(!samples)
+ {
+ auto ptr = static_cast<uint8_t*>(alMapBufferSOFT(bufid, 0, buffer_len,
+ AL_MAP_WRITE_BIT_SOFT));
+ bool got_audio{readAudio(ptr, static_cast<unsigned int>(buffer_len))};
+ alUnmapBufferSOFT(bufid);
+ if(!got_audio) break;
+ }
+ else
#endif
- if(!got_audio) break;
-
- if(samples)
+ {
+ auto ptr = static_cast<uint8_t*>(samples);
+ if(!readAudio(ptr, static_cast<unsigned int>(buffer_len)))
+ break;
alBufferData(bufid, mFormat, samples, buffer_len, mCodecCtx->sample_rate);
+ }
alSourceQueueBuffers(mSource, 1, &bufid);
mBufferIdx = (mBufferIdx+1) % mBuffers.size();
@@ -992,19 +1085,29 @@ int AudioState::handler()
*/
alSourceRewind(mSource);
alSourcei(mSource, AL_BUFFER, 0);
+ if(alcGetInteger64vSOFT)
+ {
+ /* Also update the device start time with the current device
+ * clock, so the decoder knows we're running behind.
+ */
+ int64_t devtime{};
+ alcGetInteger64vSOFT(alcGetContextsDevice(alcGetCurrentContext()),
+ ALC_DEVICE_CLOCK_SOFT, 1, &devtime);
+ mDeviceStartTime = nanoseconds{devtime} - mCurrentPts;
+ }
continue;
}
/* (re)start the source if needed, and wait for a buffer to finish */
- if(state != AL_PLAYING && state != AL_PAUSED &&
- mMovie.mPlaying.load(std::memory_order_relaxed))
+ if(state != AL_PLAYING && state != AL_PAUSED)
startPlayback();
- mSrcCond.wait_for(lock, sleep_time);
+ mSrcCond.wait_for(srclock, sleep_time);
}
alSourceRewind(mSource);
alSourcei(mSource, AL_BUFFER, 0);
+ srclock.unlock();
finish:
av_freep(&samples);
@@ -1024,350 +1127,267 @@ finish:
nanoseconds VideoState::getClock()
{
/* NOTE: This returns incorrect times while not playing. */
- auto delta = get_avtime() - mCurrentPtsTime;
- return mCurrentPts + delta;
-}
-
-bool VideoState::isBufferFilled()
-{
- std::unique_lock<std::mutex> lock(mPictQMutex);
- return mPictQSize >= mPictQ.size();
-}
-
-Uint32 SDLCALL VideoState::sdl_refresh_timer_cb(Uint32 /*interval*/, void *opaque)
-{
- SDL_Event evt{};
- evt.user.type = FF_REFRESH_EVENT;
- evt.user.data1 = opaque;
- SDL_PushEvent(&evt);
- return 0; /* 0 means stop timer */
-}
-
-/* Schedules an FF_REFRESH_EVENT event to occur in 'delay' ms. */
-void VideoState::schedRefresh(milliseconds delay)
-{
- SDL_AddTimer(delay.count(), sdl_refresh_timer_cb, this);
+ std::lock_guard<std::mutex> _{mDispPtsMutex};
+ if(mDisplayPtsTime == microseconds::min())
+ return nanoseconds::zero();
+ auto delta = get_avtime() - mDisplayPtsTime;
+ return mDisplayPts + delta;
}
-/* Called by VideoState::refreshTimer to display the next video frame. */
+/* Called by VideoState::updateVideo to display the next video frame. */
void VideoState::display(SDL_Window *screen, SDL_Renderer *renderer)
{
- Picture *vp = &mPictQ[mPictQRead];
-
- if(!vp->mImage)
+ if(!mImage)
return;
- float aspect_ratio;
+ double aspect_ratio;
int win_w, win_h;
int w, h, x, y;
if(mCodecCtx->sample_aspect_ratio.num == 0)
- aspect_ratio = 0.0f;
+ aspect_ratio = 0.0;
else
{
aspect_ratio = av_q2d(mCodecCtx->sample_aspect_ratio) * mCodecCtx->width /
mCodecCtx->height;
}
- if(aspect_ratio <= 0.0f)
- aspect_ratio = (float)mCodecCtx->width / (float)mCodecCtx->height;
+ if(aspect_ratio <= 0.0)
+ aspect_ratio = static_cast<double>(mCodecCtx->width) / mCodecCtx->height;
SDL_GetWindowSize(screen, &win_w, &win_h);
h = win_h;
- w = ((int)rint(h * aspect_ratio) + 3) & ~3;
+ w = (static_cast<int>(std::rint(h * aspect_ratio)) + 3) & ~3;
if(w > win_w)
{
w = win_w;
- h = ((int)rint(w / aspect_ratio) + 3) & ~3;
+ h = (static_cast<int>(std::rint(w / aspect_ratio)) + 3) & ~3;
}
x = (win_w - w) / 2;
y = (win_h - h) / 2;
- SDL_Rect src_rect{ 0, 0, vp->mWidth, vp->mHeight };
+ SDL_Rect src_rect{ 0, 0, mWidth, mHeight };
SDL_Rect dst_rect{ x, y, w, h };
- SDL_RenderCopy(renderer, vp->mImage, &src_rect, &dst_rect);
+ SDL_RenderCopy(renderer, mImage, &src_rect, &dst_rect);
SDL_RenderPresent(renderer);
}
-/* FF_REFRESH_EVENT handler called on the main thread where the SDL_Renderer
- * was created. It handles the display of the next decoded video frame (if not
- * falling behind), and sets up the timer for the following video frame.
+/* Called regularly on the main thread where the SDL_Renderer was created. It
+ * handles updating the textures of decoded frames and displaying the latest
+ * frame.
*/
-void VideoState::refreshTimer(SDL_Window *screen, SDL_Renderer *renderer)
+void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw)
{
- if(!mStream)
- {
- if(mEOS)
- {
- mFinalUpdate = true;
- std::unique_lock<std::mutex>(mPictQMutex).unlock();
- mPictQCond.notify_all();
- return;
- }
- schedRefresh(milliseconds(100));
- return;
- }
- if(!mMovie.mPlaying.load(std::memory_order_relaxed))
+ size_t read_idx{mPictQRead.load(std::memory_order_relaxed)};
+ Picture *vp{&mPictQ[read_idx]};
+
+ auto clocktime = mMovie.getMasterClock();
+ bool updated{false};
+ while(1)
{
- schedRefresh(milliseconds(1));
- return;
- }
+ size_t next_idx{(read_idx+1)%mPictQ.size()};
+ if(next_idx == mPictQWrite.load(std::memory_order_acquire))
+ break;
+ Picture *nextvp{&mPictQ[next_idx]};
+ if(clocktime < nextvp->mPts)
+ break;
- std::unique_lock<std::mutex> lock(mPictQMutex);
-retry:
- if(mPictQSize == 0)
+ vp = nextvp;
+ updated = true;
+ read_idx = next_idx;
+ }
+ if(mMovie.mQuit.load(std::memory_order_relaxed))
{
if(mEOS)
mFinalUpdate = true;
- else
- schedRefresh(milliseconds(1));
- lock.unlock();
- mPictQCond.notify_all();
+ mPictQRead.store(read_idx, std::memory_order_release);
+ std::unique_lock<std::mutex>{mPictQMutex}.unlock();
+ mPictQCond.notify_one();
return;
}
- Picture *vp = &mPictQ[mPictQRead];
- mCurrentPts = vp->mPts;
- mCurrentPtsTime = get_avtime();
-
- /* Get delay using the frame pts and the pts from last frame. */
- auto delay = vp->mPts - mFrameLastPts;
- if(delay <= seconds::zero() || delay >= seconds(1))
- {
- /* If incorrect delay, use previous one. */
- delay = mFrameLastDelay;
- }
- /* Save for next frame. */
- mFrameLastDelay = delay;
- mFrameLastPts = vp->mPts;
-
- /* Update delay to sync to clock if not master source. */
- if(mMovie.mAVSyncType != SyncMaster::Video)
+ if(updated)
{
- auto ref_clock = mMovie.getMasterClock();
- auto diff = vp->mPts - ref_clock;
+ mPictQRead.store(read_idx, std::memory_order_release);
+ std::unique_lock<std::mutex>{mPictQMutex}.unlock();
+ mPictQCond.notify_one();
- /* Skip or repeat the frame. Take delay into account. */
- auto sync_threshold = std::min<nanoseconds>(delay, VideoSyncThreshold);
- if(!(diff < AVNoSyncThreshold && diff > -AVNoSyncThreshold))
+ /* allocate or resize the buffer! */
+ bool fmt_updated{false};
+ if(!mImage || mWidth != mCodecCtx->width || mHeight != mCodecCtx->height)
{
- if(diff <= -sync_threshold)
- delay = nanoseconds::zero();
- else if(diff >= sync_threshold)
- delay *= 2;
- }
- }
-
- mFrameTimer += delay;
- /* Compute the REAL delay. */
- auto actual_delay = mFrameTimer - get_avtime();
- if(!(actual_delay >= VideoSyncThreshold))
- {
- /* We don't have time to handle this picture, just skip to the next one. */
- mPictQRead = (mPictQRead+1)%mPictQ.size();
- mPictQSize--;
- goto retry;
- }
- schedRefresh(std::chrono::duration_cast<milliseconds>(actual_delay));
+ fmt_updated = true;
+ if(mImage)
+ SDL_DestroyTexture(mImage);
+ mImage = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,
+ mCodecCtx->coded_width, mCodecCtx->coded_height);
+ if(!mImage)
+ std::cerr<< "Failed to create YV12 texture!" <<std::endl;
+ mWidth = mCodecCtx->width;
+ mHeight = mCodecCtx->height;
+
+ if(mFirstUpdate && mWidth > 0 && mHeight > 0)
+ {
+ /* For the first update, set the window size to the video size. */
+ mFirstUpdate = false;
- /* Show the picture! */
- display(screen, renderer);
+ int w{mWidth};
+ int h{mHeight};
+ if(mCodecCtx->sample_aspect_ratio.den != 0)
+ {
+ double aspect_ratio = av_q2d(mCodecCtx->sample_aspect_ratio);
+ if(aspect_ratio >= 1.0)
+ w = static_cast<int>(w*aspect_ratio + 0.5);
+ else if(aspect_ratio > 0.0)
+ h = static_cast<int>(h/aspect_ratio + 0.5);
+ }
+ SDL_SetWindowSize(screen, w, h);
+ }
+ }
- /* Update queue for next picture. */
- mPictQRead = (mPictQRead+1)%mPictQ.size();
- mPictQSize--;
- lock.unlock();
- mPictQCond.notify_all();
-}
+ if(mImage)
+ {
+ AVFrame *frame{vp->mFrame.get()};
+ void *pixels{nullptr};
+ int pitch{0};
+
+ if(mCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P)
+ SDL_UpdateYUVTexture(mImage, nullptr,
+ frame->data[0], frame->linesize[0],
+ frame->data[1], frame->linesize[1],
+ frame->data[2], frame->linesize[2]
+ );
+ else if(SDL_LockTexture(mImage, nullptr, &pixels, &pitch) != 0)
+ std::cerr<< "Failed to lock texture" <<std::endl;
+ else
+ {
+ // Convert the image into YUV format that SDL uses
+ int coded_w{mCodecCtx->coded_width};
+ int coded_h{mCodecCtx->coded_height};
+ int w{mCodecCtx->width};
+ int h{mCodecCtx->height};
+ if(!mSwscaleCtx || fmt_updated)
+ {
+ mSwscaleCtx.reset(sws_getContext(
+ w, h, mCodecCtx->pix_fmt,
+ w, h, AV_PIX_FMT_YUV420P, 0,
+ nullptr, nullptr, nullptr
+ ));
+ }
-/* FF_UPDATE_EVENT handler, updates the picture's texture. It's called on the
- * main thread where the renderer was created.
- */
-void VideoState::updatePicture(SDL_Window *screen, SDL_Renderer *renderer)
-{
- Picture *vp = &mPictQ[mPictQWrite];
- bool fmt_updated = false;
-
- /* allocate or resize the buffer! */
- if(!vp->mImage || vp->mWidth != mCodecCtx->width || vp->mHeight != mCodecCtx->height)
- {
- fmt_updated = true;
- if(vp->mImage)
- SDL_DestroyTexture(vp->mImage);
- vp->mImage = SDL_CreateTexture(
- renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,
- mCodecCtx->coded_width, mCodecCtx->coded_height
- );
- if(!vp->mImage)
- std::cerr<< "Failed to create YV12 texture!" <<std::endl;
- vp->mWidth = mCodecCtx->width;
- vp->mHeight = mCodecCtx->height;
+ /* point pict at the queue */
+ uint8_t *pict_data[3];
+ pict_data[0] = static_cast<uint8_t*>(pixels);
+ pict_data[1] = pict_data[0] + coded_w*coded_h;
+ pict_data[2] = pict_data[1] + coded_w*coded_h/4;
- if(mFirstUpdate && vp->mWidth > 0 && vp->mHeight > 0)
- {
- /* For the first update, set the window size to the video size. */
- mFirstUpdate = false;
+ int pict_linesize[3];
+ pict_linesize[0] = pitch;
+ pict_linesize[1] = pitch / 2;
+ pict_linesize[2] = pitch / 2;
- int w = vp->mWidth;
- int h = vp->mHeight;
- if(mCodecCtx->sample_aspect_ratio.den != 0)
- {
- double aspect_ratio = av_q2d(mCodecCtx->sample_aspect_ratio);
- if(aspect_ratio >= 1.0)
- w = (int)(w*aspect_ratio + 0.5);
- else if(aspect_ratio > 0.0)
- h = (int)(h/aspect_ratio + 0.5);
+ sws_scale(mSwscaleCtx.get(), reinterpret_cast<uint8_t**>(frame->data), frame->linesize,
+ 0, h, pict_data, pict_linesize);
+ SDL_UnlockTexture(mImage);
}
- SDL_SetWindowSize(screen, w, h);
}
+
+ redraw = true;
}
- if(vp->mImage)
+ if(redraw)
{
- AVFrame *frame = mDecodedFrame.get();
- void *pixels = nullptr;
- int pitch = 0;
-
- if(mCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P)
- SDL_UpdateYUVTexture(vp->mImage, nullptr,
- frame->data[0], frame->linesize[0],
- frame->data[1], frame->linesize[1],
- frame->data[2], frame->linesize[2]
- );
- else if(SDL_LockTexture(vp->mImage, nullptr, &pixels, &pitch) != 0)
- std::cerr<< "Failed to lock texture" <<std::endl;
- else
- {
- // Convert the image into YUV format that SDL uses
- int coded_w = mCodecCtx->coded_width;
- int coded_h = mCodecCtx->coded_height;
- int w = mCodecCtx->width;
- int h = mCodecCtx->height;
- if(!mSwscaleCtx || fmt_updated)
- {
- mSwscaleCtx.reset(sws_getContext(
- w, h, mCodecCtx->pix_fmt,
- w, h, AV_PIX_FMT_YUV420P, 0,
- nullptr, nullptr, nullptr
- ));
- }
-
- /* point pict at the queue */
- uint8_t *pict_data[3];
- pict_data[0] = reinterpret_cast<uint8_t*>(pixels);
- pict_data[1] = pict_data[0] + coded_w*coded_h;
- pict_data[2] = pict_data[1] + coded_w*coded_h/4;
+ /* Show the picture! */
+ display(screen, renderer);
+ }
- int pict_linesize[3];
- pict_linesize[0] = pitch;
- pict_linesize[1] = pitch / 2;
- pict_linesize[2] = pitch / 2;
+ if(updated)
+ {
+ auto disp_time = get_avtime();
- sws_scale(mSwscaleCtx.get(), (const uint8_t**)frame->data,
- frame->linesize, 0, h, pict_data, pict_linesize);
- SDL_UnlockTexture(vp->mImage);
+ std::lock_guard<std::mutex> _{mDispPtsMutex};
+ mDisplayPts = vp->mPts;
+ mDisplayPtsTime = disp_time;
+ }
+ if(mEOS.load(std::memory_order_acquire))
+ {
+ if((read_idx+1)%mPictQ.size() == mPictQWrite.load(std::memory_order_acquire))
+ {
+ mFinalUpdate = true;
+ std::unique_lock<std::mutex>{mPictQMutex}.unlock();
+ mPictQCond.notify_one();
}
}
-
- vp->mUpdated.store(true, std::memory_order_release);
- std::unique_lock<std::mutex>(mPictQMutex).unlock();
- mPictQCond.notify_one();
}
-int VideoState::queuePicture(nanoseconds pts)
+int VideoState::handler()
{
- /* Wait until we have space for a new pic */
- std::unique_lock<std::mutex> lock(mPictQMutex);
- while(mPictQSize >= mPictQ.size() && !mMovie.mQuit.load(std::memory_order_relaxed))
- mPictQCond.wait(lock);
- lock.unlock();
-
- if(mMovie.mQuit.load(std::memory_order_relaxed))
- return -1;
-
- Picture *vp = &mPictQ[mPictQWrite];
-
- /* We have to create/update the picture in the main thread */
- vp->mUpdated.store(false, std::memory_order_relaxed);
- SDL_Event evt{};
- evt.user.type = FF_UPDATE_EVENT;
- evt.user.data1 = this;
- SDL_PushEvent(&evt);
+ std::for_each(mPictQ.begin(), mPictQ.end(),
+ [](Picture &pict) -> void
+ { pict.mFrame = AVFramePtr{av_frame_alloc()}; });
+
+ /* Prefill the codec buffer. */
+ do {
+ const int ret{mPackets.sendTo(mCodecCtx.get())};
+ if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
+ break;
+ } while(1);
- /* Wait until the picture is updated. */
- lock.lock();
- while(!vp->mUpdated.load(std::memory_order_relaxed))
{
- if(mMovie.mQuit.load(std::memory_order_relaxed))
- return -1;
- mPictQCond.wait(lock);
+ std::lock_guard<std::mutex> _{mDispPtsMutex};
+ mDisplayPtsTime = get_avtime();
}
- if(mMovie.mQuit.load(std::memory_order_relaxed))
- return -1;
- vp->mPts = pts;
-
- mPictQWrite = (mPictQWrite+1)%mPictQ.size();
- mPictQSize++;
- lock.unlock();
- return 0;
-}
-
-int VideoState::handler()
-{
- mDecodedFrame.reset(av_frame_alloc());
+ auto current_pts = nanoseconds::zero();
while(!mMovie.mQuit.load(std::memory_order_relaxed))
{
- std::unique_lock<std::mutex> lock(mQueueMtx);
- /* Decode video frame */
- int ret = avcodec_receive_frame(mCodecCtx.get(), mDecodedFrame.get());
- if(ret == AVERROR(EAGAIN))
- {
- mMovie.mSendDataGood.clear(std::memory_order_relaxed);
- std::unique_lock<std::mutex>(mMovie.mSendMtx).unlock();
- mMovie.mSendCond.notify_one();
- do {
- mQueueCond.wait(lock);
- ret = avcodec_receive_frame(mCodecCtx.get(), mDecodedFrame.get());
- } while(ret == AVERROR(EAGAIN));
- }
- lock.unlock();
- if(ret == AVERROR_EOF) break;
- mMovie.mSendDataGood.clear(std::memory_order_relaxed);
- mMovie.mSendCond.notify_one();
- if(ret < 0)
+ size_t write_idx{mPictQWrite.load(std::memory_order_relaxed)};
+ Picture *vp{&mPictQ[write_idx]};
+
+ /* Retrieve video frame. */
+ AVFrame *decoded_frame{vp->mFrame.get()};
+ int ret;
+ while((ret=avcodec_receive_frame(mCodecCtx.get(), decoded_frame)) == AVERROR(EAGAIN))
+ mPackets.sendTo(mCodecCtx.get());
+ if(ret != 0)
{
- std::cerr<< "Failed to decode frame: "<<ret <<std::endl;
+ if(ret == AVERROR_EOF) break;
+ std::cerr<< "Failed to receive frame: "<<ret <<std::endl;
continue;
}
/* Get the PTS for this frame. */
- nanoseconds pts;
- if(mDecodedFrame->best_effort_timestamp != AV_NOPTS_VALUE)
- mClock = std::chrono::duration_cast<nanoseconds>(
- seconds_d64(av_q2d(mStream->time_base)*mDecodedFrame->best_effort_timestamp)
- );
- pts = mClock;
+ if(decoded_frame->best_effort_timestamp != AV_NOPTS_VALUE)
+ current_pts = std::chrono::duration_cast<nanoseconds>(
+ seconds_d64{av_q2d(mStream->time_base)*decoded_frame->best_effort_timestamp});
+ vp->mPts = current_pts;
/* Update the video clock to the next expected PTS. */
auto frame_delay = av_q2d(mCodecCtx->time_base);
- frame_delay += mDecodedFrame->repeat_pict * (frame_delay * 0.5);
- mClock += std::chrono::duration_cast<nanoseconds>(seconds_d64(frame_delay));
+ frame_delay += decoded_frame->repeat_pict * (frame_delay * 0.5);
+ current_pts += std::chrono::duration_cast<nanoseconds>(seconds_d64{frame_delay});
- if(queuePicture(pts) < 0)
- break;
- av_frame_unref(mDecodedFrame.get());
+ /* Put the frame in the queue to be loaded into a texture and displayed
+ * by the rendering thread.
+ */
+ write_idx = (write_idx+1)%mPictQ.size();
+ mPictQWrite.store(write_idx, std::memory_order_release);
+
+ /* Send a packet now so it's hopefully ready by the time it's needed. */
+ mPackets.sendTo(mCodecCtx.get());
+
+ if(write_idx == mPictQRead.load(std::memory_order_acquire))
+ {
+ /* Wait until we have space for a new pic */
+ std::unique_lock<std::mutex> lock{mPictQMutex};
+ while(write_idx == mPictQRead.load(std::memory_order_acquire) &&
+ !mMovie.mQuit.load(std::memory_order_relaxed))
+ mPictQCond.wait(lock);
+ }
}
mEOS = true;
- std::unique_lock<std::mutex> lock(mPictQMutex);
- if(mMovie.mQuit.load(std::memory_order_relaxed))
- {
- mPictQRead = 0;
- mPictQWrite = 0;
- mPictQSize = 0;
- }
- while(!mFinalUpdate)
- mPictQCond.wait(lock);
+ std::unique_lock<std::mutex> lock{mPictQMutex};
+ while(!mFinalUpdate) mPictQCond.wait(lock);
return 0;
}
@@ -1375,13 +1395,13 @@ int VideoState::handler()
int MovieState::decode_interrupt_cb(void *ctx)
{
- return reinterpret_cast<MovieState*>(ctx)->mQuit.load(std::memory_order_relaxed);
+ return static_cast<MovieState*>(ctx)->mQuit.load(std::memory_order_relaxed);
}
bool MovieState::prepare()
{
- AVIOContext *avioctx = nullptr;
- AVIOInterruptCB intcb = { decode_interrupt_cb, this };
+ AVIOContext *avioctx{nullptr};
+ AVIOInterruptCB intcb{decode_interrupt_cb, this};
if(avio_open2(&avioctx, mFilename.c_str(), AVIO_FLAG_READ, &intcb, nullptr))
{
std::cerr<< "Failed to open "<<mFilename <<std::endl;
@@ -1392,7 +1412,7 @@ bool MovieState::prepare()
/* Open movie file. If avformat_open_input fails it will automatically free
* this context, so don't set it onto a smart pointer yet.
*/
- AVFormatContext *fmtctx = avformat_alloc_context();
+ AVFormatContext *fmtctx{avformat_alloc_context()};
fmtctx->pb = mIOContext.get();
fmtctx->interrupt_callback = intcb;
if(avformat_open_input(&fmtctx, mFilename.c_str(), nullptr, nullptr) != 0)
@@ -1409,9 +1429,7 @@ bool MovieState::prepare()
return false;
}
- mVideo.schedRefresh(milliseconds(40));
-
- mParseThread = std::thread(std::mem_fn(&MovieState::parse_handler), this);
+ mParseThread = std::thread{std::mem_fn(&MovieState::parse_handler), this};
return true;
}
@@ -1427,7 +1445,7 @@ void MovieState::setTitle(SDL_Window *window)
nanoseconds MovieState::getClock()
{
- if(!mPlaying.load(std::memory_order_relaxed))
+ if(mClockBase == microseconds::min())
return nanoseconds::zero();
return get_avtime() - mClockBase;
}
@@ -1444,25 +1462,25 @@ nanoseconds MovieState::getMasterClock()
nanoseconds MovieState::getDuration()
{ return std::chrono::duration<int64_t,std::ratio<1,AV_TIME_BASE>>(mFormatCtx->duration); }
-int MovieState::streamComponentOpen(int stream_index)
+int MovieState::streamComponentOpen(unsigned int stream_index)
{
- if(stream_index < 0 || (unsigned int)stream_index >= mFormatCtx->nb_streams)
+ if(stream_index >= mFormatCtx->nb_streams)
return -1;
/* Get a pointer to the codec context for the stream, and open the
* associated codec.
*/
- AVCodecCtxPtr avctx(avcodec_alloc_context3(nullptr));
+ AVCodecCtxPtr avctx{avcodec_alloc_context3(nullptr)};
if(!avctx) return -1;
if(avcodec_parameters_to_context(avctx.get(), mFormatCtx->streams[stream_index]->codecpar))
return -1;
- AVCodec *codec = avcodec_find_decoder(avctx->codec_id);
+ AVCodec *codec{avcodec_find_decoder(avctx->codec_id)};
if(!codec || avcodec_open2(avctx.get(), codec, nullptr) < 0)
{
std::cerr<< "Unsupported codec: "<<avcodec_get_name(avctx->codec_id)
- << " (0x"<<std::hex<<avctx->codec_id<<std::dec<<")" <<std::endl;
+ << " (0x"<<std::hex<<avctx->codec_id<<std::dec<<")" <<std::endl;
return -1;
}
@@ -1472,37 +1490,36 @@ int MovieState::streamComponentOpen(int stream_index)
case AVMEDIA_TYPE_AUDIO:
mAudio.mStream = mFormatCtx->streams[stream_index];
mAudio.mCodecCtx = std::move(avctx);
-
- mAudioThread = std::thread(std::mem_fn(&AudioState::handler), &mAudio);
break;
case AVMEDIA_TYPE_VIDEO:
mVideo.mStream = mFormatCtx->streams[stream_index];
mVideo.mCodecCtx = std::move(avctx);
-
- mVideoThread = std::thread(std::mem_fn(&VideoState::handler), &mVideo);
break;
default:
return -1;
}
- return stream_index;
+ return static_cast<int>(stream_index);
}
int MovieState::parse_handler()
{
- int video_index = -1;
- int audio_index = -1;
+ auto &audio_queue = mAudio.mPackets;
+ auto &video_queue = mVideo.mPackets;
+
+ int video_index{-1};
+ int audio_index{-1};
/* Dump information about file onto standard error */
av_dump_format(mFormatCtx.get(), 0, mFilename.c_str(), 0);
/* Find the first video and audio streams */
- for(unsigned int i = 0;i < mFormatCtx->nb_streams;i++)
+ for(unsigned int i{0u};i < mFormatCtx->nb_streams;i++)
{
auto codecpar = mFormatCtx->streams[i]->codecpar;
- if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0)
+ if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !DisableVideo && video_index < 0)
video_index = streamComponentOpen(i);
else if(codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0)
audio_index = streamComponentOpen(i);
@@ -1514,95 +1531,38 @@ int MovieState::parse_handler()
mQuit = true;
}
- PacketQueue audio_queue, video_queue;
- bool input_finished = false;
+ /* Set the base time 750ms ahead of the current av time. */
+ mClockBase = get_avtime() + milliseconds{750};
+
+ if(audio_index >= 0)
+ mAudioThread = std::thread{std::mem_fn(&AudioState::handler), &mAudio};
+ if(video_index >= 0)
+ mVideoThread = std::thread{std::mem_fn(&VideoState::handler), &mVideo};
/* Main packet reading/dispatching loop */
- while(!mQuit.load(std::memory_order_relaxed) && !input_finished)
+ while(!mQuit.load(std::memory_order_relaxed))
{
AVPacket packet;
if(av_read_frame(mFormatCtx.get(), &packet) < 0)
- input_finished = true;
- else
- {
- /* Copy the packet into the queue it's meant for. */
- if(packet.stream_index == video_index)
- video_queue.put(&packet);
- else if(packet.stream_index == audio_index)
- audio_queue.put(&packet);
- av_packet_unref(&packet);
- }
-
- do {
- /* Send whatever queued packets we have. */
- if(!audio_queue.empty())
- {
- std::unique_lock<std::mutex> lock(mAudio.mQueueMtx);
- int ret;
- do {
- ret = avcodec_send_packet(mAudio.mCodecCtx.get(), audio_queue.front());
- if(ret != AVERROR(EAGAIN)) audio_queue.pop();
- } while(ret != AVERROR(EAGAIN) && !audio_queue.empty());
- lock.unlock();
- mAudio.mQueueCond.notify_one();
- }
- if(!video_queue.empty())
- {
- std::unique_lock<std::mutex> lock(mVideo.mQueueMtx);
- int ret;
- do {
- ret = avcodec_send_packet(mVideo.mCodecCtx.get(), video_queue.front());
- if(ret != AVERROR(EAGAIN)) video_queue.pop();
- } while(ret != AVERROR(EAGAIN) && !video_queue.empty());
- lock.unlock();
- mVideo.mQueueCond.notify_one();
- }
- /* If the queues are completely empty, or it's not full and there's
- * more input to read, go get more.
- */
- size_t queue_size = audio_queue.totalSize() + video_queue.totalSize();
- if(queue_size == 0 || (queue_size < MAX_QUEUE_SIZE && !input_finished))
- break;
+ break;
- if(!mPlaying.load(std::memory_order_relaxed))
- {
- if((!mAudio.mCodecCtx || mAudio.isBufferFilled()) &&
- (!mVideo.mCodecCtx || mVideo.isBufferFilled()))
- {
- /* Set the base time 50ms ahead of the current av time. */
- mClockBase = get_avtime() + milliseconds(50);
- mVideo.mCurrentPtsTime = mClockBase;
- mVideo.mFrameTimer = mVideo.mCurrentPtsTime;
- mAudio.startPlayback();
- mPlaying.store(std::memory_order_release);
- }
- }
- /* Nothing to send or get for now, wait a bit and try again. */
- { std::unique_lock<std::mutex> lock(mSendMtx);
- if(mSendDataGood.test_and_set(std::memory_order_relaxed))
- mSendCond.wait_for(lock, milliseconds(10));
- }
- } while(!mQuit.load(std::memory_order_relaxed));
- }
- /* Pass a null packet to finish the send buffers (the receive functions
- * will get AVERROR_EOF when emptied).
- */
- if(mVideo.mCodecCtx)
- {
- { std::lock_guard<std::mutex> lock(mVideo.mQueueMtx);
- avcodec_send_packet(mVideo.mCodecCtx.get(), nullptr);
+ /* Copy the packet into the queue it's meant for. */
+ if(packet.stream_index == video_index)
+ {
+ while(!mQuit.load(std::memory_order_acquire) && !video_queue.put(&packet))
+ std::this_thread::sleep_for(milliseconds{100});
}
- mVideo.mQueueCond.notify_one();
- }
- if(mAudio.mCodecCtx)
- {
- { std::lock_guard<std::mutex> lock(mAudio.mQueueMtx);
- avcodec_send_packet(mAudio.mCodecCtx.get(), nullptr);
+ else if(packet.stream_index == audio_index)
+ {
+ while(!mQuit.load(std::memory_order_acquire) && !audio_queue.put(&packet))
+ std::this_thread::sleep_for(milliseconds{100});
}
- mAudio.mQueueCond.notify_one();
+
+ av_packet_unref(&packet);
}
- video_queue.clear();
- audio_queue.clear();
+ /* Finish the queues so the receivers know nothing more is coming. */
+ if(mVideo.mCodecCtx) video_queue.setFinished();
+ if(mAudio.mCodecCtx) audio_queue.setFinished();
/* all done - wait for it */
if(mVideoThread.joinable())
@@ -1611,7 +1571,7 @@ int MovieState::parse_handler()
mAudioThread.join();
mVideo.mEOS = true;
- std::unique_lock<std::mutex> lock(mVideo.mPictQMutex);
+ std::unique_lock<std::mutex> lock{mVideo.mPictQMutex};
while(!mVideo.mFinalUpdate)
mVideo.mPictQCond.wait(lock);
lock.unlock();
@@ -1628,13 +1588,13 @@ int MovieState::parse_handler()
struct PrettyTime {
seconds mTime;
};
-inline std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs)
+std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs)
{
using hours = std::chrono::hours;
using minutes = std::chrono::minutes;
using std::chrono::duration_cast;
- seconds t = rhs.mTime;
+ seconds t{rhs.mTime};
if(t.count() < 0)
{
os << '-';
@@ -1642,7 +1602,7 @@ inline std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs)
}
// Only handle up to hour formatting
- if(t >= hours(1))
+ if(t >= hours{1})
os << duration_cast<hours>(t).count() << 'h' << std::setfill('0') << std::setw(2)
<< (duration_cast<minutes>(t).count() % 60) << 'm';
else
@@ -1665,36 +1625,38 @@ int main(int argc, char *argv[])
return 1;
}
/* Register all formats and codecs */
+#if !(LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100))
av_register_all();
+#endif
/* Initialize networking protocols */
avformat_network_init();
- if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER))
+ if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
{
std::cerr<< "Could not initialize SDL - <<"<<SDL_GetError() <<std::endl;
return 1;
}
/* Make a window to put our video */
- SDL_Window *screen = SDL_CreateWindow(AppName.c_str(), 0, 0, 640, 480, SDL_WINDOW_RESIZABLE);
+ SDL_Window *screen{SDL_CreateWindow(AppName.c_str(), 0, 0, 640, 480, SDL_WINDOW_RESIZABLE)};
if(!screen)
{
std::cerr<< "SDL: could not set video mode - exiting" <<std::endl;
return 1;
}
/* Make a renderer to handle the texture image surface and rendering. */
- Uint32 render_flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC;
- SDL_Renderer *renderer = SDL_CreateRenderer(screen, -1, render_flags);
+ Uint32 render_flags{SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC};
+ SDL_Renderer *renderer{SDL_CreateRenderer(screen, -1, render_flags)};
if(renderer)
{
SDL_RendererInfo rinf{};
- bool ok = false;
+ bool ok{false};
/* Make sure the renderer supports IYUV textures. If not, fallback to a
* software renderer. */
if(SDL_GetRendererInfo(renderer, &rinf) == 0)
{
- for(Uint32 i = 0;!ok && i < rinf.num_texture_formats;i++)
+ for(Uint32 i{0u};!ok && i < rinf.num_texture_formats;i++)
ok = (rinf.texture_formats[i] == SDL_PIXELFORMAT_IYUV);
}
if(!ok)
@@ -1726,7 +1688,8 @@ int main(int argc, char *argv[])
return 1;
}
- { auto device = alcGetContextsDevice(alcGetCurrentContext());
+ {
+ auto device = alcGetContextsDevice(alcGetCurrentContext());
if(alcIsExtensionPresent(device, "ALC_SOFT_device_clock"))
{
std::cout<< "Found ALC_SOFT_device_clock" <<std::endl;
@@ -1766,7 +1729,7 @@ int main(int argc, char *argv[])
}
#endif
- int fileidx = 0;
+ int fileidx{0};
for(;fileidx < argc;++fileidx)
{
if(strcmp(argv[fileidx], "-direct") == 0)
@@ -1789,13 +1752,15 @@ int main(int argc, char *argv[])
EnableWideStereo = true;
}
}
+ else if(strcmp(argv[fileidx], "-novideo") == 0)
+ DisableVideo = true;
else
break;
}
while(fileidx < argc && !movState)
{
- movState = std::unique_ptr<MovieState>(new MovieState(argv[fileidx++]));
+ movState = std::unique_ptr<MovieState>{new MovieState{argv[fileidx++]}};
if(!movState->prepare()) movState = nullptr;
}
if(!movState)
@@ -1808,106 +1773,103 @@ int main(int argc, char *argv[])
/* Default to going to the next movie at the end of one. */
enum class EomAction {
Next, Quit
- } eom_action = EomAction::Next;
- seconds last_time(-1);
- SDL_Event event;
+ } eom_action{EomAction::Next};
+ seconds last_time{seconds::min()};
while(1)
{
- int have_evt = SDL_WaitEventTimeout(&event, 10);
+ SDL_Event event{};
+ int have_evt{SDL_WaitEventTimeout(&event, 10)};
auto cur_time = std::chrono::duration_cast<seconds>(movState->getMasterClock());
if(cur_time != last_time)
{
auto end_time = std::chrono::duration_cast<seconds>(movState->getDuration());
- std::cout<< "\r "<<PrettyTime{cur_time}<<" / "<<PrettyTime{end_time} <<std::flush;
+ std::cout<< " \r "<<PrettyTime{cur_time}<<" / "<<PrettyTime{end_time} <<std::flush;
last_time = cur_time;
}
- if(!have_evt) continue;
-
- switch(event.type)
- {
- case SDL_KEYDOWN:
- switch(event.key.keysym.sym)
- {
- case SDLK_ESCAPE:
- movState->mQuit = true;
- eom_action = EomAction::Quit;
- break;
-
- case SDLK_n:
- movState->mQuit = true;
- eom_action = EomAction::Next;
- break;
-
- default:
- break;
- }
- break;
-
- case SDL_WINDOWEVENT:
- switch(event.window.event)
- {
- case SDL_WINDOWEVENT_RESIZED:
- SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
- SDL_RenderFillRect(renderer, nullptr);
- break;
-
- default:
- break;
- }
- break;
-
- case SDL_QUIT:
- movState->mQuit = true;
- eom_action = EomAction::Quit;
- break;
-
- case FF_UPDATE_EVENT:
- reinterpret_cast<VideoState*>(event.user.data1)->updatePicture(
- screen, renderer
- );
- break;
- case FF_REFRESH_EVENT:
- reinterpret_cast<VideoState*>(event.user.data1)->refreshTimer(
- screen, renderer
- );
- break;
+ bool force_redraw{false};
+ if(have_evt) do {
+ switch(event.type)
+ {
+ case SDL_KEYDOWN:
+ switch(event.key.keysym.sym)
+ {
+ case SDLK_ESCAPE:
+ movState->mQuit = true;
+ eom_action = EomAction::Quit;
+ break;
+
+ case SDLK_n:
+ movState->mQuit = true;
+ eom_action = EomAction::Next;
+ break;
+
+ default:
+ break;
+ }
+ break;
- case FF_MOVIE_DONE_EVENT:
- std::cout<<'\n';
- last_time = seconds(-1);
- if(eom_action != EomAction::Quit)
- {
- movState = nullptr;
- while(fileidx < argc && !movState)
+ case SDL_WINDOWEVENT:
+ switch(event.window.event)
{
- movState = std::unique_ptr<MovieState>(new MovieState(argv[fileidx++]));
- if(!movState->prepare()) movState = nullptr;
+ case SDL_WINDOWEVENT_RESIZED:
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderFillRect(renderer, nullptr);
+ force_redraw = true;
+ break;
+
+ case SDL_WINDOWEVENT_EXPOSED:
+ force_redraw = true;
+ break;
+
+ default:
+ break;
}
- if(movState)
+ break;
+
+ case SDL_QUIT:
+ movState->mQuit = true;
+ eom_action = EomAction::Quit;
+ break;
+
+ case FF_MOVIE_DONE_EVENT:
+ std::cout<<'\n';
+ last_time = seconds::min();
+ if(eom_action != EomAction::Quit)
{
- movState->setTitle(screen);
- break;
+ movState = nullptr;
+ while(fileidx < argc && !movState)
+ {
+ movState = std::unique_ptr<MovieState>{new MovieState{argv[fileidx++]}};
+ if(!movState->prepare()) movState = nullptr;
+ }
+ if(movState)
+ {
+ movState->setTitle(screen);
+ break;
+ }
}
- }
- /* Nothing more to play. Shut everything down and quit. */
- movState = nullptr;
+ /* Nothing more to play. Shut everything down and quit. */
+ movState = nullptr;
- CloseAL();
+ CloseAL();
- SDL_DestroyRenderer(renderer);
- renderer = nullptr;
- SDL_DestroyWindow(screen);
- screen = nullptr;
+ SDL_DestroyRenderer(renderer);
+ renderer = nullptr;
+ SDL_DestroyWindow(screen);
+ screen = nullptr;
- SDL_Quit();
- exit(0);
+ SDL_Quit();
+ exit(0);
- default:
- break;
- }
+ default:
+ break;
+ }
+ } while(SDL_PollEvent(&event));
+
+ movState->mVideo.updateVideo(screen, renderer, force_redraw);
}
std::cerr<< "SDL_WaitEvent error - "<<SDL_GetError() <<std::endl;
diff --git a/examples/alhrtf.c b/examples/alhrtf.c
index f9150ae1..2be28a91 100644
--- a/examples/alhrtf.c
+++ b/examples/alhrtf.c
@@ -24,11 +24,14 @@
/* This file contains an example for selecting an HRTF. */
-#include <stdio.h>
#include <assert.h>
#include <math.h>
+#include <stdio.h>
+#include <string.h>
-#include <SDL_sound.h>
+#include "SDL_sound.h"
+#include "SDL_audio.h"
+#include "SDL_stdinc.h"
#include "AL/al.h"
#include "AL/alc.h"
@@ -109,7 +112,7 @@ static ALuint LoadSound(const char *filename)
* close the file. */
buffer = 0;
alGenBuffers(1, &buffer);
- alBufferData(buffer, format, sample->buffer, slen, sample->actual.rate);
+ alBufferData(buffer, format, sample->buffer, (ALsizei)slen, (ALsizei)sample->actual.rate);
Sound_FreeSample(sample);
/* Check if an error occured, and clean up if so. */
@@ -129,6 +132,7 @@ static ALuint LoadSound(const char *filename)
int main(int argc, char **argv)
{
ALCdevice *device;
+ ALCcontext *context;
ALboolean has_angle_ext;
ALuint source, buffer;
const char *soundname;
@@ -150,7 +154,8 @@ int main(int argc, char **argv)
if(InitAL(&argv, &argc) != 0)
return 1;
- device = alcGetContextsDevice(alcGetCurrentContext());
+ context = alcGetCurrentContext();
+ device = alcGetContextsDevice(context);
if(!alcIsExtensionPresent(device, "ALC_SOFT_HRTF"))
{
fprintf(stderr, "Error: ALC_SOFT_HRTF not supported\n");
@@ -159,16 +164,16 @@ int main(int argc, char **argv)
}
/* Define a macro to help load the function pointers. */
-#define LOAD_PROC(d, x) ((x) = alcGetProcAddress((d), #x))
- LOAD_PROC(device, alcGetStringiSOFT);
- LOAD_PROC(device, alcResetDeviceSOFT);
+#define LOAD_PROC(d, T, x) ((x) = (T)alcGetProcAddress((d), #x))
+ LOAD_PROC(device, LPALCGETSTRINGISOFT, alcGetStringiSOFT);
+ LOAD_PROC(device, LPALCRESETDEVICESOFT, alcResetDeviceSOFT);
#undef LOAD_PROC
/* Check for the AL_EXT_STEREO_ANGLES extension to be able to also rotate
* stereo sources.
*/
has_angle_ext = alIsExtensionPresent("AL_EXT_STEREO_ANGLES");
- printf("AL_EXT_STEREO_ANGLES%s found\n", has_angle_ext?"":" not");
+ printf("AL_EXT_STEREO_ANGLES %sfound\n", has_angle_ext?"":"not ");
/* Check for user-preferred HRTF */
if(strcmp(argv[0], "-hrtf") == 0)
@@ -252,7 +257,7 @@ int main(int argc, char **argv)
alGenSources(1, &source);
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(source, AL_POSITION, 0.0f, 0.0f, -1.0f);
- alSourcei(source, AL_BUFFER, buffer);
+ alSourcei(source, AL_BUFFER, (ALint)buffer);
assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source");
/* Play the sound until it finishes. */
@@ -261,6 +266,8 @@ int main(int argc, char **argv)
do {
al_nssleep(10000000);
+ alcSuspendContext(context);
+
/* Rotate the source around the listener by about 1/4 cycle per second,
* and keep it within -pi...+pi.
*/
@@ -279,6 +286,7 @@ int main(int argc, char **argv)
ALfloat angles[2] = { (ALfloat)(M_PI/6.0 - angle), (ALfloat)(-M_PI/6.0 - angle) };
alSourcefv(source, AL_STEREO_ANGLES, angles);
}
+ alcProcessContext(context);
alGetSourcei(source, AL_SOURCE_STATE, &state);
} while(alGetError() == AL_NO_ERROR && state == AL_PLAYING);
diff --git a/examples/allatency.c b/examples/allatency.c
index d561373f..a61fb820 100644
--- a/examples/allatency.c
+++ b/examples/allatency.c
@@ -27,10 +27,11 @@
#include <stdio.h>
#include <assert.h>
-#include <SDL_sound.h>
+#include "SDL_sound.h"
+#include "SDL_audio.h"
+#include "SDL_stdinc.h"
#include "AL/al.h"
-#include "AL/alc.h"
#include "AL/alext.h"
#include "common/alhelpers.h"
@@ -114,7 +115,7 @@ static ALuint LoadSound(const char *filename)
* close the file. */
buffer = 0;
alGenBuffers(1, &buffer);
- alBufferData(buffer, format, sample->buffer, slen, sample->actual.rate);
+ alBufferData(buffer, format, sample->buffer, (ALsizei)slen, (ALsizei)sample->actual.rate);
Sound_FreeSample(sample);
/* Check if an error occured, and clean up if so. */
@@ -157,19 +158,19 @@ int main(int argc, char **argv)
}
/* Define a macro to help load the function pointers. */
-#define LOAD_PROC(x) ((x) = alGetProcAddress(#x))
- LOAD_PROC(alSourcedSOFT);
- LOAD_PROC(alSource3dSOFT);
- LOAD_PROC(alSourcedvSOFT);
- LOAD_PROC(alGetSourcedSOFT);
- LOAD_PROC(alGetSource3dSOFT);
- LOAD_PROC(alGetSourcedvSOFT);
- LOAD_PROC(alSourcei64SOFT);
- LOAD_PROC(alSource3i64SOFT);
- LOAD_PROC(alSourcei64vSOFT);
- LOAD_PROC(alGetSourcei64SOFT);
- LOAD_PROC(alGetSource3i64SOFT);
- LOAD_PROC(alGetSourcei64vSOFT);
+#define LOAD_PROC(T, x) ((x) = (T)alGetProcAddress(#x))
+ LOAD_PROC(LPALSOURCEDSOFT, alSourcedSOFT);
+ LOAD_PROC(LPALSOURCE3DSOFT, alSource3dSOFT);
+ LOAD_PROC(LPALSOURCEDVSOFT, alSourcedvSOFT);
+ LOAD_PROC(LPALGETSOURCEDSOFT, alGetSourcedSOFT);
+ LOAD_PROC(LPALGETSOURCE3DSOFT, alGetSource3dSOFT);
+ LOAD_PROC(LPALGETSOURCEDVSOFT, alGetSourcedvSOFT);
+ LOAD_PROC(LPALSOURCEI64SOFT, alSourcei64SOFT);
+ LOAD_PROC(LPALSOURCE3I64SOFT, alSource3i64SOFT);
+ LOAD_PROC(LPALSOURCEI64VSOFT, alSourcei64vSOFT);
+ LOAD_PROC(LPALGETSOURCEI64SOFT, alGetSourcei64SOFT);
+ LOAD_PROC(LPALGETSOURCE3I64SOFT, alGetSource3i64SOFT);
+ LOAD_PROC(LPALGETSOURCEI64VSOFT, alGetSourcei64vSOFT);
#undef LOAD_PROC
/* Initialize SDL_sound. */
@@ -187,7 +188,7 @@ int main(int argc, char **argv)
/* Create the source to play the sound with. */
source = 0;
alGenSources(1, &source);
- alSourcei(source, AL_BUFFER, buffer);
+ alSourcei(source, AL_BUFFER, (ALint)buffer);
assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source");
/* Play the sound until it finishes. */
diff --git a/examples/alloopback.c b/examples/alloopback.c
index 16553f9b..844efa74 100644
--- a/examples/alloopback.c
+++ b/examples/alloopback.c
@@ -26,11 +26,14 @@
* output handling.
*/
-#include <stdio.h>
#include <assert.h>
#include <math.h>
+#include <stdio.h>
-#include <SDL.h>
+#include "SDL.h"
+#include "SDL_audio.h"
+#include "SDL_error.h"
+#include "SDL_stdinc.h"
#include "AL/al.h"
#include "AL/alc.h"
@@ -146,10 +149,10 @@ int main(int argc, char *argv[])
}
/* Define a macro to help load the function pointers. */
-#define LOAD_PROC(x) ((x) = alcGetProcAddress(NULL, #x))
- LOAD_PROC(alcLoopbackOpenDeviceSOFT);
- LOAD_PROC(alcIsRenderFormatSupportedSOFT);
- LOAD_PROC(alcRenderSamplesSOFT);
+#define LOAD_PROC(T, x) ((x) = (T)alcGetProcAddress(NULL, #x))
+ LOAD_PROC(LPALCLOOPBACKOPENDEVICESOFT, alcLoopbackOpenDeviceSOFT);
+ LOAD_PROC(LPALCISRENDERFORMATSUPPORTEDSOFT, alcIsRenderFormatSupportedSOFT);
+ LOAD_PROC(LPALCRENDERSAMPLESSOFT, alcRenderSamplesSOFT);
#undef LOAD_PROC
if(SDL_Init(SDL_INIT_AUDIO) == -1)
@@ -246,7 +249,7 @@ int main(int argc, char *argv[])
/* Create the source to play the sound with. */
source = 0;
alGenSources(1, &source);
- alSourcei(source, AL_BUFFER, buffer);
+ alSourcei(source, AL_BUFFER, (ALint)buffer);
assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source");
/* Play the sound until it finishes. */
diff --git a/examples/almultireverb.c b/examples/almultireverb.c
index a2587585..a90b3368 100644
--- a/examples/almultireverb.c
+++ b/examples/almultireverb.c
@@ -29,15 +29,18 @@
* listener.
*/
-#include <stdio.h>
#include <assert.h>
#include <math.h>
+#include <stdio.h>
+#include <string.h>
-#include <SDL_sound.h>
+#include "SDL_sound.h"
+#include "SDL_audio.h"
+#include "SDL_stdinc.h"
#include "AL/al.h"
#include "AL/alc.h"
-#include "AL/alext.h"
+#include "AL/efx.h"
#include "AL/efx-presets.h"
#include "common/alhelpers.h"
@@ -208,7 +211,7 @@ static ALuint LoadSound(const char *filename)
* close the file. */
buffer = 0;
alGenBuffers(1, &buffer);
- alBufferData(buffer, format, sample->buffer, slen, sample->actual.rate);
+ alBufferData(buffer, format, sample->buffer, (ALsizei)slen, (ALsizei)sample->actual.rate);
Sound_FreeSample(sample);
/* Check if an error occured, and clean up if so. */
@@ -440,8 +443,8 @@ static void UpdateListenerAndEffects(float timediff, const ALuint slots[2], cons
}
/* Finally, update the effect slots with the updated effect parameters. */
- alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, effects[0]);
- alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, effects[1]);
+ alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, (ALint)effects[0]);
+ alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, (ALint)effects[1]);
}
@@ -452,7 +455,6 @@ int main(int argc, char **argv)
EFX_REVERB_PRESET_CARPETEDHALLWAY,
EFX_REVERB_PRESET_BATHROOM
};
- struct timespec basetime;
ALCdevice *device = NULL;
ALCcontext *context = NULL;
ALuint effects[2] = { 0, 0 };
@@ -463,6 +465,7 @@ int main(int argc, char **argv)
ALCint num_sends = 0;
ALenum state = AL_INITIAL;
ALfloat direct_gain = 1.0f;
+ int basetime = 0;
int loops = 0;
/* Print out usage if no arguments were specified */
@@ -520,42 +523,42 @@ int main(int argc, char **argv)
}
/* Define a macro to help load the function pointers. */
-#define LOAD_PROC(x) ((x) = alGetProcAddress(#x))
- LOAD_PROC(alGenFilters);
- LOAD_PROC(alDeleteFilters);
- LOAD_PROC(alIsFilter);
- LOAD_PROC(alFilteri);
- LOAD_PROC(alFilteriv);
- LOAD_PROC(alFilterf);
- LOAD_PROC(alFilterfv);
- LOAD_PROC(alGetFilteri);
- LOAD_PROC(alGetFilteriv);
- LOAD_PROC(alGetFilterf);
- LOAD_PROC(alGetFilterfv);
-
- LOAD_PROC(alGenEffects);
- LOAD_PROC(alDeleteEffects);
- LOAD_PROC(alIsEffect);
- LOAD_PROC(alEffecti);
- LOAD_PROC(alEffectiv);
- LOAD_PROC(alEffectf);
- LOAD_PROC(alEffectfv);
- LOAD_PROC(alGetEffecti);
- LOAD_PROC(alGetEffectiv);
- LOAD_PROC(alGetEffectf);
- LOAD_PROC(alGetEffectfv);
-
- LOAD_PROC(alGenAuxiliaryEffectSlots);
- LOAD_PROC(alDeleteAuxiliaryEffectSlots);
- LOAD_PROC(alIsAuxiliaryEffectSlot);
- LOAD_PROC(alAuxiliaryEffectSloti);
- LOAD_PROC(alAuxiliaryEffectSlotiv);
- LOAD_PROC(alAuxiliaryEffectSlotf);
- LOAD_PROC(alAuxiliaryEffectSlotfv);
- LOAD_PROC(alGetAuxiliaryEffectSloti);
- LOAD_PROC(alGetAuxiliaryEffectSlotiv);
- LOAD_PROC(alGetAuxiliaryEffectSlotf);
- LOAD_PROC(alGetAuxiliaryEffectSlotfv);
+#define LOAD_PROC(T, x) ((x) = (T)alGetProcAddress(#x))
+ LOAD_PROC(LPALGENFILTERS, alGenFilters);
+ LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters);
+ LOAD_PROC(LPALISFILTER, alIsFilter);
+ LOAD_PROC(LPALFILTERI, alFilteri);
+ LOAD_PROC(LPALFILTERIV, alFilteriv);
+ LOAD_PROC(LPALFILTERF, alFilterf);
+ LOAD_PROC(LPALFILTERFV, alFilterfv);
+ LOAD_PROC(LPALGETFILTERI, alGetFilteri);
+ LOAD_PROC(LPALGETFILTERIV, alGetFilteriv);
+ LOAD_PROC(LPALGETFILTERF, alGetFilterf);
+ LOAD_PROC(LPALGETFILTERFV, alGetFilterfv);
+
+ LOAD_PROC(LPALGENEFFECTS, alGenEffects);
+ LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects);
+ LOAD_PROC(LPALISEFFECT, alIsEffect);
+ LOAD_PROC(LPALEFFECTI, alEffecti);
+ LOAD_PROC(LPALEFFECTIV, alEffectiv);
+ LOAD_PROC(LPALEFFECTF, alEffectf);
+ LOAD_PROC(LPALEFFECTFV, alEffectfv);
+ LOAD_PROC(LPALGETEFFECTI, alGetEffecti);
+ LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv);
+ LOAD_PROC(LPALGETEFFECTF, alGetEffectf);
+ LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv);
+
+ LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots);
+ LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots);
+ LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv);
#undef LOAD_PROC
/* Initialize SDL_sound. */
@@ -595,8 +598,8 @@ int main(int argc, char **argv)
* effect properties. Modifying or deleting the effect object afterward
* won't directly affect the effect slot until they're reapplied like this.
*/
- alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, effects[0]);
- alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, effects[1]);
+ alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, (ALint)effects[0]);
+ alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, (ALint)effects[1]);
assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot");
/* For the purposes of this example, prepare a filter that optionally
@@ -618,8 +621,8 @@ int main(int argc, char **argv)
alGenSources(1, &source);
alSourcei(source, AL_LOOPING, AL_TRUE);
alSource3f(source, AL_POSITION, -5.0f, 0.0f, -2.0f);
- alSourcei(source, AL_DIRECT_FILTER, direct_filter);
- alSourcei(source, AL_BUFFER, buffer);
+ alSourcei(source, AL_DIRECT_FILTER, (ALint)direct_filter);
+ alSourcei(source, AL_BUFFER, (ALint)buffer);
/* Connect the source to the effect slots. Here, we connect source send 0
* to Zone 0's slot, and send 1 to Zone 1's slot. Filters can be specified
@@ -628,19 +631,19 @@ int main(int argc, char **argv)
* can only see a zone through a window or thin wall may be attenuated for
* that zone.
*/
- alSource3i(source, AL_AUXILIARY_SEND_FILTER, slots[0], 0, AL_FILTER_NULL);
- alSource3i(source, AL_AUXILIARY_SEND_FILTER, slots[1], 1, AL_FILTER_NULL);
+ alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)slots[0], 0, AL_FILTER_NULL);
+ alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)slots[1], 1, AL_FILTER_NULL);
assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source");
/* Get the current time as the base for timing in the main loop. */
- altimespec_get(&basetime, AL_TIME_UTC);
+ basetime = altime_get();
loops = 0;
printf("Transition %d of %d...\n", loops+1, MaxTransitions);
/* Play the sound for a while. */
alSourcePlay(source);
do {
- struct timespec curtime;
+ int curtime;
ALfloat timediff;
/* Start a batch update, to ensure all changes apply simultaneously. */
@@ -649,14 +652,13 @@ int main(int argc, char **argv)
/* Get the current time to track the amount of time that passed.
* Convert the difference to seconds.
*/
- altimespec_get(&curtime, AL_TIME_UTC);
- timediff = (ALfloat)(curtime.tv_sec - basetime.tv_sec);
- timediff += (ALfloat)(curtime.tv_nsec - basetime.tv_nsec) / 1000000000.0f;
+ curtime = altime_get();
+ timediff = (float)(curtime - basetime) / 1000.0f;
/* Avoid negative time deltas, in case of non-monotonic clocks. */
if(timediff < 0.0f)
timediff = 0.0f;
- else while(timediff >= 4.0f*((loops&1)+1))
+ else while(timediff >= 4.0f*(float)((loops&1)+1))
{
/* For this example, each transition occurs over 4 seconds, and
* there's 2 transitions per cycle.
@@ -669,7 +671,7 @@ int main(int argc, char **argv)
* time to start a new cycle.
*/
timediff -= 8.0f;
- basetime.tv_sec += 8;
+ basetime += 8000;
}
}
diff --git a/examples/alplay.c b/examples/alplay.c
index 81cb56d5..09ad96b4 100644
--- a/examples/alplay.c
+++ b/examples/alplay.c
@@ -27,10 +27,11 @@
#include <stdio.h>
#include <assert.h>
-#include <SDL_sound.h>
+#include "SDL_sound.h"
+#include "SDL_audio.h"
+#include "SDL_stdinc.h"
#include "AL/al.h"
-#include "AL/alc.h"
#include "common/alhelpers.h"
@@ -100,7 +101,7 @@ static ALuint LoadSound(const char *filename)
* close the file. */
buffer = 0;
alGenBuffers(1, &buffer);
- alBufferData(buffer, format, sample->buffer, slen, sample->actual.rate);
+ alBufferData(buffer, format, sample->buffer, (ALsizei)slen, (ALsizei)sample->actual.rate);
Sound_FreeSample(sample);
/* Check if an error occured, and clean up if so. */
@@ -150,7 +151,7 @@ int main(int argc, char **argv)
/* Create the source to play the sound with. */
source = 0;
alGenSources(1, &source);
- alSourcei(source, AL_BUFFER, buffer);
+ alSourcei(source, AL_BUFFER, (ALint)buffer);
assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source");
/* Play the sound until it finishes. */
diff --git a/examples/alrecord.c b/examples/alrecord.c
index 43b26d35..a66e5471 100644
--- a/examples/alrecord.c
+++ b/examples/alrecord.c
@@ -27,7 +27,7 @@
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
-#include <math.h>
+#include <errno.h>
#include "AL/al.h"
#include "AL/alc.h"
@@ -54,13 +54,14 @@ static float msvc_strtof(const char *str, char **end)
static void fwrite16le(ALushort val, FILE *f)
{
- ALubyte data[2] = { val&0xff, (val>>8)&0xff };
+ ALubyte data[2] = { (ALubyte)(val&0xff), (ALubyte)((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 };
+ ALubyte data[4] = { (ALubyte)(val&0xff), (ALubyte)((val>>8)&0xff), (ALubyte)((val>>16)&0xff),
+ (ALubyte)((val>>24)&0xff) };
fwrite(data, 1, 4, f);
}
@@ -73,9 +74,9 @@ typedef struct Recorder {
ALuint mDataSize;
float mRecTime;
- int mChannels;
- int mBits;
- int mSampleRate;
+ ALuint mChannels;
+ ALuint mBits;
+ ALuint mSampleRate;
ALuint mFrameSize;
ALbyte *mBuffer;
ALsizei mBufferSize;
@@ -133,13 +134,13 @@ int main(int argc, char **argv)
break;
else if(strcmp(argv[0], "--channels") == 0 || strcmp(argv[0], "-c") == 0)
{
- if(!(argc > 1))
+ if(argc < 2)
{
fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
return 1;
}
- recorder.mChannels = strtol(argv[1], &end, 0);
+ recorder.mChannels = (ALuint)strtoul(argv[1], &end, 0);
if((recorder.mChannels != 1 && recorder.mChannels != 2) || (end && *end != '\0'))
{
fprintf(stderr, "Invalid channels: %s\n", argv[1]);
@@ -150,13 +151,13 @@ int main(int argc, char **argv)
}
else if(strcmp(argv[0], "--bits") == 0 || strcmp(argv[0], "-b") == 0)
{
- if(!(argc > 1))
+ if(argc < 2)
{
fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
return 1;
}
- recorder.mBits = strtol(argv[1], &end, 0);
+ recorder.mBits = (ALuint)strtoul(argv[1], &end, 0);
if((recorder.mBits != 8 && recorder.mBits != 16 && recorder.mBits != 32) ||
(end && *end != '\0'))
{
@@ -168,13 +169,13 @@ int main(int argc, char **argv)
}
else if(strcmp(argv[0], "--rate") == 0 || strcmp(argv[0], "-r") == 0)
{
- if(!(argc > 1))
+ if(argc < 2)
{
fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
return 1;
}
- recorder.mSampleRate = strtol(argv[1], &end, 0);
+ recorder.mSampleRate = (ALuint)strtoul(argv[1], &end, 0);
if(!(recorder.mSampleRate >= 8000 && recorder.mSampleRate <= 96000) || (end && *end != '\0'))
{
fprintf(stderr, "Invalid sample rate: %s\n", argv[1]);
@@ -185,7 +186,7 @@ int main(int argc, char **argv)
}
else if(strcmp(argv[0], "--time") == 0 || strcmp(argv[0], "-t") == 0)
{
- if(!(argc > 1))
+ if(argc < 2)
{
fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
return 1;
@@ -202,7 +203,7 @@ int main(int argc, char **argv)
}
else if(strcmp(argv[0], "--outfile") == 0 || strcmp(argv[0], "-o") == 0)
{
- if(!(argc > 1))
+ if(argc < 2)
{
fprintf(stderr, "Missing argument for option: %s\n", argv[0]);
return 1;
@@ -285,15 +286,15 @@ int main(int argc, char **argv)
// 16-bit val, format type id (1 = integer PCM, 3 = float PCM)
fwrite16le((recorder.mBits == 32) ? 0x0003 : 0x0001, recorder.mFile);
// 16-bit val, channel count
- fwrite16le(recorder.mChannels, recorder.mFile);
+ fwrite16le((ALushort)recorder.mChannels, recorder.mFile);
// 32-bit val, frequency
fwrite32le(recorder.mSampleRate, recorder.mFile);
// 32-bit val, bytes per second
fwrite32le(recorder.mSampleRate * recorder.mFrameSize, recorder.mFile);
// 16-bit val, frame size
- fwrite16le(recorder.mFrameSize, recorder.mFile);
+ fwrite16le((ALushort)recorder.mFrameSize, recorder.mFile);
// 16-bit val, bits per sample
- fwrite16le(recorder.mBits, recorder.mFile);
+ fwrite16le((ALushort)recorder.mBits, recorder.mFile);
// 16-bit val, extra byte count
fwrite16le(0, recorder.mFile);
@@ -316,6 +317,7 @@ int main(int argc, char **argv)
recorder.mRecTime, (recorder.mRecTime != 1.0f) ? "s" : ""
);
+ err = ALC_NO_ERROR;
alcCaptureStart(recorder.mDevice);
while((double)recorder.mDataSize/(double)recorder.mSampleRate < recorder.mRecTime &&
(err=alcGetError(recorder.mDevice)) == ALC_NO_ERROR && !ferror(recorder.mFile))
@@ -330,7 +332,7 @@ int main(int argc, char **argv)
}
if(count > recorder.mBufferSize)
{
- ALbyte *data = calloc(recorder.mFrameSize, count);
+ ALbyte *data = calloc(recorder.mFrameSize, (ALuint)count);
free(recorder.mBuffer);
recorder.mBuffer = data;
recorder.mBufferSize = count;
@@ -364,7 +366,7 @@ int main(int argc, char **argv)
}
}
#endif
- recorder.mDataSize += (ALuint)fwrite(recorder.mBuffer, recorder.mFrameSize, count,
+ recorder.mDataSize += (ALuint)fwrite(recorder.mBuffer, recorder.mFrameSize, (ALuint)count,
recorder.mFile);
}
alcCaptureStop(recorder.mDevice);
@@ -384,7 +386,7 @@ int main(int argc, char **argv)
{
fwrite32le(recorder.mDataSize*recorder.mFrameSize, recorder.mFile);
if(fseek(recorder.mFile, 4, SEEK_SET) == 0)
- fwrite32le(total_size - 8, recorder.mFile);
+ fwrite32le((ALuint)total_size - 8, recorder.mFile);
}
fclose(recorder.mFile);
diff --git a/examples/alreverb.c b/examples/alreverb.c
index e6c9e606..d789dffe 100644
--- a/examples/alreverb.c
+++ b/examples/alreverb.c
@@ -27,11 +27,13 @@
#include <stdio.h>
#include <assert.h>
-#include <SDL_sound.h>
+#include "SDL_sound.h"
+#include "SDL_audio.h"
+#include "SDL_stdinc.h"
#include "AL/al.h"
#include "AL/alc.h"
-#include "AL/alext.h"
+#include "AL/efx.h"
#include "AL/efx-presets.h"
#include "common/alhelpers.h"
@@ -207,7 +209,7 @@ static ALuint LoadSound(const char *filename)
* close the file. */
buffer = 0;
alGenBuffers(1, &buffer);
- alBufferData(buffer, format, sample->buffer, slen, sample->actual.rate);
+ alBufferData(buffer, format, sample->buffer, (ALsizei)slen, (ALsizei)sample->actual.rate);
Sound_FreeSample(sample);
/* Check if an error occured, and clean up if so. */
@@ -250,30 +252,30 @@ int main(int argc, char **argv)
}
/* Define a macro to help load the function pointers. */
-#define LOAD_PROC(x) ((x) = alGetProcAddress(#x))
- LOAD_PROC(alGenEffects);
- LOAD_PROC(alDeleteEffects);
- LOAD_PROC(alIsEffect);
- LOAD_PROC(alEffecti);
- LOAD_PROC(alEffectiv);
- LOAD_PROC(alEffectf);
- LOAD_PROC(alEffectfv);
- LOAD_PROC(alGetEffecti);
- LOAD_PROC(alGetEffectiv);
- LOAD_PROC(alGetEffectf);
- LOAD_PROC(alGetEffectfv);
-
- LOAD_PROC(alGenAuxiliaryEffectSlots);
- LOAD_PROC(alDeleteAuxiliaryEffectSlots);
- LOAD_PROC(alIsAuxiliaryEffectSlot);
- LOAD_PROC(alAuxiliaryEffectSloti);
- LOAD_PROC(alAuxiliaryEffectSlotiv);
- LOAD_PROC(alAuxiliaryEffectSlotf);
- LOAD_PROC(alAuxiliaryEffectSlotfv);
- LOAD_PROC(alGetAuxiliaryEffectSloti);
- LOAD_PROC(alGetAuxiliaryEffectSlotiv);
- LOAD_PROC(alGetAuxiliaryEffectSlotf);
- LOAD_PROC(alGetAuxiliaryEffectSlotfv);
+#define LOAD_PROC(T, x) ((x) = (T)alGetProcAddress(#x))
+ LOAD_PROC(LPALGENEFFECTS, alGenEffects);
+ LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects);
+ LOAD_PROC(LPALISEFFECT, alIsEffect);
+ LOAD_PROC(LPALEFFECTI, alEffecti);
+ LOAD_PROC(LPALEFFECTIV, alEffectiv);
+ LOAD_PROC(LPALEFFECTF, alEffectf);
+ LOAD_PROC(LPALEFFECTFV, alEffectfv);
+ LOAD_PROC(LPALGETEFFECTI, alGetEffecti);
+ LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv);
+ LOAD_PROC(LPALGETEFFECTF, alGetEffectf);
+ LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv);
+
+ LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots);
+ LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots);
+ LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf);
+ LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf);
+ LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv);
#undef LOAD_PROC
/* Initialize SDL_sound. */
@@ -307,18 +309,18 @@ int main(int argc, char **argv)
* effectively copies the effect properties. You can modify or delete the
* effect object afterward without affecting the effect slot.
*/
- alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, effect);
+ alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, (ALint)effect);
assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot");
/* Create the source to play the sound with. */
source = 0;
alGenSources(1, &source);
- alSourcei(source, AL_BUFFER, buffer);
+ alSourcei(source, AL_BUFFER, (ALint)buffer);
/* Connect the source to the effect slot. This tells the source to use the
* effect slot 'slot', on send #0 with the AL_FILTER_NULL filter object.
*/
- alSource3i(source, AL_AUXILIARY_SEND_FILTER, slot, 0, AL_FILTER_NULL);
+ alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)slot, 0, AL_FILTER_NULL);
assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source");
/* Play the sound until it finishes. */
diff --git a/examples/alstream.c b/examples/alstream.c
index 68115e8d..56505ddb 100644
--- a/examples/alstream.c
+++ b/examples/alstream.c
@@ -27,14 +27,13 @@
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
-#include <signal.h>
#include <assert.h>
-#include <SDL_sound.h>
+#include "SDL_sound.h"
+#include "SDL_audio.h"
+#include "SDL_stdinc.h"
#include "AL/al.h"
-#include "AL/alc.h"
-#include "AL/alext.h"
#include "common/alhelpers.h"
@@ -161,10 +160,10 @@ static int OpenPlayerFile(StreamPlayer *player, const char *filename)
fprintf(stderr, "Unsupported channel count: %d\n", player->sample->actual.channels);
goto error;
}
- player->srate = player->sample->actual.rate;
+ player->srate = (ALsizei)player->sample->actual.rate;
frame_size = player->sample->actual.channels *
- SDL_AUDIO_BITSIZE(player->sample->actual.format) / 8;
+ SDL_AUDIO_BITSIZE(player->sample->actual.format) / 8;
/* Set the buffer size, given the desired millisecond length. */
Sound_SetBufferSize(player->sample, (Uint32)((Uint64)player->srate*BUFFER_TIME_MS/1000) *
@@ -192,7 +191,7 @@ static void ClosePlayerFile(StreamPlayer *player)
/* Prebuffers some audio from the file, and starts playing the source */
static int StartPlayer(StreamPlayer *player)
{
- size_t i;
+ ALsizei i;
/* Rewind the source position and clear the buffer queue */
alSourceRewind(player->source);
@@ -205,8 +204,8 @@ static int StartPlayer(StreamPlayer *player)
Uint32 slen = Sound_Decode(player->sample);
if(slen == 0) break;
- alBufferData(player->buffers[i], player->format,
- player->sample->buffer, slen, player->srate);
+ alBufferData(player->buffers[i], player->format, player->sample->buffer, (ALsizei)slen,
+ player->srate);
}
if(alGetError() != AL_NO_ERROR)
{
@@ -256,8 +255,8 @@ static int UpdatePlayer(StreamPlayer *player)
slen = Sound_Decode(player->sample);
if(slen > 0)
{
- alBufferData(bufid, player->format, player->sample->buffer, slen,
- player->srate);
+ alBufferData(bufid, player->format, player->sample->buffer, (ALsizei)slen,
+ player->srate);
alSourceQueueBuffers(player->source, 1, &bufid);
}
if(alGetError() != AL_NO_ERROR)
@@ -324,8 +323,7 @@ int main(int argc, char **argv)
else
namepart = argv[i];
- printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format),
- player->srate);
+ printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format), player->srate);
fflush(stdout);
if(!StartPlayer(player))
diff --git a/examples/altonegen.c b/examples/altonegen.c
index 628e695d..553bc996 100644
--- a/examples/altonegen.c
+++ b/examples/altonegen.c
@@ -82,7 +82,10 @@ static void ApplySin(ALfloat *data, ALdouble g, ALuint srate, ALuint freq)
ALdouble smps_per_cycle = (ALdouble)srate / freq;
ALuint i;
for(i = 0;i < srate;i++)
- data[i] += (ALfloat)(sin(i/smps_per_cycle * 2.0*M_PI) * g);
+ {
+ ALdouble ival;
+ data[i] += (ALfloat)(sin(modf(i/smps_per_cycle, &ival) * 2.0*M_PI) * g);
+ }
}
/* Generates waveforms using additive synthesis. Each waveform is constructed
@@ -91,13 +94,13 @@ static void ApplySin(ALfloat *data, ALdouble g, ALuint srate, ALuint freq)
static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate)
{
ALuint seed = 22222;
- ALint data_size;
+ ALuint data_size;
ALfloat *data;
ALuint buffer;
ALenum err;
ALuint i;
- data_size = srate * sizeof(ALfloat);
+ data_size = (ALuint)(srate * sizeof(ALfloat));
data = calloc(1, data_size);
switch(type)
{
@@ -142,7 +145,7 @@ static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate)
/* Buffer the audio data into a new buffer object. */
buffer = 0;
alGenBuffers(1, &buffer);
- alBufferData(buffer, AL_FORMAT_MONO_FLOAT32, data, data_size, srate);
+ alBufferData(buffer, AL_FORMAT_MONO_FLOAT32, data, (ALsizei)data_size, (ALsizei)srate);
free(data);
/* Check if an error occured, and clean up if so. */
@@ -257,7 +260,7 @@ int main(int argc, char *argv[])
srate = dev_rate;
/* Load the sound into a buffer. */
- buffer = CreateWave(wavetype, tone_freq, srate);
+ buffer = CreateWave(wavetype, (ALuint)tone_freq, (ALuint)srate);
if(!buffer)
{
CloseAL();
@@ -271,7 +274,7 @@ int main(int argc, char *argv[])
/* Create the source to play the sound with. */
source = 0;
alGenSources(1, &source);
- alSourcei(source, AL_BUFFER, buffer);
+ alSourcei(source, AL_BUFFER, (ALint)buffer);
assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source");
/* Play the sound for a while. */
diff --git a/examples/common/alhelpers.c b/examples/common/alhelpers.c
index fab039e9..0febef43 100644
--- a/examples/common/alhelpers.c
+++ b/examples/common/alhelpers.c
@@ -28,14 +28,14 @@
* finding an appropriate buffer format, and getting readable strings for
* channel configs and sample types. */
+#include "alhelpers.h"
+
#include <stdio.h>
+#include <errno.h>
#include <string.h>
#include "AL/al.h"
#include "AL/alc.h"
-#include "AL/alext.h"
-
-#include "alhelpers.h"
/* InitAL opens a device and sets up a context using default attributes, making
@@ -114,3 +114,71 @@ const char *FormatName(ALenum format)
}
return "Unknown Format";
}
+
+
+#ifdef _WIN32
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <mmsystem.h>
+
+int altime_get(void)
+{
+ static int start_time = 0;
+ int cur_time;
+ union {
+ FILETIME ftime;
+ ULARGE_INTEGER ulint;
+ } systime;
+ GetSystemTimeAsFileTime(&systime.ftime);
+ /* FILETIME is in 100-nanosecond units, or 1/10th of a microsecond. */
+ cur_time = (int)(systime.ulint.QuadPart/10000);
+
+ if(!start_time)
+ start_time = cur_time;
+ return cur_time - start_time;
+}
+
+void al_nssleep(unsigned long nsec)
+{
+ Sleep(nsec / 1000000);
+}
+
+#else
+
+#include <sys/time.h>
+#include <unistd.h>
+#include <time.h>
+
+int altime_get(void)
+{
+ static int start_time = 0u;
+ int cur_time;
+
+#if _POSIX_TIMERS > 0
+ struct timespec ts;
+ int ret = clock_gettime(CLOCK_REALTIME, &ts);
+ if(ret != 0) return 0;
+ cur_time = (int)(ts.tv_sec*1000 + ts.tv_nsec/1000000);
+#else /* _POSIX_TIMERS > 0 */
+ struct timeval tv;
+ int ret = gettimeofday(&tv, NULL);
+ if(ret != 0) return 0;
+ cur_time = (int)(tv.tv_sec*1000 + tv.tv_usec/1000);
+#endif
+
+ if(!start_time)
+ start_time = cur_time;
+ return cur_time - start_time;
+}
+
+void al_nssleep(unsigned long nsec)
+{
+ struct timespec ts, rem;
+ ts.tv_sec = (time_t)(nsec / 1000000000ul);
+ ts.tv_nsec = (long)(nsec % 1000000000ul);
+ while(nanosleep(&ts, &rem) == -1 && errno == EINTR)
+ ts = rem;
+}
+
+#endif
diff --git a/examples/common/alhelpers.h b/examples/common/alhelpers.h
index 41a7ce58..3752d218 100644
--- a/examples/common/alhelpers.h
+++ b/examples/common/alhelpers.h
@@ -1,15 +1,11 @@
#ifndef ALHELPERS_H
#define ALHELPERS_H
-#include "AL/alc.h"
#include "AL/al.h"
-#include "AL/alext.h"
-
-#include "threads.h"
#ifdef __cplusplus
extern "C" {
-#endif /* __cplusplus */
+#endif
/* Some helper functions to get the name from the format enums. */
const char *FormatName(ALenum type);
@@ -18,8 +14,12 @@ const char *FormatName(ALenum type);
int InitAL(char ***argv, int *argc);
void CloseAL(void);
+/* Cross-platform timeget and sleep functions. */
+int altime_get(void);
+void al_nssleep(unsigned long nsec);
+
#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+} // extern "C"
+#endif
#endif /* ALHELPERS_H */
diff --git a/include/AL/alc.h b/include/AL/alc.h
index 294e8b33..5786bad2 100644
--- a/include/AL/alc.h
+++ b/include/AL/alc.h
@@ -31,9 +31,9 @@ extern "C" {
#define ALC_VERSION_0_1 1
/** Opaque device handle */
-typedef struct ALCdevice_struct ALCdevice;
+typedef struct ALCdevice ALCdevice;
/** Opaque context handle */
-typedef struct ALCcontext_struct ALCcontext;
+typedef struct ALCcontext ALCcontext;
/** 8-bit boolean */
typedef char ALCboolean;
diff --git a/include/AL/efx.h b/include/AL/efx.h
index 57766983..34085651 100644
--- a/include/AL/efx.h
+++ b/include/AL/efx.h
@@ -1,6 +1,7 @@
#ifndef AL_EFX_H
#define AL_EFX_H
+#include <float.h>
#include "alc.h"
#include "al.h"
diff --git a/native-tools/bsincgen.c b/native-tools/bsincgen.c
index 72bd8183..7e7a1fa3 100644
--- a/native-tools/bsincgen.c
+++ b/native-tools/bsincgen.c
@@ -43,26 +43,22 @@
#ifndef M_PI
-#define M_PI (3.14159265358979323846)
+#define M_PI 3.14159265358979323846
#endif
#if !(defined(_ISOC99_SOURCE) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L))
#define log2(x) (log(x) / log(2.0))
#endif
-/* Same as in alu.h! */
-#define FRACTIONBITS (12)
-#define FRACTIONONE (1<<FRACTIONBITS)
-
// The number of distinct scale and phase intervals within the filter table.
// Must be the same as in alu.h!
-#define BSINC_SCALE_COUNT (16)
-#define BSINC_PHASE_COUNT (16)
+#define BSINC_SCALE_COUNT 16
+#define BSINC_PHASE_COUNT 32
/* 48 points includes the doubling for downsampling, so the maximum number of
* base sample points is 24, which is 23rd order.
*/
-#define BSINC_POINTS_MAX (48)
+#define BSINC_POINTS_MAX 48
static double MinDouble(double a, double b)
{ return (a <= b) ? a : b; }
@@ -141,8 +137,8 @@ static double CalcKaiserBeta(const double rejection)
static void BsiGenerateTables(FILE *output, const char *tabname, const double rejection, const int order)
{
static double filter[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT + 1][BSINC_POINTS_MAX];
- static double scDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT ][BSINC_POINTS_MAX];
static double phDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT + 1][BSINC_POINTS_MAX];
+ static double scDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT ][BSINC_POINTS_MAX];
static double spDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT ][BSINC_POINTS_MAX];
static int mt[BSINC_SCALE_COUNT];
static double at[BSINC_SCALE_COUNT];
@@ -151,8 +147,8 @@ static void BsiGenerateTables(FILE *output, const char *tabname, const double re
int si, pi, i;
memset(filter, 0, sizeof(filter));
- memset(scDeltas, 0, sizeof(scDeltas));
memset(phDeltas, 0, sizeof(phDeltas));
+ memset(scDeltas, 0, sizeof(scDeltas));
memset(spDeltas, 0, sizeof(spDeltas));
/* Calculate windowing parameters. The width describes the transition
@@ -176,8 +172,8 @@ static void BsiGenerateTables(FILE *output, const char *tabname, const double re
}
/* Calculate the Kaiser-windowed Sinc filter coefficients for each scale
- and phase.
- */
+ * and phase.
+ */
for(si = 0; si < BSINC_SCALE_COUNT; si++)
{
const int m = mt[si];
@@ -199,13 +195,10 @@ static void BsiGenerateTables(FILE *output, const char *tabname, const double re
}
}
- /* Linear interpolation between scales is simplified by pre-calculating
- the delta (b - a) in: x = a + f (b - a)
-
- Given a difference in points between scales, the destination points
- will be 0, thus: x = a + f (-a)
- */
- for(si = 0; si < (BSINC_SCALE_COUNT - 1); si++)
+ /* Linear interpolation between phases is simplified by pre-calculating the
+ * delta (b - a) in: x = a + f (b - a)
+ */
+ for(si = 0; si < BSINC_SCALE_COUNT; si++)
{
const int m = mt[si];
const int o = num_points_min - (m / 2);
@@ -213,12 +206,16 @@ static void BsiGenerateTables(FILE *output, const char *tabname, const double re
for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
{
for(i = 0; i < m; i++)
- scDeltas[si][pi][o + i] = filter[si + 1][pi][o + i] - filter[si][pi][o + i];
+ phDeltas[si][pi][o + i] = filter[si][pi + 1][o + i] - filter[si][pi][o + i];
}
}
- // Linear interpolation between phases is also simplified.
- for(si = 0; si < BSINC_SCALE_COUNT; si++)
+ /* Linear interpolation between scales is also simplified.
+ *
+ * Given a difference in points between scales, the destination points will
+ * be 0, thus: x = a + f (-a)
+ */
+ for(si = 0; si < (BSINC_SCALE_COUNT - 1); si++)
{
const int m = mt[si];
const int o = num_points_min - (m / 2);
@@ -226,13 +223,13 @@ static void BsiGenerateTables(FILE *output, const char *tabname, const double re
for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
{
for(i = 0; i < m; i++)
- phDeltas[si][pi][o + i] = filter[si][pi + 1][o + i] - filter[si][pi][o + i];
+ scDeltas[si][pi][o + i] = filter[si + 1][pi][o + i] - filter[si][pi][o + i];
}
}
/* This last simplification is done to complete the bilinear equation for
- the combination of scale and phase.
- */
+ * the combination of phase and scale.
+ */
for(si = 0; si < (BSINC_SCALE_COUNT - 1); si++)
{
const int m = mt[si];
@@ -245,10 +242,15 @@ static void BsiGenerateTables(FILE *output, const char *tabname, const double re
}
}
- // Make sure the number of points is a multiple of 4 (for SIMD).
+ /* Make sure the number of points is a multiple of 4 (for SIMD). */
for(si = 0; si < BSINC_SCALE_COUNT; si++)
mt[si] = (mt[si]+3) & ~3;
+ /* Calculate the table size. */
+ i = 0;
+ for(si = 0; si < BSINC_SCALE_COUNT; si++)
+ i += 4 * BSINC_PHASE_COUNT * mt[si];
+
fprintf(output,
"/* This %d%s order filter has a rejection of -%.0fdB, yielding a transition width\n"
" * of ~%.3f (normalized frequency). Order increases when downsampling to a\n"
@@ -256,12 +258,36 @@ static void BsiGenerateTables(FILE *output, const char *tabname, const double re
" * width) suffers to reduce the CPU cost. The bandlimiting will cut all sound\n"
" * after downsampling by ~%.2f octaves.\n"
" */\n"
-"const BSincTable %s = {\n",
+"alignas(16) constexpr float %s_tab[%d] = {\n",
order, (((order%100)/10) == 1) ? "th" :
((order%10) == 1) ? "st" :
((order%10) == 2) ? "nd" :
((order%10) == 3) ? "rd" : "th",
- rejection, width, log2(1.0/scaleBase), tabname);
+ rejection, width, log2(1.0/scaleBase), tabname, i);
+ for(si = 0; si < BSINC_SCALE_COUNT; si++)
+ {
+ const int m = mt[si];
+ const int o = num_points_min - (m / 2);
+
+ for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
+ {
+ fprintf(output, " /* %2d,%2d (%d) */", si, pi, m);
+ fprintf(output, "\n ");
+ for(i = 0; i < m; i++)
+ fprintf(output, " %+14.9ef,", filter[si][pi][o + i]);
+ fprintf(output, "\n ");
+ for(i = 0; i < m; i++)
+ fprintf(output, " %+14.9ef,", phDeltas[si][pi][o + i]);
+ fprintf(output, "\n ");
+ for(i = 0; i < m; i++)
+ fprintf(output, " %+14.9ef,", scDeltas[si][pi][o + i]);
+ fprintf(output, "\n ");
+ for(i = 0; i < m; i++)
+ fprintf(output, " %+14.9ef,", spDeltas[si][pi][o + i]);
+ fprintf(output, "\n");
+ }
+ }
+ fprintf(output, "};\nconstexpr BSincTable %s = {\n", tabname);
/* The scaleBase is calculated from the Kaiser window transition width.
It represents the absolute limit to the filter before it fully cuts
@@ -271,52 +297,23 @@ static void BsiGenerateTables(FILE *output, const char *tabname, const double re
fprintf(output, " /* scaleBase */ %.9ef, /* scaleRange */ %.9ef,\n", scaleBase, 1.0 / scaleRange);
fprintf(output, " /* m */ {");
- fprintf(output, " %d", mt[0]);
+ fprintf(output, " %du", mt[0]);
for(si = 1; si < BSINC_SCALE_COUNT; si++)
- fprintf(output, ", %d", mt[si]);
+ fprintf(output, ", %du", mt[si]);
fprintf(output, " },\n");
fprintf(output, " /* filterOffset */ {");
- fprintf(output, " %d", 0);
+ fprintf(output, " %du", 0);
i = mt[0]*4*BSINC_PHASE_COUNT;
for(si = 1; si < BSINC_SCALE_COUNT; si++)
{
- fprintf(output, ", %d", i);
+ fprintf(output, ", %du", i);
i += mt[si]*4*BSINC_PHASE_COUNT;
}
fprintf(output, " },\n");
-
- // Calculate the table size.
- i = 0;
- for(si = 0; si < BSINC_SCALE_COUNT; si++)
- i += 4 * BSINC_PHASE_COUNT * mt[si];
-
- fprintf(output, "\n /* Tab (%d entries) */ {\n", i);
- for(si = 0; si < BSINC_SCALE_COUNT; si++)
- {
- const int m = mt[si];
- const int o = num_points_min - (m / 2);
-
- for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
- {
- fprintf(output, " /* %2d,%2d (%d) */", si, pi, m);
- fprintf(output, "\n ");
- for(i = 0; i < m; i++)
- fprintf(output, " %+14.9ef,", filter[si][pi][o + i]);
- fprintf(output, "\n ");
- for(i = 0; i < m; i++)
- fprintf(output, " %+14.9ef,", scDeltas[si][pi][o + i]);
- fprintf(output, "\n ");
- for(i = 0; i < m; i++)
- fprintf(output, " %+14.9ef,", phDeltas[si][pi][o + i]);
- fprintf(output, "\n ");
- for(i = 0; i < m; i++)
- fprintf(output, " %+14.9ef,", spDeltas[si][pi][o + i]);
- fprintf(output, "\n");
- }
- }
- fprintf(output, " }\n};\n\n");
+ fprintf(output, " %s_tab\n", tabname);
+ fprintf(output, "};\n\n");
}
@@ -344,21 +341,23 @@ int main(int argc, char *argv[])
else
output = stdout;
- fprintf(output, "/* Generated by bsincgen, do not edit! */\n\n"
+ fprintf(output, "/* Generated by bsincgen, do not edit! */\n"
+"#pragma once\n\n"
"static_assert(BSINC_SCALE_COUNT == %d, \"Unexpected BSINC_SCALE_COUNT value!\");\n"
-"static_assert(BSINC_PHASE_COUNT == %d, \"Unexpected BSINC_PHASE_COUNT value!\");\n"
-"static_assert(FRACTIONONE == %d, \"Unexpected FRACTIONONE value!\");\n\n"
-"typedef struct BSincTable {\n"
+"static_assert(BSINC_PHASE_COUNT == %d, \"Unexpected BSINC_PHASE_COUNT value!\");\n\n"
+"namespace {\n\n"
+"struct BSincTable {\n"
" const float scaleBase, scaleRange;\n"
-" const int m[BSINC_SCALE_COUNT];\n"
-" const int filterOffset[BSINC_SCALE_COUNT];\n"
-" alignas(16) const float Tab[];\n"
-"} BSincTable;\n\n", BSINC_SCALE_COUNT, BSINC_PHASE_COUNT, FRACTIONONE);
+" const unsigned int m[BSINC_SCALE_COUNT];\n"
+" const unsigned int filterOffset[BSINC_SCALE_COUNT];\n"
+" const float *Tab;\n"
+"};\n\n", BSINC_SCALE_COUNT, BSINC_PHASE_COUNT);
/* A 23rd order filter with a -60dB drop at nyquist. */
BsiGenerateTables(output, "bsinc24", 60.0, 23);
- /* An 11th order filter with a -40dB drop at nyquist. */
- BsiGenerateTables(output, "bsinc12", 40.0, 11);
+ /* An 11th order filter with a -60dB drop at nyquist. */
+ BsiGenerateTables(output, "bsinc12", 60.0, 11);
+ fprintf(output, "} // namespace\n");
if(output != stdout)
fclose(output);
output = NULL;
diff --git a/resources/openal32.rc b/resources/openal32.rc
new file mode 100644
index 00000000..0c59ede9
--- /dev/null
+++ b/resources/openal32.rc
@@ -0,0 +1,90 @@
+#pragma code_page(65001)
+// Microsoft Visual C++ generated resource script.
+//
+#include <windows.h>
+#include "resource.h"
+#include "version.h"
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION ALSOFT_VERSION_NUM
+ PRODUCTVERSION ALSOFT_VERSION_NUM
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS_NT_WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", ""
+ VALUE "FileDescription", "Main implementation library"
+ VALUE "FileVersion", ALSOFT_VERSION
+ VALUE "InternalName", "OpenAL32.dll"
+ VALUE "LegalCopyright", "GNU LGPL - Version 2, June 1991"
+ VALUE "OriginalFilename", "OpenAL32.dll"
+ VALUE "ProductName", "OpenAL Soft"
+ VALUE "ProductVersion", ALSOFT_VERSION
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/resources/resource.h b/resources/resource.h
new file mode 100644
index 00000000..287c9113
--- /dev/null
+++ b/resources/resource.h
@@ -0,0 +1,15 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by openal32.rc, router.rc, soft_oal.rc
+//
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1000
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/resources/router.rc b/resources/router.rc
new file mode 100644
index 00000000..00713141
--- /dev/null
+++ b/resources/router.rc
@@ -0,0 +1,90 @@
+#pragma code_page(65001)
+// Microsoft Visual C++ generated resource script.
+//
+#include <windows.h>
+#include "resource.h"
+#include "version.h"
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION ALSOFT_VERSION_NUM
+ PRODUCTVERSION ALSOFT_VERSION_NUM
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS_NT_WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", ""
+ VALUE "FileDescription", "Router library"
+ VALUE "FileVersion", ALSOFT_VERSION
+ VALUE "InternalName", "OpenAL32.dll"
+ VALUE "LegalCopyright", "GNU LGPL - Version 2, June 1991"
+ VALUE "OriginalFilename", "OpenAL32.dll"
+ VALUE "ProductName", "OpenAL Soft"
+ VALUE "ProductVersion", ALSOFT_VERSION
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/resources/soft_oal.rc b/resources/soft_oal.rc
new file mode 100644
index 00000000..09bca3be
--- /dev/null
+++ b/resources/soft_oal.rc
@@ -0,0 +1,90 @@
+#pragma code_page(65001)
+// Microsoft Visual C++ generated resource script.
+//
+#include <windows.h>
+#include "resource.h"
+#include "version.h"
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION ALSOFT_VERSION_NUM
+ PRODUCTVERSION ALSOFT_VERSION_NUM
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS_NT_WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", ""
+ VALUE "FileDescription", "Main implementation library"
+ VALUE "FileVersion", ALSOFT_VERSION
+ VALUE "InternalName", "soft_oal.dll"
+ VALUE "LegalCopyright", "GNU LGPL - Version 2, June 1991"
+ VALUE "OriginalFilename", "soft_oal.dll"
+ VALUE "ProductName", "OpenAL Soft"
+ VALUE "ProductVersion", ALSOFT_VERSION
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/router/al.c b/router/al.cpp
index 8dd888d9..4c8b0006 100644
--- a/router/al.c
+++ b/router/al.cpp
@@ -7,36 +7,36 @@
#include "router.h"
-atomic_DriverIfacePtr CurrentCtxDriver = ATOMIC_INIT_STATIC(NULL);
+std::atomic<DriverIface*> CurrentCtxDriver{nullptr};
#define DECL_THUNK1(R,n,T1) AL_API R AL_APIENTRY n(T1 a) \
{ \
- DriverIface *iface = altss_get(ThreadCtxDriver); \
- if(!iface) iface = ATOMIC_LOAD(&CurrentCtxDriver, almemory_order_acquire);\
+ DriverIface *iface = ThreadCtxDriver; \
+ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \
return iface->n(a); \
}
#define DECL_THUNK2(R,n,T1,T2) AL_API R AL_APIENTRY n(T1 a, T2 b) \
{ \
- DriverIface *iface = altss_get(ThreadCtxDriver); \
- if(!iface) iface = ATOMIC_LOAD(&CurrentCtxDriver, almemory_order_acquire);\
+ DriverIface *iface = ThreadCtxDriver; \
+ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \
return iface->n(a, b); \
}
#define DECL_THUNK3(R,n,T1,T2,T3) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c) \
{ \
- DriverIface *iface = altss_get(ThreadCtxDriver); \
- if(!iface) iface = ATOMIC_LOAD(&CurrentCtxDriver, almemory_order_acquire);\
+ DriverIface *iface = ThreadCtxDriver; \
+ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \
return iface->n(a, b, c); \
}
#define DECL_THUNK4(R,n,T1,T2,T3,T4) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d) \
{ \
- DriverIface *iface = altss_get(ThreadCtxDriver); \
- if(!iface) iface = ATOMIC_LOAD(&CurrentCtxDriver, almemory_order_acquire);\
+ DriverIface *iface = ThreadCtxDriver; \
+ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \
return iface->n(a, b, c, d); \
}
#define DECL_THUNK5(R,n,T1,T2,T3,T4,T5) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d, T5 e) \
{ \
- DriverIface *iface = altss_get(ThreadCtxDriver); \
- if(!iface) iface = ATOMIC_LOAD(&CurrentCtxDriver, almemory_order_acquire);\
+ DriverIface *iface = ThreadCtxDriver; \
+ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \
return iface->n(a, b, c, d, e); \
}
@@ -46,8 +46,8 @@ atomic_DriverIfacePtr CurrentCtxDriver = ATOMIC_INIT_STATIC(NULL);
*/
AL_API ALenum AL_APIENTRY alGetError(void)
{
- DriverIface *iface = altss_get(ThreadCtxDriver);
- if(!iface) iface = ATOMIC_LOAD(&CurrentCtxDriver, almemory_order_acquire);
+ DriverIface *iface = ThreadCtxDriver;
+ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire);
return iface ? iface->alGetError() : AL_NO_ERROR;
}
diff --git a/router/alc.c b/router/alc.cpp
index 946c7d4c..c99e7c93 100644
--- a/router/alc.c
+++ b/router/alc.cpp
@@ -6,18 +6,21 @@
#include <string.h>
#include <stdio.h>
+#include <mutex>
+#include <algorithm>
+#include <array>
+
#include "AL/alc.h"
+#include "alstring.h"
#include "router.h"
-#include "almalloc.h"
-
-#define COUNTOF(x) (sizeof(x)/sizeof(x[0]))
-#define DECL(x) { #x, (ALCvoid*)(x) }
-static const struct {
+#define DECL(x) { #x, reinterpret_cast<void*>(x) }
+struct FuncExportEntry {
const ALCchar *funcName;
ALCvoid *address;
-} alcFunctions[] = {
+};
+static const std::array<FuncExportEntry,95> alcFunctions{{
DECL(alcCreateContext),
DECL(alcMakeContextCurrent),
DECL(alcProcessContext),
@@ -115,14 +118,15 @@ static const struct {
DECL(alDopplerVelocity),
DECL(alSpeedOfSound),
DECL(alDistanceModel),
-};
+}};
#undef DECL
#define DECL(x) { #x, (x) }
-static const struct {
+struct EnumExportEntry {
const ALCchar *enumName;
ALCenum value;
-} alcEnumerations[] = {
+};
+static const std::array<EnumExportEntry,92> alcEnumerations{{
DECL(ALC_INVALID),
DECL(ALC_FALSE),
DECL(ALC_TRUE),
@@ -228,7 +232,7 @@ static const struct {
DECL(AL_LINEAR_DISTANCE_CLAMPED),
DECL(AL_EXPONENT_DISTANCE),
DECL(AL_EXPONENT_DISTANCE_CLAMPED),
-};
+}};
#undef DECL
static const ALCchar alcNoError[] = "No Error";
@@ -245,77 +249,54 @@ static const ALCint alcMajorVersion = 1;
static const ALCint alcMinorVersion = 1;
-static almtx_t EnumerationLock;
-static almtx_t ContextSwitchLock;
+static std::mutex EnumerationLock;
+static std::mutex ContextSwitchLock;
-static ATOMIC(ALCenum) LastError = ATOMIC_INIT_STATIC(ALC_NO_ERROR);
-static PtrIntMap DeviceIfaceMap = PTRINTMAP_STATIC_INITIALIZE;
-static PtrIntMap ContextIfaceMap = PTRINTMAP_STATIC_INITIALIZE;
+static std::atomic<ALCenum> LastError{ALC_NO_ERROR};
+static PtrIntMap DeviceIfaceMap;
+static PtrIntMap ContextIfaceMap;
typedef struct EnumeratedList {
- ALCchar *Names;
- ALCchar *NamesEnd;
- ALCint *Indicies;
- ALCsizei IndexSize;
-} EnumeratedList;
-static EnumeratedList DevicesList = { NULL, NULL, NULL, 0 };
-static EnumeratedList AllDevicesList = { NULL, NULL, NULL, 0 };
-static EnumeratedList CaptureDevicesList = { NULL, NULL, NULL, 0 };
-
-static void ClearDeviceList(EnumeratedList *list)
-{
- al_free(list->Names);
- list->Names = NULL;
- list->NamesEnd = NULL;
+ std::vector<ALCchar> Names;
+ std::vector<ALCint> Indicies;
- al_free(list->Indicies);
- list->Indicies = NULL;
- list->IndexSize = 0;
-}
+ void clear()
+ {
+ Names.clear();
+ Indicies.clear();
+ }
+} EnumeratedList;
+static EnumeratedList DevicesList;
+static EnumeratedList AllDevicesList;
+static EnumeratedList CaptureDevicesList;
static void AppendDeviceList(EnumeratedList *list, const ALCchar *names, ALint idx)
{
const ALCchar *name_end = names;
- ALCsizei count = 0;
- ALCchar *new_list;
- ALCint *new_indicies;
- size_t len;
- ALCsizei i;
+ if(!name_end) return;
- if(!name_end)
- return;
+ ALCsizei count = 0;
while(*name_end)
{
TRACE("Enumerated \"%s\", driver %d\n", name_end, idx);
- count++;
+ ++count;
name_end += strlen(name_end)+1;
}
if(names == name_end)
return;
- len = (list->NamesEnd - list->Names) + (name_end - names);
- new_list = al_calloc(DEF_ALIGN, len + 1);
- memcpy(new_list, list->Names, list->NamesEnd - list->Names);
- memcpy(new_list + (list->NamesEnd - list->Names), names, name_end - names);
- al_free(list->Names);
- list->Names = new_list;
- list->NamesEnd = list->Names + len;
-
- new_indicies = al_calloc(16, sizeof(ALCint)*(list->IndexSize + count));
- for(i = 0;i < list->IndexSize;i++)
- new_indicies[i] = list->Indicies[i];
- for(i = 0;i < count;i++)
- new_indicies[list->IndexSize+i] = idx;
- al_free(list->Indicies);
- list->Indicies = new_indicies;
- list->IndexSize += count;
+ list->Names.reserve(list->Names.size() + (name_end - names) + 1);
+ list->Names.insert(list->Names.cend(), names, name_end);
+
+ list->Indicies.reserve(list->Indicies.size() + count);
+ list->Indicies.insert(list->Indicies.cend(), count, idx);
}
static ALint GetDriverIndexForName(const EnumeratedList *list, const ALCchar *name)
{
- const ALCchar *devnames = list->Names;
- const ALCint *index = list->Indicies;
+ const ALCchar *devnames = list->Names.data();
+ const ALCint *index = list->Indicies.data();
while(devnames && *devnames)
{
@@ -327,30 +308,11 @@ static ALint GetDriverIndexForName(const EnumeratedList *list, const ALCchar *na
return -1;
}
-void InitALC(void)
-{
- almtx_init(&EnumerationLock, almtx_recursive);
- almtx_init(&ContextSwitchLock, almtx_plain);
-}
-
-void ReleaseALC(void)
-{
- ClearDeviceList(&DevicesList);
- ClearDeviceList(&AllDevicesList);
- ClearDeviceList(&CaptureDevicesList);
-
- ResetPtrIntMap(&ContextIfaceMap);
- ResetPtrIntMap(&DeviceIfaceMap);
-
- almtx_destroy(&ContextSwitchLock);
- almtx_destroy(&EnumerationLock);
-}
-
ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename)
{
- ALCdevice *device = NULL;
- ALint idx;
+ ALCdevice *device = nullptr;
+ ALint idx = 0;
/* Prior to the enumeration extension, apps would hardcode these names as a
* quality hint for the wrapper driver. Ignore them since there's no sane
@@ -360,51 +322,51 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename)
strcmp(devicename, "DirectSound3D") == 0 ||
strcmp(devicename, "DirectSound") == 0 ||
strcmp(devicename, "MMSYSTEM") == 0))
- devicename = NULL;
+ devicename = nullptr;
if(devicename)
{
- almtx_lock(&EnumerationLock);
- if(!DevicesList.Names)
- (void)alcGetString(NULL, ALC_DEVICE_SPECIFIER);
- idx = GetDriverIndexForName(&DevicesList, devicename);
- if(idx < 0)
- {
- if(!AllDevicesList.Names)
- (void)alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
- idx = GetDriverIndexForName(&AllDevicesList, devicename);
+ { std::lock_guard<std::mutex> _{EnumerationLock};
+ if(DevicesList.Names.empty())
+ (void)alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
+ idx = GetDriverIndexForName(&DevicesList, devicename);
+ if(idx < 0)
+ {
+ if(AllDevicesList.Names.empty())
+ (void)alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
+ idx = GetDriverIndexForName(&AllDevicesList, devicename);
+ }
}
- almtx_unlock(&EnumerationLock);
+
if(idx < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_VALUE);
+ LastError.store(ALC_INVALID_VALUE);
TRACE("Failed to find driver for name \"%s\"\n", devicename);
- return NULL;
+ return nullptr;
}
TRACE("Found driver %d for name \"%s\"\n", idx, devicename);
device = DriverList[idx].alcOpenDevice(devicename);
}
else
{
- int i;
- for(i = 0;i < DriverListSize;i++)
+ for(const auto &drv : DriverList)
{
- if(DriverList[i].ALCVer >= MAKE_ALC_VER(1, 1) ||
- DriverList[i].alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"))
+ if(drv.ALCVer >= MAKE_ALC_VER(1, 1) ||
+ drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"))
{
- idx = i;
TRACE("Using default device from driver %d\n", idx);
- device = DriverList[idx].alcOpenDevice(NULL);
+ device = drv.alcOpenDevice(nullptr);
break;
}
+ ++idx;
}
}
if(device)
{
- if(InsertPtrIntMapEntry(&DeviceIfaceMap, device, idx) != ALC_NO_ERROR)
+ if(DeviceIfaceMap.insert(device, idx) != ALC_NO_ERROR)
{
DriverList[idx].alcCloseDevice(device);
- device = NULL;
+ device = nullptr;
}
}
@@ -415,14 +377,14 @@ ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device)
{
ALint idx;
- if(!device || (idx=LookupPtrIntMapKey(&DeviceIfaceMap, device)) < 0)
+ if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
+ LastError.store(ALC_INVALID_DEVICE);
return ALC_FALSE;
}
if(!DriverList[idx].alcCloseDevice(device))
return ALC_FALSE;
- RemovePtrIntMapKey(&DeviceIfaceMap, device);
+ DeviceIfaceMap.removeByKey(device);
return ALC_TRUE;
}
@@ -432,18 +394,18 @@ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCin
ALCcontext *context;
ALint idx;
- if(!device || (idx=LookupPtrIntMapKey(&DeviceIfaceMap, device)) < 0)
+ if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
- return ALC_FALSE;
+ LastError.store(ALC_INVALID_DEVICE);
+ return nullptr;
}
context = DriverList[idx].alcCreateContext(device, attrlist);
if(context)
{
- if(InsertPtrIntMapEntry(&ContextIfaceMap, context, idx) != ALC_NO_ERROR)
+ if(ContextIfaceMap.insert(context, idx) != ALC_NO_ERROR)
{
DriverList[idx].alcDestroyContext(context);
- context = NULL;
+ context = nullptr;
}
}
@@ -454,21 +416,17 @@ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context)
{
ALint idx = -1;
- almtx_lock(&ContextSwitchLock);
+ std::lock_guard<std::mutex> _{ContextSwitchLock};
if(context)
{
- idx = LookupPtrIntMapKey(&ContextIfaceMap, context);
+ idx = ContextIfaceMap.lookupByKey(context);
if(idx < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_CONTEXT);
- almtx_unlock(&ContextSwitchLock);
+ LastError.store(ALC_INVALID_CONTEXT);
return ALC_FALSE;
}
if(!DriverList[idx].alcMakeContextCurrent(context))
- {
- almtx_unlock(&ContextSwitchLock);
return ALC_FALSE;
- }
}
/* Unset the context from the old driver if it's different from the new
@@ -476,22 +434,21 @@ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context)
*/
if(idx < 0)
{
- DriverIface *oldiface = altss_get(ThreadCtxDriver);
- if(oldiface) oldiface->alcSetThreadContext(NULL);
- oldiface = ATOMIC_EXCHANGE_PTR_SEQ(&CurrentCtxDriver, NULL);
- if(oldiface) oldiface->alcMakeContextCurrent(NULL);
+ DriverIface *oldiface = ThreadCtxDriver;
+ if(oldiface) oldiface->alcSetThreadContext(nullptr);
+ oldiface = CurrentCtxDriver.exchange(nullptr);
+ if(oldiface) oldiface->alcMakeContextCurrent(nullptr);
}
else
{
- DriverIface *oldiface = altss_get(ThreadCtxDriver);
+ DriverIface *oldiface = ThreadCtxDriver;
if(oldiface && oldiface != &DriverList[idx])
- oldiface->alcSetThreadContext(NULL);
- oldiface = ATOMIC_EXCHANGE_PTR_SEQ(&CurrentCtxDriver, &DriverList[idx]);
+ oldiface->alcSetThreadContext(nullptr);
+ oldiface = CurrentCtxDriver.exchange(&DriverList[idx]);
if(oldiface && oldiface != &DriverList[idx])
- oldiface->alcMakeContextCurrent(NULL);
+ oldiface->alcMakeContextCurrent(nullptr);
}
- almtx_unlock(&ContextSwitchLock);
- altss_set(ThreadCtxDriver, NULL);
+ ThreadCtxDriver = nullptr;
return ALC_TRUE;
}
@@ -500,55 +457,55 @@ ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context)
{
if(context)
{
- ALint idx = LookupPtrIntMapKey(&ContextIfaceMap, context);
+ ALint idx = ContextIfaceMap.lookupByKey(context);
if(idx >= 0)
return DriverList[idx].alcProcessContext(context);
}
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_CONTEXT);
+ LastError.store(ALC_INVALID_CONTEXT);
}
ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context)
{
if(context)
{
- ALint idx = LookupPtrIntMapKey(&ContextIfaceMap, context);
+ ALint idx = ContextIfaceMap.lookupByKey(context);
if(idx >= 0)
return DriverList[idx].alcSuspendContext(context);
}
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_CONTEXT);
+ LastError.store(ALC_INVALID_CONTEXT);
}
ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context)
{
ALint idx;
- if(!context || (idx=LookupPtrIntMapKey(&ContextIfaceMap, context)) < 0)
+ if(!context || (idx=ContextIfaceMap.lookupByKey(context)) < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_CONTEXT);
+ LastError.store(ALC_INVALID_CONTEXT);
return;
}
DriverList[idx].alcDestroyContext(context);
- RemovePtrIntMapKey(&ContextIfaceMap, context);
+ ContextIfaceMap.removeByKey(context);
}
ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void)
{
- DriverIface *iface = altss_get(ThreadCtxDriver);
- if(!iface) iface = ATOMIC_LOAD_SEQ(&CurrentCtxDriver);
- return iface ? iface->alcGetCurrentContext() : NULL;
+ DriverIface *iface = ThreadCtxDriver;
+ if(!iface) iface = CurrentCtxDriver.load();
+ return iface ? iface->alcGetCurrentContext() : nullptr;
}
ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context)
{
if(context)
{
- ALint idx = LookupPtrIntMapKey(&ContextIfaceMap, context);
+ ALint idx = ContextIfaceMap.lookupByKey(context);
if(idx >= 0)
return DriverList[idx].alcGetContextsDevice(context);
}
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_CONTEXT);
- return NULL;
+ LastError.store(ALC_INVALID_CONTEXT);
+ return nullptr;
}
@@ -556,11 +513,11 @@ ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device)
{
if(device)
{
- ALint idx = LookupPtrIntMapKey(&DeviceIfaceMap, device);
+ ALint idx = DeviceIfaceMap.lookupByKey(device);
if(idx < 0) return ALC_INVALID_DEVICE;
return DriverList[idx].alcGetError(device);
}
- return ATOMIC_EXCHANGE_SEQ(&LastError, ALC_NO_ERROR);
+ return LastError.exchange(ALC_NO_ERROR);
}
ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname)
@@ -570,10 +527,10 @@ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const A
if(device)
{
- ALint idx = LookupPtrIntMapKey(&DeviceIfaceMap, device);
+ ALint idx = DeviceIfaceMap.lookupByKey(device);
if(idx < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
+ LastError.store(ALC_INVALID_DEVICE);
return ALC_FALSE;
}
return DriverList[idx].alcIsExtensionPresent(device, extname);
@@ -583,9 +540,9 @@ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const A
ptr = alcExtensionList;
while(ptr && *ptr)
{
- if(strncasecmp(ptr, extname, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len])))
+ if(al::strncasecmp(ptr, extname, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len])))
return ALC_TRUE;
- if((ptr=strchr(ptr, ' ')) != NULL)
+ if((ptr=strchr(ptr, ' ')) != nullptr)
{
do {
++ptr;
@@ -597,61 +554,53 @@ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const A
ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname)
{
- size_t i;
-
if(device)
{
- ALint idx = LookupPtrIntMapKey(&DeviceIfaceMap, device);
+ ALint idx = DeviceIfaceMap.lookupByKey(device);
if(idx < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
- return NULL;
+ LastError.store(ALC_INVALID_DEVICE);
+ return nullptr;
}
return DriverList[idx].alcGetProcAddress(device, funcname);
}
- for(i = 0;i < COUNTOF(alcFunctions);i++)
- {
- if(strcmp(funcname, alcFunctions[i].funcName) == 0)
- return alcFunctions[i].address;
- }
- return NULL;
+ auto iter = std::find_if(alcFunctions.cbegin(), alcFunctions.cend(),
+ [funcname](const FuncExportEntry &entry) -> bool
+ { return strcmp(funcname, entry.funcName) == 0; }
+ );
+ return (iter != alcFunctions.cend()) ? iter->address : nullptr;
}
ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname)
{
- size_t i;
-
if(device)
{
- ALint idx = LookupPtrIntMapKey(&DeviceIfaceMap, device);
+ ALint idx = DeviceIfaceMap.lookupByKey(device);
if(idx < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
+ LastError.store(ALC_INVALID_DEVICE);
return 0;
}
return DriverList[idx].alcGetEnumValue(device, enumname);
}
- for(i = 0;i < COUNTOF(alcEnumerations);i++)
- {
- if(strcmp(enumname, alcEnumerations[i].enumName) == 0)
- return alcEnumerations[i].value;
- }
- return 0;
+ auto iter = std::find_if(alcEnumerations.cbegin(), alcEnumerations.cend(),
+ [enumname](const EnumExportEntry &entry) -> bool
+ { return strcmp(enumname, entry.enumName) == 0; }
+ );
+ return (iter != alcEnumerations.cend()) ? iter->value : 0;
}
ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param)
{
- ALsizei i = 0;
-
if(device)
{
- ALint idx = LookupPtrIntMapKey(&DeviceIfaceMap, device);
+ ALint idx = DeviceIfaceMap.lookupByKey(device);
if(idx < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
- return NULL;
+ LastError.store(ALC_INVALID_DEVICE);
+ return nullptr;
}
return DriverList[idx].alcGetString(device, param);
}
@@ -674,104 +623,135 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para
return alcExtensionList;
case ALC_DEVICE_SPECIFIER:
- almtx_lock(&EnumerationLock);
- ClearDeviceList(&DevicesList);
- for(i = 0;i < DriverListSize;i++)
+ { std::lock_guard<std::mutex> _{EnumerationLock};
+ DevicesList.clear();
+ ALint idx = 0;
+ for(const auto &drv : DriverList)
{
/* Only enumerate names from drivers that support it. */
- if(DriverList[i].ALCVer >= MAKE_ALC_VER(1, 1) ||
- DriverList[i].alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"))
+ if(drv.ALCVer >= MAKE_ALC_VER(1, 1) ||
+ drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"))
AppendDeviceList(&DevicesList,
- DriverList[i].alcGetString(NULL, ALC_DEVICE_SPECIFIER), i
+ drv.alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx
);
+ idx++;
}
- almtx_unlock(&EnumerationLock);
- return DevicesList.Names;
+ /* Ensure the list is double-null termianted. */
+ if(DevicesList.Names.empty())
+ DevicesList.Names.emplace_back('\0');
+ DevicesList.Names.emplace_back('\0');
+ return DevicesList.Names.data();
+ }
case ALC_ALL_DEVICES_SPECIFIER:
- almtx_lock(&EnumerationLock);
- ClearDeviceList(&AllDevicesList);
- for(i = 0;i < DriverListSize;i++)
+ { std::lock_guard<std::mutex> _{EnumerationLock};
+ AllDevicesList.clear();
+ ALint idx = 0;
+ for(const auto &drv : DriverList)
{
/* If the driver doesn't support ALC_ENUMERATE_ALL_EXT, substitute
* standard enumeration.
*/
- if(DriverList[i].alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT"))
+ if(drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT"))
AppendDeviceList(&AllDevicesList,
- DriverList[i].alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER), i
+ drv.alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER), idx
);
- else if(DriverList[i].ALCVer >= MAKE_ALC_VER(1, 1) ||
- DriverList[i].alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"))
+ else if(drv.ALCVer >= MAKE_ALC_VER(1, 1) ||
+ drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"))
AppendDeviceList(&AllDevicesList,
- DriverList[i].alcGetString(NULL, ALC_DEVICE_SPECIFIER), i
+ drv.alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx
);
+ ++idx;
}
- almtx_unlock(&EnumerationLock);
- return AllDevicesList.Names;
+ /* Ensure the list is double-null termianted. */
+ if(AllDevicesList.Names.empty())
+ AllDevicesList.Names.emplace_back('\0');
+ AllDevicesList.Names.emplace_back('\0');
+ return AllDevicesList.Names.data();
+ }
case ALC_CAPTURE_DEVICE_SPECIFIER:
- almtx_lock(&EnumerationLock);
- ClearDeviceList(&CaptureDevicesList);
- for(i = 0;i < DriverListSize;i++)
+ { std::lock_guard<std::mutex> _{EnumerationLock};
+ CaptureDevicesList.clear();
+ ALint idx = 0;
+ for(const auto &drv : DriverList)
{
- if(DriverList[i].ALCVer >= MAKE_ALC_VER(1, 1) ||
- DriverList[i].alcIsExtensionPresent(NULL, "ALC_EXT_CAPTURE"))
+ if(drv.ALCVer >= MAKE_ALC_VER(1, 1) ||
+ drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE"))
AppendDeviceList(&CaptureDevicesList,
- DriverList[i].alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER), i
+ drv.alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER), idx
);
+ ++idx;
}
- almtx_unlock(&EnumerationLock);
- return CaptureDevicesList.Names;
+ /* Ensure the list is double-null termianted. */
+ if(CaptureDevicesList.Names.empty())
+ CaptureDevicesList.Names.emplace_back('\0');
+ CaptureDevicesList.Names.emplace_back('\0');
+ return CaptureDevicesList.Names.data();
+ }
case ALC_DEFAULT_DEVICE_SPECIFIER:
- for(i = 0;i < DriverListSize;i++)
- {
- if(DriverList[i].ALCVer >= MAKE_ALC_VER(1, 1) ||
- DriverList[i].alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"))
- return DriverList[i].alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
- }
+ {
+ auto iter = std::find_if(DriverList.cbegin(), DriverList.cend(),
+ [](const DriverIface &drv) -> bool
+ {
+ return drv.ALCVer >= MAKE_ALC_VER(1, 1) ||
+ drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT");
+ }
+ );
+ if(iter != DriverList.cend())
+ return iter->alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
return "";
+ }
case ALC_DEFAULT_ALL_DEVICES_SPECIFIER:
- for(i = 0;i < DriverListSize;i++)
- {
- if(DriverList[i].alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT"))
- return DriverList[i].alcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
- }
+ {
+ auto iter = std::find_if(DriverList.cbegin(), DriverList.cend(),
+ [](const DriverIface &drv) -> bool
+ { return drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") != ALC_FALSE; }
+ );
+ if(iter != DriverList.cend())
+ return iter->alcGetString(nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
return "";
+ }
case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER:
- for(i = 0;i < DriverListSize;i++)
- {
- if(DriverList[i].ALCVer >= MAKE_ALC_VER(1, 1) ||
- DriverList[i].alcIsExtensionPresent(NULL, "ALC_EXT_CAPTURE"))
- return DriverList[i].alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
- }
+ {
+ auto iter = std::find_if(DriverList.cbegin(), DriverList.cend(),
+ [](const DriverIface &drv) -> bool
+ {
+ return drv.ALCVer >= MAKE_ALC_VER(1, 1) ||
+ drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE");
+ }
+ );
+ if(iter != DriverList.cend())
+ return iter->alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
return "";
+ }
default:
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_ENUM);
+ LastError.store(ALC_INVALID_ENUM);
break;
}
- return NULL;
+ return nullptr;
}
ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values)
{
if(device)
{
- ALint idx = LookupPtrIntMapKey(&DeviceIfaceMap, device);
+ ALint idx = DeviceIfaceMap.lookupByKey(device);
if(idx < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
+ LastError.store(ALC_INVALID_DEVICE);
return;
}
return DriverList[idx].alcGetIntegerv(device, param, size, values);
}
- if(size <= 0 || values == NULL)
+ if(size <= 0 || values == nullptr)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_VALUE);
+ LastError.store(ALC_INVALID_VALUE);
return;
}
@@ -790,7 +770,7 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsi
values[0] = alcMinorVersion;
return;
}
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_VALUE);
+ LastError.store(ALC_INVALID_VALUE);
return;
case ALC_ATTRIBUTES_SIZE:
@@ -801,11 +781,11 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsi
case ALC_MONO_SOURCES:
case ALC_STEREO_SOURCES:
case ALC_CAPTURE_SAMPLES:
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
+ LastError.store(ALC_INVALID_DEVICE);
return;
default:
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_ENUM);
+ LastError.store(ALC_INVALID_ENUM);
return;
}
}
@@ -813,23 +793,24 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsi
ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize)
{
- ALCdevice *device = NULL;
- ALint idx;
+ ALCdevice *device = nullptr;
+ ALint idx = 0;
if(devicename && devicename[0] == '\0')
- devicename = NULL;
+ devicename = nullptr;
if(devicename)
{
- almtx_lock(&EnumerationLock);
- if(!CaptureDevicesList.Names)
- (void)alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
- idx = GetDriverIndexForName(&CaptureDevicesList, devicename);
- almtx_unlock(&EnumerationLock);
+ { std::lock_guard<std::mutex> _{EnumerationLock};
+ if(CaptureDevicesList.Names.empty())
+ (void)alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
+ idx = GetDriverIndexForName(&CaptureDevicesList, devicename);
+ }
+
if(idx < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_VALUE);
+ LastError.store(ALC_INVALID_VALUE);
TRACE("Failed to find driver for name \"%s\"\n", devicename);
- return NULL;
+ return nullptr;
}
TRACE("Found driver %d for name \"%s\"\n", idx, devicename);
device = DriverList[idx].alcCaptureOpenDevice(
@@ -838,28 +819,27 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename,
}
else
{
- int i;
- for(i = 0;i < DriverListSize;i++)
+ for(const auto &drv : DriverList)
{
- if(DriverList[i].ALCVer >= MAKE_ALC_VER(1, 1) ||
- DriverList[i].alcIsExtensionPresent(NULL, "ALC_EXT_CAPTURE"))
+ if(drv.ALCVer >= MAKE_ALC_VER(1, 1) ||
+ drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE"))
{
- idx = i;
TRACE("Using default capture device from driver %d\n", idx);
- device = DriverList[idx].alcCaptureOpenDevice(
- NULL, frequency, format, buffersize
+ device = drv.alcCaptureOpenDevice(
+ nullptr, frequency, format, buffersize
);
break;
}
+ ++idx;
}
}
if(device)
{
- if(InsertPtrIntMapEntry(&DeviceIfaceMap, device, idx) != ALC_NO_ERROR)
+ if(DeviceIfaceMap.insert(device, idx) != ALC_NO_ERROR)
{
DriverList[idx].alcCaptureCloseDevice(device);
- device = NULL;
+ device = nullptr;
}
}
@@ -870,14 +850,14 @@ ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device)
{
ALint idx;
- if(!device || (idx=LookupPtrIntMapKey(&DeviceIfaceMap, device)) < 0)
+ if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0)
{
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
+ LastError.store(ALC_INVALID_DEVICE);
return ALC_FALSE;
}
if(!DriverList[idx].alcCaptureCloseDevice(device))
return ALC_FALSE;
- RemovePtrIntMapKey(&DeviceIfaceMap, device);
+ DeviceIfaceMap.removeByKey(device);
return ALC_TRUE;
}
@@ -885,33 +865,33 @@ ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device)
{
if(device)
{
- ALint idx = LookupPtrIntMapKey(&DeviceIfaceMap, device);
+ ALint idx = DeviceIfaceMap.lookupByKey(device);
if(idx >= 0)
return DriverList[idx].alcCaptureStart(device);
}
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
+ LastError.store(ALC_INVALID_DEVICE);
}
ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device)
{
if(device)
{
- ALint idx = LookupPtrIntMapKey(&DeviceIfaceMap, device);
+ ALint idx = DeviceIfaceMap.lookupByKey(device);
if(idx >= 0)
return DriverList[idx].alcCaptureStop(device);
}
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
+ LastError.store(ALC_INVALID_DEVICE);
}
ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples)
{
if(device)
{
- ALint idx = LookupPtrIntMapKey(&DeviceIfaceMap, device);
+ ALint idx = DeviceIfaceMap.lookupByKey(device);
if(idx >= 0)
return DriverList[idx].alcCaptureSamples(device, buffer, samples);
}
- ATOMIC_STORE_SEQ(&LastError, ALC_INVALID_DEVICE);
+ LastError.store(ALC_INVALID_DEVICE);
}
@@ -922,35 +902,35 @@ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context)
if(!context)
{
- DriverIface *oldiface = altss_get(ThreadCtxDriver);
- if(oldiface && !oldiface->alcSetThreadContext(NULL))
+ DriverIface *oldiface = ThreadCtxDriver;
+ if(oldiface && !oldiface->alcSetThreadContext(nullptr))
return ALC_FALSE;
- altss_set(ThreadCtxDriver, NULL);
+ ThreadCtxDriver = nullptr;
return ALC_TRUE;
}
- idx = LookupPtrIntMapKey(&ContextIfaceMap, context);
+ idx = ContextIfaceMap.lookupByKey(context);
if(idx >= 0)
{
if(DriverList[idx].alcSetThreadContext(context))
{
- DriverIface *oldiface = altss_get(ThreadCtxDriver);
+ DriverIface *oldiface = ThreadCtxDriver;
if(oldiface != &DriverList[idx])
{
- altss_set(ThreadCtxDriver, &DriverList[idx]);
- if(oldiface) oldiface->alcSetThreadContext(NULL);
+ ThreadCtxDriver = &DriverList[idx];
+ if(oldiface) oldiface->alcSetThreadContext(nullptr);
}
return ALC_TRUE;
}
- err = DriverList[idx].alcGetError(NULL);
+ err = DriverList[idx].alcGetError(nullptr);
}
- ATOMIC_STORE_SEQ(&LastError, err);
+ LastError.store(err);
return ALC_FALSE;
}
ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void)
{
- DriverIface *iface = altss_get(ThreadCtxDriver);
+ DriverIface *iface = ThreadCtxDriver;
if(iface) return iface->alcGetThreadContext();
- return NULL;
+ return nullptr;
}
diff --git a/router/router.c b/router/router.cpp
index bff73776..85a75aea 100644
--- a/router/router.c
+++ b/router/router.cpp
@@ -3,22 +3,23 @@
#include "router.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <algorithm>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
#include "AL/alc.h"
#include "AL/al.h"
+
#include "almalloc.h"
+#include "strutils.h"
#include "version.h"
-DriverIface *DriverList = NULL;
-int DriverListSize = 0;
-static int DriverListSizeMax = 0;
+std::vector<DriverIface> DriverList;
-altss_t ThreadCtxDriver;
+thread_local DriverIface *ThreadCtxDriver;
enum LogLevel LogLevel = LogLevel_Error;
FILE *LogFile;
@@ -26,95 +27,65 @@ FILE *LogFile;
static void LoadDriverList(void);
-BOOL APIENTRY DllMain(HINSTANCE UNUSED(module), DWORD reason, void* UNUSED(reserved))
+BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*)
{
- const char *str;
- int i;
-
switch(reason)
{
- case DLL_PROCESS_ATTACH:
- LogFile = stderr;
- str = getenv("ALROUTER_LOGFILE");
- if(str && *str != '\0')
- {
- FILE *f = fopen(str, "w");
- if(f == NULL)
- ERR("Could not open log file: %s\n", str);
- else
- LogFile = f;
- }
- str = getenv("ALROUTER_LOGLEVEL");
- if(str && *str != '\0')
- {
- char *end = NULL;
- long l = strtol(str, &end, 0);
- if(!end || *end != '\0')
- ERR("Invalid log level value: %s\n", str);
- else if(l < LogLevel_None || l > LogLevel_Trace)
- ERR("Log level out of range: %s\n", str);
- else
- LogLevel = l;
- }
- TRACE("Initializing router v0.1-%s %s\n", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH);
- LoadDriverList();
-
- altss_create(&ThreadCtxDriver, NULL);
- InitALC();
- break;
+ case DLL_PROCESS_ATTACH:
+ LogFile = stderr;
+ if(auto logfname = al::getenv("ALROUTER_LOGFILE"))
+ {
+ FILE *f = fopen(logfname->c_str(), "w");
+ if(f == nullptr)
+ ERR("Could not open log file: %s\n", logfname->c_str());
+ else
+ LogFile = f;
+ }
+ if(auto loglev = al::getenv("ALROUTER_LOGLEVEL"))
+ {
+ char *end = nullptr;
+ long l = strtol(loglev->c_str(), &end, 0);
+ if(!end || *end != '\0')
+ ERR("Invalid log level value: %s\n", loglev->c_str());
+ else if(l < LogLevel_None || l > LogLevel_Trace)
+ ERR("Log level out of range: %s\n", loglev->c_str());
+ else
+ LogLevel = static_cast<enum LogLevel>(l);
+ }
+ TRACE("Initializing router v0.1-%s %s\n", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH);
+ LoadDriverList();
- case DLL_THREAD_ATTACH:
- break;
- case DLL_THREAD_DETACH:
- althrd_thread_detach();
- break;
+ break;
- case DLL_PROCESS_DETACH:
- ReleaseALC();
- altss_delete(ThreadCtxDriver);
+ case DLL_THREAD_ATTACH:
+ break;
+ case DLL_THREAD_DETACH:
+ break;
- for(i = 0;i < DriverListSize;i++)
- {
- if(DriverList[i].Module)
- FreeLibrary(DriverList[i].Module);
- }
- al_free(DriverList);
- DriverList = NULL;
- DriverListSize = 0;
- DriverListSizeMax = 0;
+ case DLL_PROCESS_DETACH:
+ DriverList.clear();
- if(LogFile && LogFile != stderr)
- fclose(LogFile);
- LogFile = NULL;
+ if(LogFile && LogFile != stderr)
+ fclose(LogFile);
+ LogFile = nullptr;
- althrd_deinit();
- break;
+ break;
}
return TRUE;
}
-#ifdef __GNUC__
-#define CAST_FUNC(x) (__typeof(x))
-#else
-#define CAST_FUNC(x) (void*)
-#endif
-
static void AddModule(HMODULE module, const WCHAR *name)
{
- DriverIface newdrv;
- int err = 0;
- int i;
-
- for(i = 0;i < DriverListSize;i++)
+ for(auto &drv : DriverList)
{
- if(DriverList[i].Module == module)
+ if(drv.Module == module)
{
- TRACE("Skipping already-loaded module %p\n", module);
+ TRACE("Skipping already-loaded module %p\n", decltype(std::declval<void*>()){module});
FreeLibrary(module);
return;
}
- if(wcscmp(DriverList[i].Name, name) == 0)
+ if(drv.Name == name)
{
TRACE("Skipping similarly-named module %ls\n", name);
FreeLibrary(module);
@@ -122,22 +93,14 @@ static void AddModule(HMODULE module, const WCHAR *name)
}
}
- if(DriverListSize == DriverListSizeMax)
- {
- int newmax = DriverListSizeMax ? DriverListSizeMax<<1 : 4;
- void *newlist = al_calloc(DEF_ALIGN, sizeof(DriverList[0])*newmax);
- if(!newlist) return;
-
- memcpy(newlist, DriverList, DriverListSize*sizeof(DriverList[0]));
- al_free(DriverList);
- DriverList = newlist;
- DriverListSizeMax = newmax;
- }
+ DriverList.emplace_back(name, module);
+ DriverIface &newdrv = DriverList.back();
- memset(&newdrv, 0, sizeof(newdrv));
/* Load required functions. */
+ int err = 0;
#define LOAD_PROC(x) do { \
- newdrv.x = CAST_FUNC(newdrv.x) GetProcAddress(module, #x); \
+ newdrv.x = reinterpret_cast<decltype(newdrv.x)>(reinterpret_cast<void*>( \
+ GetProcAddress(module, #x))); \
if(!newdrv.x) \
{ \
ERR("Failed to find entry point for %s in %ls\n", #x, name); \
@@ -241,63 +204,62 @@ static void AddModule(HMODULE module, const WCHAR *name)
if(!err)
{
ALCint alc_ver[2] = { 0, 0 };
- wcsncpy(newdrv.Name, name, 32);
- newdrv.Module = module;
- newdrv.alcGetIntegerv(NULL, ALC_MAJOR_VERSION, 1, &alc_ver[0]);
- newdrv.alcGetIntegerv(NULL, ALC_MINOR_VERSION, 1, &alc_ver[1]);
- if(newdrv.alcGetError(NULL) == ALC_NO_ERROR)
+ newdrv.alcGetIntegerv(nullptr, ALC_MAJOR_VERSION, 1, &alc_ver[0]);
+ newdrv.alcGetIntegerv(nullptr, ALC_MINOR_VERSION, 1, &alc_ver[1]);
+ if(newdrv.alcGetError(nullptr) == ALC_NO_ERROR)
newdrv.ALCVer = MAKE_ALC_VER(alc_ver[0], alc_ver[1]);
else
newdrv.ALCVer = MAKE_ALC_VER(1, 0);
#undef LOAD_PROC
#define LOAD_PROC(x) do { \
- newdrv.x = CAST_FUNC(newdrv.x) newdrv.alcGetProcAddress(NULL, #x); \
+ newdrv.x = reinterpret_cast<decltype(newdrv.x)>( \
+ newdrv.alcGetProcAddress(nullptr, #x)); \
if(!newdrv.x) \
{ \
ERR("Failed to find entry point for %s in %ls\n", #x, name); \
err = 1; \
} \
} while(0)
- if(newdrv.alcIsExtensionPresent(NULL, "ALC_EXT_thread_local_context"))
+ if(newdrv.alcIsExtensionPresent(nullptr, "ALC_EXT_thread_local_context"))
{
LOAD_PROC(alcSetThreadContext);
LOAD_PROC(alcGetThreadContext);
}
}
- if(!err)
+ if(err)
{
- TRACE("Loaded module %p, %ls, ALC %d.%d\n", module, name,
- newdrv.ALCVer>>8, newdrv.ALCVer&255);
- DriverList[DriverListSize++] = newdrv;
+ DriverList.pop_back();
+ return;
}
+ TRACE("Loaded module %p, %ls, ALC %d.%d\n", decltype(std::declval<void*>()){module}, name,
+ newdrv.ALCVer>>8, newdrv.ALCVer&255);
#undef LOAD_PROC
}
static void SearchDrivers(WCHAR *path)
{
- WCHAR srchPath[MAX_PATH+1] = L"";
WIN32_FIND_DATAW fdata;
- HANDLE srchHdl;
TRACE("Searching for drivers in %ls...\n", path);
- wcsncpy(srchPath, path, MAX_PATH);
- wcsncat(srchPath, L"\\*oal.dll", MAX_PATH - lstrlenW(srchPath));
- srchHdl = FindFirstFileW(srchPath, &fdata);
+ std::wstring srchPath = path;
+ srchPath += L"\\*oal.dll";
+
+ HANDLE srchHdl = FindFirstFileW(srchPath.c_str(), &fdata);
if(srchHdl != INVALID_HANDLE_VALUE)
{
do {
HMODULE mod;
- wcsncpy(srchPath, path, MAX_PATH);
- wcsncat(srchPath, L"\\", MAX_PATH - lstrlenW(srchPath));
- wcsncat(srchPath, fdata.cFileName, MAX_PATH - lstrlenW(srchPath));
- TRACE("Found %ls\n", srchPath);
+ srchPath = path;
+ srchPath += L"\\";
+ srchPath += fdata.cFileName;
+ TRACE("Found %ls\n", srchPath.c_str());
- mod = LoadLibraryW(srchPath);
+ mod = LoadLibraryW(srchPath.c_str());
if(!mod)
- WARN("Could not load %ls\n", srchPath);
+ WARN("Could not load %ls\n", srchPath.c_str());
else
AddModule(mod, fdata.cFileName);
} while(FindNextFileW(srchHdl, &fdata));
@@ -307,7 +269,7 @@ static void SearchDrivers(WCHAR *path)
static WCHAR *strrchrW(WCHAR *str, WCHAR ch)
{
- WCHAR *res = NULL;
+ WCHAR *res = nullptr;
while(str && *str != '\0')
{
if(*str == ch)
@@ -319,7 +281,7 @@ static WCHAR *strrchrW(WCHAR *str, WCHAR ch)
static int GetLoadedModuleDirectory(const WCHAR *name, WCHAR *moddir, DWORD length)
{
- HMODULE module = NULL;
+ HMODULE module = nullptr;
WCHAR *sep0, *sep1;
if(name)
@@ -359,7 +321,7 @@ void LoadDriverList(void)
cwd_path[len-1] = '\0';
TRACE("Got current working directory %ls\n", cwd_path);
- if(GetLoadedModuleDirectory(NULL, proc_path, MAX_PATH))
+ if(GetLoadedModuleDirectory(nullptr, proc_path, MAX_PATH))
TRACE("Got proc path %ls\n", proc_path);
GetSystemDirectoryW(sys_path, MAX_PATH);
@@ -388,150 +350,90 @@ void LoadDriverList(void)
}
-void InitPtrIntMap(PtrIntMap *map)
-{
- map->keys = NULL;
- map->values = NULL;
- map->size = 0;
- map->capacity = 0;
- RWLockInit(&map->lock);
-}
-
-void ResetPtrIntMap(PtrIntMap *map)
+PtrIntMap::~PtrIntMap()
{
- WriteLock(&map->lock);
- al_free(map->keys);
- map->keys = NULL;
- map->values = NULL;
- map->size = 0;
- map->capacity = 0;
- WriteUnlock(&map->lock);
+ std::lock_guard<std::mutex> maplock{mLock};
+ al_free(mKeys);
+ mKeys = nullptr;
+ mValues = nullptr;
+ mSize = 0;
+ mCapacity = 0;
}
-ALenum InsertPtrIntMapEntry(PtrIntMap *map, ALvoid *key, ALint value)
+ALenum PtrIntMap::insert(ALvoid *key, ALint value)
{
- ALsizei pos = 0;
+ std::lock_guard<std::mutex> maplock{mLock};
+ auto iter = std::lower_bound(mKeys, mKeys+mSize, key);
+ auto pos = static_cast<ALsizei>(std::distance(mKeys, iter));
- WriteLock(&map->lock);
- if(map->size > 0)
+ if(pos == mSize || mKeys[pos] != key)
{
- ALsizei count = map->size;
- do {
- ALsizei step = count>>1;
- ALsizei i = pos+step;
- if(!(map->keys[i] < key))
- count = step;
- else
- {
- pos = i+1;
- count -= step+1;
- }
- } while(count > 0);
- }
-
- if(pos == map->size || map->keys[pos] != key)
- {
- if(map->size == map->capacity)
+ if(mSize == mCapacity)
{
- ALvoid **keys = NULL;
- ALint *values;
- ALsizei newcap;
-
- newcap = (map->capacity ? (map->capacity<<1) : 4);
- if(newcap > map->capacity)
- keys = al_calloc(16, (sizeof(map->keys[0])+sizeof(map->values[0]))*newcap);
- if(!keys)
- {
- WriteUnlock(&map->lock);
+ ALvoid **newkeys{nullptr};
+ ALsizei newcap{mCapacity ? (mCapacity<<1) : 4};
+ if(newcap > mCapacity)
+ newkeys = static_cast<ALvoid**>(
+ al_calloc(16, (sizeof(mKeys[0])+sizeof(mValues[0]))*newcap)
+ );
+ if(!newkeys)
return AL_OUT_OF_MEMORY;
- }
- values = (ALint*)&keys[newcap];
+ auto newvalues = reinterpret_cast<ALint*>(&newkeys[newcap]);
- if(map->keys)
+ if(mKeys)
{
- memcpy(keys, map->keys, map->size*sizeof(map->keys[0]));
- memcpy(values, map->values, map->size*sizeof(map->values[0]));
+ std::copy_n(mKeys, mSize, newkeys);
+ std::copy_n(mValues, mSize, newvalues);
}
- al_free(map->keys);
- map->keys = keys;
- map->values = values;
- map->capacity = newcap;
+ al_free(mKeys);
+ mKeys = newkeys;
+ mValues = newvalues;
+ mCapacity = newcap;
}
- if(pos < map->size)
+ if(pos < mSize)
{
- memmove(&map->keys[pos+1], &map->keys[pos],
- (map->size-pos)*sizeof(map->keys[0]));
- memmove(&map->values[pos+1], &map->values[pos],
- (map->size-pos)*sizeof(map->values[0]));
+ std::copy_backward(mKeys+pos, mKeys+mSize, mKeys+mSize+1);
+ std::copy_backward(mValues+pos, mValues+mSize, mValues+mSize+1);
}
- map->size++;
+ mSize++;
}
- map->keys[pos] = key;
- map->values[pos] = value;
- WriteUnlock(&map->lock);
+ mKeys[pos] = key;
+ mValues[pos] = value;
return AL_NO_ERROR;
}
-ALint RemovePtrIntMapKey(PtrIntMap *map, ALvoid *key)
+ALint PtrIntMap::removeByKey(ALvoid *key)
{
ALint ret = -1;
- WriteLock(&map->lock);
- if(map->size > 0)
+
+ std::lock_guard<std::mutex> maplock{mLock};
+ auto iter = std::lower_bound(mKeys, mKeys+mSize, key);
+ auto pos = static_cast<ALsizei>(std::distance(mKeys, iter));
+ if(pos < mSize && mKeys[pos] == key)
{
- ALsizei pos = 0;
- ALsizei count = map->size;
- do {
- ALsizei step = count>>1;
- ALsizei i = pos+step;
- if(!(map->keys[i] < key))
- count = step;
- else
- {
- pos = i+1;
- count -= step+1;
- }
- } while(count > 0);
- if(pos < map->size && map->keys[pos] == key)
+ ret = mValues[pos];
+ if(pos+1 < mSize)
{
- ret = map->values[pos];
- if(pos < map->size-1)
- {
- memmove(&map->keys[pos], &map->keys[pos+1],
- (map->size-1-pos)*sizeof(map->keys[0]));
- memmove(&map->values[pos], &map->values[pos+1],
- (map->size-1-pos)*sizeof(map->values[0]));
- }
- map->size--;
+ std::copy(mKeys+pos+1, mKeys+mSize, mKeys+pos);
+ std::copy(mValues+pos+1, mValues+mSize, mValues+pos);
}
+ mSize--;
}
- WriteUnlock(&map->lock);
+
return ret;
}
-ALint LookupPtrIntMapKey(PtrIntMap *map, ALvoid *key)
+ALint PtrIntMap::lookupByKey(ALvoid *key)
{
ALint ret = -1;
- ReadLock(&map->lock);
- if(map->size > 0)
- {
- ALsizei pos = 0;
- ALsizei count = map->size;
- do {
- ALsizei step = count>>1;
- ALsizei i = pos+step;
- if(!(map->keys[i] < key))
- count = step;
- else
- {
- pos = i+1;
- count -= step+1;
- }
- } while(count > 0);
- if(pos < map->size && map->keys[pos] == key)
- ret = map->values[pos];
- }
- ReadUnlock(&map->lock);
+
+ std::lock_guard<std::mutex> maplock{mLock};
+ auto iter = std::lower_bound(mKeys, mKeys+mSize, key);
+ auto pos = static_cast<ALsizei>(std::distance(mKeys, iter));
+ if(pos < mSize && mKeys[pos] == key)
+ ret = mValues[pos];
+
return ret;
}
diff --git a/router/router.h b/router/router.h
index 32a91dcb..007b6a16 100644
--- a/router/router.h
+++ b/router/router.h
@@ -7,160 +7,155 @@
#include <stdio.h>
+#include <vector>
+#include <string>
+#include <atomic>
+#include <mutex>
+
#include "AL/alc.h"
#include "AL/al.h"
#include "AL/alext.h"
-#include "atomic.h"
-#include "rwlock.h"
-#include "threads.h"
-
-
-#ifndef UNUSED
-#if defined(__cplusplus)
-#define UNUSED(x)
-#elif defined(__GNUC__)
-#define UNUSED(x) UNUSED_##x __attribute__((unused))
-#elif defined(__LCLINT__)
-#define UNUSED(x) /*@unused@*/ x
-#else
-#define UNUSED(x) x
-#endif
-#endif
+
#define MAKE_ALC_VER(major, minor) (((major)<<8) | (minor))
-typedef struct DriverIface {
- WCHAR Name[32];
- HMODULE Module;
- int ALCVer;
-
- LPALCCREATECONTEXT alcCreateContext;
- LPALCMAKECONTEXTCURRENT alcMakeContextCurrent;
- LPALCPROCESSCONTEXT alcProcessContext;
- LPALCSUSPENDCONTEXT alcSuspendContext;
- LPALCDESTROYCONTEXT alcDestroyContext;
- LPALCGETCURRENTCONTEXT alcGetCurrentContext;
- LPALCGETCONTEXTSDEVICE alcGetContextsDevice;
- LPALCOPENDEVICE alcOpenDevice;
- LPALCCLOSEDEVICE alcCloseDevice;
- LPALCGETERROR alcGetError;
- LPALCISEXTENSIONPRESENT alcIsExtensionPresent;
- LPALCGETPROCADDRESS alcGetProcAddress;
- LPALCGETENUMVALUE alcGetEnumValue;
- LPALCGETSTRING alcGetString;
- LPALCGETINTEGERV alcGetIntegerv;
- LPALCCAPTUREOPENDEVICE alcCaptureOpenDevice;
- LPALCCAPTURECLOSEDEVICE alcCaptureCloseDevice;
- LPALCCAPTURESTART alcCaptureStart;
- LPALCCAPTURESTOP alcCaptureStop;
- LPALCCAPTURESAMPLES alcCaptureSamples;
-
- PFNALCSETTHREADCONTEXTPROC alcSetThreadContext;
- PFNALCGETTHREADCONTEXTPROC alcGetThreadContext;
-
- LPALENABLE alEnable;
- LPALDISABLE alDisable;
- LPALISENABLED alIsEnabled;
- LPALGETSTRING alGetString;
- LPALGETBOOLEANV alGetBooleanv;
- LPALGETINTEGERV alGetIntegerv;
- LPALGETFLOATV alGetFloatv;
- LPALGETDOUBLEV alGetDoublev;
- LPALGETBOOLEAN alGetBoolean;
- LPALGETINTEGER alGetInteger;
- LPALGETFLOAT alGetFloat;
- LPALGETDOUBLE alGetDouble;
- LPALGETERROR alGetError;
- LPALISEXTENSIONPRESENT alIsExtensionPresent;
- LPALGETPROCADDRESS alGetProcAddress;
- LPALGETENUMVALUE alGetEnumValue;
- LPALLISTENERF alListenerf;
- LPALLISTENER3F alListener3f;
- LPALLISTENERFV alListenerfv;
- LPALLISTENERI alListeneri;
- LPALLISTENER3I alListener3i;
- LPALLISTENERIV alListeneriv;
- LPALGETLISTENERF alGetListenerf;
- LPALGETLISTENER3F alGetListener3f;
- LPALGETLISTENERFV alGetListenerfv;
- LPALGETLISTENERI alGetListeneri;
- LPALGETLISTENER3I alGetListener3i;
- LPALGETLISTENERIV alGetListeneriv;
- LPALGENSOURCES alGenSources;
- LPALDELETESOURCES alDeleteSources;
- LPALISSOURCE alIsSource;
- LPALSOURCEF alSourcef;
- LPALSOURCE3F alSource3f;
- LPALSOURCEFV alSourcefv;
- LPALSOURCEI alSourcei;
- LPALSOURCE3I alSource3i;
- LPALSOURCEIV alSourceiv;
- LPALGETSOURCEF alGetSourcef;
- LPALGETSOURCE3F alGetSource3f;
- LPALGETSOURCEFV alGetSourcefv;
- LPALGETSOURCEI alGetSourcei;
- LPALGETSOURCE3I alGetSource3i;
- LPALGETSOURCEIV alGetSourceiv;
- LPALSOURCEPLAYV alSourcePlayv;
- LPALSOURCESTOPV alSourceStopv;
- LPALSOURCEREWINDV alSourceRewindv;
- LPALSOURCEPAUSEV alSourcePausev;
- LPALSOURCEPLAY alSourcePlay;
- LPALSOURCESTOP alSourceStop;
- LPALSOURCEREWIND alSourceRewind;
- LPALSOURCEPAUSE alSourcePause;
- LPALSOURCEQUEUEBUFFERS alSourceQueueBuffers;
- LPALSOURCEUNQUEUEBUFFERS alSourceUnqueueBuffers;
- LPALGENBUFFERS alGenBuffers;
- LPALDELETEBUFFERS alDeleteBuffers;
- LPALISBUFFER alIsBuffer;
- LPALBUFFERF alBufferf;
- LPALBUFFER3F alBuffer3f;
- LPALBUFFERFV alBufferfv;
- LPALBUFFERI alBufferi;
- LPALBUFFER3I alBuffer3i;
- LPALBUFFERIV alBufferiv;
- LPALGETBUFFERF alGetBufferf;
- LPALGETBUFFER3F alGetBuffer3f;
- LPALGETBUFFERFV alGetBufferfv;
- LPALGETBUFFERI alGetBufferi;
- LPALGETBUFFER3I alGetBuffer3i;
- LPALGETBUFFERIV alGetBufferiv;
- LPALBUFFERDATA alBufferData;
- LPALDOPPLERFACTOR alDopplerFactor;
- LPALDOPPLERVELOCITY alDopplerVelocity;
- LPALSPEEDOFSOUND alSpeedOfSound;
- LPALDISTANCEMODEL alDistanceModel;
-} DriverIface;
-
-extern DriverIface *DriverList;
-extern int DriverListSize;
-
-extern altss_t ThreadCtxDriver;
-typedef ATOMIC(DriverIface*) atomic_DriverIfacePtr;
-extern atomic_DriverIfacePtr CurrentCtxDriver;
-
-
-typedef struct PtrIntMap {
- ALvoid **keys;
- /* Shares memory with keys. */
- ALint *values;
+struct DriverIface {
+ std::wstring Name;
+ HMODULE Module{nullptr};
+ int ALCVer{0};
+
+ LPALCCREATECONTEXT alcCreateContext{nullptr};
+ LPALCMAKECONTEXTCURRENT alcMakeContextCurrent{nullptr};
+ LPALCPROCESSCONTEXT alcProcessContext{nullptr};
+ LPALCSUSPENDCONTEXT alcSuspendContext{nullptr};
+ LPALCDESTROYCONTEXT alcDestroyContext{nullptr};
+ LPALCGETCURRENTCONTEXT alcGetCurrentContext{nullptr};
+ LPALCGETCONTEXTSDEVICE alcGetContextsDevice{nullptr};
+ LPALCOPENDEVICE alcOpenDevice{nullptr};
+ LPALCCLOSEDEVICE alcCloseDevice{nullptr};
+ LPALCGETERROR alcGetError{nullptr};
+ LPALCISEXTENSIONPRESENT alcIsExtensionPresent{nullptr};
+ LPALCGETPROCADDRESS alcGetProcAddress{nullptr};
+ LPALCGETENUMVALUE alcGetEnumValue{nullptr};
+ LPALCGETSTRING alcGetString{nullptr};
+ LPALCGETINTEGERV alcGetIntegerv{nullptr};
+ LPALCCAPTUREOPENDEVICE alcCaptureOpenDevice{nullptr};
+ LPALCCAPTURECLOSEDEVICE alcCaptureCloseDevice{nullptr};
+ LPALCCAPTURESTART alcCaptureStart{nullptr};
+ LPALCCAPTURESTOP alcCaptureStop{nullptr};
+ LPALCCAPTURESAMPLES alcCaptureSamples{nullptr};
+
+ PFNALCSETTHREADCONTEXTPROC alcSetThreadContext{nullptr};
+ PFNALCGETTHREADCONTEXTPROC alcGetThreadContext{nullptr};
+
+ LPALENABLE alEnable{nullptr};
+ LPALDISABLE alDisable{nullptr};
+ LPALISENABLED alIsEnabled{nullptr};
+ LPALGETSTRING alGetString{nullptr};
+ LPALGETBOOLEANV alGetBooleanv{nullptr};
+ LPALGETINTEGERV alGetIntegerv{nullptr};
+ LPALGETFLOATV alGetFloatv{nullptr};
+ LPALGETDOUBLEV alGetDoublev{nullptr};
+ LPALGETBOOLEAN alGetBoolean{nullptr};
+ LPALGETINTEGER alGetInteger{nullptr};
+ LPALGETFLOAT alGetFloat{nullptr};
+ LPALGETDOUBLE alGetDouble{nullptr};
+ LPALGETERROR alGetError{nullptr};
+ LPALISEXTENSIONPRESENT alIsExtensionPresent{nullptr};
+ LPALGETPROCADDRESS alGetProcAddress{nullptr};
+ LPALGETENUMVALUE alGetEnumValue{nullptr};
+ LPALLISTENERF alListenerf{nullptr};
+ LPALLISTENER3F alListener3f{nullptr};
+ LPALLISTENERFV alListenerfv{nullptr};
+ LPALLISTENERI alListeneri{nullptr};
+ LPALLISTENER3I alListener3i{nullptr};
+ LPALLISTENERIV alListeneriv{nullptr};
+ LPALGETLISTENERF alGetListenerf{nullptr};
+ LPALGETLISTENER3F alGetListener3f{nullptr};
+ LPALGETLISTENERFV alGetListenerfv{nullptr};
+ LPALGETLISTENERI alGetListeneri{nullptr};
+ LPALGETLISTENER3I alGetListener3i{nullptr};
+ LPALGETLISTENERIV alGetListeneriv{nullptr};
+ LPALGENSOURCES alGenSources{nullptr};
+ LPALDELETESOURCES alDeleteSources{nullptr};
+ LPALISSOURCE alIsSource{nullptr};
+ LPALSOURCEF alSourcef{nullptr};
+ LPALSOURCE3F alSource3f{nullptr};
+ LPALSOURCEFV alSourcefv{nullptr};
+ LPALSOURCEI alSourcei{nullptr};
+ LPALSOURCE3I alSource3i{nullptr};
+ LPALSOURCEIV alSourceiv{nullptr};
+ LPALGETSOURCEF alGetSourcef{nullptr};
+ LPALGETSOURCE3F alGetSource3f{nullptr};
+ LPALGETSOURCEFV alGetSourcefv{nullptr};
+ LPALGETSOURCEI alGetSourcei{nullptr};
+ LPALGETSOURCE3I alGetSource3i{nullptr};
+ LPALGETSOURCEIV alGetSourceiv{nullptr};
+ LPALSOURCEPLAYV alSourcePlayv{nullptr};
+ LPALSOURCESTOPV alSourceStopv{nullptr};
+ LPALSOURCEREWINDV alSourceRewindv{nullptr};
+ LPALSOURCEPAUSEV alSourcePausev{nullptr};
+ LPALSOURCEPLAY alSourcePlay{nullptr};
+ LPALSOURCESTOP alSourceStop{nullptr};
+ LPALSOURCEREWIND alSourceRewind{nullptr};
+ LPALSOURCEPAUSE alSourcePause{nullptr};
+ LPALSOURCEQUEUEBUFFERS alSourceQueueBuffers{nullptr};
+ LPALSOURCEUNQUEUEBUFFERS alSourceUnqueueBuffers{nullptr};
+ LPALGENBUFFERS alGenBuffers{nullptr};
+ LPALDELETEBUFFERS alDeleteBuffers{nullptr};
+ LPALISBUFFER alIsBuffer{nullptr};
+ LPALBUFFERF alBufferf{nullptr};
+ LPALBUFFER3F alBuffer3f{nullptr};
+ LPALBUFFERFV alBufferfv{nullptr};
+ LPALBUFFERI alBufferi{nullptr};
+ LPALBUFFER3I alBuffer3i{nullptr};
+ LPALBUFFERIV alBufferiv{nullptr};
+ LPALGETBUFFERF alGetBufferf{nullptr};
+ LPALGETBUFFER3F alGetBuffer3f{nullptr};
+ LPALGETBUFFERFV alGetBufferfv{nullptr};
+ LPALGETBUFFERI alGetBufferi{nullptr};
+ LPALGETBUFFER3I alGetBuffer3i{nullptr};
+ LPALGETBUFFERIV alGetBufferiv{nullptr};
+ LPALBUFFERDATA alBufferData{nullptr};
+ LPALDOPPLERFACTOR alDopplerFactor{nullptr};
+ LPALDOPPLERVELOCITY alDopplerVelocity{nullptr};
+ LPALSPEEDOFSOUND alSpeedOfSound{nullptr};
+ LPALDISTANCEMODEL alDistanceModel{nullptr};
+
+ DriverIface(std::wstring name, HMODULE mod)
+ : Name(std::move(name)), Module(mod)
+ { }
+ ~DriverIface()
+ {
+ if(Module)
+ FreeLibrary(Module);
+ Module = nullptr;
+ }
+};
- ALsizei size;
- ALsizei capacity;
- RWLock lock;
-} PtrIntMap;
-#define PTRINTMAP_STATIC_INITIALIZE { NULL, NULL, 0, 0, RWLOCK_STATIC_INITIALIZE }
+extern std::vector<DriverIface> DriverList;
-void InitPtrIntMap(PtrIntMap *map);
-void ResetPtrIntMap(PtrIntMap *map);
-ALenum InsertPtrIntMapEntry(PtrIntMap *map, ALvoid *key, ALint value);
-ALint RemovePtrIntMapKey(PtrIntMap *map, ALvoid *key);
-ALint LookupPtrIntMapKey(PtrIntMap *map, ALvoid *key);
+extern thread_local DriverIface *ThreadCtxDriver;
+extern std::atomic<DriverIface*> CurrentCtxDriver;
-void InitALC(void);
-void ReleaseALC(void);
+class PtrIntMap {
+ ALvoid **mKeys{nullptr};
+ /* Shares memory with keys. */
+ ALint *mValues{nullptr};
+
+ ALsizei mSize{0};
+ ALsizei mCapacity{0};
+ std::mutex mLock;
+
+public:
+ PtrIntMap() = default;
+ ~PtrIntMap();
+
+ ALenum insert(ALvoid *key, ALint value);
+ ALint removeByKey(ALvoid *key);
+ ALint lookupByKey(ALvoid *key);
+};
enum LogLevel {
diff --git a/utils/CIAIR.def b/utils/CIAIR.def
index 4876dc50..5fabdb3f 100644
--- a/utils/CIAIR.def
+++ b/utils/CIAIR.def
@@ -1,5 +1,5 @@
-# This is a makehrtf HRIR definition file. It is used to define the layout
-# and source data to be processed into an OpenAL Soft compatible HRTF.
+# This is a makemhr HRIR definition file. It is used to define the layout and
+# source data to be processed into an OpenAL Soft compatible HRTF.
#
# This definition is used to transform the left and right ear HRIRs from a
# data set used in several papers and articles by Fumitada Itakura, Kazuya
diff --git a/utils/IRC_1005.def b/utils/IRC_1005.def
index c2fd90b5..c91e7b4d 100644
--- a/utils/IRC_1005.def
+++ b/utils/IRC_1005.def
@@ -1,5 +1,5 @@
-# This is a makehrtf HRIR definition file. It is used to define the layout
-# and source data to be processed into an OpenAL Soft compatible HRTF.
+# This is a makemhr HRIR definition file. It is used to define the layout and
+# source data to be processed into an OpenAL Soft compatible HRTF.
#
# This definition is used to transform the left and right ear HRIRs of any
# raw data set from the IRCAM/AKG Listen HRTF database.
diff --git a/utils/MIT_KEMAR.def b/utils/MIT_KEMAR.def
index e6b0ddff..15036d9b 100644
--- a/utils/MIT_KEMAR.def
+++ b/utils/MIT_KEMAR.def
@@ -1,5 +1,5 @@
-# This is a makehrtf HRIR definition file. It is used to define the layout
-# and source data to be processed into an OpenAL Soft compatible HRTF.
+# This is a makemhr HRIR definition file. It is used to define the layout and
+# source data to be processed into an OpenAL Soft compatible HRTF.
#
# This definition is used to transform the left ear HRIRs from the full set
# of KEMAR HRIRs provided by Bill Gardner <[email protected]> and Keith
@@ -33,9 +33,9 @@ type = mono
points = 512
# The radius of the listener's head (measured ear-to-ear in meters). The
-# makehrtf utility uses this value to rescale measured propagation delays
-# when a custom head radius is specified on the command line. It is also
-# used as the default radius when the spherical model is used to calculate an
+# makemhr utility uses this value to rescale measured propagation delays when
+# a custom head radius is specified on the command line. It is also used as
+# the default radius when the spherical model is used to calculate an
# approximate set of delays. This should match the data set as close as
# possible for accurate rescaling when using the measured delays (the
# default). At the moment, radius rescaling does not adjust HRIR coupling.
diff --git a/utils/MIT_KEMAR_sofa.def b/utils/MIT_KEMAR_sofa.def
new file mode 100644
index 00000000..5f25815f
--- /dev/null
+++ b/utils/MIT_KEMAR_sofa.def
@@ -0,0 +1,51 @@
+# This is a makemhr HRIR definition file. It is used to define the layout and
+# source data to be processed into an OpenAL Soft compatible HRTF.
+#
+# This definition is used to transform the SOFA packaged KEMAR HRIRs
+# originally provided by Bill Gardner <[email protected]> and Keith Martin
+# <[email protected]> of MIT Media Laboratory.
+#
+# The SOFA conversion is available from:
+#
+# http://sofacoustics.org/data/database/mit/
+#
+# The original data is available from:
+#
+# http://sound.media.mit.edu/resources/KEMAR.html
+#
+# It is copyrighted 1994 by MIT Media Laboratory, and provided free of charge
+# with no restrictions on use so long as the authors (above) are cited.
+
+# Sampling rate of the HRIR data (in hertz).
+rate = 44100
+
+# The SOFA file is stereo, but the original data was mono. Channels are just
+# mirrored by azimuth; so save some memory by allowing OpenAL Soft to mirror
+# them at run time.
+type = mono
+
+points = 512
+
+radius = 0.09
+
+# The MIT set has only one field with a distance of 1.4m.
+distance = 1.4
+
+# The MIT set varies the number of azimuths for each elevation to maintain
+# an average distance between them.
+azimuths = 1, 12, 24, 36, 45, 56, 60, 72, 72, 72, 72, 72, 60, 56, 45, 36, 24, 12, 1
+
+# Normally the dataset would be composed manually by listing all necessary
+# 'sofa' sources with the appropriate radius, elevation, azimuth (counter-
+# clockwise for SOFA files) and receiver arguments:
+#
+# [ 5, 0 ] = sofa (1.4, -40.0, 0.0 : 0) : "./mit_kemar_normal_pinna.sofa"
+# [ 5, 1 ] = sofa (1.4, -40.0, 353.6 : 0) : "./mit_kemar_normal_pinna.sofa"
+# [ 5, 2 ] = sofa (1.4, -40.0, 347.1 : 0) : "./mit_kemar_normal_pinna.sofa"
+# [ 5, 3 ] = sofa (1.4, -40.0, 340.7 : 0) : "./mit_kemar_normal_pinna.sofa"
+# ...
+#
+# If HRIR composition isn't necessary, it's easier to just use the following:
+
+[ * ] = sofa : "./mit_kemar_normal_pinna.sofa" mono
+
diff --git a/utils/SCUT_KEMAR.def b/utils/SCUT_KEMAR.def
new file mode 100644
index 00000000..e5ae4ff8
--- /dev/null
+++ b/utils/SCUT_KEMAR.def
@@ -0,0 +1,48 @@
+# This is a makemhr HRIR definition file. It is used to define the layout and
+# source data to be processed into an OpenAL Soft compatible HRTF.
+#
+# This definition is used to transform the near-field KEMAR HRIRs provided by
+# Bosun Xie <[email protected]> of the South China University of
+# Technology, Guangzhou, China; and converted from SCUT to SOFA format by
+# Piotr Majdak <[email protected]> of the Acoustics Research Institute,
+# Austrian Academy of Sciences.
+#
+# A copy of the data (SCUT_KEMAR_radius_all.sofa) is available from:
+#
+# http://sofacoustics.org/data/database/scut/SCUT_KEMAR_radius_all.sofa
+#
+# It is provided under the Creative Commons CC 3.0 BY-SA-NC license:
+#
+# https://creativecommons.org/licenses/by-nc-sa/3.0/
+
+rate = 44100
+
+# While the SOFA file is stereo, doubling the size of the data set will cause
+# the utility to exhaust its address space if compiled 32-bit. Since the
+# dummy head is symmetric, the same results (ignoring variations caused by
+# measurement error) can be obtained using mono channel processing.
+type = mono
+
+points = 512
+
+radius = 0.09
+
+# This data set has 10 fields ranging from 0.2m to 1m. The layout was
+# obtained using the sofa-info utility.
+distance = 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0
+
+azimuths = 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
+ 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
+ 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
+ 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
+ 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
+ 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
+ 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
+ 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
+ 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
+ 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1
+
+# Given the above compatible layout, we can automatically process the entire
+# data set.
+[ * ] = sofa : "./SCUT_KEMAR_radius_all.sofa" mono
+
diff --git a/utils/alsoft-config/CMakeLists.txt b/utils/alsoft-config/CMakeLists.txt
index 67cc44c7..68b9e9de 100644
--- a/utils/alsoft-config/CMakeLists.txt
+++ b/utils/alsoft-config/CMakeLists.txt
@@ -8,6 +8,8 @@ set(alsoft-config_SRCS
main.cpp
mainwindow.cpp
mainwindow.h
+ verstr.cpp
+ verstr.h
)
set(alsoft-config_UIS mainwindow.ui)
set(alsoft-config_MOCS mainwindow.h)
@@ -20,6 +22,7 @@ if(Qt5Widgets_FOUND AND NOT ALSOFT_NO_QT5)
add_executable(alsoft-config ${alsoft-config_SRCS} ${UIS} ${RSCS} ${TRS} ${MOCS})
target_link_libraries(alsoft-config Qt5::Widgets)
+ target_include_directories(alsoft-config PRIVATE "${OpenAL_BINARY_DIR}")
set_property(TARGET alsoft-config APPEND PROPERTY COMPILE_FLAGS ${EXTRA_CFLAGS})
set_target_properties(alsoft-config PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR})
if(TARGET build_version)
@@ -43,6 +46,7 @@ else()
add_executable(alsoft-config ${alsoft-config_SRCS} ${UIS} ${RSCS} ${TRS} ${MOCS})
target_link_libraries(alsoft-config ${QT_LIBRARIES})
+ target_include_directories(alsoft-config PRIVATE "${OpenAL_BINARY_DIR}")
set_property(TARGET alsoft-config APPEND PROPERTY COMPILE_FLAGS ${EXTRA_CFLAGS})
set_target_properties(alsoft-config PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR})
if(TARGET build_version)
diff --git a/utils/alsoft-config/mainwindow.cpp b/utils/alsoft-config/mainwindow.cpp
index 110fe4ed..fed9de59 100644
--- a/utils/alsoft-config/mainwindow.cpp
+++ b/utils/alsoft-config/mainwindow.cpp
@@ -1,7 +1,7 @@
#include "config.h"
-#include "version.h"
+#include "mainwindow.h"
#include <iostream>
#include <cmath>
@@ -11,8 +11,13 @@
#include <QCloseEvent>
#include <QSettings>
#include <QtGlobal>
-#include "mainwindow.h"
#include "ui_mainwindow.h"
+#include "verstr.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#include <shlobj.h>
+#endif
namespace {
@@ -101,7 +106,9 @@ static const struct NameValuePair {
{ "Linear", "linear" },
{ "Default (Linear)", "" },
{ "Cubic Spline", "cubic" },
+ { "11th order Sinc (fast)", "fast_bsinc12" },
{ "11th order Sinc", "bsinc12" },
+ { "23rd order Sinc (fast)", "fast_bsinc24" },
{ "23rd order Sinc", "bsinc24" },
{ "", "" }
@@ -119,18 +126,32 @@ static const struct NameValuePair {
{ "", "" }
}, ambiFormatList[] = {
{ "Default", "" },
- { "ACN + SN3D", "acn+sn3d" },
- { "ACN + N3D", "acn+n3d" },
+ { "AmbiX (ACN, SN3D)", "ambix" },
+ { "ACN, N3D", "acn+n3d" },
{ "Furse-Malham", "fuma" },
{ "", "" }
+}, hrtfModeList[] = {
+ { "1st Order Ambisonic", "ambi1" },
+ { "2nd Order Ambisonic", "ambi2" },
+ { "Default (Full)", "" },
+ { "Full", "full" },
+
+ { "", "" }
};
static QString getDefaultConfigName()
{
#ifdef Q_OS_WIN32
static const char fname[] = "alsoft.ini";
- QByteArray base = qgetenv("AppData");
+ auto get_appdata_path = []() noexcept -> QString
+ {
+ WCHAR buffer[MAX_PATH];
+ if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
+ return QString::fromWCharArray(buffer);
+ return QString();
+ };
+ QString base = get_appdata_path();
#else
static const char fname[] = "alsoft.conf";
QByteArray base = qgetenv("XDG_CONFIG_HOME");
@@ -149,7 +170,14 @@ static QString getDefaultConfigName()
static QString getBaseDataPath()
{
#ifdef Q_OS_WIN32
- QByteArray base = qgetenv("AppData");
+ auto get_appdata_path = []() noexcept -> QString
+ {
+ WCHAR buffer[MAX_PATH];
+ if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
+ return QString::fromWCharArray(buffer);
+ return QString();
+ };
+ QString base = get_appdata_path();
#else
QByteArray base = qgetenv("XDG_DATA_HOME");
if(base.isEmpty())
@@ -162,7 +190,7 @@ static QString getBaseDataPath()
return base;
}
-static QStringList getAllDataPaths(QString append=QString())
+static QStringList getAllDataPaths(const QString &append)
{
QStringList list;
list.append(getBaseDataPath());
@@ -196,7 +224,7 @@ static QString getValueFromName(const NameValuePair (&list)[N], const QString &s
if(str == list[i].name)
return list[i].value;
}
- return QString();
+ return QString{};
}
template<size_t N>
@@ -207,7 +235,27 @@ static QString getNameFromValue(const NameValuePair (&list)[N], const QString &s
if(str == list[i].value)
return list[i].name;
}
- return QString();
+ return QString{};
+}
+
+
+Qt::CheckState getCheckState(const QVariant &var)
+{
+ if(var.isNull())
+ return Qt::PartiallyChecked;
+ if(var.toBool())
+ return Qt::Checked;
+ return Qt::Unchecked;
+}
+
+QString getCheckValue(const QCheckBox *checkbox)
+{
+ const Qt::CheckState state{checkbox->checkState()};
+ if(state == Qt::Checked)
+ return QString{"true"};
+ if(state == Qt::Unchecked)
+ return QString{"false"};
+ return QString{};
}
}
@@ -215,13 +263,13 @@ static QString getNameFromValue(const NameValuePair (&list)[N], const QString &s
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
- mPeriodSizeValidator(NULL),
- mPeriodCountValidator(NULL),
- mSourceCountValidator(NULL),
- mEffectSlotValidator(NULL),
- mSourceSendValidator(NULL),
- mSampleRateValidator(NULL),
- mJackBufferValidator(NULL),
+ mPeriodSizeValidator(nullptr),
+ mPeriodCountValidator(nullptr),
+ mSourceCountValidator(nullptr),
+ mEffectSlotValidator(nullptr),
+ mSourceSendValidator(nullptr),
+ mSampleRateValidator(nullptr),
+ mJackBufferValidator(nullptr),
mNeedsSave(false)
{
ui->setupUi(this);
@@ -247,6 +295,9 @@ MainWindow::MainWindow(QWidget *parent) :
}
ui->resamplerSlider->setRange(0, count-1);
+ for(count = 0;hrtfModeList[count].name[0];count++) {
+ }
+ ui->hrtfmodeSlider->setRange(0, count-1);
ui->hrtfStateComboBox->adjustSize();
#if !defined(HAVE_NEON) && !defined(HAVE_SSE)
@@ -289,125 +340,131 @@ MainWindow::MainWindow(QWidget *parent) :
#endif
- mPeriodSizeValidator = new QIntValidator(64, 8192, this);
+ mPeriodSizeValidator = new QIntValidator{64, 8192, this};
ui->periodSizeEdit->setValidator(mPeriodSizeValidator);
- mPeriodCountValidator = new QIntValidator(2, 16, this);
+ mPeriodCountValidator = new QIntValidator{2, 16, this};
ui->periodCountEdit->setValidator(mPeriodCountValidator);
- mSourceCountValidator = new QIntValidator(0, 4096, this);
+ mSourceCountValidator = new QIntValidator{0, 4096, this};
ui->srcCountLineEdit->setValidator(mSourceCountValidator);
- mEffectSlotValidator = new QIntValidator(0, 64, this);
+ mEffectSlotValidator = new QIntValidator{0, 64, this};
ui->effectSlotLineEdit->setValidator(mEffectSlotValidator);
- mSourceSendValidator = new QIntValidator(0, 16, this);
+ mSourceSendValidator = new QIntValidator{0, 16, this};
ui->srcSendLineEdit->setValidator(mSourceSendValidator);
- mSampleRateValidator = new QIntValidator(8000, 192000, this);
+ mSampleRateValidator = new QIntValidator{8000, 192000, this};
ui->sampleRateCombo->lineEdit()->setValidator(mSampleRateValidator);
- mJackBufferValidator = new QIntValidator(0, 8192, this);
+ mJackBufferValidator = new QIntValidator{0, 8192, this};
ui->jackBufferSizeLine->setValidator(mJackBufferValidator);
- connect(ui->actionLoad, SIGNAL(triggered()), this, SLOT(loadConfigFromFile()));
- connect(ui->actionSave_As, SIGNAL(triggered()), this, SLOT(saveConfigAsFile()));
-
- connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(showAboutPage()));
-
- connect(ui->closeCancelButton, SIGNAL(clicked()), this, SLOT(cancelCloseAction()));
- connect(ui->applyButton, SIGNAL(clicked()), this, SLOT(saveCurrentConfig()));
-
- connect(ui->channelConfigCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton()));
- connect(ui->sampleFormatCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton()));
- connect(ui->stereoModeCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton()));
- connect(ui->sampleRateCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton()));
- connect(ui->sampleRateCombo, SIGNAL(editTextChanged(const QString&)), this, SLOT(enableApplyButton()));
-
- connect(ui->resamplerSlider, SIGNAL(valueChanged(int)), this, SLOT(updateResamplerLabel(int)));
-
- connect(ui->periodSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(updatePeriodSizeEdit(int)));
- connect(ui->periodSizeEdit, SIGNAL(editingFinished()), this, SLOT(updatePeriodSizeSlider()));
- connect(ui->periodCountSlider, SIGNAL(valueChanged(int)), this, SLOT(updatePeriodCountEdit(int)));
- connect(ui->periodCountEdit, SIGNAL(editingFinished()), this, SLOT(updatePeriodCountSlider()));
-
- connect(ui->stereoEncodingComboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->ambiFormatComboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->outputLimiterCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->outputDitherCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
-
- connect(ui->decoderHQModeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->decoderDistCompCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->decoderNFEffectsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->decoderNFRefDelaySpinBox, SIGNAL(valueChanged(double)), this, SLOT(enableApplyButton()));
- connect(ui->decoderQuadLineEdit, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->decoderQuadButton, SIGNAL(clicked()), this, SLOT(selectQuadDecoderFile()));
- connect(ui->decoder51LineEdit, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->decoder51Button, SIGNAL(clicked()), this, SLOT(select51DecoderFile()));
- connect(ui->decoder61LineEdit, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->decoder61Button, SIGNAL(clicked()), this, SLOT(select61DecoderFile()));
- connect(ui->decoder71LineEdit, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->decoder71Button, SIGNAL(clicked()), this, SLOT(select71DecoderFile()));
-
- connect(ui->preferredHrtfComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton()));
- connect(ui->hrtfStateComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton()));
- connect(ui->hrtfAddButton, SIGNAL(clicked()), this, SLOT(addHrtfFile()));
- connect(ui->hrtfRemoveButton, SIGNAL(clicked()), this, SLOT(removeHrtfFile()));
- connect(ui->hrtfFileList, SIGNAL(itemSelectionChanged()), this, SLOT(updateHrtfRemoveButton()));
- connect(ui->defaultHrtfPathsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
-
- connect(ui->srcCountLineEdit, SIGNAL(editingFinished()), this, SLOT(enableApplyButton()));
- connect(ui->srcSendLineEdit, SIGNAL(editingFinished()), this, SLOT(enableApplyButton()));
- connect(ui->effectSlotLineEdit, SIGNAL(editingFinished()), this, SLOT(enableApplyButton()));
-
- connect(ui->enableSSECheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableSSE2CheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableSSE3CheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableSSE41CheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableNeonCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
+ connect(ui->actionLoad, &QAction::triggered, this, &MainWindow::loadConfigFromFile);
+ connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::saveConfigAsFile);
+
+ connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAboutPage);
+
+ connect(ui->closeCancelButton, &QPushButton::clicked, this, &MainWindow::cancelCloseAction);
+ connect(ui->applyButton, &QPushButton::clicked, this, &MainWindow::saveCurrentConfig);
+
+ auto qcb_cicstr = static_cast<void(QComboBox::*)(const QString&)>(&QComboBox::currentIndexChanged);
+ connect(ui->channelConfigCombo, qcb_cicstr, this, &MainWindow::enableApplyButton);
+ connect(ui->sampleFormatCombo, qcb_cicstr, this, &MainWindow::enableApplyButton);
+ connect(ui->stereoModeCombo, qcb_cicstr, this, &MainWindow::enableApplyButton);
+ connect(ui->sampleRateCombo, qcb_cicstr, this, &MainWindow::enableApplyButton);
+ connect(ui->sampleRateCombo, &QComboBox::editTextChanged, this, &MainWindow::enableApplyButton);
+
+ connect(ui->resamplerSlider, &QSlider::valueChanged, this, &MainWindow::updateResamplerLabel);
+
+ connect(ui->periodSizeSlider, &QSlider::valueChanged, this, &MainWindow::updatePeriodSizeEdit);
+ connect(ui->periodSizeEdit, &QLineEdit::editingFinished, this, &MainWindow::updatePeriodSizeSlider);
+ connect(ui->periodCountSlider, &QSlider::valueChanged, this, &MainWindow::updatePeriodCountEdit);
+ connect(ui->periodCountEdit, &QLineEdit::editingFinished, this, &MainWindow::updatePeriodCountSlider);
+
+ connect(ui->stereoEncodingComboBox, qcb_cicstr, this, &MainWindow::enableApplyButton);
+ connect(ui->ambiFormatComboBox, qcb_cicstr, this, &MainWindow::enableApplyButton);
+ connect(ui->outputLimiterCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->outputDitherCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+
+ connect(ui->decoderHQModeCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->decoderDistCompCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->decoderNFEffectsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ auto qdsb_vcd = static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged);
+ connect(ui->decoderNFRefDelaySpinBox, qdsb_vcd, this, &MainWindow::enableApplyButton);
+ connect(ui->decoderQuadLineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->decoderQuadButton, &QPushButton::clicked, this, &MainWindow::selectQuadDecoderFile);
+ connect(ui->decoder51LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->decoder51Button, &QPushButton::clicked, this, &MainWindow::select51DecoderFile);
+ connect(ui->decoder61LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->decoder61Button, &QPushButton::clicked, this, &MainWindow::select61DecoderFile);
+ connect(ui->decoder71LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->decoder71Button, &QPushButton::clicked, this, &MainWindow::select71DecoderFile);
+
+ connect(ui->preferredHrtfComboBox, qcb_cicstr, this, &MainWindow::enableApplyButton);
+ connect(ui->hrtfStateComboBox, qcb_cicstr, this, &MainWindow::enableApplyButton);
+ connect(ui->hrtfmodeSlider, &QSlider::valueChanged, this, &MainWindow::updateHrtfModeLabel);
+
+ connect(ui->hrtfAddButton, &QPushButton::clicked, this, &MainWindow::addHrtfFile);
+ connect(ui->hrtfRemoveButton, &QPushButton::clicked, this, &MainWindow::removeHrtfFile);
+ connect(ui->hrtfFileList, &QListWidget::itemSelectionChanged, this, &MainWindow::updateHrtfRemoveButton);
+ connect(ui->defaultHrtfPathsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+
+ connect(ui->srcCountLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton);
+ connect(ui->srcSendLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton);
+ connect(ui->effectSlotLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton);
+
+ connect(ui->enableSSECheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableSSE2CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableSSE3CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableSSE41CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableNeonCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
ui->enabledBackendList->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(ui->enabledBackendList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showEnabledBackendMenu(QPoint)));
+ connect(ui->enabledBackendList, &QListWidget::customContextMenuRequested, this, &MainWindow::showEnabledBackendMenu);
ui->disabledBackendList->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(ui->disabledBackendList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showDisabledBackendMenu(QPoint)));
- connect(ui->backendCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
-
- connect(ui->defaultReverbComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(enableApplyButton()));
- connect(ui->enableEaxReverbCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableStdReverbCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableAutowahCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableChorusCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableCompressorCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableDistortionCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableEchoCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableEqualizerCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableFlangerCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableFrequencyShifterCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableModulatorCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enableDedicatedCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->enablePitchShifterCheck, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
-
- connect(ui->pulseAutospawnCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->pulseAllowMovesCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->pulseFixRateCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
-
- connect(ui->jackAutospawnCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->jackBufferSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(updateJackBufferSizeEdit(int)));
- connect(ui->jackBufferSizeLine, SIGNAL(editingFinished()), this, SLOT(updateJackBufferSizeSlider()));
-
- connect(ui->alsaDefaultDeviceLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->alsaDefaultCaptureLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->alsaResamplerCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
- connect(ui->alsaMmapCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
-
- connect(ui->ossDefaultDeviceLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->ossPlaybackPushButton, SIGNAL(clicked(bool)), this, SLOT(selectOSSPlayback()));
- connect(ui->ossDefaultCaptureLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->ossCapturePushButton, SIGNAL(clicked(bool)), this, SLOT(selectOSSCapture()));
-
- connect(ui->solarisDefaultDeviceLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->solarisPlaybackPushButton, SIGNAL(clicked(bool)), this, SLOT(selectSolarisPlayback()));
-
- connect(ui->waveOutputLine, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton()));
- connect(ui->waveOutputButton, SIGNAL(clicked(bool)), this, SLOT(selectWaveOutput()));
- connect(ui->waveBFormatCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enableApplyButton()));
+ connect(ui->disabledBackendList, &QListWidget::customContextMenuRequested, this, &MainWindow::showDisabledBackendMenu);
+ connect(ui->backendCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+
+ connect(ui->defaultReverbComboBox, qcb_cicstr, this, &MainWindow::enableApplyButton);
+ connect(ui->enableEaxReverbCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableStdReverbCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableAutowahCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableChorusCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableCompressorCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableDistortionCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableEchoCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableEqualizerCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableFlangerCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableFrequencyShifterCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableModulatorCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableDedicatedCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enablePitchShifterCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->enableVocalMorpherCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+
+ connect(ui->pulseAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->pulseAllowMovesCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->pulseFixRateCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->pulseAdjLatencyCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+
+ connect(ui->jackAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->jackBufferSizeSlider, &QSlider::valueChanged, this, &MainWindow::updateJackBufferSizeEdit);
+ connect(ui->jackBufferSizeLine, &QLineEdit::editingFinished, this, &MainWindow::updateJackBufferSizeSlider);
+
+ connect(ui->alsaDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->alsaDefaultCaptureLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->alsaResamplerCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->alsaMmapCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
+
+ connect(ui->ossDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->ossPlaybackPushButton, &QPushButton::clicked, this, &MainWindow::selectOSSPlayback);
+ connect(ui->ossDefaultCaptureLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->ossCapturePushButton, &QPushButton::clicked, this, &MainWindow::selectOSSCapture);
+
+ connect(ui->solarisDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->solarisPlaybackPushButton, &QPushButton::clicked, this, &MainWindow::selectSolarisPlayback);
+
+ connect(ui->waveOutputLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
+ connect(ui->waveOutputButton, &QPushButton::clicked, this, &MainWindow::selectWaveOutput);
+ connect(ui->waveBFormatCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
ui->backendListWidget->setCurrentRow(0);
ui->tabWidget->setCurrentIndex(0);
@@ -446,8 +503,7 @@ void MainWindow::closeEvent(QCloseEvent *event)
{
QMessageBox::StandardButton btn = QMessageBox::warning(this,
tr("Apply changes?"), tr("Save changes before quitting?"),
- QMessageBox::Save | QMessageBox::No | QMessageBox::Cancel
- );
+ QMessageBox::Save | QMessageBox::No | QMessageBox::Cancel);
if(btn == QMessageBox::Save)
saveCurrentConfig();
if(btn == QMessageBox::Cancel)
@@ -467,9 +523,8 @@ void MainWindow::cancelCloseAction()
void MainWindow::showAboutPage()
{
QMessageBox::information(this, tr("About"),
- tr("OpenAL Soft Configuration Utility.\nBuilt for OpenAL Soft library version ")+
- (ALSOFT_VERSION "-" ALSOFT_GIT_COMMIT_HASH " (" ALSOFT_GIT_BRANCH " branch).")
- );
+ tr("OpenAL Soft Configuration Utility.\nBuilt for OpenAL Soft library version ") +
+ GetVersionString());
}
@@ -486,17 +541,17 @@ QStringList MainWindow::collectHrtfs()
{
if(!fname.endsWith(".mhr", Qt::CaseInsensitive))
continue;
- QString fullname = dir.absoluteFilePath(fname);
+ QString fullname{dir.absoluteFilePath(fname)};
if(processed.contains(fullname))
continue;
processed.push_back(fullname);
- QString name = fname.left(fname.length()-4);
+ QString name{fname.left(fname.length()-4)};
if(!ret.contains(name))
ret.push_back(name);
else
{
- size_t i = 2;
+ size_t i{2};
do {
QString s = name+" #"+QString::number(i);
if(!ret.contains(s))
@@ -515,25 +570,25 @@ QStringList MainWindow::collectHrtfs()
QStringList paths = getAllDataPaths("/openal/hrtf");
foreach(const QString &name, paths)
{
- QDir dir(name);
- QStringList fnames = dir.entryList(QDir::Files | QDir::Readable, QDir::Name);
+ QDir dir{name};
+ QStringList fnames{dir.entryList(QDir::Files | QDir::Readable, QDir::Name)};
foreach(const QString &fname, fnames)
{
if(!fname.endsWith(".mhr", Qt::CaseInsensitive))
continue;
- QString fullname = dir.absoluteFilePath(fname);
+ QString fullname{dir.absoluteFilePath(fname)};
if(processed.contains(fullname))
continue;
processed.push_back(fullname);
- QString name = fname.left(fname.length()-4);
+ QString name{fname.left(fname.length()-4)};
if(!ret.contains(name))
ret.push_back(name);
else
{
- size_t i = 2;
+ size_t i{2};
do {
- QString s = name+" #"+QString::number(i);
+ QString s{name+" #"+QString::number(i)};
if(!ret.contains(s))
{
ret.push_back(s);
@@ -563,33 +618,33 @@ void MainWindow::loadConfigFromFile()
void MainWindow::loadConfig(const QString &fname)
{
- QSettings settings(fname, QSettings::IniFormat);
+ QSettings settings{fname, QSettings::IniFormat};
QString sampletype = settings.value("sample-type").toString();
ui->sampleFormatCombo->setCurrentIndex(0);
if(sampletype.isEmpty() == false)
{
- QString str = getNameFromValue(sampleTypeList, sampletype);
+ QString str{getNameFromValue(sampleTypeList, sampletype)};
if(!str.isEmpty())
{
- int j = ui->sampleFormatCombo->findText(str);
+ const int j{ui->sampleFormatCombo->findText(str)};
if(j > 0) ui->sampleFormatCombo->setCurrentIndex(j);
}
}
- QString channelconfig = settings.value("channels").toString();
+ QString channelconfig{settings.value("channels").toString()};
ui->channelConfigCombo->setCurrentIndex(0);
if(channelconfig.isEmpty() == false)
{
- QString str = getNameFromValue(speakerModeList, channelconfig);
+ QString str{getNameFromValue(speakerModeList, channelconfig)};
if(!str.isEmpty())
{
- int j = ui->channelConfigCombo->findText(str);
+ const int j{ui->channelConfigCombo->findText(str)};
if(j > 0) ui->channelConfigCombo->setCurrentIndex(j);
}
}
- QString srate = settings.value("frequency").toString();
+ QString srate{settings.value("frequency").toString()};
if(srate.isEmpty())
ui->sampleRateCombo->setCurrentIndex(0);
else
@@ -608,11 +663,11 @@ void MainWindow::loadConfig(const QString &fname)
QString resampler = settings.value("resampler").toString().trimmed();
ui->resamplerSlider->setValue(2);
ui->resamplerLabel->setText(resamplerList[2].name);
- /* The "cubic" and "sinc8" resamplers are no longer supported. Use "sinc4"
+ /* The "sinc4" and "sinc8" resamplers are no longer supported. Use "cubic"
* as a fallback.
*/
- if(resampler == "cubic" || resampler == "sinc8")
- resampler = "sinc4";
+ if(resampler == "sinc4" || resampler == "sinc8")
+ resampler = "cubic";
/* The "bsinc" resampler name is an alias for "bsinc12". */
else if(resampler == "bsinc")
resampler = "bsinc12";
@@ -630,15 +685,15 @@ void MainWindow::loadConfig(const QString &fname)
ui->stereoModeCombo->setCurrentIndex(0);
if(stereomode.isEmpty() == false)
{
- QString str = getNameFromValue(stereoModeList, stereomode);
+ QString str{getNameFromValue(stereoModeList, stereomode)};
if(!str.isEmpty())
{
- int j = ui->stereoModeCombo->findText(str);
+ const int j{ui->stereoModeCombo->findText(str)};
if(j > 0) ui->stereoModeCombo->setCurrentIndex(j);
}
}
- int periodsize = settings.value("period_size").toInt();
+ int periodsize{settings.value("period_size").toInt()};
ui->periodSizeEdit->clear();
if(periodsize >= 64)
{
@@ -646,7 +701,7 @@ void MainWindow::loadConfig(const QString &fname)
updatePeriodSizeSlider();
}
- int periodcount = settings.value("periods").toInt();
+ int periodcount{settings.value("periods").toInt()};
ui->periodCountEdit->clear();
if(periodcount >= 2)
{
@@ -654,51 +709,38 @@ void MainWindow::loadConfig(const QString &fname)
updatePeriodCountSlider();
}
- if(settings.value("output-limiter").isNull())
- ui->outputLimiterCheckBox->setCheckState(Qt::PartiallyChecked);
- else
- ui->outputLimiterCheckBox->setCheckState(
- settings.value("output-limiter").toBool() ? Qt::Checked : Qt::Unchecked
- );
-
- if(settings.value("dither").isNull())
- ui->outputDitherCheckBox->setCheckState(Qt::PartiallyChecked);
- else
- ui->outputDitherCheckBox->setCheckState(
- settings.value("dither").toBool() ? Qt::Checked : Qt::Unchecked
- );
+ ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value("output-limiter")));
+ ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value("dither")));
- QString stereopan = settings.value("stereo-encoding").toString();
+ QString stereopan{settings.value("stereo-encoding").toString()};
ui->stereoEncodingComboBox->setCurrentIndex(0);
if(stereopan.isEmpty() == false)
{
- QString str = getNameFromValue(stereoEncList, stereopan);
+ QString str{getNameFromValue(stereoEncList, stereopan)};
if(!str.isEmpty())
{
- int j = ui->stereoEncodingComboBox->findText(str);
+ const int j{ui->stereoEncodingComboBox->findText(str)};
if(j > 0) ui->stereoEncodingComboBox->setCurrentIndex(j);
}
}
- QString ambiformat = settings.value("ambi-format").toString();
+ QString ambiformat{settings.value("ambi-format").toString()};
ui->ambiFormatComboBox->setCurrentIndex(0);
if(ambiformat.isEmpty() == false)
{
- QString str = getNameFromValue(ambiFormatList, ambiformat);
+ QString str{getNameFromValue(ambiFormatList, ambiformat)};
if(!str.isEmpty())
{
- int j = ui->ambiFormatComboBox->findText(str);
+ const int j{ui->ambiFormatComboBox->findText(str)};
if(j > 0) ui->ambiFormatComboBox->setCurrentIndex(j);
}
}
- bool hqmode = settings.value("decoder/hq-mode", false).toBool();
+ bool hqmode{settings.value("decoder/hq-mode", true).toBool()};
ui->decoderHQModeCheckBox->setChecked(hqmode);
- bool distcomp = settings.value("decoder/distance-comp", true).toBool();
- ui->decoderDistCompCheckBox->setChecked(distcomp);
- bool nfeffects = settings.value("decoder/nfc", true).toBool();
- ui->decoderNFEffectsCheckBox->setChecked(nfeffects);
- double refdelay = settings.value("decoder/nfc-ref-delay", 0.0).toDouble();
+ ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value("decoder/distance-comp")));
+ ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value("decoder/nfc")));
+ double refdelay{settings.value("decoder/nfc-ref-delay", 0.0).toDouble()};
ui->decoderNFRefDelaySpinBox->setValue(refdelay);
ui->decoderQuadLineEdit->setText(settings.value("decoder/quad").toString());
@@ -706,22 +748,40 @@ void MainWindow::loadConfig(const QString &fname)
ui->decoder61LineEdit->setText(settings.value("decoder/surround61").toString());
ui->decoder71LineEdit->setText(settings.value("decoder/surround71").toString());
- QStringList disabledCpuExts = settings.value("disable-cpu-exts").toStringList();
+ QStringList disabledCpuExts{settings.value("disable-cpu-exts").toStringList()};
if(disabledCpuExts.size() == 1)
disabledCpuExts = disabledCpuExts[0].split(QChar(','));
- for(QStringList::iterator iter = disabledCpuExts.begin();iter != disabledCpuExts.end();iter++)
- *iter = iter->trimmed();
+ for(QString &name : disabledCpuExts)
+ name = name.trimmed();
ui->enableSSECheckBox->setChecked(!disabledCpuExts.contains("sse", Qt::CaseInsensitive));
ui->enableSSE2CheckBox->setChecked(!disabledCpuExts.contains("sse2", Qt::CaseInsensitive));
ui->enableSSE3CheckBox->setChecked(!disabledCpuExts.contains("sse3", Qt::CaseInsensitive));
ui->enableSSE41CheckBox->setChecked(!disabledCpuExts.contains("sse4.1", Qt::CaseInsensitive));
ui->enableNeonCheckBox->setChecked(!disabledCpuExts.contains("neon", Qt::CaseInsensitive));
- QStringList hrtf_paths = settings.value("hrtf-paths").toStringList();
+ QString hrtfmode{settings.value("hrtf-mode").toString().trimmed()};
+ ui->hrtfmodeSlider->setValue(2);
+ ui->hrtfmodeLabel->setText(hrtfModeList[2].name);
+ /* The "basic" mode name is no longer supported, and "ambi3" is temporarily
+ * disabled. Use "ambi2" instead.
+ */
+ if(hrtfmode == "basic" || hrtfmode == "ambi3")
+ hrtfmode = "ambi2";
+ for(int i = 0;hrtfModeList[i].name[0];i++)
+ {
+ if(hrtfmode == hrtfModeList[i].value)
+ {
+ ui->hrtfmodeSlider->setValue(i);
+ ui->hrtfmodeLabel->setText(hrtfModeList[i].name);
+ break;
+ }
+ }
+
+ QStringList hrtf_paths{settings.value("hrtf-paths").toStringList()};
if(hrtf_paths.size() == 1)
hrtf_paths = hrtf_paths[0].split(QChar(','));
- for(QStringList::iterator iter = hrtf_paths.begin();iter != hrtf_paths.end();iter++)
- *iter = iter->trimmed();
+ for(QString &name : hrtf_paths)
+ name = name.trimmed();
if(!hrtf_paths.empty() && !hrtf_paths.back().isEmpty())
ui->defaultHrtfPathsCheckBox->setCheckState(Qt::Unchecked);
else
@@ -734,7 +794,7 @@ void MainWindow::loadConfig(const QString &fname)
ui->hrtfFileList->addItems(hrtf_paths);
updateHrtfRemoveButton();
- QString hrtfstate = settings.value("hrtf").toString().toLower();
+ QString hrtfstate{settings.value("hrtf").toString().toLower()};
if(hrtfstate == "true")
ui->hrtfStateComboBox->setCurrentIndex(1);
else if(hrtfstate == "false")
@@ -746,16 +806,16 @@ void MainWindow::loadConfig(const QString &fname)
ui->preferredHrtfComboBox->addItem("- Any -");
if(ui->defaultHrtfPathsCheckBox->isChecked())
{
- QStringList hrtfs = collectHrtfs();
+ QStringList hrtfs{collectHrtfs()};
foreach(const QString &name, hrtfs)
ui->preferredHrtfComboBox->addItem(name);
}
- QString defaulthrtf = settings.value("default-hrtf").toString();
+ QString defaulthrtf{settings.value("default-hrtf").toString()};
ui->preferredHrtfComboBox->setCurrentIndex(0);
if(defaulthrtf.isEmpty() == false)
{
- int i = ui->preferredHrtfComboBox->findText(defaulthrtf);
+ int i{ui->preferredHrtfComboBox->findText(defaulthrtf)};
if(i > 0)
ui->preferredHrtfComboBox->setCurrentIndex(i);
else
@@ -769,23 +829,23 @@ void MainWindow::loadConfig(const QString &fname)
ui->enabledBackendList->clear();
ui->disabledBackendList->clear();
- QStringList drivers = settings.value("drivers").toStringList();
+ QStringList drivers{settings.value("drivers").toStringList()};
if(drivers.size() == 0)
ui->backendCheckBox->setChecked(true);
else
{
if(drivers.size() == 1)
drivers = drivers[0].split(QChar(','));
- for(QStringList::iterator iter = drivers.begin();iter != drivers.end();iter++)
+ for(QString &name : drivers)
{
- *iter = iter->trimmed();
+ name = name.trimmed();
/* Convert "mmdevapi" references to "wasapi" for backwards
* compatibility.
*/
- if(*iter == "-mmdevapi")
- *iter = "-wasapi";
- else if(*iter == "mmdevapi")
- *iter = "wasapi";
+ if(name == "-mmdevapi")
+ name = "-wasapi";
+ else if(name == "mmdevapi")
+ name = "wasapi";
}
bool lastWasEmpty = false;
@@ -805,7 +865,7 @@ void MainWindow::loadConfig(const QString &fname)
}
else if(backend.size() > 1)
{
- QStringRef backendref = backend.rightRef(backend.size()-1);
+ QStringRef backendref{backend.rightRef(backend.size()-1)};
for(int j = 0;backendList[j].backend_name[0];j++)
{
if(backendref == backendList[j].backend_name)
@@ -819,7 +879,7 @@ void MainWindow::loadConfig(const QString &fname)
ui->backendCheckBox->setChecked(lastWasEmpty);
}
- QString defaultreverb = settings.value("default-reverb").toString().toLower();
+ QString defaultreverb{settings.value("default-reverb").toString().toLower()};
ui->defaultReverbComboBox->setCurrentIndex(0);
if(defaultreverb.isEmpty() == false)
{
@@ -833,11 +893,11 @@ void MainWindow::loadConfig(const QString &fname)
}
}
- QStringList excludefx = settings.value("excludefx").toStringList();
+ QStringList excludefx{settings.value("excludefx").toStringList()};
if(excludefx.size() == 1)
excludefx = excludefx[0].split(QChar(','));
- for(QStringList::iterator iter = excludefx.begin();iter != excludefx.end();iter++)
- *iter = iter->trimmed();
+ for(QString &name : excludefx)
+ name = name.trimmed();
ui->enableEaxReverbCheck->setChecked(!excludefx.contains("eaxreverb", Qt::CaseInsensitive));
ui->enableStdReverbCheck->setChecked(!excludefx.contains("reverb", Qt::CaseInsensitive));
ui->enableAutowahCheck->setChecked(!excludefx.contains("autowah", Qt::CaseInsensitive));
@@ -851,19 +911,21 @@ void MainWindow::loadConfig(const QString &fname)
ui->enableModulatorCheck->setChecked(!excludefx.contains("modulator", Qt::CaseInsensitive));
ui->enableDedicatedCheck->setChecked(!excludefx.contains("dedicated", Qt::CaseInsensitive));
ui->enablePitchShifterCheck->setChecked(!excludefx.contains("pshifter", Qt::CaseInsensitive));
+ ui->enableVocalMorpherCheck->setChecked(!excludefx.contains("vmorpher", Qt::CaseInsensitive));
- ui->pulseAutospawnCheckBox->setChecked(settings.value("pulse/spawn-server", true).toBool());
- ui->pulseAllowMovesCheckBox->setChecked(settings.value("pulse/allow-moves", false).toBool());
- ui->pulseFixRateCheckBox->setChecked(settings.value("pulse/fix-rate", false).toBool());
+ ui->pulseAutospawnCheckBox->setCheckState(getCheckState(settings.value("pulse/spawn-server")));
+ ui->pulseAllowMovesCheckBox->setCheckState(getCheckState(settings.value("pulse/allow-moves")));
+ ui->pulseFixRateCheckBox->setCheckState(getCheckState(settings.value("pulse/fix-rate")));
+ ui->pulseAdjLatencyCheckBox->setCheckState(getCheckState(settings.value("pulse/adjust-latency")));
- ui->jackAutospawnCheckBox->setChecked(settings.value("jack/spawn-server", false).toBool());
+ ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value("jack/spawn-server")));
ui->jackBufferSizeLine->setText(settings.value("jack/buffer-size", QString()).toString());
updateJackBufferSizeSlider();
ui->alsaDefaultDeviceLine->setText(settings.value("alsa/device", QString()).toString());
ui->alsaDefaultCaptureLine->setText(settings.value("alsa/capture", QString()).toString());
- ui->alsaResamplerCheckBox->setChecked(settings.value("alsa/allow-resampler", false).toBool());
- ui->alsaMmapCheckBox->setChecked(settings.value("alsa/mmap", true).toBool());
+ ui->alsaResamplerCheckBox->setCheckState(getCheckState(settings.value("alsa/allow-resampler")));
+ ui->alsaMmapCheckBox->setCheckState(getCheckState(settings.value("alsa/mmap")));
ui->ossDefaultDeviceLine->setText(settings.value("oss/device", QString()).toString());
ui->ossDefaultCaptureLine->setText(settings.value("oss/capture", QString()).toString());
@@ -885,12 +947,12 @@ void MainWindow::saveCurrentConfig()
ui->closeCancelButton->setText(tr("Close"));
mNeedsSave = false;
QMessageBox::information(this, tr("Information"),
- tr("Applications using OpenAL need to be restarted for changes to take effect."));
+ tr("Applications using OpenAL need to be restarted for changes to take effect."));
}
void MainWindow::saveConfigAsFile()
{
- QString fname = QFileDialog::getOpenFileName(this, tr("Select Files"));
+ QString fname{QFileDialog::getOpenFileName(this, tr("Select Files"))};
if(fname.isEmpty() == false)
{
saveConfig(fname);
@@ -901,13 +963,13 @@ void MainWindow::saveConfigAsFile()
void MainWindow::saveConfig(const QString &fname) const
{
- QSettings settings(fname, QSettings::IniFormat);
+ QSettings settings{fname, QSettings::IniFormat};
/* HACK: Compound any stringlist values into a comma-separated string. */
- QStringList allkeys = settings.allKeys();
+ QStringList allkeys{settings.allKeys()};
foreach(const QString &key, allkeys)
{
- QStringList vals = settings.value(key).toStringList();
+ QStringList vals{settings.value(key).toStringList()};
if(vals.size() > 1)
settings.setValue(key, vals.join(QChar(',')));
}
@@ -915,9 +977,9 @@ void MainWindow::saveConfig(const QString &fname) const
settings.setValue("sample-type", getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText()));
settings.setValue("channels", getValueFromName(speakerModeList, ui->channelConfigCombo->currentText()));
- uint rate = ui->sampleRateCombo->currentText().toUInt();
- if(!(rate > 0))
- settings.setValue("frequency", QString());
+ uint rate{ui->sampleRateCombo->currentText().toUInt()};
+ if(rate <= 0)
+ settings.setValue("frequency", QString{});
else
settings.setValue("frequency", rate);
@@ -933,34 +995,17 @@ void MainWindow::saveConfig(const QString &fname) const
settings.setValue("stereo-encoding", getValueFromName(stereoEncList, ui->stereoEncodingComboBox->currentText()));
settings.setValue("ambi-format", getValueFromName(ambiFormatList, ui->ambiFormatComboBox->currentText()));
- Qt::CheckState limiter = ui->outputLimiterCheckBox->checkState();
- if(limiter == Qt::PartiallyChecked)
- settings.setValue("output-limiter", QString());
- else if(limiter == Qt::Checked)
- settings.setValue("output-limiter", QString("true"));
- else if(limiter == Qt::Unchecked)
- settings.setValue("output-limiter", QString("false"));
-
- Qt::CheckState dither = ui->outputDitherCheckBox->checkState();
- if(dither == Qt::PartiallyChecked)
- settings.setValue("dither", QString());
- else if(dither == Qt::Checked)
- settings.setValue("dither", QString("true"));
- else if(dither == Qt::Unchecked)
- settings.setValue("dither", QString("false"));
+ settings.setValue("output-limiter", getCheckValue(ui->outputLimiterCheckBox));
+ settings.setValue("dither", getCheckValue(ui->outputDitherCheckBox));
settings.setValue("decoder/hq-mode",
- ui->decoderHQModeCheckBox->isChecked() ? QString("true") : QString(/*"false"*/)
- );
- settings.setValue("decoder/distance-comp",
- ui->decoderDistCompCheckBox->isChecked() ? QString(/*"true"*/) : QString("false")
- );
- settings.setValue("decoder/nfc",
- ui->decoderNFEffectsCheckBox->isChecked() ? QString(/*"true"*/) : QString("false")
+ ui->decoderHQModeCheckBox->isChecked() ? QString{/*"true"*/} : QString{"false"}
);
+ settings.setValue("decoder/distance-comp", getCheckValue(ui->decoderDistCompCheckBox));
+ settings.setValue("decoder/nfc", getCheckValue(ui->decoderNFEffectsCheckBox));
double refdelay = ui->decoderNFRefDelaySpinBox->value();
settings.setValue("decoder/nfc-ref-delay",
- (refdelay > 0.0) ? QString::number(refdelay) : QString()
+ (refdelay > 0.0) ? QString::number(refdelay) : QString{}
);
settings.setValue("decoder/quad", ui->decoderQuadLineEdit->text());
@@ -981,32 +1026,35 @@ void MainWindow::saveConfig(const QString &fname) const
strlist.append("neon");
settings.setValue("disable-cpu-exts", strlist.join(QChar(',')));
+ settings.setValue("hrtf-mode", hrtfModeList[ui->hrtfmodeSlider->value()].value);
+
if(ui->hrtfStateComboBox->currentIndex() == 1)
settings.setValue("hrtf", "true");
else if(ui->hrtfStateComboBox->currentIndex() == 2)
settings.setValue("hrtf", "false");
else
- settings.setValue("hrtf", QString());
+ settings.setValue("hrtf", QString{});
if(ui->preferredHrtfComboBox->currentIndex() == 0)
- settings.setValue("default-hrtf", QString());
+ settings.setValue("default-hrtf", QString{});
else
{
- QString str = ui->preferredHrtfComboBox->currentText();
+ QString str{ui->preferredHrtfComboBox->currentText()};
settings.setValue("default-hrtf", str);
}
strlist.clear();
+ strlist.reserve(ui->hrtfFileList->count());
for(int i = 0;i < ui->hrtfFileList->count();i++)
strlist.append(ui->hrtfFileList->item(i)->text());
if(!strlist.empty() && ui->defaultHrtfPathsCheckBox->isChecked())
- strlist.append(QString());
- settings.setValue("hrtf-paths", strlist.join(QChar(',')));
+ strlist.append(QString{});
+ settings.setValue("hrtf-paths", strlist.join(QChar{','}));
strlist.clear();
for(int i = 0;i < ui->enabledBackendList->count();i++)
{
- QString label = ui->enabledBackendList->item(i)->text();
+ QString label{ui->enabledBackendList->item(i)->text()};
for(int j = 0;backendList[j].backend_name[0];j++)
{
if(label == backendList[j].full_string)
@@ -1018,12 +1066,12 @@ void MainWindow::saveConfig(const QString &fname) const
}
for(int i = 0;i < ui->disabledBackendList->count();i++)
{
- QString label = ui->disabledBackendList->item(i)->text();
+ QString label{ui->disabledBackendList->item(i)->text()};
for(int j = 0;backendList[j].backend_name[0];j++)
{
if(label == backendList[j].full_string)
{
- strlist.append(QChar('-')+QString(backendList[j].backend_name));
+ strlist.append(QChar{'-'}+QString{backendList[j].backend_name});
break;
}
}
@@ -1031,15 +1079,15 @@ void MainWindow::saveConfig(const QString &fname) const
if(strlist.size() == 0 && !ui->backendCheckBox->isChecked())
strlist.append("-all");
else if(ui->backendCheckBox->isChecked())
- strlist.append(QString());
+ strlist.append(QString{});
settings.setValue("drivers", strlist.join(QChar(',')));
// TODO: Remove check when we can properly match global values.
if(ui->defaultReverbComboBox->currentIndex() == 0)
- settings.setValue("default-reverb", QString());
+ settings.setValue("default-reverb", QString{});
else
{
- QString str = ui->defaultReverbComboBox->currentText().toLower();
+ QString str{ui->defaultReverbComboBox->currentText().toLower()};
settings.setValue("default-reverb", str);
}
@@ -1070,31 +1118,22 @@ void MainWindow::saveConfig(const QString &fname) const
strlist.append("dedicated");
if(!ui->enablePitchShifterCheck->isChecked())
strlist.append("pshifter");
- settings.setValue("excludefx", strlist.join(QChar(',')));
+ if(!ui->enableVocalMorpherCheck->isChecked())
+ strlist.append("vmorpher");
+ settings.setValue("excludefx", strlist.join(QChar{','}));
- settings.setValue("pulse/spawn-server",
- ui->pulseAutospawnCheckBox->isChecked() ? QString(/*"true"*/) : QString("false")
- );
- settings.setValue("pulse/allow-moves",
- ui->pulseAllowMovesCheckBox->isChecked() ? QString("true") : QString(/*"false"*/)
- );
- settings.setValue("pulse/fix-rate",
- ui->pulseFixRateCheckBox->isChecked() ? QString("true") : QString(/*"false"*/)
- );
+ settings.setValue("pulse/spawn-server", getCheckValue(ui->pulseAutospawnCheckBox));
+ settings.setValue("pulse/allow-moves", getCheckValue(ui->pulseAllowMovesCheckBox));
+ settings.setValue("pulse/fix-rate", getCheckValue(ui->pulseFixRateCheckBox));
+ settings.setValue("pulse/adjust-latency", getCheckValue(ui->pulseAdjLatencyCheckBox));
- settings.setValue("jack/spawn-server",
- ui->jackAutospawnCheckBox->isChecked() ? QString("true") : QString(/*"false"*/)
- );
+ settings.setValue("jack/spawn-server", getCheckValue(ui->jackAutospawnCheckBox));
settings.setValue("jack/buffer-size", ui->jackBufferSizeLine->text());
settings.setValue("alsa/device", ui->alsaDefaultDeviceLine->text());
settings.setValue("alsa/capture", ui->alsaDefaultCaptureLine->text());
- settings.setValue("alsa/allow-resampler",
- ui->alsaResamplerCheckBox->isChecked() ? QString("true") : QString(/*"false"*/)
- );
- settings.setValue("alsa/mmap",
- ui->alsaMmapCheckBox->isChecked() ? QString(/*"true"*/) : QString("false")
- );
+ settings.setValue("alsa/allow-resampler", getCheckValue(ui->alsaResamplerCheckBox));
+ settings.setValue("alsa/mmap", getCheckValue(ui->alsaMmapCheckBox));
settings.setValue("oss/device", ui->ossDefaultDeviceLine->text());
settings.setValue("oss/capture", ui->ossDefaultCaptureLine->text());
@@ -1103,7 +1142,7 @@ void MainWindow::saveConfig(const QString &fname) const
settings.setValue("wave/file", ui->waveOutputLine->text());
settings.setValue("wave/bformat",
- ui->waveBFormatCheckBox->isChecked() ? QString("true") : QString(/*"false"*/)
+ ui->waveBFormatCheckBox->isChecked() ? QString{"true"} : QString{/*"false"*/}
);
/* Remove empty keys
@@ -1112,8 +1151,8 @@ void MainWindow::saveConfig(const QString &fname) const
allkeys = settings.allKeys();
foreach(const QString &key, allkeys)
{
- QString str = settings.value(key).toString();
- if(str == QString())
+ QString str{settings.value(key).toString()};
+ if(str == QString{})
settings.remove(key);
}
}
@@ -1139,10 +1178,7 @@ void MainWindow::updatePeriodSizeEdit(int size)
{
ui->periodSizeEdit->clear();
if(size >= 64)
- {
- size = (size+32)&~0x3f;
ui->periodSizeEdit->insert(QString::number(size));
- }
enableApplyButton();
}
@@ -1188,13 +1224,13 @@ void MainWindow::select71DecoderFile()
{ selectDecoderFile(ui->decoder71LineEdit, "Select 7.1 Surround Decoder");}
void MainWindow::selectDecoderFile(QLineEdit *line, const char *caption)
{
- QString dir = line->text();
+ QString dir{line->text()};
if(dir.isEmpty() || QDir::isRelativePath(dir))
{
- QStringList paths = getAllDataPaths("/openal/presets");
+ QStringList paths{getAllDataPaths("/openal/presets")};
while(!paths.isEmpty())
{
- if(QDir(paths.last()).exists())
+ if(QDir{paths.last()}.exists())
{
dir = paths.last();
break;
@@ -1202,9 +1238,8 @@ void MainWindow::selectDecoderFile(QLineEdit *line, const char *caption)
paths.removeLast();
}
}
- QString fname = QFileDialog::getOpenFileName(this, tr(caption),
- dir, tr("AmbDec Files (*.ambdec);;All Files (*.*)")
- );
+ QString fname{QFileDialog::getOpenFileName(this, tr(caption),
+ dir, tr("AmbDec Files (*.ambdec);;All Files (*.*)"))};
if(!fname.isEmpty())
{
line->setText(fname);
@@ -1223,16 +1258,23 @@ void MainWindow::updateJackBufferSizeEdit(int size)
void MainWindow::updateJackBufferSizeSlider()
{
- int value = ui->jackBufferSizeLine->text().toInt();
- int pos = (int)floor(log2(value) + 0.5);
+ int value{ui->jackBufferSizeLine->text().toInt()};
+ auto pos = static_cast<int>(floor(log2(value) + 0.5));
ui->jackBufferSizeSlider->setSliderPosition(pos);
enableApplyButton();
}
+void MainWindow::updateHrtfModeLabel(int num)
+{
+ ui->hrtfmodeLabel->setText(hrtfModeList[num].name);
+ enableApplyButton();
+}
+
+
void MainWindow::addHrtfFile()
{
- QString path = QFileDialog::getExistingDirectory(this, tr("Select HRTF Path"));
+ QString path{QFileDialog::getExistingDirectory(this, tr("Select HRTF Path"))};
if(path.isEmpty() == false && !getAllDataPaths("/openal/hrtf").contains(path))
{
ui->hrtfFileList->addItem(path);
@@ -1242,7 +1284,7 @@ void MainWindow::addHrtfFile()
void MainWindow::removeHrtfFile()
{
- QList<QListWidgetItem*> selected = ui->hrtfFileList->selectedItems();
+ QList<QListWidgetItem*> selected{ui->hrtfFileList->selectedItems()};
if(!selected.isEmpty())
{
foreach(QListWidgetItem *item, selected)
@@ -1258,37 +1300,37 @@ void MainWindow::updateHrtfRemoveButton()
void MainWindow::showEnabledBackendMenu(QPoint pt)
{
- QMap<QAction*,QString> actionMap;
+ QHash<QAction*,QString> actionMap;
pt = ui->enabledBackendList->mapToGlobal(pt);
QMenu ctxmenu;
- QAction *removeAction = ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove");
+ QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")};
if(ui->enabledBackendList->selectedItems().size() == 0)
removeAction->setEnabled(false);
ctxmenu.addSeparator();
for(size_t i = 0;backendList[i].backend_name[0];i++)
{
- QString backend = backendList[i].full_string;
- QAction *action = ctxmenu.addAction(QString("Add ")+backend);
+ QString backend{backendList[i].full_string};
+ QAction *action{ctxmenu.addAction(QString("Add ")+backend)};
actionMap[action] = backend;
if(ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0 ||
ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0)
action->setEnabled(false);
}
- QAction *gotAction = ctxmenu.exec(pt);
+ QAction *gotAction{ctxmenu.exec(pt)};
if(gotAction == removeAction)
{
- QList<QListWidgetItem*> selected = ui->enabledBackendList->selectedItems();
+ QList<QListWidgetItem*> selected{ui->enabledBackendList->selectedItems()};
foreach(QListWidgetItem *item, selected)
delete item;
enableApplyButton();
}
- else if(gotAction != NULL)
+ else if(gotAction != nullptr)
{
- QMap<QAction*,QString>::const_iterator iter = actionMap.find(gotAction);
- if(iter != actionMap.end())
+ auto iter = actionMap.constFind(gotAction);
+ if(iter != actionMap.cend())
ui->enabledBackendList->addItem(iter.value());
enableApplyButton();
}
@@ -1296,37 +1338,37 @@ void MainWindow::showEnabledBackendMenu(QPoint pt)
void MainWindow::showDisabledBackendMenu(QPoint pt)
{
- QMap<QAction*,QString> actionMap;
+ QHash<QAction*,QString> actionMap;
pt = ui->disabledBackendList->mapToGlobal(pt);
QMenu ctxmenu;
- QAction *removeAction = ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove");
+ QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")};
if(ui->disabledBackendList->selectedItems().size() == 0)
removeAction->setEnabled(false);
ctxmenu.addSeparator();
for(size_t i = 0;backendList[i].backend_name[0];i++)
{
- QString backend = backendList[i].full_string;
- QAction *action = ctxmenu.addAction(QString("Add ")+backend);
+ QString backend{backendList[i].full_string};
+ QAction *action{ctxmenu.addAction(QString("Add ")+backend)};
actionMap[action] = backend;
if(ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0 ||
ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0)
action->setEnabled(false);
}
- QAction *gotAction = ctxmenu.exec(pt);
+ QAction *gotAction{ctxmenu.exec(pt)};
if(gotAction == removeAction)
{
- QList<QListWidgetItem*> selected = ui->disabledBackendList->selectedItems();
+ QList<QListWidgetItem*> selected{ui->disabledBackendList->selectedItems()};
foreach(QListWidgetItem *item, selected)
delete item;
enableApplyButton();
}
- else if(gotAction != NULL)
+ else if(gotAction != nullptr)
{
- QMap<QAction*,QString>::const_iterator iter = actionMap.find(gotAction);
- if(iter != actionMap.end())
+ auto iter = actionMap.constFind(gotAction);
+ if(iter != actionMap.cend())
ui->disabledBackendList->addItem(iter.value());
enableApplyButton();
}
@@ -1334,9 +1376,9 @@ void MainWindow::showDisabledBackendMenu(QPoint pt)
void MainWindow::selectOSSPlayback()
{
- QString current = ui->ossDefaultDeviceLine->text();
+ QString current{ui->ossDefaultDeviceLine->text()};
if(current.isEmpty()) current = ui->ossDefaultDeviceLine->placeholderText();
- QString fname = QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current);
+ QString fname{QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current)};
if(!fname.isEmpty())
{
ui->ossDefaultDeviceLine->setText(fname);
@@ -1346,9 +1388,9 @@ void MainWindow::selectOSSPlayback()
void MainWindow::selectOSSCapture()
{
- QString current = ui->ossDefaultCaptureLine->text();
+ QString current{ui->ossDefaultCaptureLine->text()};
if(current.isEmpty()) current = ui->ossDefaultCaptureLine->placeholderText();
- QString fname = QFileDialog::getOpenFileName(this, tr("Select Capture Device"), current);
+ QString fname{QFileDialog::getOpenFileName(this, tr("Select Capture Device"), current)};
if(!fname.isEmpty())
{
ui->ossDefaultCaptureLine->setText(fname);
@@ -1358,9 +1400,9 @@ void MainWindow::selectOSSCapture()
void MainWindow::selectSolarisPlayback()
{
- QString current = ui->solarisDefaultDeviceLine->text();
+ QString current{ui->solarisDefaultDeviceLine->text()};
if(current.isEmpty()) current = ui->solarisDefaultDeviceLine->placeholderText();
- QString fname = QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current);
+ QString fname{QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current)};
if(!fname.isEmpty())
{
ui->solarisDefaultDeviceLine->setText(fname);
@@ -1370,9 +1412,8 @@ void MainWindow::selectSolarisPlayback()
void MainWindow::selectWaveOutput()
{
- QString fname = QFileDialog::getSaveFileName(this, tr("Select Wave File Output"),
- ui->waveOutputLine->text(), tr("Wave Files (*.wav *.amb);;All Files (*.*)")
- );
+ QString fname{QFileDialog::getSaveFileName(this, tr("Select Wave File Output"),
+ ui->waveOutputLine->text(), tr("Wave Files (*.wav *.amb);;All Files (*.*)"))};
if(!fname.isEmpty())
{
ui->waveOutputLine->setText(fname);
diff --git a/utils/alsoft-config/mainwindow.h b/utils/alsoft-config/mainwindow.h
index 8b763845..ca53582b 100644
--- a/utils/alsoft-config/mainwindow.h
+++ b/utils/alsoft-config/mainwindow.h
@@ -43,6 +43,7 @@ private slots:
void updateJackBufferSizeEdit(int size);
void updateJackBufferSizeSlider();
+ void updateHrtfModeLabel(int num);
void addHrtfFile();
void removeHrtfFile();
diff --git a/utils/alsoft-config/mainwindow.ui b/utils/alsoft-config/mainwindow.ui
index 9c89cbc7..0506304e 100644
--- a/utils/alsoft-config/mainwindow.ui
+++ b/utils/alsoft-config/mainwindow.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>564</width>
- <height>460</height>
+ <height>469</height>
</rect>
</property>
<property name="minimumSize">
@@ -21,8 +21,7 @@
</property>
<property name="windowIcon">
<iconset theme="preferences-desktop-sound">
- <normaloff/>
- </iconset>
+ <normaloff>.</normaloff>.</iconset>
</property>
<widget class="QWidget" name="centralWidget">
<widget class="QPushButton" name="applyButton">
@@ -31,7 +30,7 @@
<x>470</x>
<y>405</y>
<width>81</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="text">
@@ -39,8 +38,7 @@
</property>
<property name="icon">
<iconset theme="dialog-ok-apply">
- <normaloff/>
- </iconset>
+ <normaloff>.</normaloff>.</iconset>
</property>
</widget>
<widget class="QTabWidget" name="tabWidget">
@@ -53,7 +51,7 @@
</rect>
</property>
<property name="currentIndex">
- <number>5</number>
+ <number>0</number>
</property>
<widget class="QWidget" name="tab_3">
<attribute name="title">
@@ -64,8 +62,8 @@
<rect>
<x>110</x>
<y>50</y>
- <width>78</width>
- <height>21</height>
+ <width>76</width>
+ <height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -82,7 +80,7 @@ float and converted to the output sample type as needed.</string>
<x>0</x>
<y>50</y>
<width>101</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="text">
@@ -98,7 +96,7 @@ float and converted to the output sample type as needed.</string>
<x>0</x>
<y>20</y>
<width>101</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="text">
@@ -113,8 +111,8 @@ float and converted to the output sample type as needed.</string>
<rect>
<x>110</x>
<y>20</y>
- <width>78</width>
- <height>21</height>
+ <width>76</width>
+ <height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -131,8 +129,8 @@ to stereo output.</string>
<rect>
<x>380</x>
<y>20</y>
- <width>80</width>
- <height>20</height>
+ <width>96</width>
+ <height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -194,7 +192,7 @@ to stereo output.</string>
<x>290</x>
<y>20</y>
<width>81</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="text">
@@ -210,7 +208,7 @@ to stereo output.</string>
<x>290</x>
<y>50</y>
<width>81</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="text">
@@ -225,8 +223,8 @@ to stereo output.</string>
<rect>
<x>380</x>
<y>50</y>
- <width>78</width>
- <height>21</height>
+ <width>101</width>
+ <height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -256,7 +254,7 @@ otherwise be suitable for speakers.</string>
<x>20</x>
<y>30</y>
<width>511</width>
- <height>91</height>
+ <height>81</height>
</rect>
</property>
<property name="title">
@@ -283,9 +281,9 @@ mixed and being heard.</string>
<widget class="QLabel" name="label_11">
<property name="geometry">
<rect>
- <x>20</x>
+ <x>60</x>
<y>0</y>
- <width>201</width>
+ <width>161</width>
<height>21</height>
</rect>
</property>
@@ -299,9 +297,9 @@ mixed and being heard.</string>
<widget class="QSlider" name="periodCountSlider">
<property name="geometry">
<rect>
- <x>80</x>
+ <x>99</x>
<y>20</y>
- <width>160</width>
+ <width>141</width>
<height>21</height>
</rect>
</property>
@@ -336,7 +334,7 @@ mixed and being heard.</string>
<widget class="QLineEdit" name="periodCountEdit">
<property name="geometry">
<rect>
- <x>20</x>
+ <x>40</x>
<y>20</y>
<width>51</width>
<height>21</height>
@@ -365,24 +363,24 @@ frames needed for each mixing update.</string>
<rect>
<x>60</x>
<y>20</y>
- <width>160</width>
+ <width>191</width>
<height>21</height>
</rect>
</property>
<property name="minimum">
- <number>0</number>
+ <number>63</number>
</property>
<property name="maximum">
<number>8192</number>
</property>
<property name="singleStep">
- <number>64</number>
+ <number>1</number>
</property>
<property name="pageStep">
<number>1024</number>
</property>
<property name="value">
- <number>0</number>
+ <number>63</number>
</property>
<property name="tracking">
<bool>true</bool>
@@ -423,7 +421,7 @@ frames needed for each mixing update.</string>
</rect>
</property>
<property name="placeholderText">
- <string>1024</string>
+ <string>20ms</string>
</property>
</widget>
</widget>
@@ -432,9 +430,9 @@ frames needed for each mixing update.</string>
<property name="geometry">
<rect>
<x>130</x>
- <y>130</y>
- <width>131</width>
- <height>21</height>
+ <y>120</y>
+ <width>111</width>
+ <height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -451,9 +449,9 @@ receiver.</string>
<property name="geometry">
<rect>
<x>20</x>
- <y>130</y>
+ <y>120</y>
<width>101</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="text">
@@ -466,23 +464,26 @@ receiver.</string>
<widget class="QLabel" name="label_30">
<property name="geometry">
<rect>
- <x>270</x>
- <y>130</y>
- <width>111</width>
- <height>21</height>
+ <x>260</x>
+ <y>120</y>
+ <width>121</width>
+ <height>31</height>
</rect>
</property>
<property name="text">
<string>Ambisonic Format:</string>
</property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
</widget>
<widget class="QComboBox" name="ambiFormatComboBox">
<property name="geometry">
<rect>
<x>390</x>
- <y>130</y>
+ <y>120</y>
<width>131</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
</widget>
@@ -533,7 +534,7 @@ quantization with low-level whitenoise.</string>
<property name="geometry">
<rect>
<x>60</x>
- <y>80</y>
+ <y>90</y>
<width>421</width>
<height>81</height>
</rect>
@@ -611,7 +612,7 @@ quantization with low-level whitenoise.</string>
<widget class="QCheckBox" name="decoderHQModeCheckBox">
<property name="geometry">
<rect>
- <x>10</x>
+ <x>30</x>
<y>20</y>
<width>181</width>
<height>21</height>
@@ -635,7 +636,7 @@ appropriate speaker configuration you intend to use.</string>
<widget class="QCheckBox" name="decoderDistCompCheckBox">
<property name="geometry">
<rect>
- <x>10</x>
+ <x>30</x>
<y>50</y>
<width>181</width>
<height>21</height>
@@ -655,7 +656,7 @@ configuration file.</string>
<property name="text">
<string>Distance Compensation:</string>
</property>
- <property name="checked">
+ <property name="tristate">
<bool>true</bool>
</property>
</widget>
@@ -834,7 +835,7 @@ configuration file.</string>
<widget class="QCheckBox" name="decoderNFEffectsCheckBox">
<property name="geometry">
<rect>
- <x>10</x>
+ <x>30</x>
<y>80</y>
<width>181</width>
<height>21</height>
@@ -847,8 +848,7 @@ creates a more realistic perception of sound distance.
Note that the effect may be stronger or weaker than
intended if the application doesn't use or specify an
appropriate unit scale, or if incorrect speaker distances
-are set in the decoder configuration file. Requires High
-Quality Mode to be enabled.</string>
+are set in the decoder configuration file.</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
@@ -856,7 +856,7 @@ Quality Mode to be enabled.</string>
<property name="text">
<string>Near-Field Effects:</string>
</property>
- <property name="checked">
+ <property name="tristate">
<bool>true</bool>
</property>
</widget>
@@ -885,7 +885,7 @@ normal output is created with no near-field simulation.</string>
<rect>
<x>20</x>
<y>0</y>
- <width>151</width>
+ <width>171</width>
<height>21</height>
</rect>
</property>
@@ -899,9 +899,9 @@ normal output is created with no near-field simulation.</string>
<widget class="QDoubleSpinBox" name="decoderNFRefDelaySpinBox">
<property name="geometry">
<rect>
- <x>180</x>
+ <x>200</x>
<y>0</y>
- <width>91</width>
+ <width>81</width>
<height>21</height>
</rect>
</property>
@@ -999,8 +999,7 @@ normal output is created with no near-field simulation.</string>
</property>
<property name="icon">
<iconset theme="list-add">
- <normaloff/>
- </iconset>
+ <normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>false</bool>
@@ -1040,8 +1039,7 @@ listed above.</string>
</property>
<property name="icon">
<iconset theme="list-remove">
- <normaloff/>
- </iconset>
+ <normaloff>.</normaloff>.</iconset>
</property>
</widget>
</widget>
@@ -1049,10 +1047,10 @@ listed above.</string>
<widget class="QLabel" name="label_16">
<property name="geometry">
<rect>
- <x>40</x>
- <y>50</y>
+ <x>50</x>
+ <y>60</y>
<width>71</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="text">
@@ -1066,9 +1064,9 @@ listed above.</string>
<property name="geometry">
<rect>
<x>130</x>
- <y>50</y>
+ <y>60</y>
<width>161</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -1097,10 +1095,10 @@ application or system to determine if it should be used.</string>
<widget class="QLabel" name="label_12">
<property name="geometry">
<rect>
- <x>20</x>
+ <x>30</x>
<y>20</y>
<width>91</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="text">
@@ -1116,13 +1114,84 @@ application or system to determine if it should be used.</string>
<x>130</x>
<y>20</y>
<width>161</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="toolTip">
<string>The default HRTF to use if the application doesn't request one.</string>
</property>
</widget>
+ <widget class="QGroupBox" name="groupBox_9">
+ <property name="geometry">
+ <rect>
+ <x>50</x>
+ <y>100</y>
+ <width>441</width>
+ <height>81</height>
+ </rect>
+ </property>
+ <property name="title">
+ <string>HRTF Render Method</string>
+ </property>
+ <widget class="QLabel" name="label_31">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>30</y>
+ <width>51</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Speed</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_32">
+ <property name="geometry">
+ <rect>
+ <x>340</x>
+ <y>30</y>
+ <width>51</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Quality</string>
+ </property>
+ </widget>
+ <widget class="QSlider" name="hrtfmodeSlider">
+ <property name="geometry">
+ <rect>
+ <x>80</x>
+ <y>30</y>
+ <width>251</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ <widget class="QLabel" name="hrtfmodeLabel">
+ <property name="geometry">
+ <rect>
+ <x>50</x>
+ <y>50</y>
+ <width>321</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Default</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </widget>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
@@ -1185,6 +1254,9 @@ application or system to determine if it should be used.</string>
<height>361</height>
</rect>
</property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
<widget class="QWidget" name="page">
<widget class="QCheckBox" name="backendCheckBox">
<property name="geometry">
@@ -1279,7 +1351,7 @@ is not already running.</string>
<property name="text">
<string>AutoSpawn Server</string>
</property>
- <property name="checked">
+ <property name="tristate">
<bool>true</bool>
</property>
</widget>
@@ -1301,6 +1373,9 @@ to match the new device.</string>
<property name="text">
<string>Allow Moving Streams</string>
</property>
+ <property name="tristate">
+ <bool>true</bool>
+ </property>
</widget>
<widget class="QCheckBox" name="pulseFixRateCheckBox">
<property name="geometry">
@@ -1318,6 +1393,32 @@ rate to match the PulseAudio device.</string>
<property name="text">
<string>Fix Sample Rate</string>
</property>
+ <property name="tristate">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="QCheckBox" name="pulseAdjLatencyCheckBox">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>100</y>
+ <width>111</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="toolTip">
+ <string>Attempts to adjust the overall latency of device
+playback. Note that this may have adverse effects
+on the resulting internal buffer sizes and mixing
+updates, leading to performance problems and
+drop-outs.</string>
+ </property>
+ <property name="text">
+ <string>Adjust Latency</string>
+ </property>
+ <property name="tristate">
+ <bool>true</bool>
+ </property>
</widget>
</widget>
<widget class="QWidget" name="page_7">
@@ -1333,6 +1434,9 @@ rate to match the PulseAudio device.</string>
<property name="text">
<string>AutoSpawn Server</string>
</property>
+ <property name="tristate">
+ <bool>true</bool>
+ </property>
</widget>
<widget class="QGroupBox" name="groupBox_7">
<property name="geometry">
@@ -1474,6 +1578,9 @@ resample pass on top of OpenAL's resampler.</string>
<property name="text">
<string>Allow Resampler</string>
</property>
+ <property name="tristate">
+ <bool>true</bool>
+ </property>
</widget>
<widget class="QCheckBox" name="alsaMmapCheckBox">
<property name="geometry">
@@ -1492,7 +1599,7 @@ during updates.</string>
<property name="text">
<string>MMap Buffer</string>
</property>
- <property name="checked">
+ <property name="tristate">
<bool>true</bool>
</property>
</widget>
@@ -2156,6 +2263,22 @@ added by the ALC_EXT_DEDICATED extension.</string>
<bool>true</bool>
</property>
</widget>
+ <widget class="QCheckBox" name="enableVocalMorpherCheck">
+ <property name="geometry">
+ <rect>
+ <x>320</x>
+ <y>210</y>
+ <width>131</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Vocal morpher</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
</widget>
<widget class="QLabel" name="label_13">
<property name="geometry">
@@ -2163,7 +2286,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<x>10</x>
<y>20</y>
<width>141</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="text">
@@ -2178,8 +2301,8 @@ added by the ALC_EXT_DEDICATED extension.</string>
<rect>
<x>160</x>
<y>20</y>
- <width>108</width>
- <height>20</height>
+ <width>131</width>
+ <height>31</height>
</rect>
</property>
<property name="sizeAdjustPolicy">
@@ -2329,7 +2452,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<x>370</x>
<y>405</y>
<width>91</width>
- <height>21</height>
+ <height>31</height>
</rect>
</property>
<property name="text">
@@ -2337,8 +2460,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
</property>
<property name="icon">
<iconset theme="window-close">
- <normaloff/>
- </iconset>
+ <normaloff>.</normaloff>.</iconset>
</property>
</widget>
</widget>
@@ -2348,7 +2470,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<x>0</x>
<y>0</y>
<width>564</width>
- <height>21</height>
+ <height>29</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -2372,8 +2494,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<action name="actionQuit">
<property name="icon">
<iconset theme="application-exit">
- <normaloff/>
- </iconset>
+ <normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Quit</string>
@@ -2382,8 +2503,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<action name="actionSave_As">
<property name="icon">
<iconset theme="document-save-as">
- <normaloff/>
- </iconset>
+ <normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Save &amp;As...</string>
@@ -2395,8 +2515,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<action name="actionLoad">
<property name="icon">
<iconset theme="document-open">
- <normaloff/>
- </iconset>
+ <normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Load...</string>
@@ -2415,22 +2534,6 @@ added by the ALC_EXT_DEDICATED extension.</string>
<resources/>
<connections>
<connection>
- <sender>actionQuit</sender>
- <signal>activated()</signal>
- <receiver>MainWindow</receiver>
- <slot>close()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>-1</x>
- <y>-1</y>
- </hint>
- <hint type="destinationlabel">
- <x>267</x>
- <y>181</y>
- </hint>
- </hints>
- </connection>
- <connection>
<sender>backendListWidget</sender>
<signal>currentRowChanged(int)</signal>
<receiver>backendStackedWidget</receiver>
diff --git a/utils/alsoft-config/verstr.cpp b/utils/alsoft-config/verstr.cpp
new file mode 100644
index 00000000..42b1aeac
--- /dev/null
+++ b/utils/alsoft-config/verstr.cpp
@@ -0,0 +1,10 @@
+
+#include "verstr.h"
+
+#include "version.h"
+
+
+QString GetVersionString()
+{
+ return QStringLiteral(ALSOFT_VERSION "-" ALSOFT_GIT_COMMIT_HASH " (" ALSOFT_GIT_BRANCH " branch).");
+}
diff --git a/utils/alsoft-config/verstr.h b/utils/alsoft-config/verstr.h
new file mode 100644
index 00000000..73e3ecbd
--- /dev/null
+++ b/utils/alsoft-config/verstr.h
@@ -0,0 +1,8 @@
+#ifndef VERSTR_H
+#define VERSTR_H
+
+#include <QString>
+
+QString GetVersionString();
+
+#endif /* VERSTR_H */
diff --git a/utils/bsincgen.c b/utils/bsincgen.c
deleted file mode 100644
index 03421da9..00000000
--- a/utils/bsincgen.c
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Sinc interpolator coefficient and delta generator for the OpenAL Soft
- * cross platform audio library.
- *
- * Copyright (C) 2015 by Christopher Fitzgerald.
- *
- * 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 visit: http://www.gnu.org/licenses/old-licenses/lgpl-2.0.html
- *
- * --------------------------------------------------------------------------
- *
- * This is a modified version of the bandlimited windowed sinc interpolator
- * algorithm presented here:
- *
- * Smith, J.O. "Windowed Sinc Interpolation", in
- * Physical Audio Signal Processing,
- * https://ccrma.stanford.edu/~jos/pasp/Windowed_Sinc_Interpolation.html,
- * online book,
- * accessed October 2012.
- */
-
-#define _UNICODE
-#include <stdio.h>
-#include <math.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "win_main_utf8.h"
-
-
-#ifndef M_PI
-#define M_PI (3.14159265358979323846)
-#endif
-
-#if defined(__ANDROID__) && !(defined(_ISOC99_SOURCE) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L))
-#define log2(x) (log(x) / log(2.0))
-#endif
-
-// The number of distinct scale and phase intervals within the filter table.
-// Must be the same as in alu.h!
-#define BSINC_SCALE_COUNT (16)
-#define BSINC_PHASE_COUNT (16)
-
-/* 48 points includes the doubling for downsampling, so the maximum number of
- * base sample points is 24, which is 23rd order.
- */
-#define BSINC_POINTS_MAX (48)
-
-static double MinDouble(double a, double b)
-{ return (a <= b) ? a : b; }
-
-static double MaxDouble(double a, double b)
-{ return (a >= b) ? a : b; }
-
-/* NOTE: This is the normalized (instead of just sin(x)/x) cardinal sine
- * function.
- * 2 f_t sinc(2 f_t x)
- * f_t -- normalized transition frequency (0.5 is nyquist)
- * x -- sample index (-N to N)
- */
-static double Sinc(const double x)
-{
- if(fabs(x) < 1e-15)
- return 1.0;
- return sin(M_PI * x) / (M_PI * x);
-}
-
-static double BesselI_0(const double x)
-{
- double term, sum, last_sum, x2, y;
- int i;
-
- term = 1.0;
- sum = 1.0;
- x2 = x / 2.0;
- i = 1;
-
- do {
- y = x2 / i;
- i++;
- last_sum = sum;
- term *= y * y;
- sum += term;
- } while(sum != last_sum);
-
- return sum;
-}
-
-/* NOTE: k is assumed normalized (-1 to 1)
- * beta is equivalent to 2 alpha
- */
-static double Kaiser(const double b, const double k)
-{
- if(!(k >= -1.0 && k <= 1.0))
- return 0.0;
- return BesselI_0(b * sqrt(1.0 - k*k)) / BesselI_0(b);
-}
-
-/* Calculates the (normalized frequency) transition width of the Kaiser window.
- * Rejection is in dB.
- */
-static double CalcKaiserWidth(const double rejection, const int order)
-{
- double w_t = 2.0 * M_PI;
-
- if(rejection > 21.0)
- return (rejection - 7.95) / (order * 2.285 * w_t);
- /* This enforces a minimum rejection of just above 21.18dB */
- return 5.79 / (order * w_t);
-}
-
-static double CalcKaiserBeta(const double rejection)
-{
- if(rejection > 50.0)
- return 0.1102 * (rejection - 8.7);
- else if(rejection >= 21.0)
- return (0.5842 * pow(rejection - 21.0, 0.4)) +
- (0.07886 * (rejection - 21.0));
- return 0.0;
-}
-
-/* Generates the coefficient, delta, and index tables required by the bsinc resampler */
-static void BsiGenerateTables(FILE *output, const char *tabname, const double rejection, const int order)
-{
- static double filter[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT + 1][BSINC_POINTS_MAX];
- static double scDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT ][BSINC_POINTS_MAX];
- static double phDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT + 1][BSINC_POINTS_MAX];
- static double spDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT ][BSINC_POINTS_MAX];
- static int mt[BSINC_SCALE_COUNT];
- static double at[BSINC_SCALE_COUNT];
- const int num_points_min = order + 1;
- double width, beta, scaleBase, scaleRange;
- int si, pi, i;
-
- memset(filter, 0, sizeof(filter));
- memset(scDeltas, 0, sizeof(scDeltas));
- memset(phDeltas, 0, sizeof(phDeltas));
- memset(spDeltas, 0, sizeof(spDeltas));
-
- /* Calculate windowing parameters. The width describes the transition
- band, but it may vary due to the linear interpolation between scales
- of the filter.
- */
- width = CalcKaiserWidth(rejection, order);
- beta = CalcKaiserBeta(rejection);
- scaleBase = width / 2.0;
- scaleRange = 1.0 - scaleBase;
-
- // Determine filter scaling.
- for(si = 0; si < BSINC_SCALE_COUNT; si++)
- {
- const double scale = scaleBase + (scaleRange * si / (BSINC_SCALE_COUNT - 1));
- const double a = MinDouble(floor(num_points_min / (2.0 * scale)), num_points_min);
- const int m = 2 * (int)a;
-
- mt[si] = m;
- at[si] = a;
- }
-
- /* Calculate the Kaiser-windowed Sinc filter coefficients for each scale
- and phase.
- */
- for(si = 0; si < BSINC_SCALE_COUNT; si++)
- {
- const int m = mt[si];
- const int o = num_points_min - (m / 2);
- const int l = (m / 2) - 1;
- const double a = at[si];
- const double scale = scaleBase + (scaleRange * si / (BSINC_SCALE_COUNT - 1));
- const double cutoff = (0.5 * scale) - (scaleBase * MaxDouble(0.5, scale));
-
- for(pi = 0; pi <= BSINC_PHASE_COUNT; pi++)
- {
- const double phase = l + ((double)pi / BSINC_PHASE_COUNT);
-
- for(i = 0; i < m; i++)
- {
- const double x = i - phase;
- filter[si][pi][o + i] = Kaiser(beta, x / a) * 2.0 * cutoff * Sinc(2.0 * cutoff * x);
- }
- }
- }
-
- /* Linear interpolation between scales is simplified by pre-calculating
- the delta (b - a) in: x = a + f (b - a)
-
- Given a difference in points between scales, the destination points
- will be 0, thus: x = a + f (-a)
- */
- for(si = 0; si < (BSINC_SCALE_COUNT - 1); si++)
- {
- const int m = mt[si];
- const int o = num_points_min - (m / 2);
-
- for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
- {
- for(i = 0; i < m; i++)
- scDeltas[si][pi][o + i] = filter[si + 1][pi][o + i] - filter[si][pi][o + i];
- }
- }
-
- // Linear interpolation between phases is also simplified.
- for(si = 0; si < BSINC_SCALE_COUNT; si++)
- {
- const int m = mt[si];
- const int o = num_points_min - (m / 2);
-
- for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
- {
- for(i = 0; i < m; i++)
- phDeltas[si][pi][o + i] = filter[si][pi + 1][o + i] - filter[si][pi][o + i];
- }
- }
-
- /* This last simplification is done to complete the bilinear equation for
- the combination of scale and phase.
- */
- for(si = 0; si < (BSINC_SCALE_COUNT - 1); si++)
- {
- const int m = mt[si];
- const int o = num_points_min - (m / 2);
-
- for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
- {
- for(i = 0; i < m; i++)
- spDeltas[si][pi][o + i] = phDeltas[si + 1][pi][o + i] - phDeltas[si][pi][o + i];
- }
- }
-
- // Make sure the number of points is a multiple of 4 (for SIMD).
- for(si = 0; si < BSINC_SCALE_COUNT; si++)
- mt[si] = (mt[si]+3) & ~3;
-
- fprintf(output,
-"/* This %d%s order filter has a rejection of -%.0fdB, yielding a transition width\n"
-" * of ~%.3f (normalized frequency). Order increases when downsampling to a\n"
-" * limit of one octave, after which the quality of the filter (transition\n"
-" * width) suffers to reduce the CPU cost. The bandlimiting will cut all sound\n"
-" * after downsampling by ~%.2f octaves.\n"
-" */\n"
-"const BSincTable %s = {\n",
- order, (((order%100)/10) == 1) ? "th" :
- ((order%10) == 1) ? "st" :
- ((order%10) == 2) ? "nd" :
- ((order%10) == 3) ? "rd" : "th",
- rejection, width, log2(1.0/scaleBase), tabname);
-
- /* The scaleBase is calculated from the Kaiser window transition width.
- It represents the absolute limit to the filter before it fully cuts
- the signal. The limit in octaves can be calculated by taking the
- base-2 logarithm of its inverse: log_2(1 / scaleBase)
- */
- fprintf(output, " /* scaleBase */ %.9ef, /* scaleRange */ %.9ef,\n", scaleBase, 1.0 / scaleRange);
-
- fprintf(output, " /* m */ {");
- fprintf(output, " %d", mt[0]);
- for(si = 1; si < BSINC_SCALE_COUNT; si++)
- fprintf(output, ", %d", mt[si]);
- fprintf(output, " },\n");
-
- fprintf(output, " /* filterOffset */ {");
- fprintf(output, " %d", 0);
- i = mt[0]*4*BSINC_PHASE_COUNT;
- for(si = 1; si < BSINC_SCALE_COUNT; si++)
- {
- fprintf(output, ", %d", i);
- i += mt[si]*4*BSINC_PHASE_COUNT;
- }
-
- fprintf(output, " },\n");
-
- // Calculate the table size.
- i = 0;
- for(si = 0; si < BSINC_SCALE_COUNT; si++)
- i += 4 * BSINC_PHASE_COUNT * mt[si];
-
- fprintf(output, "\n /* Tab (%d entries) */ {\n", i);
- for(si = 0; si < BSINC_SCALE_COUNT; si++)
- {
- const int m = mt[si];
- const int o = num_points_min - (m / 2);
-
- for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
- {
- fprintf(output, " /* %2d,%2d (%d) */", si, pi, m);
- fprintf(output, "\n ");
- for(i = 0; i < m; i++)
- fprintf(output, " %+14.9ef,", filter[si][pi][o + i]);
- fprintf(output, "\n ");
- for(i = 0; i < m; i++)
- fprintf(output, " %+14.9ef,", scDeltas[si][pi][o + i]);
- fprintf(output, "\n ");
- for(i = 0; i < m; i++)
- fprintf(output, " %+14.9ef,", phDeltas[si][pi][o + i]);
- fprintf(output, "\n ");
- for(i = 0; i < m; i++)
- fprintf(output, " %+14.9ef,", spDeltas[si][pi][o + i]);
- fprintf(output, "\n");
- }
- }
- fprintf(output, " }\n};\n\n");
-}
-
-
-/* These methods generate a much simplified 4-point sinc interpolator using a
- * Kaiser window. This is much simpler to process at run-time, but has notably
- * more aliasing noise.
- */
-
-/* Same as in alu.h! */
-#define FRACTIONBITS (12)
-#define FRACTIONONE (1<<FRACTIONBITS)
-
-static void Sinc4GenerateTables(FILE *output, const double rejection)
-{
- static double filter[FRACTIONONE][4];
-
- const double width = CalcKaiserWidth(rejection, 3);
- const double beta = CalcKaiserBeta(rejection);
- const double scaleBase = width / 2.0;
- const double scaleRange = 1.0 - scaleBase;
- const double scale = scaleBase + scaleRange;
- const double a = MinDouble(4.0, floor(4.0 / (2.0*scale)));
- const int m = 2 * (int)a;
- const int l = (m/2) - 1;
- int pi;
- for(pi = 0;pi < FRACTIONONE;pi++)
- {
- const double phase = l + ((double)pi / FRACTIONONE);
- int i;
-
- for(i = 0;i < m;i++)
- {
- double x = i - phase;
- filter[pi][i] = Kaiser(beta, x / a) * Sinc(x);
- }
- }
-
- fprintf(output, "alignas(16) static const float sinc4Tab[FRACTIONONE][4] = {\n");
- for(pi = 0;pi < FRACTIONONE;pi++)
- fprintf(output, " { %+14.9ef, %+14.9ef, %+14.9ef, %+14.9ef },\n",
- filter[pi][0], filter[pi][1], filter[pi][2], filter[pi][3]);
- fprintf(output, "};\n\n");
-}
-
-
-int main(int argc, char *argv[])
-{
- FILE *output;
-
- if(argc > 2)
- {
- fprintf(stderr, "Usage: %s [output file]\n", argv[0]);
- return 1;
- }
-
- if(argc == 2)
- {
- output = fopen(argv[1], "wb");
- if(!output)
- {
- fprintf(stderr, "Failed to open %s for writing\n", argv[1]);
- return 1;
- }
- }
- else
- output = stdout;
-
- fprintf(output, "/* Generated by bsincgen, do not edit! */\n\n"
-"static_assert(BSINC_SCALE_COUNT == %d, \"Unexpected BSINC_SCALE_COUNT value!\");\n"
-"static_assert(BSINC_PHASE_COUNT == %d, \"Unexpected BSINC_PHASE_COUNT value!\");\n"
-"static_assert(FRACTIONONE == %d, \"Unexpected FRACTIONONE value!\");\n\n"
-"typedef struct BSincTable {\n"
-" const float scaleBase, scaleRange;\n"
-" const int m[BSINC_SCALE_COUNT];\n"
-" const int filterOffset[BSINC_SCALE_COUNT];\n"
-" alignas(16) const float Tab[];\n"
-"} BSincTable;\n\n", BSINC_SCALE_COUNT, BSINC_PHASE_COUNT, FRACTIONONE);
- /* A 23rd order filter with a -60dB drop at nyquist. */
- BsiGenerateTables(output, "bsinc24", 60.0, 23);
- /* An 11th order filter with a -60dB drop at nyquist. */
- BsiGenerateTables(output, "bsinc12", 60.0, 11);
- Sinc4GenerateTables(output, 60.0);
-
- if(output != stdout)
- fclose(output);
- output = NULL;
-
- return 0;
-}
diff --git a/utils/makehrtf.c b/utils/makehrtf.c
deleted file mode 100644
index 0bd36849..00000000
--- a/utils/makehrtf.c
+++ /dev/null
@@ -1,3455 +0,0 @@
-/*
- * HRTF utility for producing and demonstrating the process of creating an
- * OpenAL Soft compatible HRIR data set.
- *
- * Copyright (C) 2011-2017 Christopher Fitzgerald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
- *
- * --------------------------------------------------------------------------
- *
- * A big thanks goes out to all those whose work done in the field of
- * binaural sound synthesis using measured HRTFs makes this utility and the
- * OpenAL Soft implementation possible.
- *
- * The algorithm for diffuse-field equalization was adapted from the work
- * done by Rio Emmanuel and Larcher Veronique of IRCAM and Bill Gardner of
- * MIT Media Laboratory. It operates as follows:
- *
- * 1. Take the FFT of each HRIR and only keep the magnitude responses.
- * 2. Calculate the diffuse-field power-average of all HRIRs weighted by
- * their contribution to the total surface area covered by their
- * measurement.
- * 3. Take the diffuse-field average and limit its magnitude range.
- * 4. Equalize the responses by using the inverse of the diffuse-field
- * average.
- * 5. Reconstruct the minimum-phase responses.
- * 5. Zero the DC component.
- * 6. IFFT the result and truncate to the desired-length minimum-phase FIR.
- *
- * The spherical head algorithm for calculating propagation delay was adapted
- * from the paper:
- *
- * Modeling Interaural Time Difference Assuming a Spherical Head
- * Joel David Miller
- * Music 150, Musical Acoustics, Stanford University
- * December 2, 2001
- *
- * The formulae for calculating the Kaiser window metrics are from the
- * the textbook:
- *
- * Discrete-Time Signal Processing
- * Alan V. Oppenheim and Ronald W. Schafer
- * Prentice-Hall Signal Processing Series
- * 1999
- */
-
-#include "config.h"
-
-#define _UNICODE
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <stddef.h>
-#include <string.h>
-#include <limits.h>
-#include <ctype.h>
-#include <math.h>
-#ifdef HAVE_STRINGS_H
-#include <strings.h>
-#endif
-#ifdef HAVE_GETOPT
-#include <unistd.h>
-#else
-#include "getopt.h"
-#endif
-
-#include "win_main_utf8.h"
-
-/* Define int64_t and uint64_t types */
-#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
-#include <inttypes.h>
-#elif defined(_WIN32) && defined(__GNUC__)
-#include <stdint.h>
-#elif defined(_WIN32)
-typedef __int64 int64_t;
-typedef unsigned __int64 uint64_t;
-#else
-/* Fallback if nothing above works */
-#include <inttypes.h>
-#endif
-
-#ifndef M_PI
-#define M_PI (3.14159265358979323846)
-#endif
-
-#ifndef HUGE_VAL
-#define HUGE_VAL (1.0 / 0.0)
-#endif
-
-
-// The epsilon used to maintain signal stability.
-#define EPSILON (1e-9)
-
-// Constants for accessing the token reader's ring buffer.
-#define TR_RING_BITS (16)
-#define TR_RING_SIZE (1 << TR_RING_BITS)
-#define TR_RING_MASK (TR_RING_SIZE - 1)
-
-// The token reader's load interval in bytes.
-#define TR_LOAD_SIZE (TR_RING_SIZE >> 2)
-
-// The maximum identifier length used when processing the data set
-// definition.
-#define MAX_IDENT_LEN (16)
-
-// The maximum path length used when processing filenames.
-#define MAX_PATH_LEN (256)
-
-// The limits for the sample 'rate' metric in the data set definition and for
-// resampling.
-#define MIN_RATE (32000)
-#define MAX_RATE (96000)
-
-// The limits for the HRIR 'points' metric in the data set definition.
-#define MIN_POINTS (16)
-#define MAX_POINTS (8192)
-
-// The limit to the number of 'distances' listed in the data set definition.
-#define MAX_FD_COUNT (16)
-
-// The limits to the number of 'azimuths' listed in the data set definition.
-#define MIN_EV_COUNT (5)
-#define MAX_EV_COUNT (128)
-
-// The limits for each of the 'azimuths' listed in the data set definition.
-#define MIN_AZ_COUNT (1)
-#define MAX_AZ_COUNT (128)
-
-// The limits for the listener's head 'radius' in the data set definition.
-#define MIN_RADIUS (0.05)
-#define MAX_RADIUS (0.15)
-
-// The limits for the 'distance' from source to listener for each field in
-// the definition file.
-#define MIN_DISTANCE (0.05)
-#define MAX_DISTANCE (2.50)
-
-// The maximum number of channels that can be addressed for a WAVE file
-// source listed in the data set definition.
-#define MAX_WAVE_CHANNELS (65535)
-
-// The limits to the byte size for a binary source listed in the definition
-// file.
-#define MIN_BIN_SIZE (2)
-#define MAX_BIN_SIZE (4)
-
-// The minimum number of significant bits for binary sources listed in the
-// data set definition. The maximum is calculated from the byte size.
-#define MIN_BIN_BITS (16)
-
-// The limits to the number of significant bits for an ASCII source listed in
-// the data set definition.
-#define MIN_ASCII_BITS (16)
-#define MAX_ASCII_BITS (32)
-
-// The limits to the FFT window size override on the command line.
-#define MIN_FFTSIZE (65536)
-#define MAX_FFTSIZE (131072)
-
-// The limits to the equalization range limit on the command line.
-#define MIN_LIMIT (2.0)
-#define MAX_LIMIT (120.0)
-
-// The limits to the truncation window size on the command line.
-#define MIN_TRUNCSIZE (16)
-#define MAX_TRUNCSIZE (512)
-
-// The limits to the custom head radius on the command line.
-#define MIN_CUSTOM_RADIUS (0.05)
-#define MAX_CUSTOM_RADIUS (0.15)
-
-// The truncation window size must be a multiple of the below value to allow
-// for vectorized convolution.
-#define MOD_TRUNCSIZE (8)
-
-// The defaults for the command line options.
-#define DEFAULT_FFTSIZE (65536)
-#define DEFAULT_EQUALIZE (1)
-#define DEFAULT_SURFACE (1)
-#define DEFAULT_LIMIT (24.0)
-#define DEFAULT_TRUNCSIZE (32)
-#define DEFAULT_HEAD_MODEL (HM_DATASET)
-#define DEFAULT_CUSTOM_RADIUS (0.0)
-
-// The four-character-codes for RIFF/RIFX WAVE file chunks.
-#define FOURCC_RIFF (0x46464952) // 'RIFF'
-#define FOURCC_RIFX (0x58464952) // 'RIFX'
-#define FOURCC_WAVE (0x45564157) // 'WAVE'
-#define FOURCC_FMT (0x20746D66) // 'fmt '
-#define FOURCC_DATA (0x61746164) // 'data'
-#define FOURCC_LIST (0x5453494C) // 'LIST'
-#define FOURCC_WAVL (0x6C766177) // 'wavl'
-#define FOURCC_SLNT (0x746E6C73) // 'slnt'
-
-// The supported wave formats.
-#define WAVE_FORMAT_PCM (0x0001)
-#define WAVE_FORMAT_IEEE_FLOAT (0x0003)
-#define WAVE_FORMAT_EXTENSIBLE (0xFFFE)
-
-// The maximum propagation delay value supported by OpenAL Soft.
-#define MAX_HRTD (63.0)
-
-// The OpenAL Soft HRTF format marker. It stands for minimum-phase head
-// response protocol 02.
-#define MHR_FORMAT ("MinPHR02")
-
-// Sample and channel type enum values.
-typedef enum SampleTypeT {
- ST_S16 = 0,
- ST_S24 = 1
-} SampleTypeT;
-
-// Certain iterations rely on these integer enum values.
-typedef enum ChannelTypeT {
- CT_NONE = -1,
- CT_MONO = 0,
- CT_STEREO = 1
-} ChannelTypeT;
-
-// Byte order for the serialization routines.
-typedef enum ByteOrderT {
- BO_NONE,
- BO_LITTLE,
- BO_BIG
-} ByteOrderT;
-
-// Source format for the references listed in the data set definition.
-typedef enum SourceFormatT {
- SF_NONE,
- SF_WAVE, // RIFF/RIFX WAVE file.
- SF_BIN_LE, // Little-endian binary file.
- SF_BIN_BE, // Big-endian binary file.
- SF_ASCII // ASCII text file.
-} SourceFormatT;
-
-// Element types for the references listed in the data set definition.
-typedef enum ElementTypeT {
- ET_NONE,
- ET_INT, // Integer elements.
- ET_FP // Floating-point elements.
-} ElementTypeT;
-
-// Head model used for calculating the impulse delays.
-typedef enum HeadModelT {
- HM_NONE,
- HM_DATASET, // Measure the onset from the dataset.
- HM_SPHERE // Calculate the onset using a spherical head model.
-} HeadModelT;
-
-// Unsigned integer type.
-typedef unsigned int uint;
-
-// Serialization types. The trailing digit indicates the number of bits.
-typedef unsigned char uint8;
-typedef int int32;
-typedef unsigned int uint32;
-typedef uint64_t uint64;
-
-// Token reader state for parsing the data set definition.
-typedef struct TokenReaderT {
- FILE *mFile;
- const char *mName;
- uint mLine;
- uint mColumn;
- char mRing[TR_RING_SIZE];
- size_t mIn;
- size_t mOut;
-} TokenReaderT;
-
-// Source reference state used when loading sources.
-typedef struct SourceRefT {
- SourceFormatT mFormat;
- ElementTypeT mType;
- uint mSize;
- int mBits;
- uint mChannel;
- uint mSkip;
- uint mOffset;
- char mPath[MAX_PATH_LEN+1];
-} SourceRefT;
-
-// Structured HRIR storage for stereo azimuth pairs, elevations, and fields.
-typedef struct HrirAzT {
- double mAzimuth;
- uint mIndex;
- double mDelays[2];
- double *mIrs[2];
-} HrirAzT;
-
-typedef struct HrirEvT {
- double mElevation;
- uint mIrCount;
- uint mAzCount;
- HrirAzT *mAzs;
-} HrirEvT;
-
-typedef struct HrirFdT {
- double mDistance;
- uint mIrCount;
- uint mEvCount;
- uint mEvStart;
- HrirEvT *mEvs;
-} HrirFdT;
-
-// The HRIR metrics and data set used when loading, processing, and storing
-// the resulting HRTF.
-typedef struct HrirDataT {
- uint mIrRate;
- SampleTypeT mSampleType;
- ChannelTypeT mChannelType;
- uint mIrPoints;
- uint mFftSize;
- uint mIrSize;
- double mRadius;
- uint mIrCount;
- uint mFdCount;
- HrirFdT *mFds;
-} HrirDataT;
-
-// The resampler metrics and FIR filter.
-typedef struct ResamplerT {
- uint mP, mQ, mM, mL;
- double *mF;
-} ResamplerT;
-
-
-/****************************************
- *** Complex number type and routines ***
- ****************************************/
-
-typedef struct {
- double Real, Imag;
-} Complex;
-
-static Complex MakeComplex(double r, double i)
-{
- Complex c = { r, i };
- return c;
-}
-
-static Complex c_add(Complex a, Complex b)
-{
- Complex r;
- r.Real = a.Real + b.Real;
- r.Imag = a.Imag + b.Imag;
- return r;
-}
-
-static Complex c_sub(Complex a, Complex b)
-{
- Complex r;
- r.Real = a.Real - b.Real;
- r.Imag = a.Imag - b.Imag;
- return r;
-}
-
-static Complex c_mul(Complex a, Complex b)
-{
- Complex r;
- r.Real = a.Real*b.Real - a.Imag*b.Imag;
- r.Imag = a.Imag*b.Real + a.Real*b.Imag;
- return r;
-}
-
-static Complex c_muls(Complex a, double s)
-{
- Complex r;
- r.Real = a.Real * s;
- r.Imag = a.Imag * s;
- return r;
-}
-
-static double c_abs(Complex a)
-{
- return sqrt(a.Real*a.Real + a.Imag*a.Imag);
-}
-
-static Complex c_exp(Complex a)
-{
- Complex r;
- double e = exp(a.Real);
- r.Real = e * cos(a.Imag);
- r.Imag = e * sin(a.Imag);
- return r;
-}
-
-/*****************************
- *** Token reader routines ***
- *****************************/
-
-/* Whitespace is not significant. It can process tokens as identifiers, numbers
- * (integer and floating-point), strings, and operators. Strings must be
- * encapsulated by double-quotes and cannot span multiple lines.
- */
-
-// Setup the reader on the given file. The filename can be NULL if no error
-// output is desired.
-static void TrSetup(FILE *fp, const char *filename, TokenReaderT *tr)
-{
- const char *name = NULL;
-
- if(filename)
- {
- const char *slash = strrchr(filename, '/');
- if(slash)
- {
- const char *bslash = strrchr(slash+1, '\\');
- if(bslash) name = bslash+1;
- else name = slash+1;
- }
- else
- {
- const char *bslash = strrchr(filename, '\\');
- if(bslash) name = bslash+1;
- else name = filename;
- }
- }
-
- tr->mFile = fp;
- tr->mName = name;
- tr->mLine = 1;
- tr->mColumn = 1;
- tr->mIn = 0;
- tr->mOut = 0;
-}
-
-// Prime the reader's ring buffer, and return a result indicating that there
-// is text to process.
-static int TrLoad(TokenReaderT *tr)
-{
- size_t toLoad, in, count;
-
- toLoad = TR_RING_SIZE - (tr->mIn - tr->mOut);
- if(toLoad >= TR_LOAD_SIZE && !feof(tr->mFile))
- {
- // Load TR_LOAD_SIZE (or less if at the end of the file) per read.
- toLoad = TR_LOAD_SIZE;
- in = tr->mIn&TR_RING_MASK;
- count = TR_RING_SIZE - in;
- if(count < toLoad)
- {
- tr->mIn += fread(&tr->mRing[in], 1, count, tr->mFile);
- tr->mIn += fread(&tr->mRing[0], 1, toLoad-count, tr->mFile);
- }
- else
- tr->mIn += fread(&tr->mRing[in], 1, toLoad, tr->mFile);
-
- if(tr->mOut >= TR_RING_SIZE)
- {
- tr->mOut -= TR_RING_SIZE;
- tr->mIn -= TR_RING_SIZE;
- }
- }
- if(tr->mIn > tr->mOut)
- return 1;
- return 0;
-}
-
-// Error display routine. Only displays when the base name is not NULL.
-static void TrErrorVA(const TokenReaderT *tr, uint line, uint column, const char *format, va_list argPtr)
-{
- if(!tr->mName)
- return;
- fprintf(stderr, "Error (%s:%u:%u): ", tr->mName, line, column);
- vfprintf(stderr, format, argPtr);
-}
-
-// Used to display an error at a saved line/column.
-static void TrErrorAt(const TokenReaderT *tr, uint line, uint column, const char *format, ...)
-{
- va_list argPtr;
-
- va_start(argPtr, format);
- TrErrorVA(tr, line, column, format, argPtr);
- va_end(argPtr);
-}
-
-// Used to display an error at the current line/column.
-static void TrError(const TokenReaderT *tr, const char *format, ...)
-{
- va_list argPtr;
-
- va_start(argPtr, format);
- TrErrorVA(tr, tr->mLine, tr->mColumn, format, argPtr);
- va_end(argPtr);
-}
-
-// Skips to the next line.
-static void TrSkipLine(TokenReaderT *tr)
-{
- char ch;
-
- while(TrLoad(tr))
- {
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- tr->mOut++;
- if(ch == '\n')
- {
- tr->mLine++;
- tr->mColumn = 1;
- break;
- }
- tr->mColumn ++;
- }
-}
-
-// Skips to the next token.
-static int TrSkipWhitespace(TokenReaderT *tr)
-{
- char ch;
-
- while(TrLoad(tr))
- {
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- if(isspace(ch))
- {
- tr->mOut++;
- if(ch == '\n')
- {
- tr->mLine++;
- tr->mColumn = 1;
- }
- else
- tr->mColumn++;
- }
- else if(ch == '#')
- TrSkipLine(tr);
- else
- return 1;
- }
- return 0;
-}
-
-// Get the line and/or column of the next token (or the end of input).
-static void TrIndication(TokenReaderT *tr, uint *line, uint *column)
-{
- TrSkipWhitespace(tr);
- if(line) *line = tr->mLine;
- if(column) *column = tr->mColumn;
-}
-
-// Checks to see if a token is (likely to be) an identifier. It does not
-// display any errors and will not proceed to the next token.
-static int TrIsIdent(TokenReaderT *tr)
-{
- char ch;
-
- if(!TrSkipWhitespace(tr))
- return 0;
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- return ch == '_' || isalpha(ch);
-}
-
-
-// Checks to see if a token is the given operator. It does not display any
-// errors and will not proceed to the next token.
-static int TrIsOperator(TokenReaderT *tr, const char *op)
-{
- size_t out, len;
- char ch;
-
- if(!TrSkipWhitespace(tr))
- return 0;
- out = tr->mOut;
- len = 0;
- while(op[len] != '\0' && out < tr->mIn)
- {
- ch = tr->mRing[out&TR_RING_MASK];
- if(ch != op[len]) break;
- len++;
- out++;
- }
- if(op[len] == '\0')
- return 1;
- return 0;
-}
-
-/* The TrRead*() routines obtain the value of a matching token type. They
- * display type, form, and boundary errors and will proceed to the next
- * token.
- */
-
-// Reads and validates an identifier token.
-static int TrReadIdent(TokenReaderT *tr, const uint maxLen, char *ident)
-{
- uint col, len;
- char ch;
-
- col = tr->mColumn;
- if(TrSkipWhitespace(tr))
- {
- col = tr->mColumn;
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- if(ch == '_' || isalpha(ch))
- {
- len = 0;
- do {
- if(len < maxLen)
- ident[len] = ch;
- len++;
- tr->mOut++;
- if(!TrLoad(tr))
- break;
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- } while(ch == '_' || isdigit(ch) || isalpha(ch));
-
- tr->mColumn += len;
- if(len < maxLen)
- {
- ident[len] = '\0';
- return 1;
- }
- TrErrorAt(tr, tr->mLine, col, "Identifier is too long.\n");
- return 0;
- }
- }
- TrErrorAt(tr, tr->mLine, col, "Expected an identifier.\n");
- return 0;
-}
-
-// Reads and validates (including bounds) an integer token.
-static int TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int *value)
-{
- uint col, digis, len;
- char ch, temp[64+1];
-
- col = tr->mColumn;
- if(TrSkipWhitespace(tr))
- {
- col = tr->mColumn;
- len = 0;
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- if(ch == '+' || ch == '-')
- {
- temp[len] = ch;
- len++;
- tr->mOut++;
- }
- digis = 0;
- while(TrLoad(tr))
- {
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- if(!isdigit(ch)) break;
- if(len < 64)
- temp[len] = ch;
- len++;
- digis++;
- tr->mOut++;
- }
- tr->mColumn += len;
- if(digis > 0 && ch != '.' && !isalpha(ch))
- {
- if(len > 64)
- {
- TrErrorAt(tr, tr->mLine, col, "Integer is too long.");
- return 0;
- }
- temp[len] = '\0';
- *value = strtol(temp, NULL, 10);
- if(*value < loBound || *value > hiBound)
- {
- TrErrorAt(tr, tr->mLine, col, "Expected a value from %d to %d.\n", loBound, hiBound);
- return 0;
- }
- return 1;
- }
- }
- TrErrorAt(tr, tr->mLine, col, "Expected an integer.\n");
- return 0;
-}
-
-// Reads and validates (including bounds) a float token.
-static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, double *value)
-{
- uint col, digis, len;
- char ch, temp[64+1];
-
- col = tr->mColumn;
- if(TrSkipWhitespace(tr))
- {
- col = tr->mColumn;
- len = 0;
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- if(ch == '+' || ch == '-')
- {
- temp[len] = ch;
- len++;
- tr->mOut++;
- }
-
- digis = 0;
- while(TrLoad(tr))
- {
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- if(!isdigit(ch)) break;
- if(len < 64)
- temp[len] = ch;
- len++;
- digis++;
- tr->mOut++;
- }
- if(ch == '.')
- {
- if(len < 64)
- temp[len] = ch;
- len++;
- tr->mOut++;
- }
- while(TrLoad(tr))
- {
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- if(!isdigit(ch)) break;
- if(len < 64)
- temp[len] = ch;
- len++;
- digis++;
- tr->mOut++;
- }
- if(digis > 0)
- {
- if(ch == 'E' || ch == 'e')
- {
- if(len < 64)
- temp[len] = ch;
- len++;
- digis = 0;
- tr->mOut++;
- if(ch == '+' || ch == '-')
- {
- if(len < 64)
- temp[len] = ch;
- len++;
- tr->mOut++;
- }
- while(TrLoad(tr))
- {
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- if(!isdigit(ch)) break;
- if(len < 64)
- temp[len] = ch;
- len++;
- digis++;
- tr->mOut++;
- }
- }
- tr->mColumn += len;
- if(digis > 0 && ch != '.' && !isalpha(ch))
- {
- if(len > 64)
- {
- TrErrorAt(tr, tr->mLine, col, "Float is too long.");
- return 0;
- }
- temp[len] = '\0';
- *value = strtod(temp, NULL);
- if(*value < loBound || *value > hiBound)
- {
- TrErrorAt(tr, tr->mLine, col, "Expected a value from %f to %f.\n", loBound, hiBound);
- return 0;
- }
- return 1;
- }
- }
- else
- tr->mColumn += len;
- }
- TrErrorAt(tr, tr->mLine, col, "Expected a float.\n");
- return 0;
-}
-
-// Reads and validates a string token.
-static int TrReadString(TokenReaderT *tr, const uint maxLen, char *text)
-{
- uint col, len;
- char ch;
-
- col = tr->mColumn;
- if(TrSkipWhitespace(tr))
- {
- col = tr->mColumn;
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- if(ch == '\"')
- {
- tr->mOut++;
- len = 0;
- while(TrLoad(tr))
- {
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- tr->mOut++;
- if(ch == '\"')
- break;
- if(ch == '\n')
- {
- TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of line.\n");
- return 0;
- }
- if(len < maxLen)
- text[len] = ch;
- len++;
- }
- if(ch != '\"')
- {
- tr->mColumn += 1 + len;
- TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of input.\n");
- return 0;
- }
- tr->mColumn += 2 + len;
- if(len > maxLen)
- {
- TrErrorAt(tr, tr->mLine, col, "String is too long.\n");
- return 0;
- }
- text[len] = '\0';
- return 1;
- }
- }
- TrErrorAt(tr, tr->mLine, col, "Expected a string.\n");
- return 0;
-}
-
-// Reads and validates the given operator.
-static int TrReadOperator(TokenReaderT *tr, const char *op)
-{
- uint col, len;
- char ch;
-
- col = tr->mColumn;
- if(TrSkipWhitespace(tr))
- {
- col = tr->mColumn;
- len = 0;
- while(op[len] != '\0' && TrLoad(tr))
- {
- ch = tr->mRing[tr->mOut&TR_RING_MASK];
- if(ch != op[len]) break;
- len++;
- tr->mOut++;
- }
- tr->mColumn += len;
- if(op[len] == '\0')
- return 1;
- }
- TrErrorAt(tr, tr->mLine, col, "Expected '%s' operator.\n", op);
- return 0;
-}
-
-/* Performs a string substitution. Any case-insensitive occurrences of the
- * pattern string are replaced with the replacement string. The result is
- * truncated if necessary.
- */
-static int StrSubst(const char *in, const char *pat, const char *rep, const size_t maxLen, char *out)
-{
- size_t inLen, patLen, repLen;
- size_t si, di;
- int truncated;
-
- inLen = strlen(in);
- patLen = strlen(pat);
- repLen = strlen(rep);
- si = 0;
- di = 0;
- truncated = 0;
- while(si < inLen && di < maxLen)
- {
- if(patLen <= inLen-si)
- {
- if(strncasecmp(&in[si], pat, patLen) == 0)
- {
- if(repLen > maxLen-di)
- {
- repLen = maxLen - di;
- truncated = 1;
- }
- strncpy(&out[di], rep, repLen);
- si += patLen;
- di += repLen;
- }
- }
- out[di] = in[si];
- si++;
- di++;
- }
- if(si < inLen)
- truncated = 1;
- out[di] = '\0';
- return !truncated;
-}
-
-
-/*********************
- *** Math routines ***
- *********************/
-
-// Provide missing math routines for MSVC versions < 1800 (Visual Studio 2013).
-#if defined(_MSC_VER) && _MSC_VER < 1800
-static double round(double val)
-{
- if(val < 0.0)
- return ceil(val-0.5);
- return floor(val+0.5);
-}
-
-static double fmin(double a, double b)
-{
- return (a<b) ? a : b;
-}
-
-static double fmax(double a, double b)
-{
- return (a>b) ? a : b;
-}
-#endif
-
-// Simple clamp routine.
-static double Clamp(const double val, const double lower, const double upper)
-{
- return fmin(fmax(val, lower), upper);
-}
-
-// Performs linear interpolation.
-static double Lerp(const double a, const double b, const double f)
-{
- return a + f * (b - a);
-}
-
-static inline uint dither_rng(uint *seed)
-{
- *seed = *seed * 96314165 + 907633515;
- return *seed;
-}
-
-// Performs a triangular probability density function dither. The input samples
-// should be normalized (-1 to +1).
-static void TpdfDither(double *restrict out, const double *restrict in, const double scale,
- const int count, const int step, uint *seed)
-{
- static const double PRNG_SCALE = 1.0 / UINT_MAX;
- uint prn0, prn1;
- int i;
-
- for(i = 0;i < count;i++)
- {
- prn0 = dither_rng(seed);
- prn1 = dither_rng(seed);
- out[i*step] = round(in[i]*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE));
- }
-}
-
-// Allocates an array of doubles.
-static double *CreateDoubles(size_t n)
-{
- double *a;
-
- a = calloc(n?n:1, sizeof(*a));
- if(a == NULL)
- {
- fprintf(stderr, "Error: Out of memory.\n");
- exit(-1);
- }
- return a;
-}
-
-// Allocates an array of complex numbers.
-static Complex *CreateComplexes(size_t n)
-{
- Complex *a;
-
- a = calloc(n?n:1, sizeof(*a));
- if(a == NULL)
- {
- fprintf(stderr, "Error: Out of memory.\n");
- exit(-1);
- }
- return a;
-}
-
-/* Fast Fourier transform routines. The number of points must be a power of
- * two.
- */
-
-// Performs bit-reversal ordering.
-static void FftArrange(const uint n, Complex *inout)
-{
- uint rk, k, m;
-
- // Handle in-place arrangement.
- rk = 0;
- for(k = 0;k < n;k++)
- {
- if(rk > k)
- {
- Complex temp = inout[rk];
- inout[rk] = inout[k];
- inout[k] = temp;
- }
-
- m = n;
- while(rk&(m >>= 1))
- rk &= ~m;
- rk |= m;
- }
-}
-
-// Performs the summation.
-static void FftSummation(const int n, const double s, Complex *cplx)
-{
- double pi;
- int m, m2;
- int i, k, mk;
-
- pi = s * M_PI;
- for(m = 1, m2 = 2;m < n; m <<= 1, m2 <<= 1)
- {
- // v = Complex (-2.0 * sin (0.5 * pi / m) * sin (0.5 * pi / m), -sin (pi / m))
- double sm = sin(0.5 * pi / m);
- Complex v = MakeComplex(-2.0*sm*sm, -sin(pi / m));
- Complex w = MakeComplex(1.0, 0.0);
- for(i = 0;i < m;i++)
- {
- for(k = i;k < n;k += m2)
- {
- Complex t;
- mk = k + m;
- t = c_mul(w, cplx[mk]);
- cplx[mk] = c_sub(cplx[k], t);
- cplx[k] = c_add(cplx[k], t);
- }
- w = c_add(w, c_mul(v, w));
- }
- }
-}
-
-// Performs a forward FFT.
-static void FftForward(const uint n, Complex *inout)
-{
- FftArrange(n, inout);
- FftSummation(n, 1.0, inout);
-}
-
-// Performs an inverse FFT.
-static void FftInverse(const uint n, Complex *inout)
-{
- double f;
- uint i;
-
- FftArrange(n, inout);
- FftSummation(n, -1.0, inout);
- f = 1.0 / n;
- for(i = 0;i < n;i++)
- inout[i] = c_muls(inout[i], f);
-}
-
-/* Calculate the complex helical sequence (or discrete-time analytical signal)
- * of the given input using the Hilbert transform. Given the natural logarithm
- * of a signal's magnitude response, the imaginary components can be used as
- * the angles for minimum-phase reconstruction.
- */
-static void Hilbert(const uint n, Complex *inout)
-{
- uint i;
-
- // Handle in-place operation.
- for(i = 0;i < n;i++)
- inout[i].Imag = 0.0;
-
- FftInverse(n, inout);
- for(i = 1;i < (n+1)/2;i++)
- inout[i] = c_muls(inout[i], 2.0);
- /* Increment i if n is even. */
- i += (n&1)^1;
- for(;i < n;i++)
- inout[i] = MakeComplex(0.0, 0.0);
- FftForward(n, inout);
-}
-
-/* Calculate the magnitude response of the given input. This is used in
- * place of phase decomposition, since the phase residuals are discarded for
- * minimum phase reconstruction. The mirrored half of the response is also
- * discarded.
- */
-static void MagnitudeResponse(const uint n, const Complex *in, double *out)
-{
- const uint m = 1 + (n / 2);
- uint i;
- for(i = 0;i < m;i++)
- out[i] = fmax(c_abs(in[i]), EPSILON);
-}
-
-/* Apply a range limit (in dB) to the given magnitude response. This is used
- * to adjust the effects of the diffuse-field average on the equalization
- * process.
- */
-static void LimitMagnitudeResponse(const uint n, const uint m, const double limit, const double *in, double *out)
-{
- double halfLim;
- uint i, lower, upper;
- double ave;
-
- halfLim = limit / 2.0;
- // Convert the response to dB.
- for(i = 0;i < m;i++)
- out[i] = 20.0 * log10(in[i]);
- // Use six octaves to calculate the average magnitude of the signal.
- lower = ((uint)ceil(n / pow(2.0, 8.0))) - 1;
- upper = ((uint)floor(n / pow(2.0, 2.0))) - 1;
- ave = 0.0;
- for(i = lower;i <= upper;i++)
- ave += out[i];
- ave /= upper - lower + 1;
- // Keep the response within range of the average magnitude.
- for(i = 0;i < m;i++)
- out[i] = Clamp(out[i], ave - halfLim, ave + halfLim);
- // Convert the response back to linear magnitude.
- for(i = 0;i < m;i++)
- out[i] = pow(10.0, out[i] / 20.0);
-}
-
-/* Reconstructs the minimum-phase component for the given magnitude response
- * of a signal. This is equivalent to phase recomposition, sans the missing
- * residuals (which were discarded). The mirrored half of the response is
- * reconstructed.
- */
-static void MinimumPhase(const uint n, const double *in, Complex *out)
-{
- const uint m = 1 + (n / 2);
- double *mags;
- uint i;
-
- mags = CreateDoubles(n);
- for(i = 0;i < m;i++)
- {
- mags[i] = fmax(EPSILON, in[i]);
- out[i] = MakeComplex(log(mags[i]), 0.0);
- }
- for(;i < n;i++)
- {
- mags[i] = mags[n - i];
- out[i] = out[n - i];
- }
- Hilbert(n, out);
- // Remove any DC offset the filter has.
- mags[0] = EPSILON;
- for(i = 0;i < n;i++)
- {
- Complex a = c_exp(MakeComplex(0.0, out[i].Imag));
- out[i] = c_mul(MakeComplex(mags[i], 0.0), a);
- }
- free(mags);
-}
-
-
-/***************************
- *** Resampler functions ***
- ***************************/
-
-/* This is the normalized cardinal sine (sinc) function.
- *
- * sinc(x) = { 1, x = 0
- * { sin(pi x) / (pi x), otherwise.
- */
-static double Sinc(const double x)
-{
- if(fabs(x) < EPSILON)
- return 1.0;
- return sin(M_PI * x) / (M_PI * x);
-}
-
-/* The zero-order modified Bessel function of the first kind, used for the
- * Kaiser window.
- *
- * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k)
- * = sum_{k=0}^inf ((x / 2)^k / k!)^2
- */
-static double BesselI_0(const double x)
-{
- double term, sum, x2, y, last_sum;
- int k;
-
- // Start at k=1 since k=0 is trivial.
- term = 1.0;
- sum = 1.0;
- x2 = x/2.0;
- k = 1;
-
- // Let the integration converge until the term of the sum is no longer
- // significant.
- do {
- y = x2 / k;
- k++;
- last_sum = sum;
- term *= y * y;
- sum += term;
- } while(sum != last_sum);
- return sum;
-}
-
-/* Calculate a Kaiser window from the given beta value and a normalized k
- * [-1, 1].
- *
- * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1
- * { 0, elsewhere.
- *
- * Where k can be calculated as:
- *
- * k = i / l, where -l <= i <= l.
- *
- * or:
- *
- * k = 2 i / M - 1, where 0 <= i <= M.
- */
-static double Kaiser(const double b, const double k)
-{
- if(!(k >= -1.0 && k <= 1.0))
- return 0.0;
- return BesselI_0(b * sqrt(1.0 - k*k)) / BesselI_0(b);
-}
-
-// Calculates the greatest common divisor of a and b.
-static uint Gcd(uint x, uint y)
-{
- while(y > 0)
- {
- uint z = y;
- y = x % y;
- x = z;
- }
- return x;
-}
-
-/* Calculates the size (order) of the Kaiser window. Rejection is in dB and
- * the transition width is normalized frequency (0.5 is nyquist).
- *
- * M = { ceil((r - 7.95) / (2.285 2 pi f_t)), r > 21
- * { ceil(5.79 / 2 pi f_t), r <= 21.
- *
- */
-static uint CalcKaiserOrder(const double rejection, const double transition)
-{
- double w_t = 2.0 * M_PI * transition;
- if(rejection > 21.0)
- return (uint)ceil((rejection - 7.95) / (2.285 * w_t));
- return (uint)ceil(5.79 / w_t);
-}
-
-// Calculates the beta value of the Kaiser window. Rejection is in dB.
-static double CalcKaiserBeta(const double rejection)
-{
- if(rejection > 50.0)
- return 0.1102 * (rejection - 8.7);
- if(rejection >= 21.0)
- return (0.5842 * pow(rejection - 21.0, 0.4)) +
- (0.07886 * (rejection - 21.0));
- return 0.0;
-}
-
-/* Calculates a point on the Kaiser-windowed sinc filter for the given half-
- * width, beta, gain, and cutoff. The point is specified in non-normalized
- * samples, from 0 to M, where M = (2 l + 1).
- *
- * w(k) 2 p f_t sinc(2 f_t x)
- *
- * x -- centered sample index (i - l)
- * k -- normalized and centered window index (x / l)
- * w(k) -- window function (Kaiser)
- * p -- gain compensation factor when sampling
- * f_t -- normalized center frequency (or cutoff; 0.5 is nyquist)
- */
-static double SincFilter(const int l, const double b, const double gain, const double cutoff, const int i)
-{
- return Kaiser(b, (double)(i - l) / l) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * (i - l));
-}
-
-/* This is a polyphase sinc-filtered resampler.
- *
- * Upsample Downsample
- *
- * p/q = 3/2 p/q = 3/5
- *
- * M-+-+-+-> M-+-+-+->
- * -------------------+ ---------------------+
- * p s * f f f f|f| | p s * f f f f f |
- * | 0 * 0 0 0|0|0 | | 0 * 0 0 0 0|0| |
- * v 0 * 0 0|0|0 0 | v 0 * 0 0 0|0|0 |
- * s * f|f|f f f | s * f f|f|f f |
- * 0 * |0|0 0 0 0 | 0 * 0|0|0 0 0 |
- * --------+=+--------+ 0 * |0|0 0 0 0 |
- * d . d .|d|. d . d ----------+=+--------+
- * d . . . .|d|. . . .
- * q->
- * q-+-+-+->
- *
- * P_f(i,j) = q i mod p + pj
- * P_s(i,j) = floor(q i / p) - j
- * d[i=0..N-1] = sum_{j=0}^{floor((M - 1) / p)} {
- * { f[P_f(i,j)] s[P_s(i,j)], P_f(i,j) < M
- * { 0, P_f(i,j) >= M. }
- */
-
-// Calculate the resampling metrics and build the Kaiser-windowed sinc filter
-// that's used to cut frequencies above the destination nyquist.
-static void ResamplerSetup(ResamplerT *rs, const uint srcRate, const uint dstRate)
-{
- double cutoff, width, beta;
- uint gcd, l;
- int i;
-
- gcd = Gcd(srcRate, dstRate);
- rs->mP = dstRate / gcd;
- rs->mQ = srcRate / gcd;
- /* The cutoff is adjusted by half the transition width, so the transition
- * ends before the nyquist (0.5). Both are scaled by the downsampling
- * factor.
- */
- if(rs->mP > rs->mQ)
- {
- cutoff = 0.475 / rs->mP;
- width = 0.05 / rs->mP;
- }
- else
- {
- cutoff = 0.475 / rs->mQ;
- width = 0.05 / rs->mQ;
- }
- // A rejection of -180 dB is used for the stop band. Round up when
- // calculating the left offset to avoid increasing the transition width.
- l = (CalcKaiserOrder(180.0, width)+1) / 2;
- beta = CalcKaiserBeta(180.0);
- rs->mM = l*2 + 1;
- rs->mL = l;
- rs->mF = CreateDoubles(rs->mM);
- for(i = 0;i < ((int)rs->mM);i++)
- rs->mF[i] = SincFilter((int)l, beta, rs->mP, cutoff, i);
-}
-
-// Clean up after the resampler.
-static void ResamplerClear(ResamplerT *rs)
-{
- free(rs->mF);
- rs->mF = NULL;
-}
-
-// Perform the upsample-filter-downsample resampling operation using a
-// polyphase filter implementation.
-static void ResamplerRun(ResamplerT *rs, const uint inN, const double *in, const uint outN, double *out)
-{
- const uint p = rs->mP, q = rs->mQ, m = rs->mM, l = rs->mL;
- const double *f = rs->mF;
- uint j_f, j_s;
- double *work;
- uint i;
-
- if(outN == 0)
- return;
-
- // Handle in-place operation.
- if(in == out)
- work = CreateDoubles(outN);
- else
- work = out;
- // Resample the input.
- for(i = 0;i < outN;i++)
- {
- double r = 0.0;
- // Input starts at l to compensate for the filter delay. This will
- // drop any build-up from the first half of the filter.
- j_f = (l + (q * i)) % p;
- j_s = (l + (q * i)) / p;
- while(j_f < m)
- {
- // Only take input when 0 <= j_s < inN. This single unsigned
- // comparison catches both cases.
- if(j_s < inN)
- r += f[j_f] * in[j_s];
- j_f += p;
- j_s--;
- }
- work[i] = r;
- }
- // Clean up after in-place operation.
- if(work != out)
- {
- for(i = 0;i < outN;i++)
- out[i] = work[i];
- free(work);
- }
-}
-
-/*************************
- *** File source input ***
- *************************/
-
-// Read a binary value of the specified byte order and byte size from a file,
-// storing it as a 32-bit unsigned integer.
-static int ReadBin4(FILE *fp, const char *filename, const ByteOrderT order, const uint bytes, uint32 *out)
-{
- uint8 in[4];
- uint32 accum;
- uint i;
-
- if(fread(in, 1, bytes, fp) != bytes)
- {
- fprintf(stderr, "Error: Bad read from file '%s'.\n", filename);
- return 0;
- }
- accum = 0;
- switch(order)
- {
- case BO_LITTLE:
- for(i = 0;i < bytes;i++)
- accum = (accum<<8) | in[bytes - i - 1];
- break;
- case BO_BIG:
- for(i = 0;i < bytes;i++)
- accum = (accum<<8) | in[i];
- break;
- default:
- break;
- }
- *out = accum;
- return 1;
-}
-
-// Read a binary value of the specified byte order from a file, storing it as
-// a 64-bit unsigned integer.
-static int ReadBin8(FILE *fp, const char *filename, const ByteOrderT order, uint64 *out)
-{
- uint8 in [8];
- uint64 accum;
- uint i;
-
- if(fread(in, 1, 8, fp) != 8)
- {
- fprintf(stderr, "Error: Bad read from file '%s'.\n", filename);
- return 0;
- }
- accum = 0ULL;
- switch(order)
- {
- case BO_LITTLE:
- for(i = 0;i < 8;i++)
- accum = (accum<<8) | in[8 - i - 1];
- break;
- case BO_BIG:
- for(i = 0;i < 8;i++)
- accum = (accum<<8) | in[i];
- break;
- default:
- break;
- }
- *out = accum;
- return 1;
-}
-
-/* Read a binary value of the specified type, byte order, and byte size from
- * a file, converting it to a double. For integer types, the significant
- * bits are used to normalize the result. The sign of bits determines
- * whether they are padded toward the MSB (negative) or LSB (positive).
- * Floating-point types are not normalized.
- */
-static int ReadBinAsDouble(FILE *fp, const char *filename, const ByteOrderT order, const ElementTypeT type, const uint bytes, const int bits, double *out)
-{
- union {
- uint32 ui;
- int32 i;
- float f;
- } v4;
- union {
- uint64 ui;
- double f;
- } v8;
-
- *out = 0.0;
- if(bytes > 4)
- {
- if(!ReadBin8(fp, filename, order, &v8.ui))
- return 0;
- if(type == ET_FP)
- *out = v8.f;
- }
- else
- {
- if(!ReadBin4(fp, filename, order, bytes, &v4.ui))
- return 0;
- if(type == ET_FP)
- *out = v4.f;
- else
- {
- if(bits > 0)
- v4.ui >>= (8*bytes) - ((uint)bits);
- else
- v4.ui &= (0xFFFFFFFF >> (32+bits));
-
- if(v4.ui&(uint)(1<<(abs(bits)-1)))
- v4.ui |= (0xFFFFFFFF << abs (bits));
- *out = v4.i / (double)(1<<(abs(bits)-1));
- }
- }
- return 1;
-}
-
-/* Read an ascii value of the specified type from a file, converting it to a
- * double. For integer types, the significant bits are used to normalize the
- * result. The sign of the bits should always be positive. This also skips
- * up to one separator character before the element itself.
- */
-static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const ElementTypeT type, const uint bits, double *out)
-{
- if(TrIsOperator(tr, ","))
- TrReadOperator(tr, ",");
- else if(TrIsOperator(tr, ":"))
- TrReadOperator(tr, ":");
- else if(TrIsOperator(tr, ";"))
- TrReadOperator(tr, ";");
- else if(TrIsOperator(tr, "|"))
- TrReadOperator(tr, "|");
-
- if(type == ET_FP)
- {
- if(!TrReadFloat(tr, -HUGE_VAL, HUGE_VAL, out))
- {
- fprintf(stderr, "Error: Bad read from file '%s'.\n", filename);
- return 0;
- }
- }
- else
- {
- int v;
- if(!TrReadInt(tr, -(1<<(bits-1)), (1<<(bits-1))-1, &v))
- {
- fprintf(stderr, "Error: Bad read from file '%s'.\n", filename);
- return 0;
- }
- *out = v / (double)((1<<(bits-1))-1);
- }
- return 1;
-}
-
-// Read the RIFF/RIFX WAVE format chunk from a file, validating it against
-// the source parameters and data set metrics.
-static int ReadWaveFormat(FILE *fp, const ByteOrderT order, const uint hrirRate, SourceRefT *src)
-{
- uint32 fourCC, chunkSize;
- uint32 format, channels, rate, dummy, block, size, bits;
-
- chunkSize = 0;
- do {
- if(chunkSize > 0)
- fseek (fp, (long) chunkSize, SEEK_CUR);
- if(!ReadBin4(fp, src->mPath, BO_LITTLE, 4, &fourCC) ||
- !ReadBin4(fp, src->mPath, order, 4, &chunkSize))
- return 0;
- } while(fourCC != FOURCC_FMT);
- if(!ReadBin4(fp, src->mPath, order, 2, &format) ||
- !ReadBin4(fp, src->mPath, order, 2, &channels) ||
- !ReadBin4(fp, src->mPath, order, 4, &rate) ||
- !ReadBin4(fp, src->mPath, order, 4, &dummy) ||
- !ReadBin4(fp, src->mPath, order, 2, &block))
- return 0;
- block /= channels;
- if(chunkSize > 14)
- {
- if(!ReadBin4(fp, src->mPath, order, 2, &size))
- return 0;
- size /= 8;
- if(block > size)
- size = block;
- }
- else
- size = block;
- if(format == WAVE_FORMAT_EXTENSIBLE)
- {
- fseek(fp, 2, SEEK_CUR);
- if(!ReadBin4(fp, src->mPath, order, 2, &bits))
- return 0;
- if(bits == 0)
- bits = 8 * size;
- fseek(fp, 4, SEEK_CUR);
- if(!ReadBin4(fp, src->mPath, order, 2, &format))
- return 0;
- fseek(fp, (long)(chunkSize - 26), SEEK_CUR);
- }
- else
- {
- bits = 8 * size;
- if(chunkSize > 14)
- fseek(fp, (long)(chunkSize - 16), SEEK_CUR);
- else
- fseek(fp, (long)(chunkSize - 14), SEEK_CUR);
- }
- if(format != WAVE_FORMAT_PCM && format != WAVE_FORMAT_IEEE_FLOAT)
- {
- fprintf(stderr, "Error: Unsupported WAVE format in file '%s'.\n", src->mPath);
- return 0;
- }
- if(src->mChannel >= channels)
- {
- fprintf(stderr, "Error: Missing source channel in WAVE file '%s'.\n", src->mPath);
- return 0;
- }
- if(rate != hrirRate)
- {
- fprintf(stderr, "Error: Mismatched source sample rate in WAVE file '%s'.\n", src->mPath);
- return 0;
- }
- if(format == WAVE_FORMAT_PCM)
- {
- if(size < 2 || size > 4)
- {
- fprintf(stderr, "Error: Unsupported sample size in WAVE file '%s'.\n", src->mPath);
- return 0;
- }
- if(bits < 16 || bits > (8*size))
- {
- fprintf (stderr, "Error: Bad significant bits in WAVE file '%s'.\n", src->mPath);
- return 0;
- }
- src->mType = ET_INT;
- }
- else
- {
- if(size != 4 && size != 8)
- {
- fprintf(stderr, "Error: Unsupported sample size in WAVE file '%s'.\n", src->mPath);
- return 0;
- }
- src->mType = ET_FP;
- }
- src->mSize = size;
- src->mBits = (int)bits;
- src->mSkip = channels;
- return 1;
-}
-
-// Read a RIFF/RIFX WAVE data chunk, converting all elements to doubles.
-static int ReadWaveData(FILE *fp, const SourceRefT *src, const ByteOrderT order, const uint n, double *hrir)
-{
- int pre, post, skip;
- uint i;
-
- pre = (int)(src->mSize * src->mChannel);
- post = (int)(src->mSize * (src->mSkip - src->mChannel - 1));
- skip = 0;
- for(i = 0;i < n;i++)
- {
- skip += pre;
- if(skip > 0)
- fseek(fp, skip, SEEK_CUR);
- if(!ReadBinAsDouble(fp, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i]))
- return 0;
- skip = post;
- }
- if(skip > 0)
- fseek(fp, skip, SEEK_CUR);
- return 1;
-}
-
-// Read the RIFF/RIFX WAVE list or data chunk, converting all elements to
-// doubles.
-static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, const uint n, double *hrir)
-{
- uint32 fourCC, chunkSize, listSize, count;
- uint block, skip, offset, i;
- double lastSample;
-
- for(;;)
- {
- if(!ReadBin4(fp, src->mPath, BO_LITTLE, 4, &fourCC) ||
- !ReadBin4(fp, src->mPath, order, 4, &chunkSize))
- return 0;
-
- if(fourCC == FOURCC_DATA)
- {
- block = src->mSize * src->mSkip;
- count = chunkSize / block;
- if(count < (src->mOffset + n))
- {
- fprintf(stderr, "Error: Bad read from file '%s'.\n", src->mPath);
- return 0;
- }
- fseek(fp, (long)(src->mOffset * block), SEEK_CUR);
- if(!ReadWaveData(fp, src, order, n, &hrir[0]))
- return 0;
- return 1;
- }
- else if(fourCC == FOURCC_LIST)
- {
- if(!ReadBin4(fp, src->mPath, BO_LITTLE, 4, &fourCC))
- return 0;
- chunkSize -= 4;
- if(fourCC == FOURCC_WAVL)
- break;
- }
- if(chunkSize > 0)
- fseek(fp, (long)chunkSize, SEEK_CUR);
- }
- listSize = chunkSize;
- block = src->mSize * src->mSkip;
- skip = src->mOffset;
- offset = 0;
- lastSample = 0.0;
- while(offset < n && listSize > 8)
- {
- if(!ReadBin4(fp, src->mPath, BO_LITTLE, 4, &fourCC) ||
- !ReadBin4(fp, src->mPath, order, 4, &chunkSize))
- return 0;
- listSize -= 8 + chunkSize;
- if(fourCC == FOURCC_DATA)
- {
- count = chunkSize / block;
- if(count > skip)
- {
- fseek(fp, (long)(skip * block), SEEK_CUR);
- chunkSize -= skip * block;
- count -= skip;
- skip = 0;
- if(count > (n - offset))
- count = n - offset;
- if(!ReadWaveData(fp, src, order, count, &hrir[offset]))
- return 0;
- chunkSize -= count * block;
- offset += count;
- lastSample = hrir [offset - 1];
- }
- else
- {
- skip -= count;
- count = 0;
- }
- }
- else if(fourCC == FOURCC_SLNT)
- {
- if(!ReadBin4(fp, src->mPath, order, 4, &count))
- return 0;
- chunkSize -= 4;
- if(count > skip)
- {
- count -= skip;
- skip = 0;
- if(count > (n - offset))
- count = n - offset;
- for(i = 0; i < count; i ++)
- hrir[offset + i] = lastSample;
- offset += count;
- }
- else
- {
- skip -= count;
- count = 0;
- }
- }
- if(chunkSize > 0)
- fseek(fp, (long)chunkSize, SEEK_CUR);
- }
- if(offset < n)
- {
- fprintf(stderr, "Error: Bad read from file '%s'.\n", src->mPath);
- return 0;
- }
- return 1;
-}
-
-// Load a source HRIR from a RIFF/RIFX WAVE file.
-static int LoadWaveSource(FILE *fp, SourceRefT *src, const uint hrirRate, const uint n, double *hrir)
-{
- uint32 fourCC, dummy;
- ByteOrderT order;
-
- if(!ReadBin4(fp, src->mPath, BO_LITTLE, 4, &fourCC) ||
- !ReadBin4(fp, src->mPath, BO_LITTLE, 4, &dummy))
- return 0;
- if(fourCC == FOURCC_RIFF)
- order = BO_LITTLE;
- else if(fourCC == FOURCC_RIFX)
- order = BO_BIG;
- else
- {
- fprintf(stderr, "Error: No RIFF/RIFX chunk in file '%s'.\n", src->mPath);
- return 0;
- }
-
- if(!ReadBin4(fp, src->mPath, BO_LITTLE, 4, &fourCC))
- return 0;
- if(fourCC != FOURCC_WAVE)
- {
- fprintf(stderr, "Error: Not a RIFF/RIFX WAVE file '%s'.\n", src->mPath);
- return 0;
- }
- if(!ReadWaveFormat(fp, order, hrirRate, src))
- return 0;
- if(!ReadWaveList(fp, src, order, n, hrir))
- return 0;
- return 1;
-}
-
-// Load a source HRIR from a binary file.
-static int LoadBinarySource(FILE *fp, const SourceRefT *src, const ByteOrderT order, const uint n, double *hrir)
-{
- uint i;
-
- fseek(fp, (long)src->mOffset, SEEK_SET);
- for(i = 0;i < n;i++)
- {
- if(!ReadBinAsDouble(fp, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i]))
- return 0;
- if(src->mSkip > 0)
- fseek(fp, (long)src->mSkip, SEEK_CUR);
- }
- return 1;
-}
-
-// Load a source HRIR from an ASCII text file containing a list of elements
-// separated by whitespace or common list operators (',', ';', ':', '|').
-static int LoadAsciiSource(FILE *fp, const SourceRefT *src, const uint n, double *hrir)
-{
- TokenReaderT tr;
- uint i, j;
- double dummy;
-
- TrSetup(fp, NULL, &tr);
- for(i = 0;i < src->mOffset;i++)
- {
- if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, (uint)src->mBits, &dummy))
- return 0;
- }
- for(i = 0;i < n;i++)
- {
- if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, (uint)src->mBits, &hrir[i]))
- return 0;
- for(j = 0;j < src->mSkip;j++)
- {
- if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, (uint)src->mBits, &dummy))
- return 0;
- }
- }
- return 1;
-}
-
-// Load a source HRIR from a supported file type.
-static int LoadSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir)
-{
- int result;
- FILE *fp;
-
- if(src->mFormat == SF_ASCII)
- fp = fopen(src->mPath, "r");
- else
- fp = fopen(src->mPath, "rb");
- if(fp == NULL)
- {
- fprintf(stderr, "Error: Could not open source file '%s'.\n", src->mPath);
- return 0;
- }
- if(src->mFormat == SF_WAVE)
- result = LoadWaveSource(fp, src, hrirRate, n, hrir);
- else if(src->mFormat == SF_BIN_LE)
- result = LoadBinarySource(fp, src, BO_LITTLE, n, hrir);
- else if(src->mFormat == SF_BIN_BE)
- result = LoadBinarySource(fp, src, BO_BIG, n, hrir);
- else
- result = LoadAsciiSource(fp, src, n, hrir);
- fclose(fp);
- return result;
-}
-
-
-/***************************
- *** File storage output ***
- ***************************/
-
-// Write an ASCII string to a file.
-static int WriteAscii(const char *out, FILE *fp, const char *filename)
-{
- size_t len;
-
- len = strlen(out);
- if(fwrite(out, 1, len, fp) != len)
- {
- fclose(fp);
- fprintf(stderr, "Error: Bad write to file '%s'.\n", filename);
- return 0;
- }
- return 1;
-}
-
-// Write a binary value of the given byte order and byte size to a file,
-// loading it from a 32-bit unsigned integer.
-static int WriteBin4(const ByteOrderT order, const uint bytes, const uint32 in, FILE *fp, const char *filename)
-{
- uint8 out[4];
- uint i;
-
- switch(order)
- {
- case BO_LITTLE:
- for(i = 0;i < bytes;i++)
- out[i] = (in>>(i*8)) & 0x000000FF;
- break;
- case BO_BIG:
- for(i = 0;i < bytes;i++)
- out[bytes - i - 1] = (in>>(i*8)) & 0x000000FF;
- break;
- default:
- break;
- }
- if(fwrite(out, 1, bytes, fp) != bytes)
- {
- fprintf(stderr, "Error: Bad write to file '%s'.\n", filename);
- return 0;
- }
- return 1;
-}
-
-// Store the OpenAL Soft HRTF data set.
-static int StoreMhr(const HrirDataT *hData, const char *filename)
-{
- uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
- uint n = hData->mIrPoints;
- FILE *fp;
- uint fi, ei, ai, i;
- uint dither_seed = 22222;
-
- if((fp=fopen(filename, "wb")) == NULL)
- {
- fprintf(stderr, "Error: Could not open MHR file '%s'.\n", filename);
- return 0;
- }
- if(!WriteAscii(MHR_FORMAT, fp, filename))
- return 0;
- if(!WriteBin4(BO_LITTLE, 4, (uint32)hData->mIrRate, fp, filename))
- return 0;
- if(!WriteBin4(BO_LITTLE, 1, (uint32)hData->mSampleType, fp, filename))
- return 0;
- if(!WriteBin4(BO_LITTLE, 1, (uint32)hData->mChannelType, fp, filename))
- return 0;
- if(!WriteBin4(BO_LITTLE, 1, (uint32)hData->mIrPoints, fp, filename))
- return 0;
- if(!WriteBin4(BO_LITTLE, 1, (uint32)hData->mFdCount, fp, filename))
- return 0;
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- if(!WriteBin4(BO_LITTLE, 2, (uint32)(1000.0 * hData->mFds[fi].mDistance), fp, filename))
- return 0;
- if(!WriteBin4(BO_LITTLE, 1, (uint32)hData->mFds[fi].mEvCount, fp, filename))
- return 0;
- for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
- {
- if(!WriteBin4(BO_LITTLE, 1, (uint32)hData->mFds[fi].mEvs[ei].mAzCount, fp, filename))
- return 0;
- }
- }
-
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- const double scale = (hData->mSampleType == ST_S16) ? 32767.0 :
- ((hData->mSampleType == ST_S24) ? 8388607.0 : 0.0);
- const int bps = (hData->mSampleType == ST_S16) ? 2 :
- ((hData->mSampleType == ST_S24) ? 3 : 0);
-
- for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
- double out[2 * MAX_TRUNCSIZE];
-
- TpdfDither(out, azd->mIrs[0], scale, n, channels, &dither_seed);
- if(hData->mChannelType == CT_STEREO)
- TpdfDither(out+1, azd->mIrs[1], scale, n, channels, &dither_seed);
- for(i = 0;i < (channels * n);i++)
- {
- int v = (int)Clamp(out[i], -scale-1.0, scale);
- if(!WriteBin4(BO_LITTLE, bps, (uint32)v, fp, filename))
- return 0;
- }
- }
- }
- }
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
- int v = (int)fmin(round(hData->mIrRate * azd->mDelays[0]), MAX_HRTD);
-
- if(!WriteBin4(BO_LITTLE, 1, (uint32)v, fp, filename))
- return 0;
- if(hData->mChannelType == CT_STEREO)
- {
- v = (int)fmin(round(hData->mIrRate * azd->mDelays[1]), MAX_HRTD);
-
- if(!WriteBin4(BO_LITTLE, 1, (uint32)v, fp, filename))
- return 0;
- }
- }
- }
- }
- fclose(fp);
- return 1;
-}
-
-
-/***********************
- *** HRTF processing ***
- ***********************/
-
-// Calculate the onset time of an HRIR and average it with any existing
-// timing for its field, elevation, azimuth, and ear.
-static double AverageHrirOnset(const uint rate, const uint n, const double *hrir, const double f, const double onset)
-{
- double mag = 0.0;
- uint i;
-
- for(i = 0;i < n;i++)
- mag = fmax(fabs(hrir[i]), mag);
- mag *= 0.15;
- for(i = 0;i < n;i++)
- {
- if(fabs(hrir[i]) >= mag)
- break;
- }
- return Lerp(onset, (double)i / rate, f);
-}
-
-// Calculate the magnitude response of an HRIR and average it with any
-// existing responses for its field, elevation, azimuth, and ear.
-static void AverageHrirMagnitude(const uint points, const uint n, const double *hrir, const double f, double *mag)
-{
- uint m = 1 + (n / 2), i;
- Complex *h = CreateComplexes(n);
- double *r = CreateDoubles(n);
-
- for(i = 0;i < points;i++)
- h[i] = MakeComplex(hrir[i], 0.0);
- for(;i < n;i++)
- h[i] = MakeComplex(0.0, 0.0);
- FftForward(n, h);
- MagnitudeResponse(n, h, r);
- for(i = 0;i < m;i++)
- mag[i] = Lerp(mag[i], r[i], f);
- free(r);
- free(h);
-}
-
-/* Calculate the contribution of each HRIR to the diffuse-field average based
- * on the area of its surface patch. All patches are centered at the HRIR
- * coordinates on the unit sphere and are measured by solid angle.
- */
-static void CalculateDfWeights(const HrirDataT *hData, double *weights)
-{
- double sum, evs, ev, upperEv, lowerEv, solidAngle;
- uint fi, ei;
-
- sum = 0.0;
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- evs = M_PI / 2.0 / (hData->mFds[fi].mEvCount - 1);
- for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
- {
- // For each elevation, calculate the upper and lower limits of
- // the patch band.
- ev = hData->mFds[fi].mEvs[ei].mElevation;
- lowerEv = fmax(-M_PI / 2.0, ev - evs);
- upperEv = fmin(M_PI / 2.0, ev + evs);
- // Calculate the area of the patch band.
- solidAngle = 2.0 * M_PI * (sin(upperEv) - sin(lowerEv));
- // Each weight is the area of one patch.
- weights[(fi * MAX_EV_COUNT) + ei] = solidAngle / hData->mFds[fi].mEvs[ei].mAzCount;
- // Sum the total surface area covered by the HRIRs of all fields.
- sum += solidAngle;
- }
- }
- /* TODO: It may be interesting to experiment with how a volume-based
- weighting performs compared to the existing distance-indepenent
- surface patches.
- */
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- // Normalize the weights given the total surface coverage for all
- // fields.
- for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
- weights[(fi * MAX_EV_COUNT) + ei] /= sum;
- }
-}
-
-/* Calculate the diffuse-field average from the given magnitude responses of
- * the HRIR set. Weighting can be applied to compensate for the varying
- * surface area covered by each HRIR. The final average can then be limited
- * by the specified magnitude range (in positive dB; 0.0 to skip).
- */
-static void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, const uint m, const int weighted, const double limit, double *dfa)
-{
- double *weights = CreateDoubles(hData->mFdCount * MAX_EV_COUNT);
- uint count, ti, fi, ei, i, ai;
-
- if(weighted)
- {
- // Use coverage weighting to calculate the average.
- CalculateDfWeights(hData, weights);
- }
- else
- {
- double weight;
-
- // If coverage weighting is not used, the weights still need to be
- // averaged by the number of existing HRIRs.
- count = hData->mIrCount;
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = 0;ei < hData->mFds[fi].mEvStart;ei++)
- count -= hData->mFds[fi].mEvs[ei].mAzCount;
- }
- weight = 1.0 / count;
-
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
- weights[(fi * MAX_EV_COUNT) + ei] = weight;
- }
- }
- for(ti = 0;ti < channels;ti++)
- {
- for(i = 0;i < m;i++)
- dfa[(ti * m) + i] = 0.0;
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
- // Get the weight for this HRIR's contribution.
- double weight = weights[(fi * MAX_EV_COUNT) + ei];
-
- // Add this HRIR's weighted power average to the total.
- for(i = 0;i < m;i++)
- dfa[(ti * m) + i] += weight * azd->mIrs[ti][i] * azd->mIrs[ti][i];
- }
- }
- }
- // Finish the average calculation and keep it from being too small.
- for(i = 0;i < m;i++)
- dfa[(ti * m) + i] = fmax(sqrt(dfa[(ti * m) + i]), EPSILON);
- // Apply a limit to the magnitude range of the diffuse-field average
- // if desired.
- if(limit > 0.0)
- LimitMagnitudeResponse(hData->mFftSize, m, limit, &dfa[ti * m], &dfa[ti * m]);
- }
- free(weights);
-}
-
-// Perform diffuse-field equalization on the magnitude responses of the HRIR
-// set using the given average response.
-static void DiffuseFieldEqualize(const uint channels, const uint m, const double *dfa, const HrirDataT *hData)
-{
- uint ti, fi, ei, ai, i;
-
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
-
- for(ti = 0;ti < channels;ti++)
- {
- for(i = 0;i < m;i++)
- azd->mIrs[ti][i] /= dfa[(ti * m) + i];
- }
- }
- }
- }
-}
-
-// Perform minimum-phase reconstruction using the magnitude responses of the
-// HRIR set.
-static void ReconstructHrirs(const HrirDataT *hData)
-{
- uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
- uint n = hData->mFftSize;
- uint ti, fi, ei, ai, i;
- Complex *h = CreateComplexes(n);
- uint total, count, pcdone, lastpc;
-
- total = hData->mIrCount;
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = 0;ei < hData->mFds[fi].mEvStart;ei++)
- total -= hData->mFds[fi].mEvs[ei].mAzCount;
- }
- total *= channels;
- count = pcdone = lastpc = 0;
- printf("%3d%% done.", pcdone);
- fflush(stdout);
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
-
- for(ti = 0;ti < channels;ti++)
- {
- MinimumPhase(n, azd->mIrs[ti], h);
- FftInverse(n, h);
- for(i = 0;i < hData->mIrPoints;i++)
- azd->mIrs[ti][i] = h[i].Real;
- pcdone = ++count * 100 / total;
- if(pcdone != lastpc)
- {
- lastpc = pcdone;
- printf("\r%3d%% done.", pcdone);
- fflush(stdout);
- }
- }
- }
- }
- }
- printf("\n");
- free(h);
-}
-
-// Resamples the HRIRs for use at the given sampling rate.
-static void ResampleHrirs(const uint rate, HrirDataT *hData)
-{
- uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
- uint n = hData->mIrPoints;
- uint ti, fi, ei, ai;
- ResamplerT rs;
-
- ResamplerSetup(&rs, hData->mIrRate, rate);
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
-
- for(ti = 0;ti < channels;ti++)
- ResamplerRun(&rs, n, azd->mIrs[ti], n, azd->mIrs[ti]);
- }
- }
- }
- hData->mIrRate = rate;
- ResamplerClear(&rs);
-}
-
-/* Given field and elevation indices and an azimuth, calculate the indices of
- * the two HRIRs that bound the coordinate along with a factor for
- * calculating the continuous HRIR using interpolation.
- */
-static void CalcAzIndices(const HrirDataT *hData, const uint fi, const uint ei, const double az, uint *a0, uint *a1, double *af)
-{
- double f = (2.0*M_PI + az) * hData->mFds[fi].mEvs[ei].mAzCount / (2.0*M_PI);
- uint i = (uint)f % hData->mFds[fi].mEvs[ei].mAzCount;
-
- f -= floor(f);
- *a0 = i;
- *a1 = (i + 1) % hData->mFds[fi].mEvs[ei].mAzCount;
- *af = f;
-}
-
-// Synthesize any missing onset timings at the bottom elevations of each
-// field. This just blends between slightly exaggerated known onsets (not
-// an accurate model).
-static void SynthesizeOnsets(HrirDataT *hData)
-{
- uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
- uint ti, fi, oi, ai, ei, a0, a1;
- double t, of, af;
-
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- if(hData->mFds[fi].mEvStart <= 0)
- continue;
- oi = hData->mFds[fi].mEvStart;
-
- for(ti = 0;ti < channels;ti++)
- {
- t = 0.0;
- for(ai = 0;ai < hData->mFds[fi].mEvs[oi].mAzCount;ai++)
- t += hData->mFds[fi].mEvs[oi].mAzs[ai].mDelays[ti];
- hData->mFds[fi].mEvs[0].mAzs[0].mDelays[ti] = 1.32e-4 + (t / hData->mFds[fi].mEvs[oi].mAzCount);
- for(ei = 1;ei < hData->mFds[fi].mEvStart;ei++)
- {
- of = (double)ei / hData->mFds[fi].mEvStart;
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- CalcAzIndices(hData, fi, oi, hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth, &a0, &a1, &af);
- hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[ti] = Lerp(
- hData->mFds[fi].mEvs[0].mAzs[0].mDelays[ti],
- Lerp(hData->mFds[fi].mEvs[oi].mAzs[a0].mDelays[ti],
- hData->mFds[fi].mEvs[oi].mAzs[a1].mDelays[ti], af),
- of
- );
- }
- }
- }
- }
-}
-
-/* Attempt to synthesize any missing HRIRs at the bottom elevations of each
- * field. Right now this just blends the lowest elevation HRIRs together and
- * applies some attenuation and high frequency damping. It is a simple, if
- * inaccurate model.
- */
-static void SynthesizeHrirs(HrirDataT *hData)
-{
- uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
- uint n = hData->mIrPoints;
- uint ti, fi, ai, ei, i;
- double lp[4], s0, s1;
- double of, b;
- uint a0, a1;
- double af;
-
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- const uint oi = hData->mFds[fi].mEvStart;
- if(oi <= 0) continue;
-
- for(ti = 0;ti < channels;ti++)
- {
- for(i = 0;i < n;i++)
- hData->mFds[fi].mEvs[0].mAzs[0].mIrs[ti][i] = 0.0;
- for(ai = 0;ai < hData->mFds[fi].mEvs[oi].mAzCount;ai++)
- {
- for(i = 0;i < n;i++)
- hData->mFds[fi].mEvs[0].mAzs[0].mIrs[ti][i] += hData->mFds[fi].mEvs[oi].mAzs[ai].mIrs[ti][i] /
- hData->mFds[fi].mEvs[oi].mAzCount;
- }
- for(ei = 1;ei < hData->mFds[fi].mEvStart;ei++)
- {
- of = (double)ei / hData->mFds[fi].mEvStart;
- b = (1.0 - of) * (3.5e-6 * hData->mIrRate);
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- CalcAzIndices(hData, fi, oi, hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth, &a0, &a1, &af);
- lp[0] = 0.0;
- lp[1] = 0.0;
- lp[2] = 0.0;
- lp[3] = 0.0;
- for(i = 0;i < n;i++)
- {
- s0 = hData->mFds[fi].mEvs[0].mAzs[0].mIrs[ti][i];
- s1 = Lerp(hData->mFds[fi].mEvs[oi].mAzs[a0].mIrs[ti][i],
- hData->mFds[fi].mEvs[oi].mAzs[a1].mIrs[ti][i], af);
- s0 = Lerp(s0, s1, of);
- lp[0] = Lerp(s0, lp[0], b);
- lp[1] = Lerp(lp[0], lp[1], b);
- lp[2] = Lerp(lp[1], lp[2], b);
- lp[3] = Lerp(lp[2], lp[3], b);
- hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[ti][i] = lp[3];
- }
- }
- }
- b = 3.5e-6 * hData->mIrRate;
- lp[0] = 0.0;
- lp[1] = 0.0;
- lp[2] = 0.0;
- lp[3] = 0.0;
- for(i = 0;i < n;i++)
- {
- s0 = hData->mFds[fi].mEvs[0].mAzs[0].mIrs[ti][i];
- lp[0] = Lerp(s0, lp[0], b);
- lp[1] = Lerp(lp[0], lp[1], b);
- lp[2] = Lerp(lp[1], lp[2], b);
- lp[3] = Lerp(lp[2], lp[3], b);
- hData->mFds[fi].mEvs[0].mAzs[0].mIrs[ti][i] = lp[3];
- }
- }
- hData->mFds[fi].mEvStart = 0;
- }
-}
-
-// The following routines assume a full set of HRIRs for all elevations.
-
-// Normalize the HRIR set and slightly attenuate the result.
-static void NormalizeHrirs(const HrirDataT *hData)
-{
- uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
- uint n = hData->mIrPoints;
- uint ti, fi, ei, ai, i;
- double maxLevel = 0.0;
-
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
-
- for(ti = 0;ti < channels;ti++)
- {
- for(i = 0;i < n;i++)
- maxLevel = fmax(fabs(azd->mIrs[ti][i]), maxLevel);
- }
- }
- }
- }
- maxLevel = 1.01 * maxLevel;
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
-
- for(ti = 0;ti < channels;ti++)
- {
- for(i = 0;i < n;i++)
- azd->mIrs[ti][i] /= maxLevel;
- }
- }
- }
- }
-}
-
-// Calculate the left-ear time delay using a spherical head model.
-static double CalcLTD(const double ev, const double az, const double rad, const double dist)
-{
- double azp, dlp, l, al;
-
- azp = asin(cos(ev) * sin(az));
- dlp = sqrt((dist*dist) + (rad*rad) + (2.0*dist*rad*sin(azp)));
- l = sqrt((dist*dist) - (rad*rad));
- al = (0.5 * M_PI) + azp;
- if(dlp > l)
- dlp = l + (rad * (al - acos(rad / dist)));
- return dlp / 343.3;
-}
-
-// Calculate the effective head-related time delays for each minimum-phase
-// HRIR.
-static void CalculateHrtds(const HeadModelT model, const double radius, HrirDataT *hData)
-{
- uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
- double minHrtd = INFINITY, maxHrtd = -INFINITY;
- uint ti, fi, ei, ai;
- double t;
-
- if(model == HM_DATASET)
- {
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
-
- for(ti = 0;ti < channels;ti++)
- {
- t = azd->mDelays[ti] * radius / hData->mRadius;
- azd->mDelays[ti] = t;
- maxHrtd = fmax(t, maxHrtd);
- minHrtd = fmin(t, minHrtd);
- }
- }
- }
- }
- }
- else
- {
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
- {
- HrirEvT *evd = &hData->mFds[fi].mEvs[ei];
-
- for(ai = 0;ai < evd->mAzCount;ai++)
- {
- HrirAzT *azd = &evd->mAzs[ai];
-
- for(ti = 0;ti < channels;ti++)
- {
- t = CalcLTD(evd->mElevation, azd->mAzimuth, radius, hData->mFds[fi].mDistance);
- azd->mDelays[ti] = t;
- maxHrtd = fmax(t, maxHrtd);
- minHrtd = fmin(t, minHrtd);
- }
- }
- }
- }
- }
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ti = 0;ti < channels;ti++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[ti] -= minHrtd;
- }
- }
- }
-}
-
-// Clear the initial HRIR data state.
-static void ResetHrirData(HrirDataT *hData)
-{
- hData->mIrRate = 0;
- hData->mSampleType = ST_S24;
- hData->mChannelType = CT_NONE;
- hData->mIrPoints = 0;
- hData->mFftSize = 0;
- hData->mIrSize = 0;
- hData->mRadius = 0.0;
- hData->mIrCount = 0;
- hData->mFdCount = 0;
- hData->mFds = NULL;
-}
-
-// Allocate and configure dynamic HRIR structures.
-static int PrepareHrirData(const uint fdCount, const double distances[MAX_FD_COUNT], const uint evCounts[MAX_FD_COUNT], const uint azCounts[MAX_FD_COUNT * MAX_EV_COUNT], HrirDataT *hData)
-{
- uint evTotal = 0, azTotal = 0, fi, ei, ai;
-
- for(fi = 0;fi < fdCount;fi++)
- {
- evTotal += evCounts[fi];
- for(ei = 0;ei < evCounts[fi];ei++)
- azTotal += azCounts[(fi * MAX_EV_COUNT) + ei];
- }
- if(!fdCount || !evTotal || !azTotal)
- return 0;
-
- hData->mFds = calloc(fdCount, sizeof(*hData->mFds));
- if(hData->mFds == NULL)
- return 0;
- hData->mFds[0].mEvs = calloc(evTotal, sizeof(*hData->mFds[0].mEvs));
- if(hData->mFds[0].mEvs == NULL)
- return 0;
- hData->mFds[0].mEvs[0].mAzs = calloc(azTotal, sizeof(*hData->mFds[0].mEvs[0].mAzs));
- if(hData->mFds[0].mEvs[0].mAzs == NULL)
- return 0;
- hData->mIrCount = azTotal;
- hData->mFdCount = fdCount;
- evTotal = 0;
- azTotal = 0;
- for(fi = 0;fi < fdCount;fi++)
- {
- hData->mFds[fi].mDistance = distances[fi];
- hData->mFds[fi].mEvCount = evCounts[fi];
- hData->mFds[fi].mEvStart = 0;
- hData->mFds[fi].mEvs = &hData->mFds[0].mEvs[evTotal];
- evTotal += evCounts[fi];
- for(ei = 0;ei < evCounts[fi];ei++)
- {
- uint azCount = azCounts[(fi * MAX_EV_COUNT) + ei];
-
- hData->mFds[fi].mIrCount += azCount;
- hData->mFds[fi].mEvs[ei].mElevation = -M_PI / 2.0 + M_PI * ei / (evCounts[fi] - 1);
- hData->mFds[fi].mEvs[ei].mIrCount += azCount;
- hData->mFds[fi].mEvs[ei].mAzCount = azCount;
- hData->mFds[fi].mEvs[ei].mAzs = &hData->mFds[0].mEvs[0].mAzs[azTotal];
- for(ai = 0;ai < azCount;ai++)
- {
- hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth = 2.0 * M_PI * ai / azCount;
- hData->mFds[fi].mEvs[ei].mAzs[ai].mIndex = azTotal + ai;
- hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[0] = 0.0;
- hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[1] = 0.0;
- hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = NULL;
- hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = NULL;
- }
- azTotal += azCount;
- }
- }
- return 1;
-}
-
-// Clean up HRIR data.
-static void FreeHrirData(HrirDataT *hData)
-{
- if(hData->mFds != NULL)
- {
- if(hData->mFds[0].mEvs != NULL)
- {
- if(hData->mFds[0].mEvs[0].mAzs)
- {
- free(hData->mFds[0].mEvs[0].mAzs[0].mIrs[0]);
- free(hData->mFds[0].mEvs[0].mAzs);
- }
- free(hData->mFds[0].mEvs);
- }
- free(hData->mFds);
- hData->mFds = NULL;
- }
-}
-
-// Match the channel type from a given identifier.
-static ChannelTypeT MatchChannelType(const char *ident)
-{
- if(strcasecmp(ident, "mono") == 0)
- return CT_MONO;
- if(strcasecmp(ident, "stereo") == 0)
- return CT_STEREO;
- return CT_NONE;
-}
-
-// Process the data set definition to read and validate the data set metrics.
-static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, HrirDataT *hData)
-{
- int hasRate = 0, hasType = 0, hasPoints = 0, hasRadius = 0;
- int hasDistance = 0, hasAzimuths = 0;
- char ident[MAX_IDENT_LEN+1];
- uint line, col;
- double fpVal;
- uint points;
- int intVal;
- double distances[MAX_FD_COUNT];
- uint fdCount = 0;
- uint evCounts[MAX_FD_COUNT];
- uint *azCounts = calloc(MAX_FD_COUNT * MAX_EV_COUNT, sizeof(*azCounts));
-
- if(azCounts == NULL)
- {
- fprintf(stderr, "Error: Out of memory.\n");
- exit(-1);
- }
- TrIndication(tr, &line, &col);
- while(TrIsIdent(tr))
- {
- TrIndication(tr, &line, &col);
- if(!TrReadIdent(tr, MAX_IDENT_LEN, ident))
- goto error;
- if(strcasecmp(ident, "rate") == 0)
- {
- if(hasRate)
- {
- TrErrorAt(tr, line, col, "Redefinition of 'rate'.\n");
- goto error;
- }
- if(!TrReadOperator(tr, "="))
- goto error;
- if(!TrReadInt(tr, MIN_RATE, MAX_RATE, &intVal))
- goto error;
- hData->mIrRate = (uint)intVal;
- hasRate = 1;
- }
- else if(strcasecmp(ident, "type") == 0)
- {
- char type[MAX_IDENT_LEN+1];
-
- if(hasType)
- {
- TrErrorAt(tr, line, col, "Redefinition of 'type'.\n");
- goto error;
- }
- if(!TrReadOperator(tr, "="))
- goto error;
-
- if(!TrReadIdent(tr, MAX_IDENT_LEN, type))
- goto error;
- hData->mChannelType = MatchChannelType(type);
- if(hData->mChannelType == CT_NONE)
- {
- TrErrorAt(tr, line, col, "Expected a channel type.\n");
- goto error;
- }
- hasType = 1;
- }
- else if(strcasecmp(ident, "points") == 0)
- {
- if(hasPoints)
- {
- TrErrorAt(tr, line, col, "Redefinition of 'points'.\n");
- goto error;
- }
- if(!TrReadOperator(tr, "="))
- goto error;
- TrIndication(tr, &line, &col);
- if(!TrReadInt(tr, MIN_POINTS, MAX_POINTS, &intVal))
- goto error;
- points = (uint)intVal;
- if(fftSize > 0 && points > fftSize)
- {
- TrErrorAt(tr, line, col, "Value exceeds the overridden FFT size.\n");
- goto error;
- }
- if(points < truncSize)
- {
- TrErrorAt(tr, line, col, "Value is below the truncation size.\n");
- goto error;
- }
- hData->mIrPoints = points;
- if(fftSize <= 0)
- {
- hData->mFftSize = DEFAULT_FFTSIZE;
- hData->mIrSize = 1 + (DEFAULT_FFTSIZE / 2);
- }
- else
- {
- hData->mFftSize = fftSize;
- hData->mIrSize = 1 + (fftSize / 2);
- if(points > hData->mIrSize)
- hData->mIrSize = points;
- }
- hasPoints = 1;
- }
- else if(strcasecmp(ident, "radius") == 0)
- {
- if(hasRadius)
- {
- TrErrorAt(tr, line, col, "Redefinition of 'radius'.\n");
- goto error;
- }
- if(!TrReadOperator(tr, "="))
- goto error;
- if(!TrReadFloat(tr, MIN_RADIUS, MAX_RADIUS, &fpVal))
- goto error;
- hData->mRadius = fpVal;
- hasRadius = 1;
- }
- else if(strcasecmp(ident, "distance") == 0)
- {
- uint count = 0;
-
- if(hasDistance)
- {
- TrErrorAt(tr, line, col, "Redefinition of 'distance'.\n");
- goto error;
- }
- if(!TrReadOperator(tr, "="))
- goto error;
-
- for(;;)
- {
- if(!TrReadFloat(tr, MIN_DISTANCE, MAX_DISTANCE, &fpVal))
- goto error;
- if(count > 0 && fpVal <= distances[count - 1])
- {
- TrError(tr, "Distances are not ascending.\n");
- goto error;
- }
- distances[count++] = fpVal;
- if(!TrIsOperator(tr, ","))
- break;
- if(count >= MAX_FD_COUNT)
- {
- TrError(tr, "Exceeded the maximum of %d fields.\n", MAX_FD_COUNT);
- goto error;
- }
- TrReadOperator(tr, ",");
- }
- if(fdCount != 0 && count != fdCount)
- {
- TrError(tr, "Did not match the specified number of %d fields.\n", fdCount);
- goto error;
- }
- fdCount = count;
- hasDistance = 1;
- }
- else if(strcasecmp(ident, "azimuths") == 0)
- {
- uint count = 0;
-
- if(hasAzimuths)
- {
- TrErrorAt(tr, line, col, "Redefinition of 'azimuths'.\n");
- goto error;
- }
- if(!TrReadOperator(tr, "="))
- goto error;
-
- evCounts[0] = 0;
- for(;;)
- {
- if(!TrReadInt(tr, MIN_AZ_COUNT, MAX_AZ_COUNT, &intVal))
- goto error;
- azCounts[(count * MAX_EV_COUNT) + evCounts[count]++] = (uint)intVal;
- if(TrIsOperator(tr, ","))
- {
- if(evCounts[count] >= MAX_EV_COUNT)
- {
- TrError(tr, "Exceeded the maximum of %d elevations.\n", MAX_EV_COUNT);
- goto error;
- }
- TrReadOperator(tr, ",");
- }
- else
- {
- if(evCounts[count] < MIN_EV_COUNT)
- {
- TrErrorAt(tr, line, col, "Did not reach the minimum of %d azimuth counts.\n", MIN_EV_COUNT);
- goto error;
- }
- if(azCounts[count * MAX_EV_COUNT] != 1 || azCounts[(count * MAX_EV_COUNT) + evCounts[count] - 1] != 1)
- {
- TrError(tr, "Poles are not singular for field %d.\n", count - 1);
- goto error;
- }
- count++;
- if(TrIsOperator(tr, ";"))
- {
- if(count >= MAX_FD_COUNT)
- {
- TrError(tr, "Exceeded the maximum number of %d fields.\n", MAX_FD_COUNT);
- goto error;
- }
- evCounts[count] = 0;
- TrReadOperator(tr, ";");
- }
- else
- {
- break;
- }
- }
- }
- if(fdCount != 0 && count != fdCount)
- {
- TrError(tr, "Did not match the specified number of %d fields.\n", fdCount);
- goto error;
- }
- fdCount = count;
- hasAzimuths = 1;
- }
- else
- {
- TrErrorAt(tr, line, col, "Expected a metric name.\n");
- goto error;
- }
- TrSkipWhitespace(tr);
- }
- if(!(hasRate && hasPoints && hasRadius && hasDistance && hasAzimuths))
- {
- TrErrorAt(tr, line, col, "Expected a metric name.\n");
- goto error;
- }
- if(distances[0] < hData->mRadius)
- {
- TrError(tr, "Distance cannot start below head radius.\n");
- goto error;
- }
- if(hData->mChannelType == CT_NONE)
- hData->mChannelType = CT_MONO;
- if(!PrepareHrirData(fdCount, distances, evCounts, azCounts, hData))
- {
- fprintf(stderr, "Error: Out of memory.\n");
- exit(-1);
- }
- free(azCounts);
- return 1;
-
-error:
- free(azCounts);
- return 0;
-}
-
-// Parse an index triplet from the data set definition.
-static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, uint *ei, uint *ai)
-{
- int intVal;
-
- if(hData->mFdCount > 1)
- {
- if(!TrReadInt(tr, 0, (int)hData->mFdCount - 1, &intVal))
- return 0;
- *fi = (uint)intVal;
- if(!TrReadOperator(tr, ","))
- return 0;
- }
- else
- {
- *fi = 0;
- }
- if(!TrReadInt(tr, 0, (int)hData->mFds[*fi].mEvCount - 1, &intVal))
- return 0;
- *ei = (uint)intVal;
- if(!TrReadOperator(tr, ","))
- return 0;
- if(!TrReadInt(tr, 0, (int)hData->mFds[*fi].mEvs[*ei].mAzCount - 1, &intVal))
- return 0;
- *ai = (uint)intVal;
- return 1;
-}
-
-// Match the source format from a given identifier.
-static SourceFormatT MatchSourceFormat(const char *ident)
-{
- if(strcasecmp(ident, "wave") == 0)
- return SF_WAVE;
- if(strcasecmp(ident, "bin_le") == 0)
- return SF_BIN_LE;
- if(strcasecmp(ident, "bin_be") == 0)
- return SF_BIN_BE;
- if(strcasecmp(ident, "ascii") == 0)
- return SF_ASCII;
- return SF_NONE;
-}
-
-// Match the source element type from a given identifier.
-static ElementTypeT MatchElementType(const char *ident)
-{
- if(strcasecmp(ident, "int") == 0)
- return ET_INT;
- if(strcasecmp(ident, "fp") == 0)
- return ET_FP;
- return ET_NONE;
-}
-
-// Parse and validate a source reference from the data set definition.
-static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src)
-{
- char ident[MAX_IDENT_LEN+1];
- uint line, col;
- int intVal;
-
- TrIndication(tr, &line, &col);
- if(!TrReadIdent(tr, MAX_IDENT_LEN, ident))
- return 0;
- src->mFormat = MatchSourceFormat(ident);
- if(src->mFormat == SF_NONE)
- {
- TrErrorAt(tr, line, col, "Expected a source format.\n");
- return 0;
- }
- if(!TrReadOperator(tr, "("))
- return 0;
- if(src->mFormat == SF_WAVE)
- {
- if(!TrReadInt(tr, 0, MAX_WAVE_CHANNELS, &intVal))
- return 0;
- src->mType = ET_NONE;
- src->mSize = 0;
- src->mBits = 0;
- src->mChannel = (uint)intVal;
- src->mSkip = 0;
- }
- else
- {
- TrIndication(tr, &line, &col);
- if(!TrReadIdent(tr, MAX_IDENT_LEN, ident))
- return 0;
- src->mType = MatchElementType(ident);
- if(src->mType == ET_NONE)
- {
- TrErrorAt(tr, line, col, "Expected a source element type.\n");
- return 0;
- }
- if(src->mFormat == SF_BIN_LE || src->mFormat == SF_BIN_BE)
- {
- if(!TrReadOperator(tr, ","))
- return 0;
- if(src->mType == ET_INT)
- {
- if(!TrReadInt(tr, MIN_BIN_SIZE, MAX_BIN_SIZE, &intVal))
- return 0;
- src->mSize = (uint)intVal;
- if(!TrIsOperator(tr, ","))
- src->mBits = (int)(8*src->mSize);
- else
- {
- TrReadOperator(tr, ",");
- TrIndication(tr, &line, &col);
- if(!TrReadInt(tr, -2147483647-1, 2147483647, &intVal))
- return 0;
- if(abs(intVal) < MIN_BIN_BITS || (uint)abs(intVal) > (8*src->mSize))
- {
- TrErrorAt(tr, line, col, "Expected a value of (+/-) %d to %d.\n", MIN_BIN_BITS, 8*src->mSize);
- return 0;
- }
- src->mBits = intVal;
- }
- }
- else
- {
- TrIndication(tr, &line, &col);
- if(!TrReadInt(tr, -2147483647-1, 2147483647, &intVal))
- return 0;
- if(intVal != 4 && intVal != 8)
- {
- TrErrorAt(tr, line, col, "Expected a value of 4 or 8.\n");
- return 0;
- }
- src->mSize = (uint)intVal;
- src->mBits = 0;
- }
- }
- else if(src->mFormat == SF_ASCII && src->mType == ET_INT)
- {
- if(!TrReadOperator(tr, ","))
- return 0;
- if(!TrReadInt(tr, MIN_ASCII_BITS, MAX_ASCII_BITS, &intVal))
- return 0;
- src->mSize = 0;
- src->mBits = intVal;
- }
- else
- {
- src->mSize = 0;
- src->mBits = 0;
- }
-
- if(!TrIsOperator(tr, ";"))
- src->mSkip = 0;
- else
- {
- TrReadOperator(tr, ";");
- if(!TrReadInt(tr, 0, 0x7FFFFFFF, &intVal))
- return 0;
- src->mSkip = (uint)intVal;
- }
- }
- if(!TrReadOperator(tr, ")"))
- return 0;
- if(TrIsOperator(tr, "@"))
- {
- TrReadOperator(tr, "@");
- if(!TrReadInt(tr, 0, 0x7FFFFFFF, &intVal))
- return 0;
- src->mOffset = (uint)intVal;
- }
- else
- src->mOffset = 0;
- if(!TrReadOperator(tr, ":"))
- return 0;
- if(!TrReadString(tr, MAX_PATH_LEN, src->mPath))
- return 0;
- return 1;
-}
-
-// Match the target ear (index) from a given identifier.
-static int MatchTargetEar(const char *ident)
-{
- if(strcasecmp(ident, "left") == 0)
- return 0;
- if(strcasecmp(ident, "right") == 0)
- return 1;
- return -1;
-}
-
-// Process the list of sources in the data set definition.
-static int ProcessSources(const HeadModelT model, TokenReaderT *tr, HrirDataT *hData)
-{
- uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
- double *hrirs = CreateDoubles(channels * hData->mIrCount * hData->mIrSize);
- double *hrir = CreateDoubles(hData->mIrPoints);
- uint line, col, fi, ei, ai, ti;
- int count;
-
- printf("Loading sources...");
- fflush(stdout);
- count = 0;
- while(TrIsOperator(tr, "["))
- {
- double factor[2] = { 1.0, 1.0 };
-
- TrIndication(tr, &line, &col);
- TrReadOperator(tr, "[");
- if(!ReadIndexTriplet(tr, hData, &fi, &ei, &ai))
- goto error;
- if(!TrReadOperator(tr, "]"))
- goto error;
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
-
- if(azd->mIrs[0] != NULL)
- {
- TrErrorAt(tr, line, col, "Redefinition of source.\n");
- goto error;
- }
- if(!TrReadOperator(tr, "="))
- goto error;
-
- for(;;)
- {
- SourceRefT src;
- uint ti = 0;
-
- if(!ReadSourceRef(tr, &src))
- goto error;
-
- // TODO: Would be nice to display 'x of y files', but that would
- // require preparing the source refs first to get a total count
- // before loading them.
- ++count;
- printf("\rLoading sources... %d file%s", count, (count==1)?"":"s");
- fflush(stdout);
-
- if(!LoadSource(&src, hData->mIrRate, hData->mIrPoints, hrir))
- goto error;
-
- if(hData->mChannelType == CT_STEREO)
- {
- char ident[MAX_IDENT_LEN+1];
-
- if(!TrReadIdent(tr, MAX_IDENT_LEN, ident))
- goto error;
- ti = MatchTargetEar(ident);
- if((int)ti < 0)
- {
- TrErrorAt(tr, line, col, "Expected a target ear.\n");
- goto error;
- }
- }
- azd->mIrs[ti] = &hrirs[hData->mIrSize * (ti * hData->mIrCount + azd->mIndex)];
- if(model == HM_DATASET)
- azd->mDelays[ti] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir, 1.0 / factor[ti], azd->mDelays[ti]);
- AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir, 1.0 / factor[ti], azd->mIrs[ti]);
- factor[ti] += 1.0;
- if(!TrIsOperator(tr, "+"))
- break;
- TrReadOperator(tr, "+");
- }
- if(hData->mChannelType == CT_STEREO)
- {
- if(azd->mIrs[0] == NULL)
- {
- TrErrorAt(tr, line, col, "Missing left ear source reference(s).\n");
- goto error;
- }
- else if(azd->mIrs[1] == NULL)
- {
- TrErrorAt(tr, line, col, "Missing right ear source reference(s).\n");
- goto error;
- }
- }
- }
- printf("\n");
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
-
- if(azd->mIrs[0] != NULL)
- break;
- }
- if(ai < hData->mFds[fi].mEvs[ei].mAzCount)
- break;
- }
- if(ei >= hData->mFds[fi].mEvCount)
- {
- TrError(tr, "Missing source references [ %d, *, * ].\n", fi);
- goto error;
- }
- hData->mFds[fi].mEvStart = ei;
- for(;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
-
- if(azd->mIrs[0] == NULL)
- {
- TrError(tr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai);
- goto error;
- }
- }
- }
- }
- for(ti = 0;ti < channels;ti++)
- {
- for(fi = 0;fi < hData->mFdCount;fi++)
- {
- for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
- {
- for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
- {
- HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
-
- azd->mIrs[ti] = &hrirs[hData->mIrSize * (ti * hData->mIrCount + azd->mIndex)];
- }
- }
- }
- }
- if(!TrLoad(tr))
- {
- free(hrir);
- return 1;
- }
- TrError(tr, "Errant data at end of source list.\n");
-
-error:
- free(hrir);
- return 0;
-}
-
-/* Parse the data set definition and process the source data, storing the
- * resulting data set as desired. If the input name is NULL it will read
- * from standard input.
- */
-static int ProcessDefinition(const char *inName, const uint outRate, const uint fftSize, const int equalize, const int surface, const double limit, const uint truncSize, const HeadModelT model, const double radius, const char *outName)
-{
- char rateStr[8+1], expName[MAX_PATH_LEN];
- TokenReaderT tr;
- HrirDataT hData;
- FILE *fp;
- int ret;
-
- ResetHrirData(&hData);
- fprintf(stdout, "Reading HRIR definition from %s...\n", inName?inName:"stdin");
- if(inName != NULL)
- {
- fp = fopen(inName, "r");
- if(fp == NULL)
- {
- fprintf(stderr, "Error: Could not open definition file '%s'\n", inName);
- return 0;
- }
- TrSetup(fp, inName, &tr);
- }
- else
- {
- fp = stdin;
- TrSetup(fp, "<stdin>", &tr);
- }
- if(!ProcessMetrics(&tr, fftSize, truncSize, &hData))
- {
- if(inName != NULL)
- fclose(fp);
- return 0;
- }
- if(!ProcessSources(model, &tr, &hData))
- {
- FreeHrirData(&hData);
- if(inName != NULL)
- fclose(fp);
- return 0;
- }
- if(fp != stdin)
- fclose(fp);
- if(equalize)
- {
- uint c = (hData.mChannelType == CT_STEREO) ? 2 : 1;
- uint m = 1 + hData.mFftSize / 2;
- double *dfa = CreateDoubles(c * m);
-
- fprintf(stdout, "Calculating diffuse-field average...\n");
- CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa);
- fprintf(stdout, "Performing diffuse-field equalization...\n");
- DiffuseFieldEqualize(c, m, dfa, &hData);
- free(dfa);
- }
- fprintf(stdout, "Performing minimum phase reconstruction...\n");
- ReconstructHrirs(&hData);
- if(outRate != 0 && outRate != hData.mIrRate)
- {
- fprintf(stdout, "Resampling HRIRs...\n");
- ResampleHrirs(outRate, &hData);
- }
- fprintf(stdout, "Truncating minimum-phase HRIRs...\n");
- hData.mIrPoints = truncSize;
- fprintf(stdout, "Synthesizing missing elevations...\n");
- if(model == HM_DATASET)
- SynthesizeOnsets(&hData);
- SynthesizeHrirs(&hData);
- fprintf(stdout, "Normalizing final HRIRs...\n");
- NormalizeHrirs(&hData);
- fprintf(stdout, "Calculating impulse delays...\n");
- CalculateHrtds(model, (radius > DEFAULT_CUSTOM_RADIUS) ? radius : hData.mRadius, &hData);
- snprintf(rateStr, 8, "%u", hData.mIrRate);
- StrSubst(outName, "%r", rateStr, MAX_PATH_LEN, expName);
- fprintf(stdout, "Creating MHR data set %s...\n", expName);
- ret = StoreMhr(&hData, expName);
-
- FreeHrirData(&hData);
- return ret;
-}
-
-static void PrintHelp(const char *argv0, FILE *ofile)
-{
- fprintf(ofile, "Usage: %s [<option>...]\n\n", argv0);
- fprintf(ofile, "Options:\n");
- fprintf(ofile, " -m Ignored for compatibility.\n");
- fprintf(ofile, " -r <rate> Change the data set sample rate to the specified value and\n");
- fprintf(ofile, " resample the HRIRs accordingly.\n");
- fprintf(ofile, " -f <points> Override the FFT window size (default: %u).\n", DEFAULT_FFTSIZE);
- fprintf(ofile, " -e {on|off} Toggle diffuse-field equalization (default: %s).\n", (DEFAULT_EQUALIZE ? "on" : "off"));
- fprintf(ofile, " -s {on|off} Toggle surface-weighted diffuse-field average (default: %s).\n", (DEFAULT_SURFACE ? "on" : "off"));
- fprintf(ofile, " -l {<dB>|none} Specify a limit to the magnitude range of the diffuse-field\n");
- fprintf(ofile, " average (default: %.2f).\n", DEFAULT_LIMIT);
- fprintf(ofile, " -w <points> Specify the size of the truncation window that's applied\n");
- fprintf(ofile, " after minimum-phase reconstruction (default: %u).\n", DEFAULT_TRUNCSIZE);
- fprintf(ofile, " -d {dataset| Specify the model used for calculating the head-delay timing\n");
- fprintf(ofile, " sphere} values (default: %s).\n", ((DEFAULT_HEAD_MODEL == HM_DATASET) ? "dataset" : "sphere"));
- fprintf(ofile, " -c <size> Use a customized head radius measured ear-to-ear in meters.\n");
- fprintf(ofile, " -i <filename> Specify an HRIR definition file to use (defaults to stdin).\n");
- fprintf(ofile, " -o <filename> Specify an output file. Use of '%%r' will be substituted with\n");
- fprintf(ofile, " the data set sample rate.\n");
-}
-
-// Standard command line dispatch.
-int main(int argc, char *argv[])
-{
- const char *inName = NULL, *outName = NULL;
- uint outRate, fftSize;
- int equalize, surface;
- char *end = NULL;
- HeadModelT model;
- uint truncSize;
- double radius;
- double limit;
- int opt;
-
- GET_UNICODE_ARGS(&argc, &argv);
-
- if(argc < 2)
- {
- fprintf(stdout, "HRTF Processing and Composition Utility\n\n");
- PrintHelp(argv[0], stdout);
- exit(EXIT_SUCCESS);
- }
-
- outName = "./oalsoft_hrtf_%r.mhr";
- outRate = 0;
- fftSize = 0;
- equalize = DEFAULT_EQUALIZE;
- surface = DEFAULT_SURFACE;
- limit = DEFAULT_LIMIT;
- truncSize = DEFAULT_TRUNCSIZE;
- model = DEFAULT_HEAD_MODEL;
- radius = DEFAULT_CUSTOM_RADIUS;
-
- while((opt=getopt(argc, argv, "mr:f:e:s:l:w:d:c:e:i:o:h")) != -1)
- {
- switch(opt)
- {
- case 'm':
- fprintf(stderr, "Ignoring unused command '-m'.\n");
- break;
-
- case 'r':
- outRate = strtoul(optarg, &end, 10);
- if(end[0] != '\0' || outRate < MIN_RATE || outRate > MAX_RATE)
- {
- fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, MIN_RATE, MAX_RATE);
- exit(EXIT_FAILURE);
- }
- break;
-
- case 'f':
- fftSize = strtoul(optarg, &end, 10);
- if(end[0] != '\0' || (fftSize&(fftSize-1)) || fftSize < MIN_FFTSIZE || fftSize > MAX_FFTSIZE)
- {
- fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected a power-of-two between %u to %u.\n", optarg, opt, MIN_FFTSIZE, MAX_FFTSIZE);
- exit(EXIT_FAILURE);
- }
- break;
-
- case 'e':
- if(strcmp(optarg, "on") == 0)
- equalize = 1;
- else if(strcmp(optarg, "off") == 0)
- equalize = 0;
- else
- {
- fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt);
- exit(EXIT_FAILURE);
- }
- break;
-
- case 's':
- if(strcmp(optarg, "on") == 0)
- surface = 1;
- else if(strcmp(optarg, "off") == 0)
- surface = 0;
- else
- {
- fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt);
- exit(EXIT_FAILURE);
- }
- break;
-
- case 'l':
- if(strcmp(optarg, "none") == 0)
- limit = 0.0;
- else
- {
- limit = strtod(optarg, &end);
- if(end[0] != '\0' || limit < MIN_LIMIT || limit > MAX_LIMIT)
- {
- fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected between %.0f to %.0f.\n", optarg, opt, MIN_LIMIT, MAX_LIMIT);
- exit(EXIT_FAILURE);
- }
- }
- break;
-
- case 'w':
- truncSize = strtoul(optarg, &end, 10);
- if(end[0] != '\0' || truncSize < MIN_TRUNCSIZE || truncSize > MAX_TRUNCSIZE || (truncSize%MOD_TRUNCSIZE))
- {
- fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected multiple of %u between %u to %u.\n", optarg, opt, MOD_TRUNCSIZE, MIN_TRUNCSIZE, MAX_TRUNCSIZE);
- exit(EXIT_FAILURE);
- }
- break;
-
- case 'd':
- if(strcmp(optarg, "dataset") == 0)
- model = HM_DATASET;
- else if(strcmp(optarg, "sphere") == 0)
- model = HM_SPHERE;
- else
- {
- fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected dataset or sphere.\n", optarg, opt);
- exit(EXIT_FAILURE);
- }
- break;
-
- case 'c':
- radius = strtod(optarg, &end);
- if(end[0] != '\0' || radius < MIN_CUSTOM_RADIUS || radius > MAX_CUSTOM_RADIUS)
- {
- fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected between %.2f to %.2f.\n", optarg, opt, MIN_CUSTOM_RADIUS, MAX_CUSTOM_RADIUS);
- exit(EXIT_FAILURE);
- }
- break;
-
- case 'i':
- inName = optarg;
- break;
-
- case 'o':
- outName = optarg;
- break;
-
- case 'h':
- PrintHelp(argv[0], stdout);
- exit(EXIT_SUCCESS);
-
- default: /* '?' */
- PrintHelp(argv[0], stderr);
- exit(EXIT_FAILURE);
- }
- }
-
- if(!ProcessDefinition(inName, outRate, fftSize, equalize, surface, limit,
- truncSize, model, radius, outName))
- return -1;
- fprintf(stdout, "Operation completed.\n");
-
- return EXIT_SUCCESS;
-}
diff --git a/utils/makemhr/loaddef.cpp b/utils/makemhr/loaddef.cpp
new file mode 100644
index 00000000..619f5104
--- /dev/null
+++ b/utils/makemhr/loaddef.cpp
@@ -0,0 +1,2033 @@
+/*
+ * HRTF utility for producing and demonstrating the process of creating an
+ * OpenAL Soft compatible HRIR data set.
+ *
+ * Copyright (C) 2011-2019 Christopher Fitzgerald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ */
+
+#include "loaddef.h"
+
+#include <algorithm>
+#include <cctype>
+#include <cmath>
+#include <cstring>
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <vector>
+
+#include "alfstream.h"
+#include "alstring.h"
+#include "makemhr.h"
+
+#include "mysofa.h"
+
+// Constants for accessing the token reader's ring buffer.
+#define TR_RING_BITS (16)
+#define TR_RING_SIZE (1 << TR_RING_BITS)
+#define TR_RING_MASK (TR_RING_SIZE - 1)
+
+// The token reader's load interval in bytes.
+#define TR_LOAD_SIZE (TR_RING_SIZE >> 2)
+
+// Token reader state for parsing the data set definition.
+struct TokenReaderT {
+ std::istream &mIStream;
+ const char *mName{};
+ uint mLine{};
+ uint mColumn{};
+ char mRing[TR_RING_SIZE]{};
+ std::streamsize mIn{};
+ std::streamsize mOut{};
+
+ TokenReaderT(std::istream &istream) noexcept : mIStream{istream} { }
+ TokenReaderT(const TokenReaderT&) = default;
+};
+
+
+// The maximum identifier length used when processing the data set
+// definition.
+#define MAX_IDENT_LEN (16)
+
+// The limits for the listener's head 'radius' in the data set definition.
+#define MIN_RADIUS (0.05)
+#define MAX_RADIUS (0.15)
+
+// The maximum number of channels that can be addressed for a WAVE file
+// source listed in the data set definition.
+#define MAX_WAVE_CHANNELS (65535)
+
+// The limits to the byte size for a binary source listed in the definition
+// file.
+#define MIN_BIN_SIZE (2)
+#define MAX_BIN_SIZE (4)
+
+// The minimum number of significant bits for binary sources listed in the
+// data set definition. The maximum is calculated from the byte size.
+#define MIN_BIN_BITS (16)
+
+// The limits to the number of significant bits for an ASCII source listed in
+// the data set definition.
+#define MIN_ASCII_BITS (16)
+#define MAX_ASCII_BITS (32)
+
+// The four-character-codes for RIFF/RIFX WAVE file chunks.
+#define FOURCC_RIFF (0x46464952) // 'RIFF'
+#define FOURCC_RIFX (0x58464952) // 'RIFX'
+#define FOURCC_WAVE (0x45564157) // 'WAVE'
+#define FOURCC_FMT (0x20746D66) // 'fmt '
+#define FOURCC_DATA (0x61746164) // 'data'
+#define FOURCC_LIST (0x5453494C) // 'LIST'
+#define FOURCC_WAVL (0x6C766177) // 'wavl'
+#define FOURCC_SLNT (0x746E6C73) // 'slnt'
+
+// The supported wave formats.
+#define WAVE_FORMAT_PCM (0x0001)
+#define WAVE_FORMAT_IEEE_FLOAT (0x0003)
+#define WAVE_FORMAT_EXTENSIBLE (0xFFFE)
+
+
+enum ByteOrderT {
+ BO_NONE,
+ BO_LITTLE,
+ BO_BIG
+};
+
+// Source format for the references listed in the data set definition.
+enum SourceFormatT {
+ SF_NONE,
+ SF_ASCII, // ASCII text file.
+ SF_BIN_LE, // Little-endian binary file.
+ SF_BIN_BE, // Big-endian binary file.
+ SF_WAVE, // RIFF/RIFX WAVE file.
+ SF_SOFA // Spatially Oriented Format for Accoustics (SOFA) file.
+};
+
+// Element types for the references listed in the data set definition.
+enum ElementTypeT {
+ ET_NONE,
+ ET_INT, // Integer elements.
+ ET_FP // Floating-point elements.
+};
+
+// Source reference state used when loading sources.
+struct SourceRefT {
+ SourceFormatT mFormat;
+ ElementTypeT mType;
+ uint mSize;
+ int mBits;
+ uint mChannel;
+ double mAzimuth;
+ double mElevation;
+ double mRadius;
+ uint mSkip;
+ uint mOffset;
+ char mPath[MAX_PATH_LEN+1];
+};
+
+
+/* Whitespace is not significant. It can process tokens as identifiers, numbers
+ * (integer and floating-point), strings, and operators. Strings must be
+ * encapsulated by double-quotes and cannot span multiple lines.
+ */
+
+// Setup the reader on the given file. The filename can be NULL if no error
+// output is desired.
+static void TrSetup(const char *startbytes, std::streamsize startbytecount, const char *filename,
+ TokenReaderT *tr)
+{
+ const char *name = nullptr;
+
+ if(filename)
+ {
+ const char *slash = strrchr(filename, '/');
+ if(slash)
+ {
+ const char *bslash = strrchr(slash+1, '\\');
+ if(bslash) name = bslash+1;
+ else name = slash+1;
+ }
+ else
+ {
+ const char *bslash = strrchr(filename, '\\');
+ if(bslash) name = bslash+1;
+ else name = filename;
+ }
+ }
+
+ tr->mName = name;
+ tr->mLine = 1;
+ tr->mColumn = 1;
+ tr->mIn = 0;
+ tr->mOut = 0;
+
+ if(startbytecount > 0)
+ {
+ std::copy_n(startbytes, startbytecount, std::begin(tr->mRing));
+ tr->mIn += startbytecount;
+ }
+}
+
+// Prime the reader's ring buffer, and return a result indicating that there
+// is text to process.
+static int TrLoad(TokenReaderT *tr)
+{
+ std::istream &istream = tr->mIStream;
+
+ std::streamsize toLoad{TR_RING_SIZE - static_cast<std::streamsize>(tr->mIn - tr->mOut)};
+ if(toLoad >= TR_LOAD_SIZE && istream.good())
+ {
+ // Load TR_LOAD_SIZE (or less if at the end of the file) per read.
+ toLoad = TR_LOAD_SIZE;
+ std::streamsize in{tr->mIn&TR_RING_MASK};
+ std::streamsize count{TR_RING_SIZE - in};
+ if(count < toLoad)
+ {
+ istream.read(&tr->mRing[in], count);
+ tr->mIn += istream.gcount();
+ istream.read(&tr->mRing[0], toLoad-count);
+ tr->mIn += istream.gcount();
+ }
+ else
+ {
+ istream.read(&tr->mRing[in], toLoad);
+ tr->mIn += istream.gcount();
+ }
+
+ if(tr->mOut >= TR_RING_SIZE)
+ {
+ tr->mOut -= TR_RING_SIZE;
+ tr->mIn -= TR_RING_SIZE;
+ }
+ }
+ if(tr->mIn > tr->mOut)
+ return 1;
+ return 0;
+}
+
+// Error display routine. Only displays when the base name is not NULL.
+static void TrErrorVA(const TokenReaderT *tr, uint line, uint column, const char *format, va_list argPtr)
+{
+ if(!tr->mName)
+ return;
+ fprintf(stderr, "\nError (%s:%u:%u): ", tr->mName, line, column);
+ vfprintf(stderr, format, argPtr);
+}
+
+// Used to display an error at a saved line/column.
+static void TrErrorAt(const TokenReaderT *tr, uint line, uint column, const char *format, ...)
+{
+ va_list argPtr;
+
+ va_start(argPtr, format);
+ TrErrorVA(tr, line, column, format, argPtr);
+ va_end(argPtr);
+}
+
+// Used to display an error at the current line/column.
+static void TrError(const TokenReaderT *tr, const char *format, ...)
+{
+ va_list argPtr;
+
+ va_start(argPtr, format);
+ TrErrorVA(tr, tr->mLine, tr->mColumn, format, argPtr);
+ va_end(argPtr);
+}
+
+// Skips to the next line.
+static void TrSkipLine(TokenReaderT *tr)
+{
+ char ch;
+
+ while(TrLoad(tr))
+ {
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ tr->mOut++;
+ if(ch == '\n')
+ {
+ tr->mLine++;
+ tr->mColumn = 1;
+ break;
+ }
+ tr->mColumn ++;
+ }
+}
+
+// Skips to the next token.
+static int TrSkipWhitespace(TokenReaderT *tr)
+{
+ while(TrLoad(tr))
+ {
+ char ch{tr->mRing[tr->mOut&TR_RING_MASK]};
+ if(isspace(ch))
+ {
+ tr->mOut++;
+ if(ch == '\n')
+ {
+ tr->mLine++;
+ tr->mColumn = 1;
+ }
+ else
+ tr->mColumn++;
+ }
+ else if(ch == '#')
+ TrSkipLine(tr);
+ else
+ return 1;
+ }
+ return 0;
+}
+
+// Get the line and/or column of the next token (or the end of input).
+static void TrIndication(TokenReaderT *tr, uint *line, uint *column)
+{
+ TrSkipWhitespace(tr);
+ if(line) *line = tr->mLine;
+ if(column) *column = tr->mColumn;
+}
+
+// Checks to see if a token is (likely to be) an identifier. It does not
+// display any errors and will not proceed to the next token.
+static int TrIsIdent(TokenReaderT *tr)
+{
+ if(!TrSkipWhitespace(tr))
+ return 0;
+ char ch{tr->mRing[tr->mOut&TR_RING_MASK]};
+ return ch == '_' || isalpha(ch);
+}
+
+
+// Checks to see if a token is the given operator. It does not display any
+// errors and will not proceed to the next token.
+static int TrIsOperator(TokenReaderT *tr, const char *op)
+{
+ std::streamsize out;
+ size_t len;
+ char ch;
+
+ if(!TrSkipWhitespace(tr))
+ return 0;
+ out = tr->mOut;
+ len = 0;
+ while(op[len] != '\0' && out < tr->mIn)
+ {
+ ch = tr->mRing[out&TR_RING_MASK];
+ if(ch != op[len]) break;
+ len++;
+ out++;
+ }
+ if(op[len] == '\0')
+ return 1;
+ return 0;
+}
+
+/* The TrRead*() routines obtain the value of a matching token type. They
+ * display type, form, and boundary errors and will proceed to the next
+ * token.
+ */
+
+// Reads and validates an identifier token.
+static int TrReadIdent(TokenReaderT *tr, const uint maxLen, char *ident)
+{
+ uint col, len;
+ char ch;
+
+ col = tr->mColumn;
+ if(TrSkipWhitespace(tr))
+ {
+ col = tr->mColumn;
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ if(ch == '_' || isalpha(ch))
+ {
+ len = 0;
+ do {
+ if(len < maxLen)
+ ident[len] = ch;
+ len++;
+ tr->mOut++;
+ if(!TrLoad(tr))
+ break;
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ } while(ch == '_' || isdigit(ch) || isalpha(ch));
+
+ tr->mColumn += len;
+ if(len < maxLen)
+ {
+ ident[len] = '\0';
+ return 1;
+ }
+ TrErrorAt(tr, tr->mLine, col, "Identifier is too long.\n");
+ return 0;
+ }
+ }
+ TrErrorAt(tr, tr->mLine, col, "Expected an identifier.\n");
+ return 0;
+}
+
+// Reads and validates (including bounds) an integer token.
+static int TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int *value)
+{
+ uint col, digis, len;
+ char ch, temp[64+1];
+
+ col = tr->mColumn;
+ if(TrSkipWhitespace(tr))
+ {
+ col = tr->mColumn;
+ len = 0;
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ if(ch == '+' || ch == '-')
+ {
+ temp[len] = ch;
+ len++;
+ tr->mOut++;
+ }
+ digis = 0;
+ while(TrLoad(tr))
+ {
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ if(!isdigit(ch)) break;
+ if(len < 64)
+ temp[len] = ch;
+ len++;
+ digis++;
+ tr->mOut++;
+ }
+ tr->mColumn += len;
+ if(digis > 0 && ch != '.' && !isalpha(ch))
+ {
+ if(len > 64)
+ {
+ TrErrorAt(tr, tr->mLine, col, "Integer is too long.");
+ return 0;
+ }
+ temp[len] = '\0';
+ *value = static_cast<int>(strtol(temp, nullptr, 10));
+ if(*value < loBound || *value > hiBound)
+ {
+ TrErrorAt(tr, tr->mLine, col, "Expected a value from %d to %d.\n", loBound, hiBound);
+ return 0;
+ }
+ return 1;
+ }
+ }
+ TrErrorAt(tr, tr->mLine, col, "Expected an integer.\n");
+ return 0;
+}
+
+// Reads and validates (including bounds) a float token.
+static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, double *value)
+{
+ uint col, digis, len;
+ char ch, temp[64+1];
+
+ col = tr->mColumn;
+ if(TrSkipWhitespace(tr))
+ {
+ col = tr->mColumn;
+ len = 0;
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ if(ch == '+' || ch == '-')
+ {
+ temp[len] = ch;
+ len++;
+ tr->mOut++;
+ }
+
+ digis = 0;
+ while(TrLoad(tr))
+ {
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ if(!isdigit(ch)) break;
+ if(len < 64)
+ temp[len] = ch;
+ len++;
+ digis++;
+ tr->mOut++;
+ }
+ if(ch == '.')
+ {
+ if(len < 64)
+ temp[len] = ch;
+ len++;
+ tr->mOut++;
+ }
+ while(TrLoad(tr))
+ {
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ if(!isdigit(ch)) break;
+ if(len < 64)
+ temp[len] = ch;
+ len++;
+ digis++;
+ tr->mOut++;
+ }
+ if(digis > 0)
+ {
+ if(ch == 'E' || ch == 'e')
+ {
+ if(len < 64)
+ temp[len] = ch;
+ len++;
+ digis = 0;
+ tr->mOut++;
+ if(ch == '+' || ch == '-')
+ {
+ if(len < 64)
+ temp[len] = ch;
+ len++;
+ tr->mOut++;
+ }
+ while(TrLoad(tr))
+ {
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ if(!isdigit(ch)) break;
+ if(len < 64)
+ temp[len] = ch;
+ len++;
+ digis++;
+ tr->mOut++;
+ }
+ }
+ tr->mColumn += len;
+ if(digis > 0 && ch != '.' && !isalpha(ch))
+ {
+ if(len > 64)
+ {
+ TrErrorAt(tr, tr->mLine, col, "Float is too long.");
+ return 0;
+ }
+ temp[len] = '\0';
+ *value = strtod(temp, nullptr);
+ if(*value < loBound || *value > hiBound)
+ {
+ TrErrorAt(tr, tr->mLine, col, "Expected a value from %f to %f.\n", loBound, hiBound);
+ return 0;
+ }
+ return 1;
+ }
+ }
+ else
+ tr->mColumn += len;
+ }
+ TrErrorAt(tr, tr->mLine, col, "Expected a float.\n");
+ return 0;
+}
+
+// Reads and validates a string token.
+static int TrReadString(TokenReaderT *tr, const uint maxLen, char *text)
+{
+ uint col, len;
+ char ch;
+
+ col = tr->mColumn;
+ if(TrSkipWhitespace(tr))
+ {
+ col = tr->mColumn;
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ if(ch == '\"')
+ {
+ tr->mOut++;
+ len = 0;
+ while(TrLoad(tr))
+ {
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ tr->mOut++;
+ if(ch == '\"')
+ break;
+ if(ch == '\n')
+ {
+ TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of line.\n");
+ return 0;
+ }
+ if(len < maxLen)
+ text[len] = ch;
+ len++;
+ }
+ if(ch != '\"')
+ {
+ tr->mColumn += 1 + len;
+ TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of input.\n");
+ return 0;
+ }
+ tr->mColumn += 2 + len;
+ if(len > maxLen)
+ {
+ TrErrorAt(tr, tr->mLine, col, "String is too long.\n");
+ return 0;
+ }
+ text[len] = '\0';
+ return 1;
+ }
+ }
+ TrErrorAt(tr, tr->mLine, col, "Expected a string.\n");
+ return 0;
+}
+
+// Reads and validates the given operator.
+static int TrReadOperator(TokenReaderT *tr, const char *op)
+{
+ uint col, len;
+ char ch;
+
+ col = tr->mColumn;
+ if(TrSkipWhitespace(tr))
+ {
+ col = tr->mColumn;
+ len = 0;
+ while(op[len] != '\0' && TrLoad(tr))
+ {
+ ch = tr->mRing[tr->mOut&TR_RING_MASK];
+ if(ch != op[len]) break;
+ len++;
+ tr->mOut++;
+ }
+ tr->mColumn += len;
+ if(op[len] == '\0')
+ return 1;
+ }
+ TrErrorAt(tr, tr->mLine, col, "Expected '%s' operator.\n", op);
+ return 0;
+}
+
+
+/*************************
+ *** File source input ***
+ *************************/
+
+// Read a binary value of the specified byte order and byte size from a file,
+// storing it as a 32-bit unsigned integer.
+static int ReadBin4(std::istream &istream, const char *filename, const ByteOrderT order, const uint bytes, uint32_t *out)
+{
+ uint8_t in[4];
+ istream.read(reinterpret_cast<char*>(in), static_cast<int>(bytes));
+ if(istream.gcount() != bytes)
+ {
+ fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename);
+ return 0;
+ }
+ uint32_t accum{0};
+ switch(order)
+ {
+ case BO_LITTLE:
+ for(uint i = 0;i < bytes;i++)
+ accum = (accum<<8) | in[bytes - i - 1];
+ break;
+ case BO_BIG:
+ for(uint i = 0;i < bytes;i++)
+ accum = (accum<<8) | in[i];
+ break;
+ default:
+ break;
+ }
+ *out = accum;
+ return 1;
+}
+
+// Read a binary value of the specified byte order from a file, storing it as
+// a 64-bit unsigned integer.
+static int ReadBin8(std::istream &istream, const char *filename, const ByteOrderT order, uint64_t *out)
+{
+ uint8_t in[8];
+ uint64_t accum;
+ uint i;
+
+ istream.read(reinterpret_cast<char*>(in), 8);
+ if(istream.gcount() != 8)
+ {
+ fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename);
+ return 0;
+ }
+ accum = 0;
+ switch(order)
+ {
+ case BO_LITTLE:
+ for(i = 0;i < 8;i++)
+ accum = (accum<<8) | in[8 - i - 1];
+ break;
+ case BO_BIG:
+ for(i = 0;i < 8;i++)
+ accum = (accum<<8) | in[i];
+ break;
+ default:
+ break;
+ }
+ *out = accum;
+ return 1;
+}
+
+/* Read a binary value of the specified type, byte order, and byte size from
+ * a file, converting it to a double. For integer types, the significant
+ * bits are used to normalize the result. The sign of bits determines
+ * whether they are padded toward the MSB (negative) or LSB (positive).
+ * Floating-point types are not normalized.
+ */
+static int ReadBinAsDouble(std::istream &istream, const char *filename, const ByteOrderT order,
+ const ElementTypeT type, const uint bytes, const int bits, double *out)
+{
+ union {
+ uint32_t ui;
+ int32_t i;
+ float f;
+ } v4;
+ union {
+ uint64_t ui;
+ double f;
+ } v8;
+
+ *out = 0.0;
+ if(bytes > 4)
+ {
+ if(!ReadBin8(istream, filename, order, &v8.ui))
+ return 0;
+ if(type == ET_FP)
+ *out = v8.f;
+ }
+ else
+ {
+ if(!ReadBin4(istream, filename, order, bytes, &v4.ui))
+ return 0;
+ if(type == ET_FP)
+ *out = v4.f;
+ else
+ {
+ if(bits > 0)
+ v4.ui >>= (8*bytes) - (static_cast<uint>(bits));
+ else
+ v4.ui &= (0xFFFFFFFF >> (32+bits));
+
+ if(v4.ui&static_cast<uint>(1<<(std::abs(bits)-1)))
+ v4.ui |= (0xFFFFFFFF << std::abs(bits));
+ *out = v4.i / static_cast<double>(1<<(std::abs(bits)-1));
+ }
+ }
+ return 1;
+}
+
+/* Read an ascii value of the specified type from a file, converting it to a
+ * double. For integer types, the significant bits are used to normalize the
+ * result. The sign of the bits should always be positive. This also skips
+ * up to one separator character before the element itself.
+ */
+static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const ElementTypeT type, const uint bits, double *out)
+{
+ if(TrIsOperator(tr, ","))
+ TrReadOperator(tr, ",");
+ else if(TrIsOperator(tr, ":"))
+ TrReadOperator(tr, ":");
+ else if(TrIsOperator(tr, ";"))
+ TrReadOperator(tr, ";");
+ else if(TrIsOperator(tr, "|"))
+ TrReadOperator(tr, "|");
+
+ if(type == ET_FP)
+ {
+ if(!TrReadFloat(tr, -std::numeric_limits<double>::infinity(),
+ std::numeric_limits<double>::infinity(), out))
+ {
+ fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename);
+ return 0;
+ }
+ }
+ else
+ {
+ int v;
+ if(!TrReadInt(tr, -(1<<(bits-1)), (1<<(bits-1))-1, &v))
+ {
+ fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename);
+ return 0;
+ }
+ *out = v / static_cast<double>((1<<(bits-1))-1);
+ }
+ return 1;
+}
+
+// Read the RIFF/RIFX WAVE format chunk from a file, validating it against
+// the source parameters and data set metrics.
+static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const uint hrirRate,
+ SourceRefT *src)
+{
+ uint32_t fourCC, chunkSize;
+ uint32_t format, channels, rate, dummy, block, size, bits;
+
+ chunkSize = 0;
+ do {
+ if(chunkSize > 0)
+ istream.seekg(static_cast<int>(chunkSize), std::ios::cur);
+ if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)
+ || !ReadBin4(istream, src->mPath, order, 4, &chunkSize))
+ return 0;
+ } while(fourCC != FOURCC_FMT);
+ if(!ReadBin4(istream, src->mPath, order, 2, &format)
+ || !ReadBin4(istream, src->mPath, order, 2, &channels)
+ || !ReadBin4(istream, src->mPath, order, 4, &rate)
+ || !ReadBin4(istream, src->mPath, order, 4, &dummy)
+ || !ReadBin4(istream, src->mPath, order, 2, &block))
+ return 0;
+ block /= channels;
+ if(chunkSize > 14)
+ {
+ if(!ReadBin4(istream, src->mPath, order, 2, &size))
+ return 0;
+ size /= 8;
+ if(block > size)
+ size = block;
+ }
+ else
+ size = block;
+ if(format == WAVE_FORMAT_EXTENSIBLE)
+ {
+ istream.seekg(2, std::ios::cur);
+ if(!ReadBin4(istream, src->mPath, order, 2, &bits))
+ return 0;
+ if(bits == 0)
+ bits = 8 * size;
+ istream.seekg(4, std::ios::cur);
+ if(!ReadBin4(istream, src->mPath, order, 2, &format))
+ return 0;
+ istream.seekg(static_cast<int>(chunkSize - 26), std::ios::cur);
+ }
+ else
+ {
+ bits = 8 * size;
+ if(chunkSize > 14)
+ istream.seekg(static_cast<int>(chunkSize - 16), std::ios::cur);
+ else
+ istream.seekg(static_cast<int>(chunkSize - 14), std::ios::cur);
+ }
+ if(format != WAVE_FORMAT_PCM && format != WAVE_FORMAT_IEEE_FLOAT)
+ {
+ fprintf(stderr, "\nError: Unsupported WAVE format in file '%s'.\n", src->mPath);
+ return 0;
+ }
+ if(src->mChannel >= channels)
+ {
+ fprintf(stderr, "\nError: Missing source channel in WAVE file '%s'.\n", src->mPath);
+ return 0;
+ }
+ if(rate != hrirRate)
+ {
+ fprintf(stderr, "\nError: Mismatched source sample rate in WAVE file '%s'.\n", src->mPath);
+ return 0;
+ }
+ if(format == WAVE_FORMAT_PCM)
+ {
+ if(size < 2 || size > 4)
+ {
+ fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", src->mPath);
+ return 0;
+ }
+ if(bits < 16 || bits > (8*size))
+ {
+ fprintf(stderr, "\nError: Bad significant bits in WAVE file '%s'.\n", src->mPath);
+ return 0;
+ }
+ src->mType = ET_INT;
+ }
+ else
+ {
+ if(size != 4 && size != 8)
+ {
+ fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", src->mPath);
+ return 0;
+ }
+ src->mType = ET_FP;
+ }
+ src->mSize = size;
+ src->mBits = static_cast<int>(bits);
+ src->mSkip = channels;
+ return 1;
+}
+
+// Read a RIFF/RIFX WAVE data chunk, converting all elements to doubles.
+static int ReadWaveData(std::istream &istream, const SourceRefT *src, const ByteOrderT order,
+ const uint n, double *hrir)
+{
+ int pre, post, skip;
+ uint i;
+
+ pre = static_cast<int>(src->mSize * src->mChannel);
+ post = static_cast<int>(src->mSize * (src->mSkip - src->mChannel - 1));
+ skip = 0;
+ for(i = 0;i < n;i++)
+ {
+ skip += pre;
+ if(skip > 0)
+ istream.seekg(skip, std::ios::cur);
+ if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i]))
+ return 0;
+ skip = post;
+ }
+ if(skip > 0)
+ istream.seekg(skip, std::ios::cur);
+ return 1;
+}
+
+// Read the RIFF/RIFX WAVE list or data chunk, converting all elements to
+// doubles.
+static int ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT order,
+ const uint n, double *hrir)
+{
+ uint32_t fourCC, chunkSize, listSize, count;
+ uint block, skip, offset, i;
+ double lastSample;
+
+ for(;;)
+ {
+ if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)
+ || !ReadBin4(istream, src->mPath, order, 4, &chunkSize))
+ return 0;
+
+ if(fourCC == FOURCC_DATA)
+ {
+ block = src->mSize * src->mSkip;
+ count = chunkSize / block;
+ if(count < (src->mOffset + n))
+ {
+ fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath);
+ return 0;
+ }
+ istream.seekg(static_cast<long>(src->mOffset * block), std::ios::cur);
+ if(!ReadWaveData(istream, src, order, n, &hrir[0]))
+ return 0;
+ return 1;
+ }
+ else if(fourCC == FOURCC_LIST)
+ {
+ if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC))
+ return 0;
+ chunkSize -= 4;
+ if(fourCC == FOURCC_WAVL)
+ break;
+ }
+ if(chunkSize > 0)
+ istream.seekg(static_cast<long>(chunkSize), std::ios::cur);
+ }
+ listSize = chunkSize;
+ block = src->mSize * src->mSkip;
+ skip = src->mOffset;
+ offset = 0;
+ lastSample = 0.0;
+ while(offset < n && listSize > 8)
+ {
+ if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)
+ || !ReadBin4(istream, src->mPath, order, 4, &chunkSize))
+ return 0;
+ listSize -= 8 + chunkSize;
+ if(fourCC == FOURCC_DATA)
+ {
+ count = chunkSize / block;
+ if(count > skip)
+ {
+ istream.seekg(static_cast<long>(skip * block), std::ios::cur);
+ chunkSize -= skip * block;
+ count -= skip;
+ skip = 0;
+ if(count > (n - offset))
+ count = n - offset;
+ if(!ReadWaveData(istream, src, order, count, &hrir[offset]))
+ return 0;
+ chunkSize -= count * block;
+ offset += count;
+ lastSample = hrir[offset - 1];
+ }
+ else
+ {
+ skip -= count;
+ count = 0;
+ }
+ }
+ else if(fourCC == FOURCC_SLNT)
+ {
+ if(!ReadBin4(istream, src->mPath, order, 4, &count))
+ return 0;
+ chunkSize -= 4;
+ if(count > skip)
+ {
+ count -= skip;
+ skip = 0;
+ if(count > (n - offset))
+ count = n - offset;
+ for(i = 0; i < count; i ++)
+ hrir[offset + i] = lastSample;
+ offset += count;
+ }
+ else
+ {
+ skip -= count;
+ count = 0;
+ }
+ }
+ if(chunkSize > 0)
+ istream.seekg(static_cast<long>(chunkSize), std::ios::cur);
+ }
+ if(offset < n)
+ {
+ fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath);
+ return 0;
+ }
+ return 1;
+}
+
+// Load a source HRIR from an ASCII text file containing a list of elements
+// separated by whitespace or common list operators (',', ';', ':', '|').
+static int LoadAsciiSource(std::istream &istream, const SourceRefT *src,
+ const uint n, double *hrir)
+{
+ TokenReaderT tr{istream};
+ uint i, j;
+ double dummy;
+
+ TrSetup(nullptr, 0, nullptr, &tr);
+ for(i = 0;i < src->mOffset;i++)
+ {
+ if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast<uint>(src->mBits), &dummy))
+ return 0;
+ }
+ for(i = 0;i < n;i++)
+ {
+ if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast<uint>(src->mBits), &hrir[i]))
+ return 0;
+ for(j = 0;j < src->mSkip;j++)
+ {
+ if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast<uint>(src->mBits), &dummy))
+ return 0;
+ }
+ }
+ return 1;
+}
+
+// Load a source HRIR from a binary file.
+static int LoadBinarySource(std::istream &istream, const SourceRefT *src, const ByteOrderT order,
+ const uint n, double *hrir)
+{
+ istream.seekg(static_cast<long>(src->mOffset), std::ios::beg);
+ for(uint i{0};i < n;i++)
+ {
+ if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i]))
+ return 0;
+ if(src->mSkip > 0)
+ istream.seekg(static_cast<long>(src->mSkip), std::ios::cur);
+ }
+ return 1;
+}
+
+// Load a source HRIR from a RIFF/RIFX WAVE file.
+static int LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hrirRate,
+ const uint n, double *hrir)
+{
+ uint32_t fourCC, dummy;
+ ByteOrderT order;
+
+ if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)
+ || !ReadBin4(istream, src->mPath, BO_LITTLE, 4, &dummy))
+ return 0;
+ if(fourCC == FOURCC_RIFF)
+ order = BO_LITTLE;
+ else if(fourCC == FOURCC_RIFX)
+ order = BO_BIG;
+ else
+ {
+ fprintf(stderr, "\nError: No RIFF/RIFX chunk in file '%s'.\n", src->mPath);
+ return 0;
+ }
+
+ if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC))
+ return 0;
+ if(fourCC != FOURCC_WAVE)
+ {
+ fprintf(stderr, "\nError: Not a RIFF/RIFX WAVE file '%s'.\n", src->mPath);
+ return 0;
+ }
+ if(!ReadWaveFormat(istream, order, hrirRate, src))
+ return 0;
+ if(!ReadWaveList(istream, src, order, n, hrir))
+ return 0;
+ return 1;
+}
+
+
+
+// Load a Spatially Oriented Format for Accoustics (SOFA) file.
+static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n)
+{
+ struct MYSOFA_EASY *sofa{mysofa_cache_lookup(src->mPath, static_cast<float>(hrirRate))};
+ if(sofa) return sofa;
+
+ sofa = static_cast<MYSOFA_EASY*>(calloc(1, sizeof(*sofa)));
+ if(sofa == nullptr)
+ {
+ fprintf(stderr, "\nError: Out of memory.\n");
+ return nullptr;
+ }
+ sofa->lookup = nullptr;
+ sofa->neighborhood = nullptr;
+
+ int err;
+ sofa->hrtf = mysofa_load(src->mPath, &err);
+ if(!sofa->hrtf)
+ {
+ mysofa_close(sofa);
+ fprintf(stderr, "\nError: Could not load source file '%s'.\n", src->mPath);
+ return nullptr;
+ }
+ /* NOTE: Some valid SOFA files are failing this check. */
+ err = mysofa_check(sofa->hrtf);
+ if(err != MYSOFA_OK)
+ fprintf(stderr, "\nWarning: Supposedly malformed source file '%s'.\n", src->mPath);
+ if((src->mOffset + n) > sofa->hrtf->N)
+ {
+ mysofa_close(sofa);
+ fprintf(stderr, "\nError: Not enough samples in SOFA file '%s'.\n", src->mPath);
+ return nullptr;
+ }
+ if(src->mChannel >= sofa->hrtf->R)
+ {
+ mysofa_close(sofa);
+ fprintf(stderr, "\nError: Missing source receiver in SOFA file '%s'.\n", src->mPath);
+ return nullptr;
+ }
+ mysofa_tocartesian(sofa->hrtf);
+ sofa->lookup = mysofa_lookup_init(sofa->hrtf);
+ if(sofa->lookup == nullptr)
+ {
+ mysofa_close(sofa);
+ fprintf(stderr, "\nError: Out of memory.\n");
+ return nullptr;
+ }
+ return mysofa_cache_store(sofa, src->mPath, static_cast<float>(hrirRate));
+}
+
+// Copies the HRIR data from a particular SOFA measurement.
+static void ExtractSofaHrir(const MYSOFA_EASY *sofa, const uint index, const uint channel, const uint offset, const uint n, double *hrir)
+{
+ for(uint i{0u};i < n;i++)
+ hrir[i] = sofa->hrtf->DataIR.values[(index*sofa->hrtf->R + channel)*sofa->hrtf->N + offset + i];
+}
+
+// Load a source HRIR from a Spatially Oriented Format for Accoustics (SOFA)
+// file.
+static int LoadSofaSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir)
+{
+ struct MYSOFA_EASY *sofa;
+ float target[3];
+ int nearest;
+ float *coords;
+
+ sofa = LoadSofaFile(src, hrirRate, n);
+ if(sofa == nullptr)
+ return 0;
+
+ /* NOTE: At some point it may be benficial or necessary to consider the
+ various coordinate systems, listener/source orientations, and
+ direciontal vectors defined in the SOFA file.
+ */
+ target[0] = static_cast<float>(src->mAzimuth);
+ target[1] = static_cast<float>(src->mElevation);
+ target[2] = static_cast<float>(src->mRadius);
+ mysofa_s2c(target);
+
+ nearest = mysofa_lookup(sofa->lookup, target);
+ if(nearest < 0)
+ {
+ fprintf(stderr, "\nError: Lookup failed in source file '%s'.\n", src->mPath);
+ return 0;
+ }
+
+ coords = &sofa->hrtf->SourcePosition.values[3 * nearest];
+ if(std::abs(coords[0] - target[0]) > 0.001 || std::abs(coords[1] - target[1]) > 0.001 || std::abs(coords[2] - target[2]) > 0.001)
+ {
+ fprintf(stderr, "\nError: No impulse response at coordinates (%.3fr, %.1fev, %.1faz) in file '%s'.\n", src->mRadius, src->mElevation, src->mAzimuth, src->mPath);
+ target[0] = coords[0];
+ target[1] = coords[1];
+ target[2] = coords[2];
+ mysofa_c2s(target);
+ fprintf(stderr, " Nearest candidate at (%.3fr, %.1fev, %.1faz).\n", target[2], target[1], target[0]);
+ return 0;
+ }
+
+ ExtractSofaHrir(sofa, static_cast<uint>(nearest), src->mChannel, src->mOffset, n, hrir);
+
+ return 1;
+}
+
+// Load a source HRIR from a supported file type.
+static int LoadSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir)
+{
+ std::unique_ptr<al::ifstream> istream;
+ if(src->mFormat != SF_SOFA)
+ {
+ if(src->mFormat == SF_ASCII)
+ istream.reset(new al::ifstream{src->mPath});
+ else
+ istream.reset(new al::ifstream{src->mPath, std::ios::binary});
+ if(!istream->good())
+ {
+ fprintf(stderr, "\nError: Could not open source file '%s'.\n", src->mPath);
+ return 0;
+ }
+ }
+ int result{0};
+ switch(src->mFormat)
+ {
+ case SF_ASCII:
+ result = LoadAsciiSource(*istream, src, n, hrir);
+ break;
+ case SF_BIN_LE:
+ result = LoadBinarySource(*istream, src, BO_LITTLE, n, hrir);
+ break;
+ case SF_BIN_BE:
+ result = LoadBinarySource(*istream, src, BO_BIG, n, hrir);
+ break;
+ case SF_WAVE:
+ result = LoadWaveSource(*istream, src, hrirRate, n, hrir);
+ break;
+ case SF_SOFA:
+ result = LoadSofaSource(src, hrirRate, n, hrir);
+ break;
+ case SF_NONE:
+ break;
+ }
+ return result;
+}
+
+
+// Match the channel type from a given identifier.
+static ChannelTypeT MatchChannelType(const char *ident)
+{
+ if(al::strcasecmp(ident, "mono") == 0)
+ return CT_MONO;
+ if(al::strcasecmp(ident, "stereo") == 0)
+ return CT_STEREO;
+ return CT_NONE;
+}
+
+
+// Process the data set definition to read and validate the data set metrics.
+static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData)
+{
+ int hasRate = 0, hasType = 0, hasPoints = 0, hasRadius = 0;
+ int hasDistance = 0, hasAzimuths = 0;
+ char ident[MAX_IDENT_LEN+1];
+ uint line, col;
+ double fpVal;
+ uint points;
+ int intVal;
+ double distances[MAX_FD_COUNT];
+ uint fdCount = 0;
+ uint evCounts[MAX_FD_COUNT];
+ std::vector<uint> azCounts(MAX_FD_COUNT * MAX_EV_COUNT);
+
+ TrIndication(tr, &line, &col);
+ while(TrIsIdent(tr))
+ {
+ TrIndication(tr, &line, &col);
+ if(!TrReadIdent(tr, MAX_IDENT_LEN, ident))
+ return 0;
+ if(al::strcasecmp(ident, "rate") == 0)
+ {
+ if(hasRate)
+ {
+ TrErrorAt(tr, line, col, "Redefinition of 'rate'.\n");
+ return 0;
+ }
+ if(!TrReadOperator(tr, "="))
+ return 0;
+ if(!TrReadInt(tr, MIN_RATE, MAX_RATE, &intVal))
+ return 0;
+ hData->mIrRate = static_cast<uint>(intVal);
+ hasRate = 1;
+ }
+ else if(al::strcasecmp(ident, "type") == 0)
+ {
+ char type[MAX_IDENT_LEN+1];
+
+ if(hasType)
+ {
+ TrErrorAt(tr, line, col, "Redefinition of 'type'.\n");
+ return 0;
+ }
+ if(!TrReadOperator(tr, "="))
+ return 0;
+
+ if(!TrReadIdent(tr, MAX_IDENT_LEN, type))
+ return 0;
+ hData->mChannelType = MatchChannelType(type);
+ if(hData->mChannelType == CT_NONE)
+ {
+ TrErrorAt(tr, line, col, "Expected a channel type.\n");
+ return 0;
+ }
+ else if(hData->mChannelType == CT_STEREO)
+ {
+ if(chanMode == CM_ForceMono)
+ hData->mChannelType = CT_MONO;
+ }
+ hasType = 1;
+ }
+ else if(al::strcasecmp(ident, "points") == 0)
+ {
+ if(hasPoints)
+ {
+ TrErrorAt(tr, line, col, "Redefinition of 'points'.\n");
+ return 0;
+ }
+ if(!TrReadOperator(tr, "="))
+ return 0;
+ TrIndication(tr, &line, &col);
+ if(!TrReadInt(tr, MIN_POINTS, MAX_POINTS, &intVal))
+ return 0;
+ points = static_cast<uint>(intVal);
+ if(fftSize > 0 && points > fftSize)
+ {
+ TrErrorAt(tr, line, col, "Value exceeds the overridden FFT size.\n");
+ return 0;
+ }
+ if(points < truncSize)
+ {
+ TrErrorAt(tr, line, col, "Value is below the truncation size.\n");
+ return 0;
+ }
+ hData->mIrPoints = points;
+ hData->mFftSize = fftSize;
+ hData->mIrSize = 1 + (fftSize / 2);
+ if(points > hData->mIrSize)
+ hData->mIrSize = points;
+ hasPoints = 1;
+ }
+ else if(al::strcasecmp(ident, "radius") == 0)
+ {
+ if(hasRadius)
+ {
+ TrErrorAt(tr, line, col, "Redefinition of 'radius'.\n");
+ return 0;
+ }
+ if(!TrReadOperator(tr, "="))
+ return 0;
+ if(!TrReadFloat(tr, MIN_RADIUS, MAX_RADIUS, &fpVal))
+ return 0;
+ hData->mRadius = fpVal;
+ hasRadius = 1;
+ }
+ else if(al::strcasecmp(ident, "distance") == 0)
+ {
+ uint count = 0;
+
+ if(hasDistance)
+ {
+ TrErrorAt(tr, line, col, "Redefinition of 'distance'.\n");
+ return 0;
+ }
+ if(!TrReadOperator(tr, "="))
+ return 0;
+
+ for(;;)
+ {
+ if(!TrReadFloat(tr, MIN_DISTANCE, MAX_DISTANCE, &fpVal))
+ return 0;
+ if(count > 0 && fpVal <= distances[count - 1])
+ {
+ TrError(tr, "Distances are not ascending.\n");
+ return 0;
+ }
+ distances[count++] = fpVal;
+ if(!TrIsOperator(tr, ","))
+ break;
+ if(count >= MAX_FD_COUNT)
+ {
+ TrError(tr, "Exceeded the maximum of %d fields.\n", MAX_FD_COUNT);
+ return 0;
+ }
+ TrReadOperator(tr, ",");
+ }
+ if(fdCount != 0 && count != fdCount)
+ {
+ TrError(tr, "Did not match the specified number of %d fields.\n", fdCount);
+ return 0;
+ }
+ fdCount = count;
+ hasDistance = 1;
+ }
+ else if(al::strcasecmp(ident, "azimuths") == 0)
+ {
+ uint count = 0;
+
+ if(hasAzimuths)
+ {
+ TrErrorAt(tr, line, col, "Redefinition of 'azimuths'.\n");
+ return 0;
+ }
+ if(!TrReadOperator(tr, "="))
+ return 0;
+
+ evCounts[0] = 0;
+ for(;;)
+ {
+ if(!TrReadInt(tr, MIN_AZ_COUNT, MAX_AZ_COUNT, &intVal))
+ return 0;
+ azCounts[(count * MAX_EV_COUNT) + evCounts[count]++] = static_cast<uint>(intVal);
+ if(TrIsOperator(tr, ","))
+ {
+ if(evCounts[count] >= MAX_EV_COUNT)
+ {
+ TrError(tr, "Exceeded the maximum of %d elevations.\n", MAX_EV_COUNT);
+ return 0;
+ }
+ TrReadOperator(tr, ",");
+ }
+ else
+ {
+ if(evCounts[count] < MIN_EV_COUNT)
+ {
+ TrErrorAt(tr, line, col, "Did not reach the minimum of %d azimuth counts.\n", MIN_EV_COUNT);
+ return 0;
+ }
+ if(azCounts[count * MAX_EV_COUNT] != 1 || azCounts[(count * MAX_EV_COUNT) + evCounts[count] - 1] != 1)
+ {
+ TrError(tr, "Poles are not singular for field %d.\n", count - 1);
+ return 0;
+ }
+ count++;
+ if(!TrIsOperator(tr, ";"))
+ break;
+
+ if(count >= MAX_FD_COUNT)
+ {
+ TrError(tr, "Exceeded the maximum number of %d fields.\n", MAX_FD_COUNT);
+ return 0;
+ }
+ evCounts[count] = 0;
+ TrReadOperator(tr, ";");
+ }
+ }
+ if(fdCount != 0 && count != fdCount)
+ {
+ TrError(tr, "Did not match the specified number of %d fields.\n", fdCount);
+ return 0;
+ }
+ fdCount = count;
+ hasAzimuths = 1;
+ }
+ else
+ {
+ TrErrorAt(tr, line, col, "Expected a metric name.\n");
+ return 0;
+ }
+ TrSkipWhitespace(tr);
+ }
+ if(!(hasRate && hasPoints && hasRadius && hasDistance && hasAzimuths))
+ {
+ TrErrorAt(tr, line, col, "Expected a metric name.\n");
+ return 0;
+ }
+ if(distances[0] < hData->mRadius)
+ {
+ TrError(tr, "Distance cannot start below head radius.\n");
+ return 0;
+ }
+ if(hData->mChannelType == CT_NONE)
+ hData->mChannelType = CT_MONO;
+ if(!PrepareHrirData(fdCount, distances, evCounts, azCounts.data(), hData))
+ {
+ fprintf(stderr, "Error: Out of memory.\n");
+ exit(-1);
+ }
+ return 1;
+}
+
+// Parse an index triplet from the data set definition.
+static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, uint *ei, uint *ai)
+{
+ int intVal;
+
+ if(hData->mFdCount > 1)
+ {
+ if(!TrReadInt(tr, 0, static_cast<int>(hData->mFdCount) - 1, &intVal))
+ return 0;
+ *fi = static_cast<uint>(intVal);
+ if(!TrReadOperator(tr, ","))
+ return 0;
+ }
+ else
+ {
+ *fi = 0;
+ }
+ if(!TrReadInt(tr, 0, static_cast<int>(hData->mFds[*fi].mEvCount) - 1, &intVal))
+ return 0;
+ *ei = static_cast<uint>(intVal);
+ if(!TrReadOperator(tr, ","))
+ return 0;
+ if(!TrReadInt(tr, 0, static_cast<int>(hData->mFds[*fi].mEvs[*ei].mAzCount) - 1, &intVal))
+ return 0;
+ *ai = static_cast<uint>(intVal);
+ return 1;
+}
+
+// Match the source format from a given identifier.
+static SourceFormatT MatchSourceFormat(const char *ident)
+{
+ if(al::strcasecmp(ident, "ascii") == 0)
+ return SF_ASCII;
+ if(al::strcasecmp(ident, "bin_le") == 0)
+ return SF_BIN_LE;
+ if(al::strcasecmp(ident, "bin_be") == 0)
+ return SF_BIN_BE;
+ if(al::strcasecmp(ident, "wave") == 0)
+ return SF_WAVE;
+ if(al::strcasecmp(ident, "sofa") == 0)
+ return SF_SOFA;
+ return SF_NONE;
+}
+
+// Match the source element type from a given identifier.
+static ElementTypeT MatchElementType(const char *ident)
+{
+ if(al::strcasecmp(ident, "int") == 0)
+ return ET_INT;
+ if(al::strcasecmp(ident, "fp") == 0)
+ return ET_FP;
+ return ET_NONE;
+}
+
+// Parse and validate a source reference from the data set definition.
+static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src)
+{
+ char ident[MAX_IDENT_LEN+1];
+ uint line, col;
+ double fpVal;
+ int intVal;
+
+ TrIndication(tr, &line, &col);
+ if(!TrReadIdent(tr, MAX_IDENT_LEN, ident))
+ return 0;
+ src->mFormat = MatchSourceFormat(ident);
+ if(src->mFormat == SF_NONE)
+ {
+ TrErrorAt(tr, line, col, "Expected a source format.\n");
+ return 0;
+ }
+ if(!TrReadOperator(tr, "("))
+ return 0;
+ if(src->mFormat == SF_SOFA)
+ {
+ if(!TrReadFloat(tr, MIN_DISTANCE, MAX_DISTANCE, &fpVal))
+ return 0;
+ src->mRadius = fpVal;
+ if(!TrReadOperator(tr, ","))
+ return 0;
+ if(!TrReadFloat(tr, -90.0, 90.0, &fpVal))
+ return 0;
+ src->mElevation = fpVal;
+ if(!TrReadOperator(tr, ","))
+ return 0;
+ if(!TrReadFloat(tr, -360.0, 360.0, &fpVal))
+ return 0;
+ src->mAzimuth = fpVal;
+ if(!TrReadOperator(tr, ":"))
+ return 0;
+ if(!TrReadInt(tr, 0, MAX_WAVE_CHANNELS, &intVal))
+ return 0;
+ src->mType = ET_NONE;
+ src->mSize = 0;
+ src->mBits = 0;
+ src->mChannel = static_cast<uint>(intVal);
+ src->mSkip = 0;
+ }
+ else if(src->mFormat == SF_WAVE)
+ {
+ if(!TrReadInt(tr, 0, MAX_WAVE_CHANNELS, &intVal))
+ return 0;
+ src->mType = ET_NONE;
+ src->mSize = 0;
+ src->mBits = 0;
+ src->mChannel = static_cast<uint>(intVal);
+ src->mSkip = 0;
+ }
+ else
+ {
+ TrIndication(tr, &line, &col);
+ if(!TrReadIdent(tr, MAX_IDENT_LEN, ident))
+ return 0;
+ src->mType = MatchElementType(ident);
+ if(src->mType == ET_NONE)
+ {
+ TrErrorAt(tr, line, col, "Expected a source element type.\n");
+ return 0;
+ }
+ if(src->mFormat == SF_BIN_LE || src->mFormat == SF_BIN_BE)
+ {
+ if(!TrReadOperator(tr, ","))
+ return 0;
+ if(src->mType == ET_INT)
+ {
+ if(!TrReadInt(tr, MIN_BIN_SIZE, MAX_BIN_SIZE, &intVal))
+ return 0;
+ src->mSize = static_cast<uint>(intVal);
+ if(!TrIsOperator(tr, ","))
+ src->mBits = static_cast<int>(8*src->mSize);
+ else
+ {
+ TrReadOperator(tr, ",");
+ TrIndication(tr, &line, &col);
+ if(!TrReadInt(tr, -2147483647-1, 2147483647, &intVal))
+ return 0;
+ if(std::abs(intVal) < MIN_BIN_BITS || static_cast<uint>(std::abs(intVal)) > (8*src->mSize))
+ {
+ TrErrorAt(tr, line, col, "Expected a value of (+/-) %d to %d.\n", MIN_BIN_BITS, 8*src->mSize);
+ return 0;
+ }
+ src->mBits = intVal;
+ }
+ }
+ else
+ {
+ TrIndication(tr, &line, &col);
+ if(!TrReadInt(tr, -2147483647-1, 2147483647, &intVal))
+ return 0;
+ if(intVal != 4 && intVal != 8)
+ {
+ TrErrorAt(tr, line, col, "Expected a value of 4 or 8.\n");
+ return 0;
+ }
+ src->mSize = static_cast<uint>(intVal);
+ src->mBits = 0;
+ }
+ }
+ else if(src->mFormat == SF_ASCII && src->mType == ET_INT)
+ {
+ if(!TrReadOperator(tr, ","))
+ return 0;
+ if(!TrReadInt(tr, MIN_ASCII_BITS, MAX_ASCII_BITS, &intVal))
+ return 0;
+ src->mSize = 0;
+ src->mBits = intVal;
+ }
+ else
+ {
+ src->mSize = 0;
+ src->mBits = 0;
+ }
+
+ if(!TrIsOperator(tr, ";"))
+ src->mSkip = 0;
+ else
+ {
+ TrReadOperator(tr, ";");
+ if(!TrReadInt(tr, 0, 0x7FFFFFFF, &intVal))
+ return 0;
+ src->mSkip = static_cast<uint>(intVal);
+ }
+ }
+ if(!TrReadOperator(tr, ")"))
+ return 0;
+ if(TrIsOperator(tr, "@"))
+ {
+ TrReadOperator(tr, "@");
+ if(!TrReadInt(tr, 0, 0x7FFFFFFF, &intVal))
+ return 0;
+ src->mOffset = static_cast<uint>(intVal);
+ }
+ else
+ src->mOffset = 0;
+ if(!TrReadOperator(tr, ":"))
+ return 0;
+ if(!TrReadString(tr, MAX_PATH_LEN, src->mPath))
+ return 0;
+ return 1;
+}
+
+// Parse and validate a SOFA source reference from the data set definition.
+static int ReadSofaRef(TokenReaderT *tr, SourceRefT *src)
+{
+ char ident[MAX_IDENT_LEN+1];
+ uint line, col;
+ int intVal;
+
+ TrIndication(tr, &line, &col);
+ if(!TrReadIdent(tr, MAX_IDENT_LEN, ident))
+ return 0;
+ src->mFormat = MatchSourceFormat(ident);
+ if(src->mFormat != SF_SOFA)
+ {
+ TrErrorAt(tr, line, col, "Expected the SOFA source format.\n");
+ return 0;
+ }
+
+ src->mType = ET_NONE;
+ src->mSize = 0;
+ src->mBits = 0;
+ src->mChannel = 0;
+ src->mSkip = 0;
+
+ if(TrIsOperator(tr, "@"))
+ {
+ TrReadOperator(tr, "@");
+ if(!TrReadInt(tr, 0, 0x7FFFFFFF, &intVal))
+ return 0;
+ src->mOffset = static_cast<uint>(intVal);
+ }
+ else
+ src->mOffset = 0;
+ if(!TrReadOperator(tr, ":"))
+ return 0;
+ if(!TrReadString(tr, MAX_PATH_LEN, src->mPath))
+ return 0;
+ return 1;
+}
+
+// Match the target ear (index) from a given identifier.
+static int MatchTargetEar(const char *ident)
+{
+ if(al::strcasecmp(ident, "left") == 0)
+ return 0;
+ if(al::strcasecmp(ident, "right") == 0)
+ return 1;
+ return -1;
+}
+
+// Calculate the onset time of an HRIR and average it with any existing
+// timing for its field, elevation, azimuth, and ear.
+static double AverageHrirOnset(const uint rate, const uint n, const double *hrir, const double f, const double onset)
+{
+ std::vector<double> upsampled(10 * n);
+ {
+ ResamplerT rs;
+ ResamplerSetup(&rs, rate, 10 * rate);
+ ResamplerRun(&rs, n, hrir, 10 * n, upsampled.data());
+ }
+
+ double mag{0.0};
+ for(uint i{0u};i < 10*n;i++)
+ mag = std::max(std::abs(upsampled[i]), mag);
+
+ mag *= 0.15;
+ uint i{0u};
+ for(;i < 10*n;i++)
+ {
+ if(std::abs(upsampled[i]) >= mag)
+ break;
+ }
+ return Lerp(onset, static_cast<double>(i) / (10*rate), f);
+}
+
+// Calculate the magnitude response of an HRIR and average it with any
+// existing responses for its field, elevation, azimuth, and ear.
+static void AverageHrirMagnitude(const uint points, const uint n, const double *hrir, const double f, double *mag)
+{
+ uint m = 1 + (n / 2), i;
+ std::vector<complex_d> h(n);
+ std::vector<double> r(n);
+
+ for(i = 0;i < points;i++)
+ h[i] = complex_d{hrir[i], 0.0};
+ for(;i < n;i++)
+ h[i] = complex_d{0.0, 0.0};
+ FftForward(n, h.data());
+ MagnitudeResponse(n, h.data(), r.data());
+ for(i = 0;i < m;i++)
+ mag[i] = Lerp(mag[i], r[i], f);
+}
+
+// Process the list of sources in the data set definition.
+static int ProcessSources(TokenReaderT *tr, HrirDataT *hData)
+{
+ uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
+ hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize);
+ double *hrirs = hData->mHrirsBase.data();
+ std::vector<double> hrir(hData->mIrPoints);
+ uint line, col, fi, ei, ai;
+ int count;
+
+ printf("Loading sources...");
+ fflush(stdout);
+ count = 0;
+ while(TrIsOperator(tr, "["))
+ {
+ double factor[2]{ 1.0, 1.0 };
+
+ TrIndication(tr, &line, &col);
+ TrReadOperator(tr, "[");
+
+ if(TrIsOperator(tr, "*"))
+ {
+ SourceRefT src;
+ struct MYSOFA_EASY *sofa;
+ uint si;
+
+ TrReadOperator(tr, "*");
+ if(!TrReadOperator(tr, "]") || !TrReadOperator(tr, "="))
+ return 0;
+
+ TrIndication(tr, &line, &col);
+ if(!ReadSofaRef(tr, &src))
+ return 0;
+
+ if(hData->mChannelType == CT_STEREO)
+ {
+ char type[MAX_IDENT_LEN+1];
+ ChannelTypeT channelType;
+
+ if(!TrReadIdent(tr, MAX_IDENT_LEN, type))
+ return 0;
+
+ channelType = MatchChannelType(type);
+
+ switch(channelType)
+ {
+ case CT_NONE:
+ TrErrorAt(tr, line, col, "Expected a channel type.\n");
+ return 0;
+ case CT_MONO:
+ src.mChannel = 0;
+ break;
+ case CT_STEREO:
+ src.mChannel = 1;
+ break;
+ }
+ }
+ else
+ {
+ char type[MAX_IDENT_LEN+1];
+ ChannelTypeT channelType;
+
+ if(!TrReadIdent(tr, MAX_IDENT_LEN, type))
+ return 0;
+
+ channelType = MatchChannelType(type);
+ if(channelType != CT_MONO)
+ {
+ TrErrorAt(tr, line, col, "Expected a mono channel type.\n");
+ return 0;
+ }
+ src.mChannel = 0;
+ }
+
+ sofa = LoadSofaFile(&src, hData->mIrRate, hData->mIrPoints);
+ if(!sofa) return 0;
+
+ for(si = 0;si < sofa->hrtf->M;si++)
+ {
+ printf("\rLoading sources... %d of %d", si+1, sofa->hrtf->M);
+ fflush(stdout);
+
+ float aer[3] = {
+ sofa->hrtf->SourcePosition.values[3*si],
+ sofa->hrtf->SourcePosition.values[3*si + 1],
+ sofa->hrtf->SourcePosition.values[3*si + 2]
+ };
+ mysofa_c2s(aer);
+
+ if(std::fabs(aer[1]) >= 89.999f)
+ aer[0] = 0.0f;
+ else
+ aer[0] = std::fmod(360.0f - aer[0], 360.0f);
+
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ double delta = aer[2] - hData->mFds[fi].mDistance;
+ if(std::abs(delta) < 0.001) break;
+ }
+ if(fi >= hData->mFdCount)
+ continue;
+
+ double ef{(90.0 + aer[1]) / 180.0 * (hData->mFds[fi].mEvCount - 1)};
+ ei = static_cast<uint>(std::round(ef));
+ ef = (ef - ei) * 180.0 / (hData->mFds[fi].mEvCount - 1);
+ if(std::abs(ef) >= 0.1)
+ continue;
+
+ double af{aer[0] / 360.0 * hData->mFds[fi].mEvs[ei].mAzCount};
+ ai = static_cast<uint>(std::round(af));
+ af = (af - ai) * 360.0 / hData->mFds[fi].mEvs[ei].mAzCount;
+ ai = ai % hData->mFds[fi].mEvs[ei].mAzCount;
+ if(std::abs(af) >= 0.1)
+ continue;
+
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+ if(azd->mIrs[0] != nullptr)
+ {
+ TrErrorAt(tr, line, col, "Redefinition of source [ %d, %d, %d ].\n", fi, ei, ai);
+ return 0;
+ }
+
+ ExtractSofaHrir(sofa, si, 0, src.mOffset, hData->mIrPoints, hrir.data());
+ azd->mIrs[0] = &hrirs[hData->mIrSize * azd->mIndex];
+ azd->mDelays[0] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[0]);
+ AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir.data(), 1.0, azd->mIrs[0]);
+
+ if(src.mChannel == 1)
+ {
+ ExtractSofaHrir(sofa, si, 1, src.mOffset, hData->mIrPoints, hrir.data());
+ azd->mIrs[1] = &hrirs[hData->mIrSize * (hData->mIrCount + azd->mIndex)];
+ azd->mDelays[1] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[1]);
+ AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir.data(), 1.0, azd->mIrs[1]);
+ }
+
+ // TODO: Since some SOFA files contain minimum phase HRIRs,
+ // it would be beneficial to check for per-measurement delays
+ // (when available) to reconstruct the HRTDs.
+ }
+
+ continue;
+ }
+
+ if(!ReadIndexTriplet(tr, hData, &fi, &ei, &ai))
+ return 0;
+ if(!TrReadOperator(tr, "]"))
+ return 0;
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+
+ if(azd->mIrs[0] != nullptr)
+ {
+ TrErrorAt(tr, line, col, "Redefinition of source.\n");
+ return 0;
+ }
+ if(!TrReadOperator(tr, "="))
+ return 0;
+
+ for(;;)
+ {
+ SourceRefT src;
+
+ if(!ReadSourceRef(tr, &src))
+ return 0;
+
+ // TODO: Would be nice to display 'x of y files', but that would
+ // require preparing the source refs first to get a total count
+ // before loading them.
+ ++count;
+ printf("\rLoading sources... %d file%s", count, (count==1)?"":"s");
+ fflush(stdout);
+
+ if(!LoadSource(&src, hData->mIrRate, hData->mIrPoints, hrir.data()))
+ return 0;
+
+ uint ti{0};
+ if(hData->mChannelType == CT_STEREO)
+ {
+ char ident[MAX_IDENT_LEN+1];
+
+ if(!TrReadIdent(tr, MAX_IDENT_LEN, ident))
+ return 0;
+ ti = static_cast<uint>(MatchTargetEar(ident));
+ if(static_cast<int>(ti) < 0)
+ {
+ TrErrorAt(tr, line, col, "Expected a target ear.\n");
+ return 0;
+ }
+ }
+ azd->mIrs[ti] = &hrirs[hData->mIrSize * (ti * hData->mIrCount + azd->mIndex)];
+ azd->mDelays[ti] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0 / factor[ti], azd->mDelays[ti]);
+ AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir.data(), 1.0 / factor[ti], azd->mIrs[ti]);
+ factor[ti] += 1.0;
+ if(!TrIsOperator(tr, "+"))
+ break;
+ TrReadOperator(tr, "+");
+ }
+ if(hData->mChannelType == CT_STEREO)
+ {
+ if(azd->mIrs[0] == nullptr)
+ {
+ TrErrorAt(tr, line, col, "Missing left ear source reference(s).\n");
+ return 0;
+ }
+ else if(azd->mIrs[1] == nullptr)
+ {
+ TrErrorAt(tr, line, col, "Missing right ear source reference(s).\n");
+ return 0;
+ }
+ }
+ }
+ printf("\n");
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+ if(azd->mIrs[0] != nullptr)
+ break;
+ }
+ if(ai < hData->mFds[fi].mEvs[ei].mAzCount)
+ break;
+ }
+ if(ei >= hData->mFds[fi].mEvCount)
+ {
+ TrError(tr, "Missing source references [ %d, *, * ].\n", fi);
+ return 0;
+ }
+ hData->mFds[fi].mEvStart = ei;
+ for(;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+
+ if(azd->mIrs[0] == nullptr)
+ {
+ TrError(tr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai);
+ return 0;
+ }
+ }
+ }
+ }
+ for(uint ti{0};ti < channels;ti++)
+ {
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+
+ azd->mIrs[ti] = &hrirs[hData->mIrSize * (ti * hData->mIrCount + azd->mIndex)];
+ }
+ }
+ }
+ }
+ if(!TrLoad(tr))
+ {
+ mysofa_cache_release_all();
+ return 1;
+ }
+
+ TrError(tr, "Errant data at end of source list.\n");
+ mysofa_cache_release_all();
+ return 0;
+}
+
+
+bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount,
+ const char *filename, const uint fftSize, const uint truncSize, const ChannelModeT chanMode,
+ HrirDataT *hData)
+{
+ TokenReaderT tr{istream};
+
+ TrSetup(startbytes, startbytecount, filename, &tr);
+ if(!ProcessMetrics(&tr, fftSize, truncSize, chanMode, hData)
+ || !ProcessSources(&tr, hData))
+ return false;
+
+ return true;
+}
diff --git a/utils/makemhr/loaddef.h b/utils/makemhr/loaddef.h
new file mode 100644
index 00000000..34fbb832
--- /dev/null
+++ b/utils/makemhr/loaddef.h
@@ -0,0 +1,13 @@
+#ifndef LOADDEF_H
+#define LOADDEF_H
+
+#include <istream>
+
+#include "makemhr.h"
+
+
+bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount,
+ const char *filename, const uint fftSize, const uint truncSize, const ChannelModeT chanMode,
+ HrirDataT *hData);
+
+#endif /* LOADDEF_H */
diff --git a/utils/makemhr/loadsofa.cpp b/utils/makemhr/loadsofa.cpp
new file mode 100644
index 00000000..c91613c8
--- /dev/null
+++ b/utils/makemhr/loadsofa.cpp
@@ -0,0 +1,668 @@
+/*
+ * HRTF utility for producing and demonstrating the process of creating an
+ * OpenAL Soft compatible HRIR data set.
+ *
+ * Copyright (C) 2018-2019 Christopher Fitzgerald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ */
+
+#include "loadsofa.h"
+
+#include <algorithm>
+#include <array>
+#include <cmath>
+#include <cstdio>
+#include <iterator>
+#include <memory>
+#include <numeric>
+#include <string>
+#include <vector>
+
+#include "makemhr.h"
+
+#include "mysofa.h"
+
+
+using double3 = std::array<double,3>;
+
+static const char *SofaErrorStr(int err)
+{
+ switch(err)
+ {
+ case MYSOFA_OK: return "OK";
+ case MYSOFA_INVALID_FORMAT: return "Invalid format";
+ case MYSOFA_UNSUPPORTED_FORMAT: return "Unsupported format";
+ case MYSOFA_INTERNAL_ERROR: return "Internal error";
+ case MYSOFA_NO_MEMORY: return "Out of memory";
+ case MYSOFA_READ_ERROR: return "Read error";
+ }
+ return "Unknown";
+}
+
+
+/* Produces a sorted array of unique elements from a particular axis of the
+ * triplets array. The filters are used to focus on particular coordinates
+ * of other axes as necessary. The epsilons are used to constrain the
+ * equality of unique elements.
+ */
+static uint GetUniquelySortedElems(const uint m, const double3 *aers, const uint axis,
+ const double *const (&filters)[3], const double (&epsilons)[3], double *elems)
+{
+ uint count{0u};
+ for(uint i{0u};i < m;++i)
+ {
+ const double elem{aers[i][axis]};
+
+ uint j;
+ for(j = 0;j < 3;j++)
+ {
+ if(filters[j] && std::fabs(aers[i][j] - *filters[j]) > epsilons[j])
+ break;
+ }
+ if(j < 3)
+ continue;
+
+ for(j = 0;j < count;j++)
+ {
+ const double delta{elem - elems[j]};
+
+ if(delta > epsilons[axis])
+ continue;
+
+ if(delta >= -epsilons[axis])
+ break;
+
+ for(uint k{count};k > j;k--)
+ elems[k] = elems[k - 1];
+
+ elems[j] = elem;
+ count++;
+ break;
+ }
+
+ if(j >= count)
+ elems[count++] = elem;
+ }
+
+ return count;
+}
+
+/* Given a list of elements, this will produce the smallest step size that
+ * can uniformly cover a fair portion of the list. Ideally this will be over
+ * half, but in degenerate cases this can fall to a minimum of 5 (the lower
+ * limit on elevations necessary to build a layout).
+ */
+static double GetUniformStepSize(const double epsilon, const uint m, const double *elems)
+{
+ auto steps = std::vector<double>(m, 0.0);
+ auto counts = std::vector<uint>(m, 0u);
+ uint count{0u};
+
+ for(uint stride{1u};stride < m/2;stride++)
+ {
+ for(uint i{0u};i < m-stride;i++)
+ {
+ const double step{elems[i + stride] - elems[i]};
+
+ uint j;
+ for(j = 0;j < count;j++)
+ {
+ if(std::fabs(step - steps[j]) < epsilon)
+ {
+ counts[j]++;
+ break;
+ }
+ }
+
+ if(j >= count)
+ {
+ steps[j] = step;
+ counts[j] = 1;
+ count++;
+ }
+ }
+
+ for(uint i{1u};i < count;i++)
+ {
+ if(counts[i] > counts[0])
+ {
+ steps[0] = steps[i];
+ counts[0] = counts[i];
+ }
+ }
+
+ count = 1;
+
+ if(counts[0] > m/2)
+ break;
+ }
+
+ if(counts[0] > 255)
+ {
+ uint i{2u};
+ while(counts[0]/i > 255 && (counts[0]%i) != 0)
+ ++i;
+ counts[0] /= i;
+ steps[0] *= i;
+ }
+ if(counts[0] > 5)
+ return steps[0];
+ return 0.0;
+}
+
+/* Attempts to produce a compatible layout. Most data sets tend to be
+ * uniform and have the same major axis as used by OpenAL Soft's HRTF model.
+ * This will remove outliers and produce a maximally dense layout when
+ * possible. Those sets that contain purely random measurements or use
+ * different major axes will fail.
+ */
+static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData)
+{
+ auto aers = std::vector<double3>(m, double3{});
+ auto elems = std::vector<double>(m, 0.0);
+
+ for(uint i{0u};i < m;++i)
+ {
+ float aer[3]{xyzs[i*3], xyzs[i*3 + 1], xyzs[i*3 + 2]};
+ mysofa_c2s(&aer[0]);
+ aers[i][0] = aer[0];
+ aers[i][1] = aer[1];
+ aers[i][2] = aer[2];
+ }
+
+ const uint fdCount{GetUniquelySortedElems(m, aers.data(), 2, { nullptr, nullptr, nullptr },
+ { 0.1, 0.1, 0.001 }, elems.data())};
+ if(fdCount > MAX_FD_COUNT)
+ {
+ fprintf(stdout, "Incompatible layout (inumerable radii).\n");
+ return false;
+ }
+
+ double distances[MAX_FD_COUNT]{};
+ uint evCounts[MAX_FD_COUNT]{};
+ auto azCounts = std::vector<uint>(MAX_FD_COUNT*MAX_EV_COUNT, 0u);
+ for(uint fi{0u};fi < fdCount;fi++)
+ {
+ distances[fi] = elems[fi];
+ if(fi > 0 && distances[fi] <= distances[fi-1])
+ {
+ fprintf(stderr, "Distances must increase.\n");
+ return 0;
+ }
+ }
+ if(distances[0] < hData->mRadius)
+ {
+ fprintf(stderr, "Distance cannot start below head radius.\n");
+ return 0;
+ }
+
+ for(uint fi{0u};fi < fdCount;fi++)
+ {
+ const double dist{distances[fi]};
+ uint evCount{GetUniquelySortedElems(m, aers.data(), 1, { nullptr, nullptr, &dist },
+ { 0.1, 0.1, 0.001 }, elems.data())};
+
+ if(evCount > MAX_EV_COUNT)
+ {
+ fprintf(stderr, "Incompatible layout (innumerable elevations).\n");
+ return false;
+ }
+
+ double step{GetUniformStepSize(0.1, evCount, elems.data())};
+ if(step <= 0.0)
+ {
+ fprintf(stderr, "Incompatible layout (non-uniform elevations).\n");
+ return false;
+ }
+
+ uint evStart{0u};
+ for(uint ei{0u};ei < evCount;ei++)
+ {
+ double ev{90.0 + elems[ei]};
+ double eif{std::round(ev / step)};
+ const uint ei_start{static_cast<uint>(eif)};
+
+ if(std::fabs(eif - static_cast<double>(ei_start)) < (0.1/step))
+ {
+ evStart = ei_start;
+ break;
+ }
+ }
+
+ evCount = static_cast<uint>(std::round(180.0 / step)) + 1;
+ if(evCount < 5)
+ {
+ fprintf(stderr, "Incompatible layout (too few uniform elevations).\n");
+ return false;
+ }
+
+ evCounts[fi] = evCount;
+
+ for(uint ei{evStart};ei < evCount;ei++)
+ {
+ const double ev{-90.0 + ei*180.0/(evCount - 1)};
+ const uint azCount{GetUniquelySortedElems(m, aers.data(), 0, { nullptr, &ev, &dist },
+ { 0.1, 0.1, 0.001 }, elems.data())};
+
+ if(ei > 0 && ei < (evCount - 1))
+ {
+ step = GetUniformStepSize(0.1, azCount, elems.data());
+ if(step <= 0.0)
+ {
+ fprintf(stderr, "Incompatible layout (non-uniform azimuths).\n");
+ return false;
+ }
+
+ azCounts[fi*MAX_EV_COUNT + ei] = static_cast<uint>(std::round(360.0 / step));
+ if(azCounts[fi*MAX_EV_COUNT + ei] > MAX_AZ_COUNT)
+ {
+ fprintf(stderr,
+ "Incompatible layout (too many azimuths on elev=%f, rad=%f, %u > %u).\n",
+ ev, dist, azCounts[fi*MAX_EV_COUNT + ei], MAX_AZ_COUNT);
+ return false;
+ }
+ }
+ else if(azCount != 1)
+ {
+ fprintf(stderr, "Incompatible layout (non-singular poles).\n");
+ return false;
+ }
+ else
+ {
+ azCounts[fi*MAX_EV_COUNT + ei] = 1;
+ }
+ }
+
+ for(uint ei{0u};ei < evStart;ei++)
+ azCounts[fi*MAX_EV_COUNT + ei] = azCounts[fi*MAX_EV_COUNT + evCount - ei - 1];
+ }
+ return PrepareHrirData(fdCount, distances, evCounts, azCounts.data(), hData) != 0;
+}
+
+
+bool PrepareSampleRate(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
+{
+ const char *srate_dim{nullptr};
+ const char *srate_units{nullptr};
+ MYSOFA_ARRAY *srate_array{&sofaHrtf->DataSamplingRate};
+ MYSOFA_ATTRIBUTE *srate_attrs{srate_array->attributes};
+ while(srate_attrs)
+ {
+ if(std::string{"DIMENSION_LIST"} == srate_attrs->name)
+ {
+ if(srate_dim)
+ {
+ fprintf(stderr, "Duplicate SampleRate.DIMENSION_LIST\n");
+ return false;
+ }
+ srate_dim = srate_attrs->value;
+ }
+ else if(std::string{"Units"} == srate_attrs->name)
+ {
+ if(srate_units)
+ {
+ fprintf(stderr, "Duplicate SampleRate.Units\n");
+ return false;
+ }
+ srate_units = srate_attrs->value;
+ }
+ else
+ fprintf(stderr, "Unexpected sample rate attribute: %s = %s\n", srate_attrs->name,
+ srate_attrs->value);
+ srate_attrs = srate_attrs->next;
+ }
+ if(!srate_dim)
+ {
+ fprintf(stderr, "Missing sample rate dimensions\n");
+ return false;
+ }
+ if(srate_dim != std::string{"I"})
+ {
+ fprintf(stderr, "Unsupported sample rate dimensions: %s\n", srate_dim);
+ return false;
+ }
+ if(!srate_units)
+ {
+ fprintf(stderr, "Missing sample rate unit type\n");
+ return false;
+ }
+ if(srate_units != std::string{"hertz"})
+ {
+ fprintf(stderr, "Unsupported sample rate unit type: %s\n", srate_units);
+ return false;
+ }
+ /* I dimensions guarantees 1 element, so just extract it. */
+ hData->mIrRate = static_cast<uint>(srate_array->values[0] + 0.5f);
+ if(hData->mIrRate < MIN_RATE || hData->mIrRate > MAX_RATE)
+ {
+ fprintf(stderr, "Sample rate out of range: %u (expected %u to %u)", hData->mIrRate,
+ MIN_RATE, MAX_RATE);
+ return false;
+ }
+ return true;
+}
+
+bool PrepareDelay(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
+{
+ const char *delay_dim{nullptr};
+ MYSOFA_ARRAY *delay_array{&sofaHrtf->DataDelay};
+ MYSOFA_ATTRIBUTE *delay_attrs{delay_array->attributes};
+ while(delay_attrs)
+ {
+ if(std::string{"DIMENSION_LIST"} == delay_attrs->name)
+ {
+ if(delay_dim)
+ {
+ fprintf(stderr, "Duplicate Delay.DIMENSION_LIST\n");
+ return false;
+ }
+ delay_dim = delay_attrs->value;
+ }
+ else
+ fprintf(stderr, "Unexpected delay attribute: %s = %s\n", delay_attrs->name,
+ delay_attrs->value);
+ delay_attrs = delay_attrs->next;
+ }
+ if(!delay_dim)
+ {
+ fprintf(stderr, "Missing delay dimensions\n");
+ /*return false;*/
+ }
+ else if(delay_dim != std::string{"I,R"})
+ {
+ fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim);
+ return false;
+ }
+ else if(hData->mChannelType == CT_STEREO)
+ {
+ /* I,R is 1xChannelCount. Makemhr currently removes any delay constant,
+ * so we can ignore this as long as it's equal.
+ */
+ if(delay_array->values[0] != delay_array->values[1])
+ {
+ fprintf(stderr, "Mismatched delays not supported: %f, %f\n", delay_array->values[0],
+ delay_array->values[1]);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
+{
+ const char *ir_dim{nullptr};
+ MYSOFA_ARRAY *ir_array{&sofaHrtf->DataIR};
+ MYSOFA_ATTRIBUTE *ir_attrs{ir_array->attributes};
+ while(ir_attrs)
+ {
+ if(std::string{"DIMENSION_LIST"} == ir_attrs->name)
+ {
+ if(ir_dim)
+ {
+ fprintf(stderr, "Duplicate IR.DIMENSION_LIST\n");
+ return false;
+ }
+ ir_dim = ir_attrs->value;
+ }
+ else
+ fprintf(stderr, "Unexpected IR attribute: %s = %s\n", ir_attrs->name,
+ ir_attrs->value);
+ ir_attrs = ir_attrs->next;
+ }
+ if(!ir_dim)
+ {
+ fprintf(stderr, "Missing IR dimensions\n");
+ return false;
+ }
+ if(ir_dim != std::string{"M,R,N"})
+ {
+ fprintf(stderr, "Unsupported IR dimensions: %s\n", ir_dim);
+ return false;
+ }
+ return true;
+}
+
+
+/* Calculate the onset time of a HRIR. */
+static double CalcHrirOnset(const uint rate, const uint n, std::vector<double> &upsampled,
+ const double *hrir)
+{
+ {
+ ResamplerT rs;
+ ResamplerSetup(&rs, rate, 10 * rate);
+ ResamplerRun(&rs, n, hrir, 10 * n, upsampled.data());
+ }
+
+ double mag{std::accumulate(upsampled.cbegin(), upsampled.cend(), double{0.0},
+ [](const double magnitude, const double sample) -> double
+ { return std::max(magnitude, std::abs(sample)); })};
+
+ mag *= 0.15;
+ auto iter = std::find_if(upsampled.cbegin(), upsampled.cend(),
+ [mag](const double sample) -> bool { return (std::abs(sample) >= mag); });
+ return static_cast<double>(std::distance(upsampled.cbegin(), iter)) / (10.0*rate);
+}
+
+/* Calculate the magnitude response of a HRIR. */
+static void CalcHrirMagnitude(const uint points, const uint n, std::vector<complex_d> &h,
+ const double *hrir, double *mag)
+{
+ auto iter = std::copy_n(hrir, points, h.begin());
+ std::fill(iter, h.end(), complex_d{0.0, 0.0});
+
+ FftForward(n, h.data());
+ MagnitudeResponse(n, h.data(), mag);
+}
+
+static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
+{
+ const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
+ hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize);
+ double *hrirs = hData->mHrirsBase.data();
+
+ /* Temporary buffers used to calculate the IR's onset and frequency
+ * magnitudes.
+ */
+ auto upsampled = std::vector<double>(10 * hData->mIrPoints);
+ auto htemp = std::vector<complex_d>(hData->mFftSize);
+ auto hrir = std::vector<double>(hData->mFftSize);
+
+ for(uint si{0u};si < sofaHrtf->M;si++)
+ {
+ printf("\rLoading HRIRs... %d of %d", si+1, sofaHrtf->M);
+ fflush(stdout);
+
+ float aer[3]{
+ sofaHrtf->SourcePosition.values[3*si],
+ sofaHrtf->SourcePosition.values[3*si + 1],
+ sofaHrtf->SourcePosition.values[3*si + 2]
+ };
+ mysofa_c2s(aer);
+
+ if(std::abs(aer[1]) >= 89.999f)
+ aer[0] = 0.0f;
+ else
+ aer[0] = std::fmod(360.0f - aer[0], 360.0f);
+
+ auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(),
+ [&aer](const HrirFdT &fld) -> bool
+ {
+ double delta = aer[2] - fld.mDistance;
+ return (std::abs(delta) < 0.001);
+ });
+ if(field == hData->mFds.cend())
+ continue;
+
+ double ef{(90.0+aer[1]) / 180.0 * (field->mEvCount-1)};
+ auto ei = static_cast<int>(std::round(ef));
+ ef = (ef-ei) * 180.0 / (field->mEvCount-1);
+ if(std::abs(ef) >= 0.1) continue;
+
+ double af{aer[0] / 360.0 * field->mEvs[ei].mAzCount};
+ auto ai = static_cast<int>(std::round(af));
+ af = (af-ai) * 360.0 / field->mEvs[ei].mAzCount;
+ ai %= field->mEvs[ei].mAzCount;
+ if(std::abs(af) >= 0.1) continue;
+
+ HrirAzT *azd = &field->mEvs[ei].mAzs[ai];
+ if(azd->mIrs[0] != nullptr)
+ {
+ fprintf(stderr, "Multiple measurements near [ a=%f, e=%f, r=%f ].\n",
+ aer[0], aer[1], aer[2]);
+ return false;
+ }
+
+ for(uint ti{0u};ti < channels;++ti)
+ {
+ std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N],
+ hData->mIrPoints, hrir.begin());
+ azd->mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd->mIndex)];
+ azd->mDelays[ti] = CalcHrirOnset(hData->mIrRate, hData->mIrPoints, upsampled,
+ hrir.data());
+ CalcHrirMagnitude(hData->mIrPoints, hData->mFftSize, htemp, hrir.data(),
+ azd->mIrs[ti]);
+ }
+
+ // TODO: Since some SOFA files contain minimum phase HRIRs,
+ // it would be beneficial to check for per-measurement delays
+ // (when available) to reconstruct the HRTDs.
+ }
+ printf("\n");
+ return true;
+}
+
+struct MySofaHrtfDeleter {
+ void operator()(MYSOFA_HRTF *ptr) { mysofa_free(ptr); }
+};
+using MySofaHrtfPtr = std::unique_ptr<MYSOFA_HRTF,MySofaHrtfDeleter>;
+
+bool LoadSofaFile(const char *filename, const uint fftSize, const uint truncSize,
+ const ChannelModeT chanMode, HrirDataT *hData)
+{
+ int err;
+ MySofaHrtfPtr sofaHrtf{mysofa_load(filename, &err)};
+ if(!sofaHrtf)
+ {
+ fprintf(stdout, "Error: Could not load %s: %s\n", filename, SofaErrorStr(err));
+ return false;
+ }
+
+ /* NOTE: Some valid SOFA files are failing this check. */
+ err = mysofa_check(sofaHrtf.get());
+ if(err != MYSOFA_OK)
+ fprintf(stderr, "Warning: Supposedly malformed source file '%s' (%s).\n", filename,
+ SofaErrorStr(err));
+
+ mysofa_tocartesian(sofaHrtf.get());
+
+ /* Make sure emitter and receiver counts are sane. */
+ if(sofaHrtf->E != 1)
+ {
+ fprintf(stderr, "%u emitters not supported\n", sofaHrtf->E);
+ return false;
+ }
+ if(sofaHrtf->R > 2 || sofaHrtf->R < 1)
+ {
+ fprintf(stderr, "%u receivers not supported\n", sofaHrtf->R);
+ return false;
+ }
+ /* Assume R=2 is a stereo measurement, and R=1 is mono left-ear-only. */
+ if(sofaHrtf->R == 2 && chanMode == CM_AllowStereo)
+ hData->mChannelType = CT_STEREO;
+ else
+ hData->mChannelType = CT_MONO;
+
+ /* Check and set the FFT and IR size. */
+ if(sofaHrtf->N > fftSize)
+ {
+ fprintf(stderr, "Sample points exceeds the FFT size.\n");
+ return false;
+ }
+ if(sofaHrtf->N < truncSize)
+ {
+ fprintf(stderr, "Sample points is below the truncation size.\n");
+ return false;
+ }
+ hData->mIrPoints = sofaHrtf->N;
+ hData->mFftSize = fftSize;
+ hData->mIrSize = std::max(1u + (fftSize/2u), sofaHrtf->N);
+
+ /* Assume a default head radius of 9cm. */
+ hData->mRadius = 0.09;
+
+ if(!PrepareSampleRate(sofaHrtf.get(), hData) || !PrepareDelay(sofaHrtf.get(), hData)
+ || !CheckIrData(sofaHrtf.get()))
+ return false;
+ if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData))
+ return false;
+
+ if(!LoadResponses(sofaHrtf.get(), hData))
+ return false;
+ sofaHrtf = nullptr;
+
+ for(uint fi{0u};fi < hData->mFdCount;fi++)
+ {
+ uint ei{0u};
+ for(;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ uint ai{0u};
+ for(;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
+ if(azd.mIrs[0] != nullptr) break;
+ }
+ if(ai < hData->mFds[fi].mEvs[ei].mAzCount)
+ break;
+ }
+ if(ei >= hData->mFds[fi].mEvCount)
+ {
+ fprintf(stderr, "Missing source references [ %d, *, * ].\n", fi);
+ return false;
+ }
+ hData->mFds[fi].mEvStart = ei;
+ for(;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
+ if(azd.mIrs[0] == nullptr)
+ {
+ fprintf(stderr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai);
+ return false;
+ }
+ }
+ }
+ }
+
+ const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
+ double *hrirs = hData->mHrirsBase.data();
+ for(uint fi{0u};fi < hData->mFdCount;fi++)
+ {
+ for(uint ei{0u};ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
+ for(uint ti{0u};ti < channels;ti++)
+ azd.mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd.mIndex)];
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/utils/makemhr/loadsofa.h b/utils/makemhr/loadsofa.h
new file mode 100644
index 00000000..93bf1704
--- /dev/null
+++ b/utils/makemhr/loadsofa.h
@@ -0,0 +1,10 @@
+#ifndef LOADSOFA_H
+#define LOADSOFA_H
+
+#include "makemhr.h"
+
+
+bool LoadSofaFile(const char *filename, const uint fftSize, const uint truncSize,
+ const ChannelModeT chanMode, HrirDataT *hData);
+
+#endif /* LOADSOFA_H */
diff --git a/utils/makemhr/makemhr.cpp b/utils/makemhr/makemhr.cpp
new file mode 100644
index 00000000..1e28ca4b
--- /dev/null
+++ b/utils/makemhr/makemhr.cpp
@@ -0,0 +1,1797 @@
+/*
+ * HRTF utility for producing and demonstrating the process of creating an
+ * OpenAL Soft compatible HRIR data set.
+ *
+ * Copyright (C) 2011-2019 Christopher Fitzgerald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * --------------------------------------------------------------------------
+ *
+ * A big thanks goes out to all those whose work done in the field of
+ * binaural sound synthesis using measured HRTFs makes this utility and the
+ * OpenAL Soft implementation possible.
+ *
+ * The algorithm for diffuse-field equalization was adapted from the work
+ * done by Rio Emmanuel and Larcher Veronique of IRCAM and Bill Gardner of
+ * MIT Media Laboratory. It operates as follows:
+ *
+ * 1. Take the FFT of each HRIR and only keep the magnitude responses.
+ * 2. Calculate the diffuse-field power-average of all HRIRs weighted by
+ * their contribution to the total surface area covered by their
+ * measurement. This has since been modified to use coverage volume for
+ * multi-field HRIR data sets.
+ * 3. Take the diffuse-field average and limit its magnitude range.
+ * 4. Equalize the responses by using the inverse of the diffuse-field
+ * average.
+ * 5. Reconstruct the minimum-phase responses.
+ * 5. Zero the DC component.
+ * 6. IFFT the result and truncate to the desired-length minimum-phase FIR.
+ *
+ * The spherical head algorithm for calculating propagation delay was adapted
+ * from the paper:
+ *
+ * Modeling Interaural Time Difference Assuming a Spherical Head
+ * Joel David Miller
+ * Music 150, Musical Acoustics, Stanford University
+ * December 2, 2001
+ *
+ * The formulae for calculating the Kaiser window metrics are from the
+ * the textbook:
+ *
+ * Discrete-Time Signal Processing
+ * Alan V. Oppenheim and Ronald W. Schafer
+ * Prentice-Hall Signal Processing Series
+ * 1999
+ */
+
+#define _UNICODE
+#include "config.h"
+
+#include "makemhr.h"
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <cmath>
+#include <complex>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <numeric>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#ifdef HAVE_GETOPT
+#include <unistd.h>
+#else
+#include "../getopt.h"
+#endif
+
+#include "alfstream.h"
+#include "alstring.h"
+#include "loaddef.h"
+#include "loadsofa.h"
+
+#include "win_main_utf8.h"
+
+
+namespace {
+
+using namespace std::placeholders;
+
+} // namespace
+
+#ifndef M_PI
+#define M_PI (3.14159265358979323846)
+#endif
+
+
+// Head model used for calculating the impulse delays.
+enum HeadModelT {
+ HM_NONE,
+ HM_DATASET, // Measure the onset from the dataset.
+ HM_SPHERE // Calculate the onset using a spherical head model.
+};
+
+
+// The epsilon used to maintain signal stability.
+#define EPSILON (1e-9)
+
+// The limits to the FFT window size override on the command line.
+#define MIN_FFTSIZE (65536)
+#define MAX_FFTSIZE (131072)
+
+// The limits to the equalization range limit on the command line.
+#define MIN_LIMIT (2.0)
+#define MAX_LIMIT (120.0)
+
+// The limits to the truncation window size on the command line.
+#define MIN_TRUNCSIZE (16)
+#define MAX_TRUNCSIZE (512)
+
+// The limits to the custom head radius on the command line.
+#define MIN_CUSTOM_RADIUS (0.05)
+#define MAX_CUSTOM_RADIUS (0.15)
+
+// The truncation window size must be a multiple of the below value to allow
+// for vectorized convolution.
+#define MOD_TRUNCSIZE (8)
+
+// The defaults for the command line options.
+#define DEFAULT_FFTSIZE (65536)
+#define DEFAULT_EQUALIZE (1)
+#define DEFAULT_SURFACE (1)
+#define DEFAULT_LIMIT (24.0)
+#define DEFAULT_TRUNCSIZE (32)
+#define DEFAULT_HEAD_MODEL (HM_DATASET)
+#define DEFAULT_CUSTOM_RADIUS (0.0)
+
+// The maximum propagation delay value supported by OpenAL Soft.
+#define MAX_HRTD (63.0)
+
+// The OpenAL Soft HRTF format marker. It stands for minimum-phase head
+// response protocol 02.
+#define MHR_FORMAT ("MinPHR02")
+
+/* Channel index enums. Mono uses LeftChannel only. */
+enum ChannelIndex : uint {
+ LeftChannel = 0u,
+ RightChannel = 1u
+};
+
+
+/* Performs a string substitution. Any case-insensitive occurrences of the
+ * pattern string are replaced with the replacement string. The result is
+ * truncated if necessary.
+ */
+static int StrSubst(const char *in, const char *pat, const char *rep, const size_t maxLen, char *out)
+{
+ size_t inLen, patLen, repLen;
+ size_t si, di;
+ int truncated;
+
+ inLen = strlen(in);
+ patLen = strlen(pat);
+ repLen = strlen(rep);
+ si = 0;
+ di = 0;
+ truncated = 0;
+ while(si < inLen && di < maxLen)
+ {
+ if(patLen <= inLen-si)
+ {
+ if(al::strncasecmp(&in[si], pat, patLen) == 0)
+ {
+ if(repLen > maxLen-di)
+ {
+ repLen = maxLen - di;
+ truncated = 1;
+ }
+ strncpy(&out[di], rep, repLen);
+ si += patLen;
+ di += repLen;
+ }
+ }
+ out[di] = in[si];
+ si++;
+ di++;
+ }
+ if(si < inLen)
+ truncated = 1;
+ out[di] = '\0';
+ return !truncated;
+}
+
+
+/*********************
+ *** Math routines ***
+ *********************/
+
+// Simple clamp routine.
+static double Clamp(const double val, const double lower, const double upper)
+{
+ return std::min(std::max(val, lower), upper);
+}
+
+static inline uint dither_rng(uint *seed)
+{
+ *seed = *seed * 96314165 + 907633515;
+ return *seed;
+}
+
+// Performs a triangular probability density function dither. The input samples
+// should be normalized (-1 to +1).
+static void TpdfDither(double *RESTRICT out, const double *RESTRICT in, const double scale,
+ const uint count, const uint step, uint *seed)
+{
+ static constexpr double PRNG_SCALE = 1.0 / std::numeric_limits<uint>::max();
+
+ for(uint i{0};i < count;i++)
+ {
+ uint prn0{dither_rng(seed)};
+ uint prn1{dither_rng(seed)};
+ *out = std::round(*(in++)*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE));
+ out += step;
+ }
+}
+
+/* Fast Fourier transform routines. The number of points must be a power of
+ * two.
+ */
+
+// Performs bit-reversal ordering.
+static void FftArrange(const uint n, complex_d *inout)
+{
+ // Handle in-place arrangement.
+ uint rk{0u};
+ for(uint k{0u};k < n;k++)
+ {
+ if(rk > k)
+ std::swap(inout[rk], inout[k]);
+
+ uint m{n};
+ while(rk&(m >>= 1))
+ rk &= ~m;
+ rk |= m;
+ }
+}
+
+// Performs the summation.
+static void FftSummation(const uint n, const double s, complex_d *cplx)
+{
+ double pi;
+ uint m, m2;
+ uint i, k, mk;
+
+ pi = s * M_PI;
+ for(m = 1, m2 = 2;m < n; m <<= 1, m2 <<= 1)
+ {
+ // v = Complex (-2.0 * sin (0.5 * pi / m) * sin (0.5 * pi / m), -sin (pi / m))
+ double sm = std::sin(0.5 * pi / m);
+ auto v = complex_d{-2.0*sm*sm, -std::sin(pi / m)};
+ auto w = complex_d{1.0, 0.0};
+ for(i = 0;i < m;i++)
+ {
+ for(k = i;k < n;k += m2)
+ {
+ mk = k + m;
+ auto t = w * cplx[mk];
+ cplx[mk] = cplx[k] - t;
+ cplx[k] = cplx[k] + t;
+ }
+ w += v*w;
+ }
+ }
+}
+
+// Performs a forward FFT.
+void FftForward(const uint n, complex_d *inout)
+{
+ FftArrange(n, inout);
+ FftSummation(n, 1.0, inout);
+}
+
+// Performs an inverse FFT.
+void FftInverse(const uint n, complex_d *inout)
+{
+ FftArrange(n, inout);
+ FftSummation(n, -1.0, inout);
+ double f{1.0 / n};
+ for(uint i{0};i < n;i++)
+ inout[i] *= f;
+}
+
+/* Calculate the complex helical sequence (or discrete-time analytical signal)
+ * of the given input using the Hilbert transform. Given the natural logarithm
+ * of a signal's magnitude response, the imaginary components can be used as
+ * the angles for minimum-phase reconstruction.
+ */
+static void Hilbert(const uint n, complex_d *inout)
+{
+ uint i;
+
+ // Handle in-place operation.
+ for(i = 0;i < n;i++)
+ inout[i].imag(0.0);
+
+ FftInverse(n, inout);
+ for(i = 1;i < (n+1)/2;i++)
+ inout[i] *= 2.0;
+ /* Increment i if n is even. */
+ i += (n&1)^1;
+ for(;i < n;i++)
+ inout[i] = complex_d{0.0, 0.0};
+ FftForward(n, inout);
+}
+
+/* Calculate the magnitude response of the given input. This is used in
+ * place of phase decomposition, since the phase residuals are discarded for
+ * minimum phase reconstruction. The mirrored half of the response is also
+ * discarded.
+ */
+void MagnitudeResponse(const uint n, const complex_d *in, double *out)
+{
+ const uint m = 1 + (n / 2);
+ uint i;
+ for(i = 0;i < m;i++)
+ out[i] = std::max(std::abs(in[i]), EPSILON);
+}
+
+/* Apply a range limit (in dB) to the given magnitude response. This is used
+ * to adjust the effects of the diffuse-field average on the equalization
+ * process.
+ */
+static void LimitMagnitudeResponse(const uint n, const uint m, const double limit, const double *in, double *out)
+{
+ double halfLim;
+ uint i, lower, upper;
+ double ave;
+
+ halfLim = limit / 2.0;
+ // Convert the response to dB.
+ for(i = 0;i < m;i++)
+ out[i] = 20.0 * std::log10(in[i]);
+ // Use six octaves to calculate the average magnitude of the signal.
+ lower = (static_cast<uint>(std::ceil(n / std::pow(2.0, 8.0)))) - 1;
+ upper = (static_cast<uint>(std::floor(n / std::pow(2.0, 2.0)))) - 1;
+ ave = 0.0;
+ for(i = lower;i <= upper;i++)
+ ave += out[i];
+ ave /= upper - lower + 1;
+ // Keep the response within range of the average magnitude.
+ for(i = 0;i < m;i++)
+ out[i] = Clamp(out[i], ave - halfLim, ave + halfLim);
+ // Convert the response back to linear magnitude.
+ for(i = 0;i < m;i++)
+ out[i] = std::pow(10.0, out[i] / 20.0);
+}
+
+/* Reconstructs the minimum-phase component for the given magnitude response
+ * of a signal. This is equivalent to phase recomposition, sans the missing
+ * residuals (which were discarded). The mirrored half of the response is
+ * reconstructed.
+ */
+static void MinimumPhase(const uint n, const double *in, complex_d *out)
+{
+ const uint m = 1 + (n / 2);
+ std::vector<double> mags(n);
+
+ uint i;
+ for(i = 0;i < m;i++)
+ {
+ mags[i] = std::max(EPSILON, in[i]);
+ out[i] = complex_d{std::log(mags[i]), 0.0};
+ }
+ for(;i < n;i++)
+ {
+ mags[i] = mags[n - i];
+ out[i] = out[n - i];
+ }
+ Hilbert(n, out);
+ // Remove any DC offset the filter has.
+ mags[0] = EPSILON;
+ for(i = 0;i < n;i++)
+ {
+ auto a = std::exp(complex_d{0.0, out[i].imag()});
+ out[i] = complex_d{mags[i], 0.0} * a;
+ }
+}
+
+
+/***************************
+ *** Resampler functions ***
+ ***************************/
+
+/* This is the normalized cardinal sine (sinc) function.
+ *
+ * sinc(x) = { 1, x = 0
+ * { sin(pi x) / (pi x), otherwise.
+ */
+static double Sinc(const double x)
+{
+ if(std::abs(x) < EPSILON)
+ return 1.0;
+ return std::sin(M_PI * x) / (M_PI * x);
+}
+
+/* The zero-order modified Bessel function of the first kind, used for the
+ * Kaiser window.
+ *
+ * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k)
+ * = sum_{k=0}^inf ((x / 2)^k / k!)^2
+ */
+static double BesselI_0(const double x)
+{
+ double term, sum, x2, y, last_sum;
+ int k;
+
+ // Start at k=1 since k=0 is trivial.
+ term = 1.0;
+ sum = 1.0;
+ x2 = x/2.0;
+ k = 1;
+
+ // Let the integration converge until the term of the sum is no longer
+ // significant.
+ do {
+ y = x2 / k;
+ k++;
+ last_sum = sum;
+ term *= y * y;
+ sum += term;
+ } while(sum != last_sum);
+ return sum;
+}
+
+/* Calculate a Kaiser window from the given beta value and a normalized k
+ * [-1, 1].
+ *
+ * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1
+ * { 0, elsewhere.
+ *
+ * Where k can be calculated as:
+ *
+ * k = i / l, where -l <= i <= l.
+ *
+ * or:
+ *
+ * k = 2 i / M - 1, where 0 <= i <= M.
+ */
+static double Kaiser(const double b, const double k)
+{
+ if(!(k >= -1.0 && k <= 1.0))
+ return 0.0;
+ return BesselI_0(b * std::sqrt(1.0 - k*k)) / BesselI_0(b);
+}
+
+// Calculates the greatest common divisor of a and b.
+static uint Gcd(uint x, uint y)
+{
+ while(y > 0)
+ {
+ uint z{y};
+ y = x % y;
+ x = z;
+ }
+ return x;
+}
+
+/* Calculates the size (order) of the Kaiser window. Rejection is in dB and
+ * the transition width is normalized frequency (0.5 is nyquist).
+ *
+ * M = { ceil((r - 7.95) / (2.285 2 pi f_t)), r > 21
+ * { ceil(5.79 / 2 pi f_t), r <= 21.
+ *
+ */
+static uint CalcKaiserOrder(const double rejection, const double transition)
+{
+ double w_t = 2.0 * M_PI * transition;
+ if(rejection > 21.0)
+ return static_cast<uint>(std::ceil((rejection - 7.95) / (2.285 * w_t)));
+ return static_cast<uint>(std::ceil(5.79 / w_t));
+}
+
+// Calculates the beta value of the Kaiser window. Rejection is in dB.
+static double CalcKaiserBeta(const double rejection)
+{
+ if(rejection > 50.0)
+ return 0.1102 * (rejection - 8.7);
+ if(rejection >= 21.0)
+ return (0.5842 * std::pow(rejection - 21.0, 0.4)) +
+ (0.07886 * (rejection - 21.0));
+ return 0.0;
+}
+
+/* Calculates a point on the Kaiser-windowed sinc filter for the given half-
+ * width, beta, gain, and cutoff. The point is specified in non-normalized
+ * samples, from 0 to M, where M = (2 l + 1).
+ *
+ * w(k) 2 p f_t sinc(2 f_t x)
+ *
+ * x -- centered sample index (i - l)
+ * k -- normalized and centered window index (x / l)
+ * w(k) -- window function (Kaiser)
+ * p -- gain compensation factor when sampling
+ * f_t -- normalized center frequency (or cutoff; 0.5 is nyquist)
+ */
+static double SincFilter(const uint l, const double b, const double gain, const double cutoff, const uint i)
+{
+ return Kaiser(b, static_cast<double>(i - l) / l) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * (i - l));
+}
+
+/* This is a polyphase sinc-filtered resampler.
+ *
+ * Upsample Downsample
+ *
+ * p/q = 3/2 p/q = 3/5
+ *
+ * M-+-+-+-> M-+-+-+->
+ * -------------------+ ---------------------+
+ * p s * f f f f|f| | p s * f f f f f |
+ * | 0 * 0 0 0|0|0 | | 0 * 0 0 0 0|0| |
+ * v 0 * 0 0|0|0 0 | v 0 * 0 0 0|0|0 |
+ * s * f|f|f f f | s * f f|f|f f |
+ * 0 * |0|0 0 0 0 | 0 * 0|0|0 0 0 |
+ * --------+=+--------+ 0 * |0|0 0 0 0 |
+ * d . d .|d|. d . d ----------+=+--------+
+ * d . . . .|d|. . . .
+ * q->
+ * q-+-+-+->
+ *
+ * P_f(i,j) = q i mod p + pj
+ * P_s(i,j) = floor(q i / p) - j
+ * d[i=0..N-1] = sum_{j=0}^{floor((M - 1) / p)} {
+ * { f[P_f(i,j)] s[P_s(i,j)], P_f(i,j) < M
+ * { 0, P_f(i,j) >= M. }
+ */
+
+// Calculate the resampling metrics and build the Kaiser-windowed sinc filter
+// that's used to cut frequencies above the destination nyquist.
+void ResamplerSetup(ResamplerT *rs, const uint srcRate, const uint dstRate)
+{
+ const uint gcd{Gcd(srcRate, dstRate)};
+ rs->mP = dstRate / gcd;
+ rs->mQ = srcRate / gcd;
+
+ /* The cutoff is adjusted by half the transition width, so the transition
+ * ends before the nyquist (0.5). Both are scaled by the downsampling
+ * factor.
+ */
+ double cutoff, width;
+ if(rs->mP > rs->mQ)
+ {
+ cutoff = 0.475 / rs->mP;
+ width = 0.05 / rs->mP;
+ }
+ else
+ {
+ cutoff = 0.475 / rs->mQ;
+ width = 0.05 / rs->mQ;
+ }
+ // A rejection of -180 dB is used for the stop band. Round up when
+ // calculating the left offset to avoid increasing the transition width.
+ const uint l{(CalcKaiserOrder(180.0, width)+1) / 2};
+ const double beta{CalcKaiserBeta(180.0)};
+ rs->mM = l*2 + 1;
+ rs->mL = l;
+ rs->mF.resize(rs->mM);
+ for(uint i{0};i < rs->mM;i++)
+ rs->mF[i] = SincFilter(l, beta, rs->mP, cutoff, i);
+}
+
+// Perform the upsample-filter-downsample resampling operation using a
+// polyphase filter implementation.
+void ResamplerRun(ResamplerT *rs, const uint inN, const double *in, const uint outN, double *out)
+{
+ const uint p = rs->mP, q = rs->mQ, m = rs->mM, l = rs->mL;
+ std::vector<double> workspace;
+ const double *f = rs->mF.data();
+ uint j_f, j_s;
+ double *work;
+ uint i;
+
+ if(outN == 0)
+ return;
+
+ // Handle in-place operation.
+ if(in == out)
+ {
+ workspace.resize(outN);
+ work = workspace.data();
+ }
+ else
+ work = out;
+ // Resample the input.
+ for(i = 0;i < outN;i++)
+ {
+ double r = 0.0;
+ // Input starts at l to compensate for the filter delay. This will
+ // drop any build-up from the first half of the filter.
+ j_f = (l + (q * i)) % p;
+ j_s = (l + (q * i)) / p;
+ while(j_f < m)
+ {
+ // Only take input when 0 <= j_s < inN. This single unsigned
+ // comparison catches both cases.
+ if(j_s < inN)
+ r += f[j_f] * in[j_s];
+ j_f += p;
+ j_s--;
+ }
+ work[i] = r;
+ }
+ // Clean up after in-place operation.
+ if(work != out)
+ {
+ for(i = 0;i < outN;i++)
+ out[i] = work[i];
+ }
+}
+
+
+/***************************
+ *** File storage output ***
+ ***************************/
+
+// Write an ASCII string to a file.
+static int WriteAscii(const char *out, FILE *fp, const char *filename)
+{
+ size_t len;
+
+ len = strlen(out);
+ if(fwrite(out, 1, len, fp) != len)
+ {
+ fclose(fp);
+ fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename);
+ return 0;
+ }
+ return 1;
+}
+
+// Write a binary value of the given byte order and byte size to a file,
+// loading it from a 32-bit unsigned integer.
+static int WriteBin4(const uint bytes, const uint32_t in, FILE *fp, const char *filename)
+{
+ uint8_t out[4];
+ uint i;
+
+ for(i = 0;i < bytes;i++)
+ out[i] = (in>>(i*8)) & 0x000000FF;
+
+ if(fwrite(out, 1, bytes, fp) != bytes)
+ {
+ fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename);
+ return 0;
+ }
+ return 1;
+}
+
+// Store the OpenAL Soft HRTF data set.
+static int StoreMhr(const HrirDataT *hData, const char *filename)
+{
+ uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
+ uint n = hData->mIrPoints;
+ FILE *fp;
+ uint fi, ei, ai, i;
+ uint dither_seed = 22222;
+
+ if((fp=fopen(filename, "wb")) == nullptr)
+ {
+ fprintf(stderr, "\nError: Could not open MHR file '%s'.\n", filename);
+ return 0;
+ }
+ if(!WriteAscii(MHR_FORMAT, fp, filename))
+ return 0;
+ if(!WriteBin4(4, hData->mIrRate, fp, filename))
+ return 0;
+ if(!WriteBin4(1, static_cast<uint32_t>(hData->mSampleType), fp, filename))
+ return 0;
+ if(!WriteBin4(1, static_cast<uint32_t>(hData->mChannelType), fp, filename))
+ return 0;
+ if(!WriteBin4(1, hData->mIrPoints, fp, filename))
+ return 0;
+ if(!WriteBin4(1, hData->mFdCount, fp, filename))
+ return 0;
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ auto fdist = static_cast<uint32_t>(std::round(1000.0 * hData->mFds[fi].mDistance));
+ if(!WriteBin4(2, fdist, fp, filename))
+ return 0;
+ if(!WriteBin4(1, hData->mFds[fi].mEvCount, fp, filename))
+ return 0;
+ for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ if(!WriteBin4(1, hData->mFds[fi].mEvs[ei].mAzCount, fp, filename))
+ return 0;
+ }
+ }
+
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ const double scale = (hData->mSampleType == ST_S16) ? 32767.0 :
+ ((hData->mSampleType == ST_S24) ? 8388607.0 : 0.0);
+ const uint bps = (hData->mSampleType == ST_S16) ? 2 :
+ ((hData->mSampleType == ST_S24) ? 3 : 0);
+
+ for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+ double out[2 * MAX_TRUNCSIZE];
+
+ TpdfDither(out, azd->mIrs[0], scale, n, channels, &dither_seed);
+ if(hData->mChannelType == CT_STEREO)
+ TpdfDither(out+1, azd->mIrs[1], scale, n, channels, &dither_seed);
+ for(i = 0;i < (channels * n);i++)
+ {
+ int v = static_cast<int>(Clamp(out[i], -scale-1.0, scale));
+ if(!WriteBin4(bps, static_cast<uint32_t>(v), fp, filename))
+ return 0;
+ }
+ }
+ }
+ }
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ const HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
+ int v = static_cast<int>(std::min(std::round(hData->mIrRate * azd.mDelays[0]), MAX_HRTD));
+
+ if(!WriteBin4(1, static_cast<uint32_t>(v), fp, filename))
+ return 0;
+ if(hData->mChannelType == CT_STEREO)
+ {
+ v = static_cast<int>(std::min(std::round(hData->mIrRate * azd.mDelays[1]), MAX_HRTD));
+
+ if(!WriteBin4(1, static_cast<uint32_t>(v), fp, filename))
+ return 0;
+ }
+ }
+ }
+ }
+ fclose(fp);
+ return 1;
+}
+
+
+/***********************
+ *** HRTF processing ***
+ ***********************/
+
+/* Balances the maximum HRIR magnitudes of multi-field data sets by
+ * independently normalizing each field in relation to the overall maximum.
+ * This is done to ignore distance attenuation.
+ */
+static void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, const uint m)
+{
+ double maxMags[MAX_FD_COUNT];
+ uint fi, ei, ai, ti, i;
+
+ double maxMag{0.0};
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ maxMags[fi] = 0.0;
+
+ for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+ for(ti = 0;ti < channels;ti++)
+ {
+ for(i = 0;i < m;i++)
+ maxMags[fi] = std::max(azd->mIrs[ti][i], maxMags[fi]);
+ }
+ }
+ }
+
+ maxMag = std::max(maxMags[fi], maxMag);
+ }
+
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ const double magFactor{maxMag / maxMags[fi]};
+
+ for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+ for(ti = 0;ti < channels;ti++)
+ {
+ for(i = 0;i < m;i++)
+ azd->mIrs[ti][i] *= magFactor;
+ }
+ }
+ }
+ }
+}
+
+/* Calculate the contribution of each HRIR to the diffuse-field average based
+ * on its coverage volume. All volumes are centered at the spherical HRIR
+ * coordinates and measured by extruded solid angle.
+ */
+static void CalculateDfWeights(const HrirDataT *hData, double *weights)
+{
+ double sum, innerRa, outerRa, evs, ev, upperEv, lowerEv;
+ double solidAngle, solidVolume;
+ uint fi, ei;
+
+ sum = 0.0;
+ // The head radius acts as the limit for the inner radius.
+ innerRa = hData->mRadius;
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ // Each volume ends half way between progressive field measurements.
+ if((fi + 1) < hData->mFdCount)
+ outerRa = 0.5f * (hData->mFds[fi].mDistance + hData->mFds[fi + 1].mDistance);
+ // The final volume has its limit extended to some practical value.
+ // This is done to emphasize the far-field responses in the average.
+ else
+ outerRa = 10.0f;
+
+ evs = M_PI / 2.0 / (hData->mFds[fi].mEvCount - 1);
+ for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ // For each elevation, calculate the upper and lower limits of
+ // the patch band.
+ ev = hData->mFds[fi].mEvs[ei].mElevation;
+ lowerEv = std::max(-M_PI / 2.0, ev - evs);
+ upperEv = std::min(M_PI / 2.0, ev + evs);
+ // Calculate the surface area of the patch band.
+ solidAngle = 2.0 * M_PI * (std::sin(upperEv) - std::sin(lowerEv));
+ // Then the volume of the extruded patch band.
+ solidVolume = solidAngle * (std::pow(outerRa, 3.0) - std::pow(innerRa, 3.0)) / 3.0;
+ // Each weight is the volume of one extruded patch.
+ weights[(fi * MAX_EV_COUNT) + ei] = solidVolume / hData->mFds[fi].mEvs[ei].mAzCount;
+ // Sum the total coverage volume of the HRIRs for all fields.
+ sum += solidAngle;
+ }
+
+ innerRa = outerRa;
+ }
+
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ // Normalize the weights given the total surface coverage for all
+ // fields.
+ for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
+ weights[(fi * MAX_EV_COUNT) + ei] /= sum;
+ }
+}
+
+/* Calculate the diffuse-field average from the given magnitude responses of
+ * the HRIR set. Weighting can be applied to compensate for the varying
+ * coverage of each HRIR. The final average can then be limited by the
+ * specified magnitude range (in positive dB; 0.0 to skip).
+ */
+static void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, const uint m, const int weighted, const double limit, double *dfa)
+{
+ std::vector<double> weights(hData->mFdCount * MAX_EV_COUNT);
+ uint count, ti, fi, ei, i, ai;
+
+ if(weighted)
+ {
+ // Use coverage weighting to calculate the average.
+ CalculateDfWeights(hData, weights.data());
+ }
+ else
+ {
+ double weight;
+
+ // If coverage weighting is not used, the weights still need to be
+ // averaged by the number of existing HRIRs.
+ count = hData->mIrCount;
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ for(ei = 0;ei < hData->mFds[fi].mEvStart;ei++)
+ count -= hData->mFds[fi].mEvs[ei].mAzCount;
+ }
+ weight = 1.0 / count;
+
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
+ weights[(fi * MAX_EV_COUNT) + ei] = weight;
+ }
+ }
+ for(ti = 0;ti < channels;ti++)
+ {
+ for(i = 0;i < m;i++)
+ dfa[(ti * m) + i] = 0.0;
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+ // Get the weight for this HRIR's contribution.
+ double weight = weights[(fi * MAX_EV_COUNT) + ei];
+
+ // Add this HRIR's weighted power average to the total.
+ for(i = 0;i < m;i++)
+ dfa[(ti * m) + i] += weight * azd->mIrs[ti][i] * azd->mIrs[ti][i];
+ }
+ }
+ }
+ // Finish the average calculation and keep it from being too small.
+ for(i = 0;i < m;i++)
+ dfa[(ti * m) + i] = std::max(sqrt(dfa[(ti * m) + i]), EPSILON);
+ // Apply a limit to the magnitude range of the diffuse-field average
+ // if desired.
+ if(limit > 0.0)
+ LimitMagnitudeResponse(hData->mFftSize, m, limit, &dfa[ti * m], &dfa[ti * m]);
+ }
+}
+
+// Perform diffuse-field equalization on the magnitude responses of the HRIR
+// set using the given average response.
+static void DiffuseFieldEqualize(const uint channels, const uint m, const double *dfa, const HrirDataT *hData)
+{
+ uint ti, fi, ei, ai, i;
+
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+
+ for(ti = 0;ti < channels;ti++)
+ {
+ for(i = 0;i < m;i++)
+ azd->mIrs[ti][i] /= dfa[(ti * m) + i];
+ }
+ }
+ }
+ }
+}
+
+/* Perform minimum-phase reconstruction using the magnitude responses of the
+ * HRIR set. Work is delegated to this struct, which runs asynchronously on one
+ * or more threads (sharing the same reconstructor object).
+ */
+struct HrirReconstructor {
+ std::vector<double*> mIrs;
+ std::atomic<size_t> mCurrent;
+ std::atomic<size_t> mDone;
+ uint mFftSize;
+ uint mIrPoints;
+
+ void Worker()
+ {
+ auto h = std::vector<complex_d>(mFftSize);
+
+ while(1)
+ {
+ /* Load the current index to process. */
+ size_t idx{mCurrent.load()};
+ do {
+ /* If the index is at the end, we're done. */
+ if(idx >= mIrs.size())
+ return;
+ /* Otherwise, increment the current index atomically so other
+ * threads know to go to the next one. If this call fails, the
+ * current index was just changed by another thread and the new
+ * value is loaded into idx, which we'll recheck.
+ */
+ } while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed));
+
+ /* Now do the reconstruction, and apply the inverse FFT to get the
+ * time-domain response.
+ */
+ MinimumPhase(mFftSize, mIrs[idx], h.data());
+ FftInverse(mFftSize, h.data());
+ for(uint i{0u};i < mIrPoints;++i)
+ mIrs[idx][i] = h[i].real();
+
+ /* Increment the number of IRs done. */
+ mDone.fetch_add(1);
+ }
+ }
+};
+
+static void ReconstructHrirs(const HrirDataT *hData)
+{
+ const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
+
+ /* Count the number of IRs to process (excluding elevations that will be
+ * synthesized later).
+ */
+ size_t total{hData->mIrCount};
+ for(uint fi{0u};fi < hData->mFdCount;fi++)
+ {
+ for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++)
+ total -= hData->mFds[fi].mEvs[ei].mAzCount;
+ }
+ total *= channels;
+
+ /* Set up the reconstructor with the needed size info and pointers to the
+ * IRs to process.
+ */
+ HrirReconstructor reconstructor;
+ reconstructor.mIrs.reserve(total);
+ reconstructor.mCurrent.store(0, std::memory_order_relaxed);
+ reconstructor.mDone.store(0, std::memory_order_relaxed);
+ reconstructor.mFftSize = hData->mFftSize;
+ reconstructor.mIrPoints = hData->mIrPoints;
+ for(uint fi{0u};fi < hData->mFdCount;fi++)
+ {
+ const HrirFdT &field = hData->mFds[fi];
+ for(uint ei{field.mEvStart};ei < field.mEvCount;ei++)
+ {
+ const HrirEvT &elev = field.mEvs[ei];
+ for(uint ai{0u};ai < elev.mAzCount;ai++)
+ {
+ const HrirAzT &azd = elev.mAzs[ai];
+ for(uint ti{0u};ti < channels;ti++)
+ reconstructor.mIrs.push_back(azd.mIrs[ti]);
+ }
+ }
+ }
+
+ /* Launch two threads to work on reconstruction. */
+ std::thread thrd1{std::mem_fn(&HrirReconstructor::Worker), &reconstructor};
+ std::thread thrd2{std::mem_fn(&HrirReconstructor::Worker), &reconstructor};
+
+ /* Keep track of the number of IRs done, periodically reporting it. */
+ size_t count;
+ while((count=reconstructor.mDone.load()) != total)
+ {
+ size_t pcdone{count * 100 / total};
+
+ printf("\r%3zu%% done (%zu of %zu)", pcdone, count, total);
+ fflush(stdout);
+
+ std::this_thread::sleep_for(std::chrono::milliseconds{50});
+ }
+ size_t pcdone{count * 100 / total};
+ printf("\r%3zu%% done (%zu of %zu)\n", pcdone, count, total);
+
+ if(thrd2.joinable()) thrd2.join();
+ if(thrd1.joinable()) thrd1.join();
+}
+
+// Resamples the HRIRs for use at the given sampling rate.
+static void ResampleHrirs(const uint rate, HrirDataT *hData)
+{
+ uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
+ uint n = hData->mIrPoints;
+ uint ti, fi, ei, ai;
+ ResamplerT rs;
+
+ ResamplerSetup(&rs, hData->mIrRate, rate);
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+ for(ti = 0;ti < channels;ti++)
+ ResamplerRun(&rs, n, azd->mIrs[ti], n, azd->mIrs[ti]);
+ }
+ }
+ }
+ hData->mIrRate = rate;
+}
+
+/* Given field and elevation indices and an azimuth, calculate the indices of
+ * the two HRIRs that bound the coordinate along with a factor for
+ * calculating the continuous HRIR using interpolation.
+ */
+static void CalcAzIndices(const HrirFdT &field, const uint ei, const double az, uint *a0, uint *a1, double *af)
+{
+ double f{(2.0*M_PI + az) * field.mEvs[ei].mAzCount / (2.0*M_PI)};
+ uint i{static_cast<uint>(f) % field.mEvs[ei].mAzCount};
+
+ f -= std::floor(f);
+ *a0 = i;
+ *a1 = (i + 1) % field.mEvs[ei].mAzCount;
+ *af = f;
+}
+
+/* Synthesize any missing onset timings at the bottom elevations of each field.
+ * This just mirrors some top elevations for the bottom, and blends the
+ * remaining elevations (not an accurate model).
+ */
+static void SynthesizeOnsets(HrirDataT *hData)
+{
+ const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
+
+ auto proc_field = [channels](HrirFdT &field) -> void
+ {
+ /* Get the starting elevation from the measurements, and use it as the
+ * upper elevation limit for what needs to be calculated.
+ */
+ const uint upperElevReal{field.mEvStart};
+ if(upperElevReal <= 0) return;
+
+ /* Get the lowest half of the missing elevations' delays by mirroring
+ * the top elevation delays. The responses are on a spherical grid
+ * centered between the ears, so these should align.
+ */
+ uint ei{};
+ if(channels > 1)
+ {
+ /* Take the polar opposite position of the desired measurement and
+ * swap the ears.
+ */
+ field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvCount-1].mAzs[0].mDelays[1];
+ field.mEvs[0].mAzs[0].mDelays[1] = field.mEvs[field.mEvCount-1].mAzs[0].mDelays[0];
+ for(ei = 1u;ei < (upperElevReal+1)/2;++ei)
+ {
+ const uint topElev{field.mEvCount-ei-1};
+
+ for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++)
+ {
+ uint a0, a1;
+ double af;
+
+ /* Rotate this current azimuth by a half-circle, and lookup
+ * the mirrored elevation to find the indices for the polar
+ * opposite position (may need blending).
+ */
+ const double az{field.mEvs[ei].mAzs[ai].mAzimuth + M_PI};
+ CalcAzIndices(field, topElev, az, &a0, &a1, &af);
+
+ /* Blend the delays, and again, swap the ears. */
+ field.mEvs[ei].mAzs[ai].mDelays[0] = Lerp(
+ field.mEvs[topElev].mAzs[a0].mDelays[1],
+ field.mEvs[topElev].mAzs[a1].mDelays[1], af);
+ field.mEvs[ei].mAzs[ai].mDelays[1] = Lerp(
+ field.mEvs[topElev].mAzs[a0].mDelays[0],
+ field.mEvs[topElev].mAzs[a1].mDelays[0], af);
+ }
+ }
+ }
+ else
+ {
+ field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvCount-1].mAzs[0].mDelays[0];
+ for(ei = 1u;ei < (upperElevReal+1)/2;++ei)
+ {
+ const uint topElev{field.mEvCount-ei-1};
+
+ for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++)
+ {
+ uint a0, a1;
+ double af;
+
+ /* For mono data sets, mirror the azimuth front<->back
+ * since the other ear is a mirror of what we have (e.g.
+ * the left ear's back-left is simulated with the right
+ * ear's front-right, which uses the left ear's front-left
+ * measurement).
+ */
+ double az{field.mEvs[ei].mAzs[ai].mAzimuth};
+ if(az <= M_PI) az = M_PI - az;
+ else az = (M_PI*2.0)-az + M_PI;
+ CalcAzIndices(field, topElev, az, &a0, &a1, &af);
+
+ field.mEvs[ei].mAzs[ai].mDelays[0] = Lerp(
+ field.mEvs[topElev].mAzs[a0].mDelays[0],
+ field.mEvs[topElev].mAzs[a1].mDelays[0], af);
+ }
+ }
+ }
+ /* Record the lowest elevation filled in with the mirrored top. */
+ const uint lowerElevFake{ei-1u};
+
+ /* Fill in the remaining delays using bilinear interpolation. This
+ * helps smooth the transition back to the real delays.
+ */
+ for(;ei < upperElevReal;++ei)
+ {
+ const double ef{(field.mEvs[upperElevReal].mElevation - field.mEvs[ei].mElevation) /
+ (field.mEvs[upperElevReal].mElevation - field.mEvs[lowerElevFake].mElevation)};
+
+ for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++)
+ {
+ uint a0, a1, a2, a3;
+ double af0, af1;
+
+ double az{field.mEvs[ei].mAzs[ai].mAzimuth};
+ CalcAzIndices(field, upperElevReal, az, &a0, &a1, &af0);
+ CalcAzIndices(field, lowerElevFake, az, &a2, &a3, &af1);
+ double blend[4]{
+ (1.0-ef) * (1.0-af0),
+ (1.0-ef) * ( af0),
+ ( ef) * (1.0-af1),
+ ( ef) * ( af1)
+ };
+
+ for(uint ti{0u};ti < channels;ti++)
+ {
+ field.mEvs[ei].mAzs[ai].mDelays[ti] =
+ field.mEvs[upperElevReal].mAzs[a0].mDelays[ti]*blend[0] +
+ field.mEvs[upperElevReal].mAzs[a1].mDelays[ti]*blend[1] +
+ field.mEvs[lowerElevFake].mAzs[a2].mDelays[ti]*blend[2] +
+ field.mEvs[lowerElevFake].mAzs[a3].mDelays[ti]*blend[3];
+ }
+ }
+ }
+ };
+ std::for_each(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, proc_field);
+}
+
+/* Attempt to synthesize any missing HRIRs at the bottom elevations of each
+ * field. Right now this just blends the lowest elevation HRIRs together and
+ * applies some attenuation and high frequency damping. It is a simple, if
+ * inaccurate model.
+ */
+static void SynthesizeHrirs(HrirDataT *hData)
+{
+ const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
+ const uint irSize{hData->mIrPoints};
+ const double beta{3.5e-6 * hData->mIrRate};
+
+ auto proc_field = [channels,irSize,beta](HrirFdT &field) -> void
+ {
+ const uint oi{field.mEvStart};
+ if(oi <= 0) return;
+
+ for(uint ti{0u};ti < channels;ti++)
+ {
+ for(uint i{0u};i < irSize;i++)
+ field.mEvs[0].mAzs[0].mIrs[ti][i] = 0.0;
+ /* Blend the lowest defined elevation's responses for an average
+ * -90 degree elevation response.
+ */
+ double blend_count{0.0};
+ for(uint ai{0u};ai < field.mEvs[oi].mAzCount;ai++)
+ {
+ /* Only include the left responses for the left ear, and the
+ * right responses for the right ear. This removes the cross-
+ * talk that shouldn't exist for the -90 degree elevation
+ * response (and would be mistimed anyway). NOTE: Azimuth goes
+ * from 0...2pi rather than -pi...+pi (0 in front, clockwise).
+ */
+ if(std::abs(field.mEvs[oi].mAzs[ai].mAzimuth) < EPSILON ||
+ (ti == LeftChannel && field.mEvs[oi].mAzs[ai].mAzimuth > M_PI-EPSILON) ||
+ (ti == RightChannel && field.mEvs[oi].mAzs[ai].mAzimuth < M_PI+EPSILON))
+ {
+ for(uint i{0u};i < irSize;i++)
+ field.mEvs[0].mAzs[0].mIrs[ti][i] += field.mEvs[oi].mAzs[ai].mIrs[ti][i];
+ blend_count += 1.0;
+ }
+ }
+ if(blend_count > 0.0)
+ {
+ for(uint i{0u};i < irSize;i++)
+ field.mEvs[0].mAzs[0].mIrs[ti][i] /= blend_count;
+ }
+
+ for(uint ei{1u};ei < field.mEvStart;ei++)
+ {
+ const double of{static_cast<double>(ei) / field.mEvStart};
+ const double b{(1.0 - of) * beta};
+ for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++)
+ {
+ uint a0, a1;
+ double af;
+
+ CalcAzIndices(field, oi, field.mEvs[ei].mAzs[ai].mAzimuth, &a0, &a1, &af);
+ double lp[4]{};
+ for(uint i{0u};i < irSize;i++)
+ {
+ /* Blend the two defined HRIRs closest to this azimuth,
+ * then blend that with the synthesized -90 elevation.
+ */
+ const double s1{Lerp(field.mEvs[oi].mAzs[a0].mIrs[ti][i],
+ field.mEvs[oi].mAzs[a1].mIrs[ti][i], af)};
+ const double s0{Lerp(field.mEvs[0].mAzs[0].mIrs[ti][i], s1, of)};
+ /* Apply a low-pass to simulate body occlusion. */
+ lp[0] = Lerp(s0, lp[0], b);
+ lp[1] = Lerp(lp[0], lp[1], b);
+ lp[2] = Lerp(lp[1], lp[2], b);
+ lp[3] = Lerp(lp[2], lp[3], b);
+ field.mEvs[ei].mAzs[ai].mIrs[ti][i] = lp[3];
+ }
+ }
+ }
+ const double b{beta};
+ double lp[4]{};
+ for(uint i{0u};i < irSize;i++)
+ {
+ const double s0{field.mEvs[0].mAzs[0].mIrs[ti][i]};
+ lp[0] = Lerp(s0, lp[0], b);
+ lp[1] = Lerp(lp[0], lp[1], b);
+ lp[2] = Lerp(lp[1], lp[2], b);
+ lp[3] = Lerp(lp[2], lp[3], b);
+ field.mEvs[0].mAzs[0].mIrs[ti][i] = lp[3];
+ }
+ }
+ field.mEvStart = 0;
+ };
+ std::for_each(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, proc_field);
+}
+
+// The following routines assume a full set of HRIRs for all elevations.
+
+// Normalize the HRIR set and slightly attenuate the result.
+static void NormalizeHrirs(HrirDataT *hData)
+{
+ const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
+ const uint irSize{hData->mIrPoints};
+
+ /* Find the maximum amplitude and RMS out of all the IRs. */
+ struct LevelPair { double amp, rms; };
+ auto proc0_field = [channels,irSize](const LevelPair levels0, const HrirFdT &field) -> LevelPair
+ {
+ auto proc_elev = [channels,irSize](const LevelPair levels1, const HrirEvT &elev) -> LevelPair
+ {
+ auto proc_azi = [channels,irSize](const LevelPair levels2, const HrirAzT &azi) -> LevelPair
+ {
+ auto proc_channel = [irSize](const LevelPair levels3, const double *ir) -> LevelPair
+ {
+ /* Calculate the peak amplitude and RMS of this IR. */
+ auto current = std::accumulate(ir, ir+irSize, LevelPair{0.0, 0.0},
+ [](const LevelPair cur, const double impulse) -> LevelPair
+ {
+ return {std::max(std::abs(impulse), cur.amp),
+ cur.rms + impulse*impulse};
+ });
+ current.rms = std::sqrt(current.rms / irSize);
+
+ /* Accumulate levels by taking the maximum amplitude and RMS. */
+ return LevelPair{std::max(current.amp, levels3.amp),
+ std::max(current.rms, levels3.rms)};
+ };
+ return std::accumulate(azi.mIrs, azi.mIrs+channels, levels2, proc_channel);
+ };
+ return std::accumulate(elev.mAzs, elev.mAzs+elev.mAzCount, levels1, proc_azi);
+ };
+ return std::accumulate(field.mEvs, field.mEvs+field.mEvCount, levels0, proc_elev);
+ };
+ const auto maxlev = std::accumulate(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount,
+ LevelPair{0.0, 0.0}, proc0_field);
+
+ /* Normalize using the maximum RMS of the HRIRs. The RMS measure for the
+ * non-filtered signal is of an impulse with equal length (to the filter):
+ *
+ * rms_impulse = sqrt(sum([ 1^2, 0^2, 0^2, ... ]) / n)
+ * = sqrt(1 / n)
+ *
+ * This helps keep a more consistent volume between the non-filtered signal
+ * and various data sets.
+ */
+ double factor{std::sqrt(1.0 / irSize) / maxlev.rms};
+
+ /* Also ensure the samples themselves won't clip. */
+ factor = std::min(factor, 0.99/maxlev.amp);
+
+ /* Now scale all IRs by the given factor. */
+ auto proc1_field = [channels,irSize,factor](HrirFdT &field) -> void
+ {
+ auto proc_elev = [channels,irSize,factor](HrirEvT &elev) -> void
+ {
+ auto proc_azi = [channels,irSize,factor](HrirAzT &azi) -> void
+ {
+ auto proc_channel = [irSize,factor](double *ir) -> void
+ {
+ std::transform(ir, ir+irSize, ir,
+ std::bind(std::multiplies<double>{}, _1, factor));
+ };
+ std::for_each(azi.mIrs, azi.mIrs+channels, proc_channel);
+ };
+ std::for_each(elev.mAzs, elev.mAzs+elev.mAzCount, proc_azi);
+ };
+ std::for_each(field.mEvs, field.mEvs+field.mEvCount, proc_elev);
+ };
+ std::for_each(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, proc1_field);
+}
+
+// Calculate the left-ear time delay using a spherical head model.
+static double CalcLTD(const double ev, const double az, const double rad, const double dist)
+{
+ double azp, dlp, l, al;
+
+ azp = std::asin(std::cos(ev) * std::sin(az));
+ dlp = std::sqrt((dist*dist) + (rad*rad) + (2.0*dist*rad*sin(azp)));
+ l = std::sqrt((dist*dist) - (rad*rad));
+ al = (0.5 * M_PI) + azp;
+ if(dlp > l)
+ dlp = l + (rad * (al - std::acos(rad / dist)));
+ return dlp / 343.3;
+}
+
+// Calculate the effective head-related time delays for each minimum-phase
+// HRIR. This is done per-field since distance delay is ignored.
+static void CalculateHrtds(const HeadModelT model, const double radius, HrirDataT *hData)
+{
+ uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
+ double customRatio{radius / hData->mRadius};
+ uint ti, fi, ei, ai;
+
+ if(model == HM_SPHERE)
+ {
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ HrirEvT *evd = &hData->mFds[fi].mEvs[ei];
+
+ for(ai = 0;ai < evd->mAzCount;ai++)
+ {
+ HrirAzT *azd = &evd->mAzs[ai];
+
+ for(ti = 0;ti < channels;ti++)
+ azd->mDelays[ti] = CalcLTD(evd->mElevation, azd->mAzimuth, radius, hData->mFds[fi].mDistance);
+ }
+ }
+ }
+ }
+ else if(customRatio != 1.0)
+ {
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ HrirEvT *evd = &hData->mFds[fi].mEvs[ei];
+
+ for(ai = 0;ai < evd->mAzCount;ai++)
+ {
+ HrirAzT *azd = &evd->mAzs[ai];
+ for(ti = 0;ti < channels;ti++)
+ azd->mDelays[ti] *= customRatio;
+ }
+ }
+ }
+ }
+
+ for(fi = 0;fi < hData->mFdCount;fi++)
+ {
+ double minHrtd{std::numeric_limits<double>::infinity()};
+ for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+
+ for(ti = 0;ti < channels;ti++)
+ minHrtd = std::min(azd->mDelays[ti], minHrtd);
+ }
+ }
+
+ for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
+ {
+ for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
+ {
+ HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
+
+ for(ti = 0;ti < channels;ti++)
+ azd->mDelays[ti] -= minHrtd;
+ }
+ }
+ }
+}
+
+// Allocate and configure dynamic HRIR structures.
+int PrepareHrirData(const uint fdCount, const double (&distances)[MAX_FD_COUNT],
+ const uint (&evCounts)[MAX_FD_COUNT], const uint azCounts[MAX_FD_COUNT * MAX_EV_COUNT],
+ HrirDataT *hData)
+{
+ uint evTotal = 0, azTotal = 0, fi, ei, ai;
+
+ for(fi = 0;fi < fdCount;fi++)
+ {
+ evTotal += evCounts[fi];
+ for(ei = 0;ei < evCounts[fi];ei++)
+ azTotal += azCounts[(fi * MAX_EV_COUNT) + ei];
+ }
+ if(!fdCount || !evTotal || !azTotal)
+ return 0;
+
+ hData->mEvsBase.resize(evTotal);
+ hData->mAzsBase.resize(azTotal);
+ hData->mFds.resize(fdCount);
+ hData->mIrCount = azTotal;
+ hData->mFdCount = fdCount;
+ evTotal = 0;
+ azTotal = 0;
+ for(fi = 0;fi < fdCount;fi++)
+ {
+ hData->mFds[fi].mDistance = distances[fi];
+ hData->mFds[fi].mEvCount = evCounts[fi];
+ hData->mFds[fi].mEvStart = 0;
+ hData->mFds[fi].mEvs = &hData->mEvsBase[evTotal];
+ evTotal += evCounts[fi];
+ for(ei = 0;ei < evCounts[fi];ei++)
+ {
+ uint azCount = azCounts[(fi * MAX_EV_COUNT) + ei];
+
+ hData->mFds[fi].mIrCount += azCount;
+ hData->mFds[fi].mEvs[ei].mElevation = -M_PI / 2.0 + M_PI * ei / (evCounts[fi] - 1);
+ hData->mFds[fi].mEvs[ei].mIrCount += azCount;
+ hData->mFds[fi].mEvs[ei].mAzCount = azCount;
+ hData->mFds[fi].mEvs[ei].mAzs = &hData->mAzsBase[azTotal];
+ for(ai = 0;ai < azCount;ai++)
+ {
+ hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth = 2.0 * M_PI * ai / azCount;
+ hData->mFds[fi].mEvs[ei].mAzs[ai].mIndex = azTotal + ai;
+ hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[0] = 0.0;
+ hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[1] = 0.0;
+ hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = nullptr;
+ hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = nullptr;
+ }
+ azTotal += azCount;
+ }
+ }
+ return 1;
+}
+
+
+/* Parse the data set definition and process the source data, storing the
+ * resulting data set as desired. If the input name is NULL it will read
+ * from standard input.
+ */
+static int ProcessDefinition(const char *inName, const uint outRate, const ChannelModeT chanMode,
+ const uint fftSize, const int equalize, const int surface, const double limit,
+ const uint truncSize, const HeadModelT model, const double radius, const char *outName)
+{
+ char rateStr[8+1], expName[MAX_PATH_LEN];
+ HrirDataT hData;
+
+ if(!inName)
+ {
+ inName = "stdin";
+ fprintf(stdout, "Reading HRIR definition from %s...\n", inName);
+ if(!LoadDefInput(std::cin, nullptr, 0, inName, fftSize, truncSize, chanMode, &hData))
+ return 0;
+ }
+ else
+ {
+ std::unique_ptr<al::ifstream> input{new al::ifstream{inName}};
+ if(!input->is_open())
+ {
+ fprintf(stderr, "Error: Could not open input file '%s'\n", inName);
+ return 0;
+ }
+
+ char startbytes[4]{};
+ input->read(startbytes, sizeof(startbytes));
+ std::streamsize startbytecount{input->gcount()};
+ if(startbytecount != sizeof(startbytes) || !input->good())
+ {
+ fprintf(stderr, "Error: Could not read input file '%s'\n", inName);
+ return 0;
+ }
+
+ if(startbytes[0] == '\x89' && startbytes[1] == 'H' && startbytes[2] == 'D'
+ && startbytes[3] == 'F')
+ {
+ input = nullptr;
+ fprintf(stdout, "Reading HRTF data from %s...\n", inName);
+ if(!LoadSofaFile(inName, fftSize, truncSize, chanMode, &hData))
+ return 0;
+ }
+ else
+ {
+ fprintf(stdout, "Reading HRIR definition from %s...\n", inName);
+ if(!LoadDefInput(*input, startbytes, startbytecount, inName, fftSize, truncSize, chanMode, &hData))
+ return 0;
+ }
+ }
+
+ if(equalize)
+ {
+ uint c{(hData.mChannelType == CT_STEREO) ? 2u : 1u};
+ uint m{hData.mFftSize/2u + 1u};
+ auto dfa = std::vector<double>(c * m);
+
+ if(hData.mFdCount > 1)
+ {
+ fprintf(stdout, "Balancing field magnitudes...\n");
+ BalanceFieldMagnitudes(&hData, c, m);
+ }
+ fprintf(stdout, "Calculating diffuse-field average...\n");
+ CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa.data());
+ fprintf(stdout, "Performing diffuse-field equalization...\n");
+ DiffuseFieldEqualize(c, m, dfa.data(), &hData);
+ }
+ fprintf(stdout, "Performing minimum phase reconstruction...\n");
+ ReconstructHrirs(&hData);
+ if(outRate != 0 && outRate != hData.mIrRate)
+ {
+ fprintf(stdout, "Resampling HRIRs...\n");
+ ResampleHrirs(outRate, &hData);
+ }
+ fprintf(stdout, "Truncating minimum-phase HRIRs...\n");
+ hData.mIrPoints = truncSize;
+ fprintf(stdout, "Synthesizing missing elevations...\n");
+ if(model == HM_DATASET)
+ SynthesizeOnsets(&hData);
+ SynthesizeHrirs(&hData);
+ fprintf(stdout, "Normalizing final HRIRs...\n");
+ NormalizeHrirs(&hData);
+ fprintf(stdout, "Calculating impulse delays...\n");
+ CalculateHrtds(model, (radius > DEFAULT_CUSTOM_RADIUS) ? radius : hData.mRadius, &hData);
+ snprintf(rateStr, sizeof(rateStr), "%u", hData.mIrRate);
+ StrSubst(outName, "%r", rateStr, sizeof(expName), expName);
+ fprintf(stdout, "Creating MHR data set %s...\n", expName);
+ return StoreMhr(&hData, expName);
+}
+
+static void PrintHelp(const char *argv0, FILE *ofile)
+{
+ fprintf(ofile, "Usage: %s [<option>...]\n\n", argv0);
+ fprintf(ofile, "Options:\n");
+ fprintf(ofile, " -r <rate> Change the data set sample rate to the specified value and\n");
+ fprintf(ofile, " resample the HRIRs accordingly.\n");
+ fprintf(ofile, " -m Change the data set to mono, mirroring the left ear for the\n");
+ fprintf(ofile, " right ear.\n");
+ fprintf(ofile, " -f <points> Override the FFT window size (default: %u).\n", DEFAULT_FFTSIZE);
+ fprintf(ofile, " -e {on|off} Toggle diffuse-field equalization (default: %s).\n", (DEFAULT_EQUALIZE ? "on" : "off"));
+ fprintf(ofile, " -s {on|off} Toggle surface-weighted diffuse-field average (default: %s).\n", (DEFAULT_SURFACE ? "on" : "off"));
+ fprintf(ofile, " -l {<dB>|none} Specify a limit to the magnitude range of the diffuse-field\n");
+ fprintf(ofile, " average (default: %.2f).\n", DEFAULT_LIMIT);
+ fprintf(ofile, " -w <points> Specify the size of the truncation window that's applied\n");
+ fprintf(ofile, " after minimum-phase reconstruction (default: %u).\n", DEFAULT_TRUNCSIZE);
+ fprintf(ofile, " -d {dataset| Specify the model used for calculating the head-delay timing\n");
+ fprintf(ofile, " sphere} values (default: %s).\n", ((DEFAULT_HEAD_MODEL == HM_DATASET) ? "dataset" : "sphere"));
+ fprintf(ofile, " -c <radius> Use a customized head radius measured to-ear in meters.\n");
+ fprintf(ofile, " -i <filename> Specify an HRIR definition file to use (defaults to stdin).\n");
+ fprintf(ofile, " -o <filename> Specify an output file. Use of '%%r' will be substituted with\n");
+ fprintf(ofile, " the data set sample rate.\n");
+}
+
+// Standard command line dispatch.
+int main(int argc, char *argv[])
+{
+ const char *inName = nullptr, *outName = nullptr;
+ uint outRate, fftSize;
+ int equalize, surface;
+ char *end = nullptr;
+ ChannelModeT chanMode;
+ HeadModelT model;
+ uint truncSize;
+ double radius;
+ double limit;
+ int opt;
+
+ GET_UNICODE_ARGS(&argc, &argv);
+
+ if(argc < 2)
+ {
+ fprintf(stdout, "HRTF Processing and Composition Utility\n\n");
+ PrintHelp(argv[0], stdout);
+ exit(EXIT_SUCCESS);
+ }
+
+ outName = "./oalsoft_hrtf_%r.mhr";
+ outRate = 0;
+ chanMode = CM_AllowStereo;
+ fftSize = DEFAULT_FFTSIZE;
+ equalize = DEFAULT_EQUALIZE;
+ surface = DEFAULT_SURFACE;
+ limit = DEFAULT_LIMIT;
+ truncSize = DEFAULT_TRUNCSIZE;
+ model = DEFAULT_HEAD_MODEL;
+ radius = DEFAULT_CUSTOM_RADIUS;
+
+ while((opt=getopt(argc, argv, "r:mf:e:s:l:w:d:c:e:i:o:h")) != -1)
+ {
+ switch(opt)
+ {
+ case 'r':
+ outRate = static_cast<uint>(strtoul(optarg, &end, 10));
+ if(end[0] != '\0' || outRate < MIN_RATE || outRate > MAX_RATE)
+ {
+ fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, MIN_RATE, MAX_RATE);
+ exit(EXIT_FAILURE);
+ }
+ break;
+
+ case 'm':
+ chanMode = CM_ForceMono;
+ break;
+
+ case 'f':
+ fftSize = static_cast<uint>(strtoul(optarg, &end, 10));
+ if(end[0] != '\0' || (fftSize&(fftSize-1)) || fftSize < MIN_FFTSIZE || fftSize > MAX_FFTSIZE)
+ {
+ fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected a power-of-two between %u to %u.\n", optarg, opt, MIN_FFTSIZE, MAX_FFTSIZE);
+ exit(EXIT_FAILURE);
+ }
+ break;
+
+ case 'e':
+ if(strcmp(optarg, "on") == 0)
+ equalize = 1;
+ else if(strcmp(optarg, "off") == 0)
+ equalize = 0;
+ else
+ {
+ fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt);
+ exit(EXIT_FAILURE);
+ }
+ break;
+
+ case 's':
+ if(strcmp(optarg, "on") == 0)
+ surface = 1;
+ else if(strcmp(optarg, "off") == 0)
+ surface = 0;
+ else
+ {
+ fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt);
+ exit(EXIT_FAILURE);
+ }
+ break;
+
+ case 'l':
+ if(strcmp(optarg, "none") == 0)
+ limit = 0.0;
+ else
+ {
+ limit = strtod(optarg, &end);
+ if(end[0] != '\0' || limit < MIN_LIMIT || limit > MAX_LIMIT)
+ {
+ fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %.0f to %.0f.\n", optarg, opt, MIN_LIMIT, MAX_LIMIT);
+ exit(EXIT_FAILURE);
+ }
+ }
+ break;
+
+ case 'w':
+ truncSize = static_cast<uint>(strtoul(optarg, &end, 10));
+ if(end[0] != '\0' || truncSize < MIN_TRUNCSIZE || truncSize > MAX_TRUNCSIZE || (truncSize%MOD_TRUNCSIZE))
+ {
+ fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected multiple of %u between %u to %u.\n", optarg, opt, MOD_TRUNCSIZE, MIN_TRUNCSIZE, MAX_TRUNCSIZE);
+ exit(EXIT_FAILURE);
+ }
+ break;
+
+ case 'd':
+ if(strcmp(optarg, "dataset") == 0)
+ model = HM_DATASET;
+ else if(strcmp(optarg, "sphere") == 0)
+ model = HM_SPHERE;
+ else
+ {
+ fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected dataset or sphere.\n", optarg, opt);
+ exit(EXIT_FAILURE);
+ }
+ break;
+
+ case 'c':
+ radius = strtod(optarg, &end);
+ if(end[0] != '\0' || radius < MIN_CUSTOM_RADIUS || radius > MAX_CUSTOM_RADIUS)
+ {
+ fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %.2f to %.2f.\n", optarg, opt, MIN_CUSTOM_RADIUS, MAX_CUSTOM_RADIUS);
+ exit(EXIT_FAILURE);
+ }
+ break;
+
+ case 'i':
+ inName = optarg;
+ break;
+
+ case 'o':
+ outName = optarg;
+ break;
+
+ case 'h':
+ PrintHelp(argv[0], stdout);
+ exit(EXIT_SUCCESS);
+
+ default: /* '?' */
+ PrintHelp(argv[0], stderr);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ int ret = ProcessDefinition(inName, outRate, chanMode, fftSize, equalize, surface, limit,
+ truncSize, model, radius, outName);
+ if(!ret) return -1;
+ fprintf(stdout, "Operation completed.\n");
+
+ return EXIT_SUCCESS;
+}
diff --git a/utils/makemhr/makemhr.h b/utils/makemhr/makemhr.h
new file mode 100644
index 00000000..ba19efbe
--- /dev/null
+++ b/utils/makemhr/makemhr.h
@@ -0,0 +1,128 @@
+#ifndef MAKEMHR_H
+#define MAKEMHR_H
+
+#include <vector>
+#include <complex>
+
+
+// The maximum path length used when processing filenames.
+#define MAX_PATH_LEN (256)
+
+// The limit to the number of 'distances' listed in the data set definition.
+// Must be less than 256
+#define MAX_FD_COUNT (16)
+
+// The limits to the number of 'elevations' listed in the data set definition.
+// Must be less than 256.
+#define MIN_EV_COUNT (5)
+#define MAX_EV_COUNT (181)
+
+// The limits for each of the 'azimuths' listed in the data set definition.
+// Must be less than 256.
+#define MIN_AZ_COUNT (1)
+#define MAX_AZ_COUNT (255)
+
+// The limits for the 'distance' from source to listener for each field in
+// the definition file.
+#define MIN_DISTANCE (0.05)
+#define MAX_DISTANCE (2.50)
+
+// The limits for the sample 'rate' metric in the data set definition and for
+// resampling.
+#define MIN_RATE (32000)
+#define MAX_RATE (96000)
+
+// The limits for the HRIR 'points' metric in the data set definition.
+#define MIN_POINTS (16)
+#define MAX_POINTS (8192)
+
+
+using uint = unsigned int;
+
+/* Complex double type. */
+using complex_d = std::complex<double>;
+
+
+enum ChannelModeT : bool {
+ CM_AllowStereo = false,
+ CM_ForceMono = true
+};
+
+// Sample and channel type enum values.
+enum SampleTypeT {
+ ST_S16 = 0,
+ ST_S24 = 1
+};
+
+// Certain iterations rely on these integer enum values.
+enum ChannelTypeT {
+ CT_NONE = -1,
+ CT_MONO = 0,
+ CT_STEREO = 1
+};
+
+// Structured HRIR storage for stereo azimuth pairs, elevations, and fields.
+struct HrirAzT {
+ double mAzimuth{0.0};
+ uint mIndex{0u};
+ double mDelays[2]{0.0, 0.0};
+ double *mIrs[2]{nullptr, nullptr};
+};
+
+struct HrirEvT {
+ double mElevation{0.0};
+ uint mIrCount{0u};
+ uint mAzCount{0u};
+ HrirAzT *mAzs{nullptr};
+};
+
+struct HrirFdT {
+ double mDistance{0.0};
+ uint mIrCount{0u};
+ uint mEvCount{0u};
+ uint mEvStart{0u};
+ HrirEvT *mEvs{nullptr};
+};
+
+// The HRIR metrics and data set used when loading, processing, and storing
+// the resulting HRTF.
+struct HrirDataT {
+ uint mIrRate{0u};
+ SampleTypeT mSampleType{ST_S24};
+ ChannelTypeT mChannelType{CT_NONE};
+ uint mIrPoints{0u};
+ uint mFftSize{0u};
+ uint mIrSize{0u};
+ double mRadius{0.0};
+ uint mIrCount{0u};
+ uint mFdCount{0u};
+
+ std::vector<double> mHrirsBase;
+ std::vector<HrirEvT> mEvsBase;
+ std::vector<HrirAzT> mAzsBase;
+
+ std::vector<HrirFdT> mFds;
+};
+
+
+int PrepareHrirData(const uint fdCount, const double (&distances)[MAX_FD_COUNT], const uint (&evCounts)[MAX_FD_COUNT], const uint azCounts[MAX_FD_COUNT * MAX_EV_COUNT], HrirDataT *hData);
+void MagnitudeResponse(const uint n, const complex_d *in, double *out);
+void FftForward(const uint n, complex_d *inout);
+void FftInverse(const uint n, complex_d *inout);
+
+
+// The resampler metrics and FIR filter.
+struct ResamplerT {
+ uint mP, mQ, mM, mL;
+ std::vector<double> mF;
+};
+
+void ResamplerSetup(ResamplerT *rs, const uint srcRate, const uint dstRate);
+void ResamplerRun(ResamplerT *rs, const uint inN, const double *in, const uint outN, double *out);
+
+
+// Performs linear interpolation.
+inline double Lerp(const double a, const double b, const double f)
+{ return a + f * (b - a); }
+
+#endif /* MAKEMHR_H */
diff --git a/utils/openal-info.c b/utils/openal-info.c
index 12dc6311..cdce77e0 100644
--- a/utils/openal-info.c
+++ b/utils/openal-info.c
@@ -53,7 +53,7 @@ static WCHAR *FromUTF8(const char *str)
if((len=MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0)) > 0)
{
- out = calloc(sizeof(WCHAR), len);
+ out = calloc(sizeof(WCHAR), (unsigned int)(len));
MultiByteToWideChar(CP_UTF8, 0, str, -1, out, len);
}
return out;
@@ -124,7 +124,7 @@ static void printList(const char *list, char separator)
next = strchr(list, separator);
if(next)
{
- len = next-list;
+ len = (size_t)(next-list);
do {
next++;
} while(*next == separator);
@@ -223,7 +223,7 @@ static void printHRTFInfo(ALCdevice *device)
return;
}
- alcGetStringiSOFT = alcGetProcAddress(device, "alcGetStringiSOFT");
+ alcGetStringiSOFT = (LPALCGETSTRINGISOFT)alcGetProcAddress(device, "alcGetStringiSOFT");
alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtfs);
if(!num_hrtfs)
@@ -263,7 +263,7 @@ static void printResamplerInfo(void)
return;
}
- alGetStringiSOFT = alGetProcAddress("alGetStringiSOFT");
+ alGetStringiSOFT = (LPALGETSTRINGISOFT)alGetProcAddress("alGetStringiSOFT");
num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT);
def_resampler = alGetInteger(AL_DEFAULT_RESAMPLER_SOFT);
diff --git a/utils/sofa-info.cpp b/utils/sofa-info.cpp
new file mode 100644
index 00000000..bc5b709a
--- /dev/null
+++ b/utils/sofa-info.cpp
@@ -0,0 +1,371 @@
+/*
+ * SOFA info utility for inspecting SOFA file metrics and determining HRTF
+ * utility compatible layouts.
+ *
+ * Copyright (C) 2018-2019 Christopher Fitzgerald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ */
+
+#include <stdio.h>
+
+#include <array>
+#include <cmath>
+#include <memory>
+#include <vector>
+
+#include <mysofa.h>
+
+#include "win_main_utf8.h"
+
+
+using uint = unsigned int;
+using double3 = std::array<double,3>;
+
+struct MySofaDeleter {
+ void operator()(MYSOFA_HRTF *sofa) { mysofa_free(sofa); }
+};
+using MySofaHrtfPtr = std::unique_ptr<MYSOFA_HRTF,MySofaDeleter>;
+
+// Per-field measurement info.
+struct HrirFdT {
+ double mDistance{0.0};
+ uint mEvCount{0u};
+ uint mEvStart{0u};
+ std::vector<uint> mAzCounts;
+};
+
+static const char *SofaErrorStr(int err)
+{
+ switch(err)
+ {
+ case MYSOFA_OK: return "OK";
+ case MYSOFA_INVALID_FORMAT: return "Invalid format";
+ case MYSOFA_UNSUPPORTED_FORMAT: return "Unsupported format";
+ case MYSOFA_INTERNAL_ERROR: return "Internal error";
+ case MYSOFA_NO_MEMORY: return "Out of memory";
+ case MYSOFA_READ_ERROR: return "Read error";
+ }
+ return "Unknown";
+}
+
+static void PrintSofaAttributes(const char *prefix, struct MYSOFA_ATTRIBUTE *attribute)
+{
+ while(attribute)
+ {
+ fprintf(stdout, "%s.%s: %s\n", prefix, attribute->name, attribute->value);
+ attribute = attribute->next;
+ }
+}
+
+static void PrintSofaArray(const char *prefix, struct MYSOFA_ARRAY *array)
+{
+ PrintSofaAttributes(prefix, array->attributes);
+
+ for(uint i{0u};i < array->elements;i++)
+ fprintf(stdout, "%s[%u]: %.6f\n", prefix, i, array->values[i]);
+}
+
+/* Produces a sorted array of unique elements from a particular axis of the
+ * triplets array. The filters are used to focus on particular coordinates
+ * of other axes as necessary. The epsilons are used to constrain the
+ * equality of unique elements.
+ */
+static uint GetUniquelySortedElems(const uint m, const double3 *aers, const uint axis,
+ const double *const (&filters)[3], const double (&epsilons)[3], double *elems)
+{
+ uint count{0u};
+ for(uint i{0u};i < m;++i)
+ {
+ const double elem{aers[i][axis]};
+
+ uint j;
+ for(j = 0;j < 3;j++)
+ {
+ if(filters[j] && std::fabs(aers[i][j] - *filters[j]) > epsilons[j])
+ break;
+ }
+ if(j < 3)
+ continue;
+
+ for(j = 0;j < count;j++)
+ {
+ const double delta{elem - elems[j]};
+
+ if(delta > epsilons[axis])
+ continue;
+
+ if(delta >= -epsilons[axis])
+ break;
+
+ for(uint k{count};k > j;k--)
+ elems[k] = elems[k - 1];
+
+ elems[j] = elem;
+ count++;
+ break;
+ }
+
+ if(j >= count)
+ elems[count++] = elem;
+ }
+
+ return count;
+}
+
+/* Given a list of elements, this will produce the smallest step size that
+ * can uniformly cover a fair portion of the list. Ideally this will be over
+ * half, but in degenerate cases this can fall to a minimum of 5 (the lower
+ * limit on elevations necessary to build a layout).
+ */
+static double GetUniformStepSize(const double epsilon, const uint m, const double *elems)
+{
+ auto steps = std::vector<double>(m, 0.0);
+ auto counts = std::vector<uint>(m, 0u);
+ uint count{0u};
+
+ for(uint stride{1u};stride < m/2;stride++)
+ {
+ for(uint i{0u};i < m-stride;i++)
+ {
+ const double step{elems[i + stride] - elems[i]};
+
+ uint j;
+ for(j = 0;j < count;j++)
+ {
+ if(std::fabs(step - steps[j]) < epsilon)
+ {
+ counts[j]++;
+ break;
+ }
+ }
+
+ if(j >= count)
+ {
+ steps[j] = step;
+ counts[j] = 1;
+ count++;
+ }
+ }
+
+ for(uint i{1u};i < count;i++)
+ {
+ if(counts[i] > counts[0])
+ {
+ steps[0] = steps[i];
+ counts[0] = counts[i];
+ }
+ }
+
+ count = 1;
+
+ if(counts[0] > m/2)
+ break;
+ }
+
+ if(counts[0] > 255)
+ {
+ uint i{2u};
+ while(counts[0]/i > 255 && (counts[0]%i) != 0)
+ ++i;
+ counts[0] /= i;
+ steps[0] *= i;
+ }
+ if(counts[0] > 5)
+ return steps[0];
+ return 0.0;
+}
+
+/* Attempts to produce a compatible layout. Most data sets tend to be
+ * uniform and have the same major axis as used by OpenAL Soft's HRTF model.
+ * This will remove outliers and produce a maximally dense layout when
+ * possible. Those sets that contain purely random measurements or use
+ * different major axes will fail.
+ */
+static void PrintCompatibleLayout(const uint m, const float *xyzs)
+{
+ auto aers = std::vector<double3>(m, double3{});
+ auto elems = std::vector<double>(m, {});
+
+ fprintf(stdout, "\n");
+
+ for(uint i{0u};i < m;++i)
+ {
+ float aer[3]{xyzs[i*3], xyzs[i*3 + 1], xyzs[i*3 + 2]};
+ mysofa_c2s(&aer[0]);
+ aers[i][0] = aer[0];
+ aers[i][1] = aer[1];
+ aers[i][2] = aer[2];
+ }
+
+ uint fdCount{GetUniquelySortedElems(m, aers.data(), 2, { nullptr, nullptr, nullptr },
+ { 0.1, 0.1, 0.001 }, elems.data())};
+ if(fdCount > (m / 3))
+ {
+ fprintf(stdout, "Incompatible layout (inumerable radii).\n");
+ return;
+ }
+
+ std::vector<HrirFdT> fds(fdCount);
+ for(uint fi{0u};fi < fdCount;fi++)
+ fds[fi].mDistance = elems[fi];
+
+ for(uint fi{0u};fi < fdCount;fi++)
+ {
+ const double dist{fds[fi].mDistance};
+ uint evCount{GetUniquelySortedElems(m, aers.data(), 1, { nullptr, nullptr, &dist },
+ { 0.1, 0.1, 0.001 }, elems.data())};
+
+ if(evCount > (m / 3))
+ {
+ fprintf(stdout, "Incompatible layout (innumerable elevations).\n");
+ return;
+ }
+
+ double step{GetUniformStepSize(0.1, evCount, elems.data())};
+ if(step <= 0.0)
+ {
+ fprintf(stdout, "Incompatible layout (non-uniform elevations).\n");
+ return;
+ }
+
+ uint evStart{0u};
+ for(uint ei{0u};ei < evCount;ei++)
+ {
+ double ev{90.0 + elems[ei]};
+ double eif{std::round(ev / step)};
+ const uint ev_start{static_cast<uint>(eif)};
+
+ if(std::fabs(eif - static_cast<double>(ev_start)) < (0.1/step))
+ {
+ evStart = ev_start;
+ break;
+ }
+ }
+
+ evCount = static_cast<uint>(std::round(180.0 / step)) + 1;
+ if(evCount < 5)
+ {
+ fprintf(stdout, "Incompatible layout (too few uniform elevations).\n");
+ return;
+ }
+
+ fds[fi].mEvCount = evCount;
+ fds[fi].mEvStart = evStart;
+ fds[fi].mAzCounts.resize(evCount);
+ auto &azCounts = fds[fi].mAzCounts;
+
+ for(uint ei{evStart};ei < evCount;ei++)
+ {
+ double ev{-90.0 + static_cast<double>(ei)*180.0/static_cast<double>(evCount - 1)};
+ uint azCount{GetUniquelySortedElems(m, aers.data(), 0, { nullptr, &ev, &dist },
+ { 0.1, 0.1, 0.001 }, elems.data())};
+
+ if(azCount > (m / 3))
+ {
+ fprintf(stdout, "Incompatible layout (innumerable azimuths).\n");
+ return;
+ }
+
+ if(ei > 0 && ei < (evCount - 1))
+ {
+ step = GetUniformStepSize(0.1, azCount, elems.data());
+ if(step <= 0.0)
+ {
+ fprintf(stdout, "Incompatible layout (non-uniform azimuths).\n");
+ return;
+ }
+
+ azCounts[ei] = static_cast<uint>(std::round(360.0f / step));
+ }
+ else if(azCount != 1)
+ {
+ fprintf(stdout, "Incompatible layout (non-singular poles).\n");
+ return;
+ }
+ else
+ {
+ azCounts[ei] = 1;
+ }
+ }
+
+ for(uint ei{0u};ei < evStart;ei++)
+ azCounts[ei] = azCounts[evCount - ei - 1];
+ }
+
+ fprintf(stdout, "Compatible Layout:\n\ndistance = %.3f", fds[0].mDistance);
+
+ for(uint fi{1u};fi < fdCount;fi++)
+ fprintf(stdout, ", %.3f", fds[fi].mDistance);
+
+ fprintf(stdout, "\nazimuths = ");
+ for(uint fi{0u};fi < fdCount;fi++)
+ {
+ for(uint ei{0u};ei < fds[fi].mEvCount;ei++)
+ fprintf(stdout, "%d%s", fds[fi].mAzCounts[ei],
+ (ei < (fds[fi].mEvCount - 1)) ? ", " :
+ (fi < (fdCount - 1)) ? ";\n " : "\n");
+ }
+}
+
+// Load and inspect the given SOFA file.
+static void SofaInfo(const char *filename)
+{
+ int err;
+ MySofaHrtfPtr sofa{mysofa_load(filename, &err)};
+ if(!sofa)
+ {
+ fprintf(stdout, "Error: Could not load source file '%s'.\n", filename);
+ return;
+ }
+
+ /* NOTE: Some valid SOFA files are failing this check. */
+ err = mysofa_check(sofa.get());
+ if(err != MYSOFA_OK)
+ fprintf(stdout, "Warning: Supposedly malformed source file '%s' (%s).\n", filename,
+ SofaErrorStr(err));
+
+ mysofa_tocartesian(sofa.get());
+
+ PrintSofaAttributes("Info", sofa->attributes);
+
+ fprintf(stdout, "Measurements: %u\n", sofa->M);
+ fprintf(stdout, "Receivers: %u\n", sofa->R);
+ fprintf(stdout, "Emitters: %u\n", sofa->E);
+ fprintf(stdout, "Samples: %u\n", sofa->N);
+
+ PrintSofaArray("SampleRate", &sofa->DataSamplingRate);
+ PrintSofaArray("DataDelay", &sofa->DataDelay);
+
+ PrintCompatibleLayout(sofa->M, sofa->SourcePosition.values);
+}
+
+int main(int argc, char *argv[])
+{
+ GET_UNICODE_ARGS(&argc, &argv);
+
+ if(argc != 2)
+ {
+ fprintf(stdout, "Usage: %s <sofa-file>\n", argv[0]);
+ return 0;
+ }
+
+ SofaInfo(argv[1]);
+
+ return 0;
+}
+
diff --git a/version.h.in b/version.h.in
index 56f738a3..9bb439d8 100644
--- a/version.h.in
+++ b/version.h.in
@@ -1,5 +1,6 @@
/* Define to the library version */
#define ALSOFT_VERSION "${LIB_VERSION}"
+#define ALSOFT_VERSION_NUM ${LIB_VERSION_NUM}
/* Define the branch being built */
#define ALSOFT_GIT_BRANCH "${GIT_BRANCH}"