From 6721a6b021f8b503cb739ee63763f3c09fe153c8 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sun, 30 Jun 2024 14:35:57 -0500 Subject: [PATCH 1/3] update openal --- .../lib/openal-soft/.github/workflows/ci.yml | 46 +- .../openal-soft/.github/workflows/makemhr.yml | 2 +- Engine/lib/openal-soft/.gitignore | 1 + Engine/lib/openal-soft/CMakeLists.txt | 367 +- Engine/lib/openal-soft/LICENSE-pffft | 38 + Engine/lib/openal-soft/OpenALConfig.cmake.in | 2 +- Engine/lib/openal-soft/README.md | 27 + Engine/lib/openal-soft/al/auxeffectslot.cpp | 1015 ++- Engine/lib/openal-soft/al/auxeffectslot.h | 100 +- Engine/lib/openal-soft/al/buffer.cpp | 1833 ++-- Engine/lib/openal-soft/al/buffer.h | 34 +- Engine/lib/openal-soft/al/debug.cpp | 618 ++ Engine/lib/openal-soft/al/debug.h | 70 + Engine/lib/openal-soft/al/direct_defs.h | 127 + Engine/lib/openal-soft/al/eax/api.h | 59 +- Engine/lib/openal-soft/al/eax/call.cpp | 3 +- Engine/lib/openal-soft/al/eax/call.h | 34 +- Engine/lib/openal-soft/al/eax/effect.h | 325 +- Engine/lib/openal-soft/al/eax/exception.cpp | 47 +- Engine/lib/openal-soft/al/eax/exception.h | 15 +- Engine/lib/openal-soft/al/eax/fx_slot_index.h | 7 +- Engine/lib/openal-soft/al/eax/fx_slots.h | 14 +- Engine/lib/openal-soft/al/eax/globals.cpp | 21 - Engine/lib/openal-soft/al/eax/globals.h | 20 +- Engine/lib/openal-soft/al/eax/utils.cpp | 8 +- Engine/lib/openal-soft/al/eax/utils.h | 12 +- Engine/lib/openal-soft/al/eax/x_ram.h | 12 +- Engine/lib/openal-soft/al/effect.cpp | 735 +- Engine/lib/openal-soft/al/effect.h | 52 +- Engine/lib/openal-soft/al/effects/autowah.cpp | 156 +- Engine/lib/openal-soft/al/effects/chorus.cpp | 296 +- .../lib/openal-soft/al/effects/compressor.cpp | 79 +- .../openal-soft/al/effects/convolution.cpp | 178 +- .../lib/openal-soft/al/effects/dedicated.cpp | 94 +- .../lib/openal-soft/al/effects/distortion.cpp | 160 +- Engine/lib/openal-soft/al/effects/echo.cpp | 160 +- Engine/lib/openal-soft/al/effects/effects.cpp | 6 - Engine/lib/openal-soft/al/effects/effects.h | 96 +- .../lib/openal-soft/al/effects/equalizer.cpp | 250 +- .../lib/openal-soft/al/effects/fshifter.cpp | 167 +- .../lib/openal-soft/al/effects/modulator.cpp | 177 +- Engine/lib/openal-soft/al/effects/null.cpp | 166 +- .../lib/openal-soft/al/effects/pshifter.cpp | 106 +- Engine/lib/openal-soft/al/effects/reverb.cpp | 995 +-- .../lib/openal-soft/al/effects/vmorpher.cpp | 248 +- Engine/lib/openal-soft/al/error.cpp | 113 +- Engine/lib/openal-soft/al/error.h | 27 + Engine/lib/openal-soft/al/event.cpp | 203 +- Engine/lib/openal-soft/al/extension.cpp | 52 +- Engine/lib/openal-soft/al/filter.cpp | 948 +- Engine/lib/openal-soft/al/filter.h | 76 +- Engine/lib/openal-soft/al/listener.cpp | 466 +- Engine/lib/openal-soft/al/listener.h | 4 +- Engine/lib/openal-soft/al/source.cpp | 3772 ++++---- Engine/lib/openal-soft/al/source.h | 195 +- Engine/lib/openal-soft/al/state.cpp | 1098 +-- Engine/lib/openal-soft/alc/alc.cpp | 2421 ++--- Engine/lib/openal-soft/alc/alconfig.cpp | 399 +- Engine/lib/openal-soft/alc/alconfig.h | 21 +- Engine/lib/openal-soft/alc/alu.cpp | 1124 +-- Engine/lib/openal-soft/alc/alu.h | 10 +- Engine/lib/openal-soft/alc/backends/alsa.cpp | 324 +- Engine/lib/openal-soft/alc/backends/alsa.h | 10 +- Engine/lib/openal-soft/alc/backends/base.cpp | 64 +- Engine/lib/openal-soft/alc/backends/base.h | 63 +- .../openal-soft/alc/backends/coreaudio.cpp | 270 +- .../lib/openal-soft/alc/backends/coreaudio.h | 12 +- .../lib/openal-soft/alc/backends/dsound.cpp | 203 +- Engine/lib/openal-soft/alc/backends/dsound.h | 10 +- Engine/lib/openal-soft/alc/backends/jack.cpp | 294 +- Engine/lib/openal-soft/alc/backends/jack.h | 10 +- .../lib/openal-soft/alc/backends/loopback.cpp | 10 +- .../lib/openal-soft/alc/backends/loopback.h | 10 +- Engine/lib/openal-soft/alc/backends/null.cpp | 36 +- Engine/lib/openal-soft/alc/backends/null.h | 10 +- Engine/lib/openal-soft/alc/backends/oboe.cpp | 79 +- Engine/lib/openal-soft/alc/backends/oboe.h | 10 +- .../lib/openal-soft/alc/backends/opensl.cpp | 159 +- Engine/lib/openal-soft/alc/backends/opensl.h | 10 +- Engine/lib/openal-soft/alc/backends/oss.cpp | 247 +- Engine/lib/openal-soft/alc/backends/oss.h | 10 +- .../lib/openal-soft/alc/backends/pipewire.cpp | 922 +- .../lib/openal-soft/alc/backends/pipewire.h | 14 +- .../openal-soft/alc/backends/portaudio.cpp | 278 +- .../lib/openal-soft/alc/backends/portaudio.h | 10 +- .../openal-soft/alc/backends/pulseaudio.cpp | 497 +- .../lib/openal-soft/alc/backends/pulseaudio.h | 18 +- Engine/lib/openal-soft/alc/backends/sdl2.cpp | 64 +- Engine/lib/openal-soft/alc/backends/sdl2.h | 10 +- Engine/lib/openal-soft/alc/backends/sndio.cpp | 231 +- Engine/lib/openal-soft/alc/backends/sndio.h | 10 +- .../lib/openal-soft/alc/backends/solaris.cpp | 63 +- Engine/lib/openal-soft/alc/backends/solaris.h | 10 +- .../lib/openal-soft/alc/backends/wasapi.cpp | 2065 +++-- Engine/lib/openal-soft/alc/backends/wasapi.h | 12 +- Engine/lib/openal-soft/alc/backends/wave.cpp | 168 +- Engine/lib/openal-soft/alc/backends/wave.h | 10 +- Engine/lib/openal-soft/alc/backends/winmm.cpp | 165 +- Engine/lib/openal-soft/alc/backends/winmm.h | 10 +- Engine/lib/openal-soft/alc/context.cpp | 352 +- Engine/lib/openal-soft/alc/context.h | 152 +- Engine/lib/openal-soft/alc/device.cpp | 26 +- Engine/lib/openal-soft/alc/device.h | 106 +- .../lib/openal-soft/alc/effects/autowah.cpp | 98 +- Engine/lib/openal-soft/alc/effects/base.h | 36 +- Engine/lib/openal-soft/alc/effects/chorus.cpp | 210 +- .../openal-soft/alc/effects/compressor.cpp | 145 +- .../openal-soft/alc/effects/convolution.cpp | 543 +- .../lib/openal-soft/alc/effects/dedicated.cpp | 59 +- .../openal-soft/alc/effects/distortion.cpp | 64 +- Engine/lib/openal-soft/alc/effects/echo.cpp | 77 +- .../lib/openal-soft/alc/effects/equalizer.cpp | 59 +- .../lib/openal-soft/alc/effects/fshifter.cpp | 74 +- .../lib/openal-soft/alc/effects/modulator.cpp | 175 +- Engine/lib/openal-soft/alc/effects/null.cpp | 7 +- .../lib/openal-soft/alc/effects/pshifter.cpp | 106 +- Engine/lib/openal-soft/alc/effects/reverb.cpp | 1328 +-- .../lib/openal-soft/alc/effects/vmorpher.cpp | 172 +- Engine/lib/openal-soft/alc/events.cpp | 95 + Engine/lib/openal-soft/alc/events.h | 50 + Engine/lib/openal-soft/alc/export_list.h | 917 ++ Engine/lib/openal-soft/alc/inprogext.h | 91 +- Engine/lib/openal-soft/alc/panning.cpp | 567 +- Engine/lib/openal-soft/alsoftrc.sample | 48 +- Engine/lib/openal-soft/appveyor.yml | 4 +- Engine/lib/openal-soft/cmake/FindOpenSL.cmake | 12 +- Engine/lib/openal-soft/cmake/FindSndIO.cmake | 31 + .../lib/openal-soft/cmake/FindSoundIO.cmake | 32 - Engine/lib/openal-soft/common/alassert.cpp | 38 + Engine/lib/openal-soft/common/alassert.h | 24 + Engine/lib/openal-soft/common/albit.h | 15 + Engine/lib/openal-soft/common/albyte.h | 17 - Engine/lib/openal-soft/common/alcomplex.cpp | 180 +- Engine/lib/openal-soft/common/alcomplex.h | 17 +- Engine/lib/openal-soft/common/aldeque.h | 16 - Engine/lib/openal-soft/common/alfstream.cpp | 26 - Engine/lib/openal-soft/common/alfstream.h | 45 - Engine/lib/openal-soft/common/almalloc.cpp | 61 - Engine/lib/openal-soft/common/almalloc.h | 298 +- Engine/lib/openal-soft/common/alnumbers.h | 26 +- Engine/lib/openal-soft/common/alnumeric.h | 147 +- Engine/lib/openal-soft/common/aloptional.h | 353 - .../common/{threads.cpp => alsem.cpp} | 82 +- Engine/lib/openal-soft/common/alsem.h | 43 + Engine/lib/openal-soft/common/alspan.h | 490 +- Engine/lib/openal-soft/common/alstring.cpp | 69 +- Engine/lib/openal-soft/common/alstring.h | 33 +- .../lib/openal-soft/common/althrd_setname.cpp | 77 + .../lib/openal-soft/common/althrd_setname.h | 6 + Engine/lib/openal-soft/common/althreads.h | 143 + Engine/lib/openal-soft/common/atomic.h | 87 +- Engine/lib/openal-soft/common/comptr.h | 93 +- Engine/lib/openal-soft/common/dynload.cpp | 4 +- Engine/lib/openal-soft/common/flexarray.h | 139 + Engine/lib/openal-soft/common/intrusive_ptr.h | 31 +- Engine/lib/openal-soft/common/opthelpers.h | 5 +- Engine/lib/openal-soft/common/pffft.cpp | 2308 +++++ Engine/lib/openal-soft/common/pffft.h | 212 + Engine/lib/openal-soft/common/phase_shifter.h | 244 +- .../common/polyphase_resampler.cpp | 158 +- .../openal-soft/common/polyphase_resampler.h | 6 +- Engine/lib/openal-soft/common/ringbuffer.cpp | 216 +- Engine/lib/openal-soft/common/ringbuffer.h | 152 +- Engine/lib/openal-soft/common/strutils.cpp | 39 +- Engine/lib/openal-soft/common/strutils.h | 14 +- Engine/lib/openal-soft/common/threads.h | 48 - Engine/lib/openal-soft/common/vecmat.h | 119 +- Engine/lib/openal-soft/common/vector.h | 3 +- Engine/lib/openal-soft/common/win_main_utf8.h | 13 +- Engine/lib/openal-soft/config.h.in | 22 +- Engine/lib/openal-soft/core/ambdec.cpp | 56 +- Engine/lib/openal-soft/core/ambdec.h | 19 +- Engine/lib/openal-soft/core/ambidefs.cpp | 236 +- Engine/lib/openal-soft/core/ambidefs.h | 243 +- Engine/lib/openal-soft/core/async_event.h | 94 +- Engine/lib/openal-soft/core/bformatdec.cpp | 125 +- Engine/lib/openal-soft/core/bformatdec.h | 44 +- Engine/lib/openal-soft/core/bs2b.cpp | 148 +- Engine/lib/openal-soft/core/bs2b.h | 86 +- Engine/lib/openal-soft/core/bsinc_tables.cpp | 234 +- Engine/lib/openal-soft/core/bsinc_tables.h | 9 +- .../lib/openal-soft/core/buffer_storage.cpp | 76 - Engine/lib/openal-soft/core/buffer_storage.h | 62 +- Engine/lib/openal-soft/core/bufferline.h | 2 +- Engine/lib/openal-soft/core/context.cpp | 155 +- Engine/lib/openal-soft/core/context.h | 62 +- Engine/lib/openal-soft/core/converter.cpp | 283 +- Engine/lib/openal-soft/core/converter.h | 17 +- Engine/lib/openal-soft/core/cpu_caps.cpp | 5 +- Engine/lib/openal-soft/core/cpu_caps.h | 5 +- Engine/lib/openal-soft/core/cubic_defs.h | 6 +- Engine/lib/openal-soft/core/cubic_tables.cpp | 154 +- Engine/lib/openal-soft/core/cubic_tables.h | 36 +- Engine/lib/openal-soft/core/dbus_wrap.cpp | 22 +- Engine/lib/openal-soft/core/dbus_wrap.h | 11 +- Engine/lib/openal-soft/core/devformat.cpp | 2 + Engine/lib/openal-soft/core/devformat.h | 9 +- Engine/lib/openal-soft/core/device.cpp | 11 +- Engine/lib/openal-soft/core/device.h | 186 +- Engine/lib/openal-soft/core/effects/base.h | 261 +- Engine/lib/openal-soft/core/effectslot.cpp | 10 +- Engine/lib/openal-soft/core/effectslot.h | 29 +- Engine/lib/openal-soft/core/except.cpp | 6 +- Engine/lib/openal-soft/core/except.h | 19 +- .../lib/openal-soft/core/filters/biquad.cpp | 13 +- Engine/lib/openal-soft/core/filters/biquad.h | 11 +- Engine/lib/openal-soft/core/filters/nfc.cpp | 78 +- Engine/lib/openal-soft/core/filters/nfc.h | 35 +- .../lib/openal-soft/core/filters/splitter.cpp | 18 +- .../lib/openal-soft/core/filters/splitter.h | 6 +- Engine/lib/openal-soft/core/fmt_traits.cpp | 8 +- Engine/lib/openal-soft/core/fmt_traits.h | 64 +- Engine/lib/openal-soft/core/fpu_ctrl.cpp | 82 +- Engine/lib/openal-soft/core/fpu_ctrl.h | 23 +- Engine/lib/openal-soft/core/front_stablizer.h | 1 + Engine/lib/openal-soft/core/helpers.cpp | 534 +- Engine/lib/openal-soft/core/helpers.h | 22 +- Engine/lib/openal-soft/core/hrtf.cpp | 702 +- Engine/lib/openal-soft/core/hrtf.h | 40 +- Engine/lib/openal-soft/core/logging.cpp | 113 +- Engine/lib/openal-soft/core/logging.h | 43 +- Engine/lib/openal-soft/core/mastering.cpp | 292 +- Engine/lib/openal-soft/core/mastering.h | 32 +- Engine/lib/openal-soft/core/mixer.cpp | 15 +- Engine/lib/openal-soft/core/mixer.h | 28 +- Engine/lib/openal-soft/core/mixer/defs.h | 66 +- Engine/lib/openal-soft/core/mixer/hrtfbase.h | 79 +- Engine/lib/openal-soft/core/mixer/mixer_c.cpp | 282 +- .../lib/openal-soft/core/mixer/mixer_neon.cpp | 454 +- .../lib/openal-soft/core/mixer/mixer_sse.cpp | 335 +- .../lib/openal-soft/core/mixer/mixer_sse2.cpp | 164 +- .../openal-soft/core/mixer/mixer_sse41.cpp | 164 +- .../lib/openal-soft/core/resampler_limits.h | 4 +- Engine/lib/openal-soft/core/rtkit.cpp | 24 +- .../lib/openal-soft/core/storage_formats.cpp | 85 + Engine/lib/openal-soft/core/storage_formats.h | 54 + Engine/lib/openal-soft/core/uhjfilter.cpp | 529 +- Engine/lib/openal-soft/core/uhjfilter.h | 131 +- Engine/lib/openal-soft/core/uiddefs.cpp | 12 +- Engine/lib/openal-soft/core/voice.cpp | 738 +- Engine/lib/openal-soft/core/voice.h | 98 +- Engine/lib/openal-soft/core/voice_change.h | 4 - Engine/lib/openal-soft/docs/ambisonics.txt | 4 +- Engine/lib/openal-soft/docs/env-vars.txt | 9 + Engine/lib/openal-soft/examples/alconvolve.c | 73 +- Engine/lib/openal-soft/examples/aldirect.cpp | 472 + Engine/lib/openal-soft/examples/alffplay.cpp | 441 +- Engine/lib/openal-soft/examples/alhrtf.c | 4 +- Engine/lib/openal-soft/examples/allatency.c | 4 +- Engine/lib/openal-soft/examples/alloopback.c | 3 +- .../lib/openal-soft/examples/almultireverb.c | 14 +- Engine/lib/openal-soft/examples/alplay.c | 4 +- Engine/lib/openal-soft/examples/alrecord.c | 2 +- Engine/lib/openal-soft/examples/alreverb.c | 20 +- Engine/lib/openal-soft/examples/alstream.c | 41 +- .../lib/openal-soft/examples/alstreamcb.cpp | 117 +- Engine/lib/openal-soft/examples/altonegen.c | 77 +- .../openal-soft/examples/common/alhelpers.h | 51 +- Engine/lib/openal-soft/include/AL/al.h | 313 +- Engine/lib/openal-soft/include/AL/alc.h | 101 +- Engine/lib/openal-soft/include/AL/alext.h | 608 +- .../lib/openal-soft/include/AL/efx-presets.h | 2 + Engine/lib/openal-soft/include/AL/efx.h | 134 +- Engine/lib/openal-soft/router/al.cpp | 21 +- Engine/lib/openal-soft/router/alc.cpp | 557 +- Engine/lib/openal-soft/router/router.cpp | 519 +- Engine/lib/openal-soft/router/router.h | 99 +- Engine/lib/openal-soft/tests/CMakeLists.txt | 24 + Engine/lib/openal-soft/tests/example.t.cpp | 16 + Engine/lib/openal-soft/utils/CIAIR.def | 7780 ++++++++--------- .../utils/alsoft-config/CMakeLists.txt | 3 +- .../utils/alsoft-config/mainwindow.cpp | 738 +- .../utils/alsoft-config/mainwindow.h | 39 +- Engine/lib/openal-soft/utils/getopt.c | 137 - Engine/lib/openal-soft/utils/getopt.h | 26 - .../lib/openal-soft/utils/makemhr/loaddef.cpp | 923 +- .../lib/openal-soft/utils/makemhr/loaddef.h | 7 +- .../openal-soft/utils/makemhr/loadsofa.cpp | 172 +- .../lib/openal-soft/utils/makemhr/loadsofa.h | 4 +- .../lib/openal-soft/utils/makemhr/makemhr.cpp | 767 +- .../lib/openal-soft/utils/makemhr/makemhr.h | 67 +- Engine/lib/openal-soft/utils/openal-info.c | 89 +- Engine/lib/openal-soft/utils/sofa-info.cpp | 61 +- Engine/lib/openal-soft/utils/sofa-support.cpp | 14 +- Engine/lib/openal-soft/utils/sofa-support.h | 4 +- Engine/lib/openal-soft/utils/uhjdecoder.cpp | 191 +- Engine/lib/openal-soft/utils/uhjencoder.cpp | 258 +- 287 files changed, 33851 insertions(+), 27325 deletions(-) create mode 100644 Engine/lib/openal-soft/LICENSE-pffft create mode 100644 Engine/lib/openal-soft/al/debug.cpp create mode 100644 Engine/lib/openal-soft/al/debug.h create mode 100644 Engine/lib/openal-soft/al/direct_defs.h delete mode 100644 Engine/lib/openal-soft/al/eax/globals.cpp create mode 100644 Engine/lib/openal-soft/al/error.h create mode 100644 Engine/lib/openal-soft/alc/events.cpp create mode 100644 Engine/lib/openal-soft/alc/events.h create mode 100644 Engine/lib/openal-soft/alc/export_list.h create mode 100644 Engine/lib/openal-soft/cmake/FindSndIO.cmake delete mode 100644 Engine/lib/openal-soft/cmake/FindSoundIO.cmake create mode 100644 Engine/lib/openal-soft/common/alassert.cpp create mode 100644 Engine/lib/openal-soft/common/alassert.h delete mode 100644 Engine/lib/openal-soft/common/albyte.h delete mode 100644 Engine/lib/openal-soft/common/aldeque.h delete mode 100644 Engine/lib/openal-soft/common/alfstream.cpp delete mode 100644 Engine/lib/openal-soft/common/alfstream.h delete mode 100644 Engine/lib/openal-soft/common/almalloc.cpp delete mode 100644 Engine/lib/openal-soft/common/aloptional.h rename Engine/lib/openal-soft/common/{threads.cpp => alsem.cpp} (58%) create mode 100644 Engine/lib/openal-soft/common/alsem.h create mode 100644 Engine/lib/openal-soft/common/althrd_setname.cpp create mode 100644 Engine/lib/openal-soft/common/althrd_setname.h create mode 100644 Engine/lib/openal-soft/common/althreads.h create mode 100644 Engine/lib/openal-soft/common/flexarray.h create mode 100644 Engine/lib/openal-soft/common/pffft.cpp create mode 100644 Engine/lib/openal-soft/common/pffft.h delete mode 100644 Engine/lib/openal-soft/common/threads.h create mode 100644 Engine/lib/openal-soft/core/storage_formats.cpp create mode 100644 Engine/lib/openal-soft/core/storage_formats.h create mode 100644 Engine/lib/openal-soft/examples/aldirect.cpp create mode 100644 Engine/lib/openal-soft/tests/CMakeLists.txt create mode 100644 Engine/lib/openal-soft/tests/example.t.cpp delete mode 100644 Engine/lib/openal-soft/utils/getopt.c delete mode 100644 Engine/lib/openal-soft/utils/getopt.h diff --git a/Engine/lib/openal-soft/.github/workflows/ci.yml b/Engine/lib/openal-soft/.github/workflows/ci.yml index 1a94c7609..b1c3b413b 100644 --- a/Engine/lib/openal-soft/.github/workflows/ci.yml +++ b/Engine/lib/openal-soft/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI -on: [push] +on: [push, pull_request] jobs: build: @@ -14,6 +14,7 @@ jobs: name: "Win32-Release", os: windows-latest, cmake_opts: "-A Win32 \ + -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ @@ -24,6 +25,7 @@ jobs: name: "Win32-Debug", os: windows-latest, cmake_opts: "-A Win32 \ + -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ @@ -34,6 +36,7 @@ jobs: name: "Win64-Release", os: windows-latest, cmake_opts: "-A x64 \ + -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ @@ -44,16 +47,42 @@ jobs: name: "Win64-Debug", os: windows-latest, cmake_opts: "-A x64 \ + -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ -DALSOFT_REQUIRE_WASAPI=ON", build_type: "Debug" } + - { + name: "Win64-UWP", + os: windows-latest, + cmake_opts: "-A x64 \ + -DALSOFT_TESTS=OFF \ + -DCMAKE_SYSTEM_NAME=WindowsStore \ + \"-DCMAKE_SYSTEM_VERSION=10.0\" \ + -DALSOFT_BUILD_ROUTER=ON \ + -DALSOFT_REQUIRE_WASAPI=ON", + build_type: "Release" + } - { name: "macOS-Release", os: macos-latest, - cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON", + cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON \ + -DALSOFT_TESTS=ON", + build_type: "Release" + } + - { + name: "iOS-Release", + os: macos-latest, + cmake_opts: "-GXcode \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DALSOFT_REQUIRE_COREAUDIO=ON \ + -DALSOFT_UTILS=OFF \ + -DALSOFT_EXAMPLES=OFF \ + -DALSOFT_TESTS=OFF \ + -DALSOFT_INSTALL=OFF \ + \"-DCMAKE_OSX_ARCHITECTURES=arm64\"", build_type: "Release" } - { @@ -65,7 +94,8 @@ jobs: -DALSOFT_REQUIRE_PORTAUDIO=ON \ -DALSOFT_REQUIRE_PULSEAUDIO=ON \ -DALSOFT_REQUIRE_JACK=ON \ - -DALSOFT_REQUIRE_PIPEWIRE=ON", + -DALSOFT_REQUIRE_PIPEWIRE=ON \ + -DALSOFT_TESTS=ON", deps_cmdline: "sudo apt update && sudo apt-get install -qq \ libpulse-dev \ portaudio19-dev \ @@ -78,7 +108,7 @@ jobs: } steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Install Dependencies shell: bash @@ -97,6 +127,12 @@ jobs: run: | cmake --build build --config ${{matrix.config.build_type}} + - name: Test + shell: bash + run: | + cd build + ctest + - name: Create Archive if: ${{ matrix.config.os == 'windows-latest' }} shell: bash @@ -109,7 +145,7 @@ jobs: - name: Upload Archive # Upload package as an artifact of this workflow. - uses: actions/upload-artifact@v3.1.1 + uses: actions/upload-artifact@v3.1.2 if: ${{ matrix.config.os == 'windows-latest' }} with: name: soft_oal-${{matrix.config.name}} diff --git a/Engine/lib/openal-soft/.github/workflows/makemhr.yml b/Engine/lib/openal-soft/.github/workflows/makemhr.yml index 7bde284c1..654861500 100644 --- a/Engine/lib/openal-soft/.github/workflows/makemhr.yml +++ b/Engine/lib/openal-soft/.github/workflows/makemhr.yml @@ -55,7 +55,7 @@ jobs: copy "libmysofa/windows/third-party/zlib-1.2.11/bin/zlib.dll" "Artifacts/zlib.dll" - name: Upload makemhr artifact - uses: actions/upload-artifact@v3.1.1 + uses: actions/upload-artifact@v3.1.2 with: name: makemhr path: "Artifacts/" diff --git a/Engine/lib/openal-soft/.gitignore b/Engine/lib/openal-soft/.gitignore index 4a8212b53..688980a54 100644 --- a/Engine/lib/openal-soft/.gitignore +++ b/Engine/lib/openal-soft/.gitignore @@ -1,6 +1,7 @@ build*/ winbuild win64build +.vs/ ## kdevelop *.kdev4 diff --git a/Engine/lib/openal-soft/CMakeLists.txt b/Engine/lib/openal-soft/CMakeLists.txt index 55644b045..e8c0607f9 100644 --- a/Engine/lib/openal-soft/CMakeLists.txt +++ b/Engine/lib/openal-soft/CMakeLists.txt @@ -1,6 +1,7 @@ # CMake build file list for OpenAL -cmake_minimum_required(VERSION 3.0.2) +cmake_minimum_required(VERSION 3.13) +enable_testing() if(APPLE) # The workaround for try_compile failing with code signing @@ -28,11 +29,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "iOS") FORCE) endif() endif() +elseif(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + set(ALSOFT_UWP TRUE) endif() -set(CMAKE_C_VISIBILITY_PRESET hidden) -set(CMAKE_CXX_VISIBILITY_PRESET hidden) - if(COMMAND CMAKE_POLICY) cmake_policy(SET CMP0003 NEW) cmake_policy(SET CMP0005 NEW) @@ -76,8 +76,8 @@ if(NOT CMAKE_DEBUG_POSTFIX) endif() set(DEFAULT_TARGET_PROPS - # Require C++14. - CXX_STANDARD 14 + # Require C++17. + CXX_STANDARD 17 CXX_STANDARD_REQUIRED TRUE # Prefer C11, but support C99 and earlier when possible. C_STANDARD 11) @@ -109,6 +109,7 @@ option(ALSOFT_UTILS "Build utility programs" ON) option(ALSOFT_NO_CONFIG_UTIL "Disable building the alsoft-config utility" OFF) option(ALSOFT_EXAMPLES "Build example programs" ON) +option(ALSOFT_TESTS "Build test programs" OFF) option(ALSOFT_INSTALL "Install main library" ON) option(ALSOFT_INSTALL_CONFIG "Install alsoft.conf sample configuration file" ON) @@ -148,6 +149,8 @@ set(CPP_DEFS ) # C pre-processor, not C++ set(INC_PATHS ) set(C_FLAGS ) set(LINKER_FLAGS ) +set(LINKER_FLAGS_DEBUG ) +set(LINKER_FLAGS_RELEASE ) set(EXTRA_LIBS ) if(WIN32) @@ -165,7 +168,7 @@ elseif(APPLE) endif() -# QNX's gcc do not uses /usr/include and /usr/lib pathes by default +# QNX's gcc do not uses /usr/include and /usr/lib paths by default if("${CMAKE_C_PLATFORM_ID}" STREQUAL "QNX") set(INC_PATHS ${INC_PATHS} /usr/include) set(LINKER_FLAGS ${LINKER_FLAGS} -L/usr/lib) @@ -186,29 +189,18 @@ set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0) set(EXPORT_DECL "") -if(NOT WIN32) - # Check if _POSIX_C_SOURCE and _XOPEN_SOURCE needs to be set for POSIX functions - check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_DEFAULT) - if(NOT HAVE_POSIX_MEMALIGN_DEFAULT) - set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=600") - check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_POSIX) - if(NOT HAVE_POSIX_MEMALIGN_POSIX) - set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) - else() - set(CPP_DEFS ${CPP_DEFS} _POSIX_C_SOURCE=200112L _XOPEN_SOURCE=600) - endif() - endif() - unset(OLD_REQUIRED_FLAGS) -endif() - -# C99 has restrict, but C++ does not, so we can only utilize __restrict. -check_cxx_source_compiles("int *__restrict foo; -int main() { return 0; }" HAVE___RESTRICT) -if(HAVE___RESTRICT) - set(CPP_DEFS ${CPP_DEFS} RESTRICT=__restrict) -else() - set(CPP_DEFS ${CPP_DEFS} "RESTRICT=") +# Some systems erroneously require the __STDC_FORMAT_MACROS macro to be defined +# to get the fixed-width integer type formatter macros. +check_cxx_source_compiles("#include +#include +int main() +{ + int64_t i64{}; + std::printf(\"%\" PRId64, i64); +}" +HAVE_STDC_FORMAT_MACROS) +if(NOT HAVE_STDC_FORMAT_MACROS) + set(CPP_DEFS ${CPP_DEFS} __STDC_FORMAT_MACROS) endif() # Some systems may need libatomic for atomic functions to work @@ -278,6 +270,11 @@ else() endif() endif() + check_cxx_compiler_flag(-Wno-interference-size HAVE_WNO_INTERFERENCE_SIZE) + if(HAVE_WNO_INTERFERENCE_SIZE) + set(C_FLAGS ${C_FLAGS} $<$:-Wno-interference-size>) + endif() + if(ALSOFT_WERROR) set(C_FLAGS ${C_FLAGS} -Werror) endif() @@ -312,7 +309,7 @@ else() 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") + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-static-libstdc++") check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBSTDCXX_SWITCH) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES}) unset(OLD_REQUIRED_LIBRARIES) @@ -320,14 +317,14 @@ else() 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") + set(LINKER_FLAGS ${LINKER_FLAGS} "-static-libstdc++") 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") + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lstdc++,-lwinpthread,--pop-state") check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBWINPTHREAD_SWITCH) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES}) unset(OLD_REQUIRED_LIBRARIES) @@ -335,38 +332,38 @@ else() 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") + set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--push-state,-Bstatic,-lstdc++,-lwinpthread,--pop-state") endif() endif() endif() # Set visibility/export options if available -if(WIN32) - if(NOT LIBTYPE STREQUAL "STATIC") +if(NOT LIBTYPE STREQUAL "STATIC") + if(WIN32) set(EXPORT_DECL "__declspec(dllexport)") - endif() -else() - set(OLD_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") - # Yes GCC, really don't accept visibility modes you don't support - set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Wattributes -Werror") - - check_c_source_compiles("int foo() __attribute__((visibility(\"protected\"))); - int main() {return 0;}" HAVE_GCC_PROTECTED_VISIBILITY) - if(HAVE_GCC_PROTECTED_VISIBILITY) - if(NOT LIBTYPE STREQUAL "STATIC") - set(EXPORT_DECL "__attribute__((visibility(\"protected\")))") - endif() else() - check_c_source_compiles("int foo() __attribute__((visibility(\"default\"))); - int main() {return 0;}" HAVE_GCC_DEFAULT_VISIBILITY) - if(HAVE_GCC_DEFAULT_VISIBILITY) - if(NOT LIBTYPE STREQUAL "STATIC") + set(OLD_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + # Yes GCC, really don't accept visibility modes you don't support + set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Wattributes -Werror") + + check_c_source_compiles("int foo() __attribute__((visibility(\"protected\"))); + int main() {return 0;}" HAVE_GCC_PROTECTED_VISIBILITY) + if(HAVE_GCC_PROTECTED_VISIBILITY) + set(EXPORT_DECL "__attribute__((visibility(\"protected\")))") + else() + check_c_source_compiles("int foo() __attribute__((visibility(\"default\"))); + int main() {return 0;}" HAVE_GCC_DEFAULT_VISIBILITY) + if(HAVE_GCC_DEFAULT_VISIBILITY) set(EXPORT_DECL "__attribute__((visibility(\"default\")))") endif() endif() - endif() + if(HAVE_GCC_PROTECTED_VISIBILITY OR HAVE_GCC_DEFAULT_VISIBILITY) + set(CMAKE_C_VISIBILITY_PRESET hidden) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) + endif() - set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}") + endif() endif() @@ -403,7 +400,7 @@ if(ALSOFT_CPUEXT_SSE AND HAVE_XMMINTRIN_H) set(HAVE_SSE 1) endif() if(ALSOFT_REQUIRE_SSE AND NOT HAVE_SSE) - message(FATAL_ERROR "Failed to enabled required SSE CPU extensions") + message(FATAL_ERROR "Failed to enable required SSE CPU extensions") endif() option(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON) @@ -448,10 +445,11 @@ if(ALSOFT_CPUEXT_NEON AND HAVE_ARM_NEON_H) endif() endif() if(ALSOFT_REQUIRE_NEON AND NOT HAVE_NEON) - message(FATAL_ERROR "Failed to enabled required ARM NEON CPU extensions") + message(FATAL_ERROR "Failed to enable required ARM NEON CPU extensions") endif() +set(ALSOFT_FORCE_ALIGN ) set(SSE_FLAGS ) set(FPMATH_SET "0") if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2) @@ -476,6 +474,7 @@ if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2) # OSs don't guarantee this on 32-bit, so externally-callable # functions need to ensure an aligned stack. set(EXPORT_DECL "${EXPORT_DECL}__attribute__((force_align_arg_pointer))") + set(ALSOFT_FORCE_ALIGN "__attribute__((force_align_arg_pointer))") endif() endif() endif() @@ -493,13 +492,9 @@ if(HAVE_SSE2) endif() -check_include_file(malloc.h HAVE_MALLOC_H) check_include_file(cpuid.h HAVE_CPUID_H) check_include_file(intrin.h HAVE_INTRIN_H) check_include_file(guiddef.h HAVE_GUIDDEF_H) -if(NOT HAVE_GUIDDEF_H) - check_include_file(initguid.h HAVE_INITGUID_H) -endif() # Some systems need libm for some math functions to work set(MATH_LIB ) @@ -517,7 +512,7 @@ if(HAVE_LIBRT) set(RT_LIB rt) endif() -# Check for the dlopen API (for dynamicly loading backend libs) +# Check for the dlopen API (for dynamically loading backend libs) if(ALSOFT_DLOPEN) check_include_file(dlfcn.h HAVE_DLFCN_H) check_library_exists(dl dlopen "" HAVE_LIBDL) @@ -546,8 +541,6 @@ if(HAVE_INTRIN_H) }" HAVE_CPUID_INTRINSIC) endif() -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) if(NOT WIN32) @@ -582,34 +575,36 @@ if(NOT WIN32) endif() endif() -check_symbol_exists(getopt unistd.h HAVE_GETOPT) - # Common sources used by both the OpenAL implementation library, the OpenAL # router, and certain tools and examples. set(COMMON_OBJS + common/alassert.cpp + common/alassert.h common/albit.h - common/albyte.h common/alcomplex.cpp common/alcomplex.h - common/aldeque.h - common/alfstream.cpp - common/alfstream.h - common/almalloc.cpp common/almalloc.h common/alnumbers.h common/alnumeric.h - common/aloptional.h + common/alsem.cpp + common/alsem.h common/alspan.h common/alstring.cpp common/alstring.h + common/althrd_setname.cpp + common/althrd_setname.h + common/althreads.h common/altraits.h common/atomic.h common/comptr.h common/dynload.cpp common/dynload.h + common/flexarray.h common/intrusive_ptr.h common/opthelpers.h + common/pffft.cpp + common/pffft.h common/phase_shifter.h common/polyphase_resampler.cpp common/polyphase_resampler.h @@ -618,8 +613,6 @@ set(COMMON_OBJS common/ringbuffer.h common/strutils.cpp common/strutils.h - common/threads.cpp - common/threads.h common/vecmat.h common/vector.h) @@ -680,6 +673,8 @@ set(CORE_OBJS core/mixer.cpp core/mixer.h core/resampler_limits.h + core/storage_formats.cpp + core/storage_formats.h core/uhjfilter.cpp core/uhjfilter.h core/uiddefs.cpp @@ -726,7 +721,7 @@ if(NOT WIN32) endif() endif() if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT) - message(FATAL_ERROR "Failed to enabled required RTKit support") + message(FATAL_ERROR "Failed to enable required RTKit support") endif() # Default mixers, always available @@ -742,6 +737,9 @@ set(OPENAL_OBJS al/auxeffectslot.h al/buffer.cpp al/buffer.h + al/debug.cpp + al/debug.h + al/direct_defs.h al/effect.cpp al/effect.h al/effects/autowah.cpp @@ -761,6 +759,7 @@ set(OPENAL_OBJS al/effects/reverb.cpp al/effects/vmorpher.cpp al/error.cpp + al/error.h al/event.cpp al/event.h al/extension.cpp @@ -798,6 +797,9 @@ set(ALC_OBJS alc/effects/pshifter.cpp alc/effects/reverb.cpp alc/effects/vmorpher.cpp + alc/events.cpp + alc/events.h + alc/export_list.h alc/inprogext.h alc/panning.cpp) @@ -815,7 +817,6 @@ if(ALSOFT_EAX) al/eax/fx_slot_index.h al/eax/fx_slots.cpp al/eax/fx_slots.h - al/eax/globals.cpp al/eax/globals.h al/eax/utils.cpp al/eax/utils.h @@ -899,7 +900,7 @@ if(ALSOFT_BACKEND_PIPEWIRE AND PkgConfig_FOUND) endif() endif() if(ALSOFT_REQUIRE_PIPEWIRE AND NOT HAVE_PIPEWIRE) - message(FATAL_ERROR "Failed to enabled required PipeWire backend") + message(FATAL_ERROR "Failed to enable required PipeWire backend") endif() # Check PulseAudio backend @@ -916,7 +917,7 @@ if(ALSOFT_BACKEND_PULSEAUDIO) endif() endif() if(ALSOFT_REQUIRE_PULSEAUDIO AND NOT HAVE_PULSEAUDIO) - message(FATAL_ERROR "Failed to enabled required PulseAudio backend") + message(FATAL_ERROR "Failed to enable required PulseAudio backend") endif() if(NOT WIN32) @@ -963,66 +964,72 @@ if(NOT WIN32) endif() endif() - # Check SndIO backend - option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON) + # Check SndIO backend (disabled by default on non-BSDs) + if(BSD) + option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON) + else() + option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" OFF) + endif() option(ALSOFT_REQUIRE_SNDIO "Require SndIO backend" OFF) if(ALSOFT_BACKEND_SNDIO) - find_package(SoundIO) - if(SOUNDIO_FOUND) + find_package(SndIO) + if(SNDIO_FOUND) set(HAVE_SNDIO 1) set(BACKENDS "${BACKENDS} SndIO (linked),") set(ALC_OBJS ${ALC_OBJS} alc/backends/sndio.cpp alc/backends/sndio.h) - set(EXTRA_LIBS ${SOUNDIO_LIBRARIES} ${EXTRA_LIBS}) - set(INC_PATHS ${INC_PATHS} ${SOUNDIO_INCLUDE_DIRS}) + set(EXTRA_LIBS ${SNDIO_LIBRARIES} ${EXTRA_LIBS}) + set(INC_PATHS ${INC_PATHS} ${SNDIO_INCLUDE_DIRS}) endif() endif() endif() if(ALSOFT_REQUIRE_ALSA AND NOT HAVE_ALSA) - message(FATAL_ERROR "Failed to enabled required ALSA backend") + message(FATAL_ERROR "Failed to enable required ALSA backend") endif() if(ALSOFT_REQUIRE_OSS AND NOT HAVE_OSS) - message(FATAL_ERROR "Failed to enabled required OSS backend") + message(FATAL_ERROR "Failed to enable required OSS backend") endif() if(ALSOFT_REQUIRE_SOLARIS AND NOT HAVE_SOLARIS) - message(FATAL_ERROR "Failed to enabled required Solaris backend") + message(FATAL_ERROR "Failed to enable required Solaris backend") endif() if(ALSOFT_REQUIRE_SNDIO AND NOT HAVE_SNDIO) - message(FATAL_ERROR "Failed to enabled required SndIO backend") + message(FATAL_ERROR "Failed to enable required SndIO backend") endif() # Check Windows-only backends if(WIN32) - # Check MMSystem backend - option(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON) - option(ALSOFT_REQUIRE_WINMM "Require Windows Multimedia backend" OFF) - 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) - # There doesn't seem to be good way to search for winmm.lib for MSVC. - # find_library doesn't find it without being told to look in a specific - # place in the WindowsSDK, but it links anyway. If there ends up being - # Windows targets without this, another means to detect it is needed. - set(EXTRA_LIBS winmm ${EXTRA_LIBS}) - endif() - - # Check DSound backend - option(ALSOFT_BACKEND_DSOUND "Enable DirectSound backend" ON) - option(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF) - if(ALSOFT_BACKEND_DSOUND) - check_include_file(dsound.h HAVE_DSOUND_H) - if(DXSDK_DIR) - find_path(DSOUND_INCLUDE_DIR NAMES "dsound.h" - PATHS "${DXSDK_DIR}" PATH_SUFFIXES include - DOC "The DirectSound include directory") + if (NOT ALSOFT_UWP) + # Check MMSystem backend + option(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON) + option(ALSOFT_REQUIRE_WINMM "Require Windows Multimedia backend" OFF) + 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) + # There doesn't seem to be good way to search for winmm.lib for MSVC. + # find_library doesn't find it without being told to look in a specific + # place in the WindowsSDK, but it links anyway. If there ends up being + # Windows targets without this, another means to detect it is needed. + set(EXTRA_LIBS winmm ${EXTRA_LIBS}) endif() - if(HAVE_DSOUND_H OR DSOUND_INCLUDE_DIR) - set(HAVE_DSOUND 1) - set(BACKENDS "${BACKENDS} DirectSound,") - set(ALC_OBJS ${ALC_OBJS} alc/backends/dsound.cpp alc/backends/dsound.h) - if(NOT HAVE_DSOUND_H) - set(INC_PATHS ${INC_PATHS} ${DSOUND_INCLUDE_DIR}) + # Check DSound backend + option(ALSOFT_BACKEND_DSOUND "Enable DirectSound backend" ON) + option(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF) + if(ALSOFT_BACKEND_DSOUND) + check_include_file(dsound.h HAVE_DSOUND_H) + if(DXSDK_DIR) + find_path(DSOUND_INCLUDE_DIR NAMES "dsound.h" + PATHS "${DXSDK_DIR}" PATH_SUFFIXES include + DOC "The DirectSound include directory") + endif() + if(HAVE_DSOUND_H OR DSOUND_INCLUDE_DIR) + set(HAVE_DSOUND 1) + set(BACKENDS "${BACKENDS} DirectSound,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/dsound.cpp alc/backends/dsound.h) + + if(NOT HAVE_DSOUND_H) + set(INC_PATHS ${INC_PATHS} ${DSOUND_INCLUDE_DIR}) + endif() endif() endif() endif() @@ -1040,13 +1047,13 @@ if(WIN32) endif() endif() if(ALSOFT_REQUIRE_WINMM AND NOT HAVE_WINMM) - message(FATAL_ERROR "Failed to enabled required WinMM backend") + message(FATAL_ERROR "Failed to enable required WinMM backend") endif() if(ALSOFT_REQUIRE_DSOUND AND NOT HAVE_DSOUND) - message(FATAL_ERROR "Failed to enabled required DSound backend") + message(FATAL_ERROR "Failed to enable required DSound backend") endif() if(ALSOFT_REQUIRE_WASAPI AND NOT HAVE_WASAPI) - message(FATAL_ERROR "Failed to enabled required WASAPI backend") + message(FATAL_ERROR "Failed to enable required WASAPI backend") endif() # Check JACK backend @@ -1063,7 +1070,7 @@ if(ALSOFT_BACKEND_JACK) endif() endif() if(ALSOFT_REQUIRE_JACK AND NOT HAVE_JACK) - message(FATAL_ERROR "Failed to enabled required JACK backend") + message(FATAL_ERROR "Failed to enable required JACK backend") endif() # Check CoreAudio backend @@ -1098,7 +1105,7 @@ if(ALSOFT_BACKEND_COREAUDIO) endif() endif() if(ALSOFT_REQUIRE_COREAUDIO AND NOT HAVE_COREAUDIO) - message(FATAL_ERROR "Failed to enabled required CoreAudio backend") + message(FATAL_ERROR "Failed to enable required CoreAudio backend") endif() # Check for Oboe (Android) backend @@ -1130,7 +1137,7 @@ if(ALSOFT_BACKEND_OBOE) endif() endif() if(ALSOFT_REQUIRE_OBOE AND NOT HAVE_OBOE) - message(FATAL_ERROR "Failed to enabled required Oboe backend") + message(FATAL_ERROR "Failed to enable required Oboe backend") endif() # Check for OpenSL (Android) backend @@ -1142,11 +1149,12 @@ 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::OpenSLES" ${EXTRA_LIBS}) + 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) - message(FATAL_ERROR "Failed to enabled required OpenSL backend") + message(FATAL_ERROR "Failed to enable required OpenSL backend") endif() # Check PortAudio backend @@ -1163,7 +1171,7 @@ if(ALSOFT_BACKEND_PORTAUDIO) endif() endif() if(ALSOFT_REQUIRE_PORTAUDIO AND NOT HAVE_PORTAUDIO) - message(FATAL_ERROR "Failed to enabled required PortAudio backend") + message(FATAL_ERROR "Failed to enable required PortAudio backend") endif() # Check for SDL2 backend @@ -1181,7 +1189,7 @@ if(ALSOFT_BACKEND_SDL2) endif() endif() if(ALSOFT_REQUIRE_SDL2 AND NOT SDL2_FOUND) - message(FATAL_ERROR "Failed to enabled required SDL2 backend") + message(FATAL_ERROR "Failed to enable required SDL2 backend") endif() # Optionally enable the Wave Writer backend @@ -1309,11 +1317,12 @@ configure_file( @ONLY) -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 ${DEFAULT_TARGET_PROPS} POSITION_INDEPENDENT_CODE TRUE) +add_library(alcommon STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS}) +target_include_directories(alcommon PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/include + PUBLIC ${OpenAL_SOURCE_DIR}/common) +target_compile_definitions(alcommon PRIVATE ${CPP_DEFS}) +target_compile_options(alcommon PRIVATE ${C_FLAGS}) +set_target_properties(alcommon PROPERTIES ${DEFAULT_TARGET_PROPS} POSITION_INDEPENDENT_CODE TRUE) unset(HAS_ROUTER) @@ -1348,7 +1357,7 @@ else() PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) target_compile_options(OpenAL PRIVATE ${C_FLAGS}) - target_link_libraries(OpenAL PRIVATE common ${LINKER_FLAGS}) + target_link_libraries(OpenAL PRIVATE alcommon ${LINKER_FLAGS}) target_include_directories(OpenAL PUBLIC $ @@ -1379,13 +1388,31 @@ else() if(WIN32) set_target_properties(${IMPL_TARGET} PROPERTIES PREFIX "") endif() - target_link_libraries(${IMPL_TARGET} PRIVATE common ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) + target_link_libraries(${IMPL_TARGET} PRIVATE alcommon ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) + + if(ALSOFT_UWP) + set(ALSOFT_CPPWINRT_VERSION "2.0.230706.1" CACHE STRING "The soft-oal default cppwinrt version") + + find_program(NUGET_EXE NAMES nuget) + if(NOT NUGET_EXE) + message("NUGET.EXE not found.") + message(FATAL_ERROR "Please install this executable, and run CMake again.") + endif() + + exec_program(${NUGET_EXE} + ARGS install "Microsoft.Windows.CppWinRT" -Version ${ALSOFT_CPPWINRT_VERSION} -ExcludeVersion -OutputDirectory "\"${CMAKE_BINARY_DIR}/packages\"") + + set_target_properties(${IMPL_TARGET} PROPERTIES + VS_PROJECT_IMPORT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.props + ) + target_link_libraries(${IMPL_TARGET} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.targets) + endif() if(NOT WIN32 AND NOT APPLE) # FIXME: This doesn't put a dependency on the version script. Changing # the version script will not cause a relink as it should. - set_property(TARGET ${IMPL_TARGET} APPEND_STRING PROPERTY - LINK_FLAGS " -Wl,--version-script=${OpenAL_SOURCE_DIR}/libopenal.version") + target_link_options(${IMPL_TARGET} PRIVATE + "-Wl,--version-script=${OpenAL_SOURCE_DIR}/libopenal.version") endif() if(APPLE AND ALSOFT_OSX_FRAMEWORK) @@ -1443,8 +1470,8 @@ set_target_properties(${IMPL_TARGET} PROPERTIES ${DEFAULT_TARGET_PROPS} SOVERSION ${LIB_MAJOR_VERSION} ) target_compile_definitions(${IMPL_TARGET} - PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" - ${CPP_DEFS}) + PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES $<$:ALSOFT_EAX> + "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS}) if(TARGET build_version) @@ -1462,8 +1489,7 @@ if(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC" message(STATUS "WARNING: Cannot find dlltool, disabling .def/.lib generation") endif() else() - set_property(TARGET OpenAL APPEND_STRING PROPERTY - LINK_FLAGS " -Wl,--output-def,OpenAL32.def") + target_link_options(OpenAL PRIVATE "-Wl,--output-def,${PROJECT_BINARY_DIR}/OpenAL32.def") add_custom_command(TARGET OpenAL POST_BUILD COMMAND "${SED_EXECUTABLE}" -i -e "s/ @[^ ]*//" OpenAL32.def COMMAND "${CMAKE_DLLTOOL}" -d OpenAL32.def -l OpenAL32.lib -D OpenAL32.dll @@ -1494,7 +1520,10 @@ if(FPMATH_SET) message(STATUS "Building with SSE${FPMATH_SET} codegen") message(STATUS "") endif() - +if(ALSOFT_UWP) + message(STATUS "Building with UWP support") + message(STATUS "") +endif() if(ALSOFT_EAX) message(STATUS "Building with legacy EAX extension support") message(STATUS "") @@ -1580,7 +1609,7 @@ if(ALSOFT_UTILS) target_include_directories(uhjdecoder PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(uhjdecoder PRIVATE ${C_FLAGS}) - target_link_libraries(uhjdecoder PUBLIC common + target_link_libraries(uhjdecoder PUBLIC alcommon PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) set_target_properties(uhjdecoder PROPERTIES ${DEFAULT_TARGET_PROPS}) @@ -1589,7 +1618,7 @@ if(ALSOFT_UTILS) target_include_directories(uhjencoder PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(uhjencoder PRIVATE ${C_FLAGS}) - target_link_libraries(uhjencoder PUBLIC common + target_link_libraries(uhjencoder PUBLIC alcommon PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) set_target_properties(uhjencoder PROPERTIES ${DEFAULT_TARGET_PROPS}) endif() @@ -1602,7 +1631,7 @@ if(ALSOFT_UTILS) target_compile_definitions(sofa-support PRIVATE ${CPP_DEFS}) target_include_directories(sofa-support PUBLIC ${OpenAL_SOURCE_DIR}/common) target_compile_options(sofa-support PRIVATE ${C_FLAGS}) - target_link_libraries(sofa-support PUBLIC common MySOFA::MySOFA PRIVATE ${LINKER_FLAGS}) + target_link_libraries(sofa-support PUBLIC alcommon MySOFA::MySOFA PRIVATE ${LINKER_FLAGS}) set_target_properties(sofa-support PROPERTIES ${DEFAULT_TARGET_PROPS}) set(MAKEMHR_SRCS @@ -1612,9 +1641,6 @@ if(ALSOFT_UTILS) utils/makemhr/loadsofa.h utils/makemhr/makemhr.cpp utils/makemhr/makemhr.h) - if(NOT HAVE_GETOPT) - set(MAKEMHR_SRCS ${MAKEMHR_SRCS} utils/getopt.c utils/getopt.h) - endif() add_executable(makemhr ${MAKEMHR_SRCS}) target_compile_definitions(makemhr PRIVATE ${CPP_DEFS}) target_include_directories(makemhr @@ -1644,22 +1670,22 @@ endif() # Add a static library with common functions used by multiple example targets -add_library(ex-common STATIC EXCLUDE_FROM_ALL +add_library(al-excommon 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}) -set_target_properties(ex-common PROPERTIES ${DEFAULT_TARGET_PROPS}) +target_compile_definitions(al-excommon PUBLIC ${CPP_DEFS}) +target_include_directories(al-excommon PUBLIC ${OpenAL_SOURCE_DIR}/common) +target_compile_options(al-excommon PUBLIC ${C_FLAGS}) +target_link_libraries(al-excommon PUBLIC OpenAL PRIVATE ${RT_LIB}) +set_target_properties(al-excommon PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_EXAMPLES) add_executable(altonegen examples/altonegen.c) - target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} ex-common ${UNICODE_FLAG}) + target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} al-excommon ${UNICODE_FLAG}) set_target_properties(altonegen PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(alrecord examples/alrecord.c) - target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} ex-common ${UNICODE_FLAG}) + target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} al-excommon ${UNICODE_FLAG}) set_target_properties(alrecord PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) @@ -1670,48 +1696,53 @@ if(ALSOFT_EXAMPLES) if(SNDFILE_FOUND) add_executable(alplay examples/alplay.c) - target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) set_target_properties(alplay PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(alstream examples/alstream.c) - target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) set_target_properties(alstream PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(alreverb examples/alreverb.c) - target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) set_target_properties(alreverb PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(almultireverb examples/almultireverb.c) target_link_libraries(almultireverb - PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common ${MATH_LIB} ${UNICODE_FLAG}) + PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG}) set_target_properties(almultireverb PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(allatency examples/allatency.c) - target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) set_target_properties(allatency PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(alhrtf examples/alhrtf.c) target_link_libraries(alhrtf - PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common ${MATH_LIB} ${UNICODE_FLAG}) + PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG}) set_target_properties(alhrtf PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(alstreamcb examples/alstreamcb.cpp) - target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) set_target_properties(alstreamcb PROPERTIES ${DEFAULT_TARGET_PROPS}) - add_executable(alconvolve examples/alconvolve.c) - target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} common SndFile::SndFile ex-common + add_executable(aldirect examples/aldirect.cpp) + target_link_libraries(aldirect PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) + set_target_properties(aldirect PROPERTIES ${DEFAULT_TARGET_PROPS}) + + add_executable(alconvolve examples/alconvolve.c) + target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} alcommon SndFile::SndFile + al-excommon ${UNICODE_FLAG}) set_target_properties(alconvolve PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alplay alstream alreverb almultireverb allatency - alhrtf) + alhrtf aldirect) endif() message(STATUS "Building SndFile example programs") @@ -1720,7 +1751,7 @@ if(ALSOFT_EXAMPLES) if(SDL2_FOUND) add_executable(alloopback examples/alloopback.c) target_link_libraries(alloopback - PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ex-common ${MATH_LIB}) + PRIVATE ${LINKER_FLAGS} SDL2::SDL2 al-excommon ${MATH_LIB}) set_target_properties(alloopback PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) @@ -1757,7 +1788,7 @@ if(ALSOFT_EXAMPLES) add_executable(alffplay examples/alffplay.cpp) target_include_directories(alffplay PRIVATE ${FFMPEG_INCLUDE_DIRS}) target_link_libraries(alffplay - PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ${FFMPEG_LIBRARIES} ex-common) + PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ${FFMPEG_LIBRARIES} al-excommon) set_target_properties(alffplay PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) @@ -1769,6 +1800,10 @@ if(ALSOFT_EXAMPLES) message(STATUS "") endif() +if (ALSOFT_TESTS) +add_subdirectory(tests) +endif() + if(EXTRA_INSTALLS) install(TARGETS ${EXTRA_INSTALLS} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/Engine/lib/openal-soft/LICENSE-pffft b/Engine/lib/openal-soft/LICENSE-pffft new file mode 100644 index 000000000..5f1a8493f --- /dev/null +++ b/Engine/lib/openal-soft/LICENSE-pffft @@ -0,0 +1,38 @@ +A modified PFFFT is included, with the following license. + +Copyright (c) 2023 Christopher Robinson + +Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) + +Copyright (c) 2004 the University Corporation for Atmospheric +Research ("UCAR"). All rights reserved. Developed by NCAR's +Computational and Information Systems Laboratory, UCAR, +www.cisl.ucar.edu. + +Redistribution and use of the Software in source and binary forms, +with or without modification, is permitted provided that the +following conditions are met: + +- Neither the names of NCAR's Computational and Information Systems +Laboratory, the University Corporation for Atmospheric Research, +nor the names of its sponsors or contributors may be used to +endorse or promote products derived from this Software without +specific prior written permission. + +- Redistributions of source code must retain the above copyright +notices, this list of conditions, and the disclaimer below. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions, and the disclaimer below in the +documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. diff --git a/Engine/lib/openal-soft/OpenALConfig.cmake.in b/Engine/lib/openal-soft/OpenALConfig.cmake.in index 128c1a4e0..9704d3c49 100644 --- a/Engine/lib/openal-soft/OpenALConfig.cmake.in +++ b/Engine/lib/openal-soft/OpenALConfig.cmake.in @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.1...3.18) include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake") diff --git a/Engine/lib/openal-soft/README.md b/Engine/lib/openal-soft/README.md index 50b7bfb49..dac53e71e 100644 --- a/Engine/lib/openal-soft/README.md +++ b/Engine/lib/openal-soft/README.md @@ -64,6 +64,33 @@ as application-agnostic behavior of the library. See alsoftrc.sample for available settings. +Language Bindings +----------------- + +As a C API, OpenAL Soft can be used directly by any language that can use +functions with C linkage. For languages that can't directly use C-style +headers, bindings may be developed to allow code written in that language to +call into the library. Some bindings for some languages are listed here. + +C# Bindings: +* [OpenTK](https://opentk.net/) includes low-level C# bindings for the OpenAL +API, including some extensions. It also includes utility libraries for math and +linear algebra, which can be useful for 3D calculations. + +Java Bindings: +* [JOAL](https://jogamp.org/joal/www/), part of the JogAmp project, includes +Java bindings for the OpenAL API, usable with OpenAL Soft. It also includes a +higher level Sound3D Toolkit API and utility functions to make easier use of +OpenAL features and capabilities. + +Python Bindings: +* [PyOpenAL](https://pypi.org/project/PyOpenAL/). Also includes methods to play +wave files and, with PyOgg, also Vorbis, Opus, and FLAC. + +Other bindings for these and other languages also exist. This list will grow as +more bindings are found. + + Acknowledgements ---------------- diff --git a/Engine/lib/openal-soft/al/auxeffectslot.cpp b/Engine/lib/openal-soft/al/auxeffectslot.cpp index 285da1d43..243f8fcbf 100644 --- a/Engine/lib/openal-soft/al/auxeffectslot.cpp +++ b/Engine/lib/openal-soft/al/auxeffectslot.cpp @@ -23,69 +23,82 @@ #include "auxeffectslot.h" #include -#include +#include #include +#include #include #include #include #include -#include +#include +#include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" +#include "AL/alext.h" #include "AL/efx.h" #include "albit.h" #include "alc/alu.h" #include "alc/context.h" #include "alc/device.h" +#include "alc/effects/base.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" +#include "atomic.h" #include "buffer.h" -#include "core/except.h" +#include "core/buffer_storage.h" +#include "core/device.h" #include "core/fpu_ctrl.h" #include "core/logging.h" +#include "direct_defs.h" #include "effect.h" +#include "error.h" +#include "flexarray.h" #include "opthelpers.h" +#ifdef ALSOFT_EAX +#include "eax/api.h" +#include "eax/call.h" +#include "eax/effect.h" +#include "eax/fx_slot_index.h" +#include "eax/utils.h" +#endif + namespace { -struct FactoryItem { - EffectSlotType Type; - EffectStateFactory* (&GetFactory)(void); -}; -constexpr FactoryItem FactoryList[] = { - { EffectSlotType::None, NullStateFactory_getFactory }, - { EffectSlotType::EAXReverb, ReverbStateFactory_getFactory }, - { EffectSlotType::Reverb, StdReverbStateFactory_getFactory }, - { EffectSlotType::Autowah, AutowahStateFactory_getFactory }, - { EffectSlotType::Chorus, ChorusStateFactory_getFactory }, - { EffectSlotType::Compressor, CompressorStateFactory_getFactory }, - { EffectSlotType::Distortion, DistortionStateFactory_getFactory }, - { EffectSlotType::Echo, EchoStateFactory_getFactory }, - { EffectSlotType::Equalizer, EqualizerStateFactory_getFactory }, - { EffectSlotType::Flanger, FlangerStateFactory_getFactory }, - { EffectSlotType::FrequencyShifter, FshifterStateFactory_getFactory }, - { EffectSlotType::RingModulator, ModulatorStateFactory_getFactory }, - { EffectSlotType::PitchShifter, PshifterStateFactory_getFactory }, - { EffectSlotType::VocalMorpher, VmorpherStateFactory_getFactory }, - { EffectSlotType::DedicatedDialog, DedicatedStateFactory_getFactory }, - { EffectSlotType::DedicatedLFE, DedicatedStateFactory_getFactory }, - { EffectSlotType::Convolution, ConvolutionStateFactory_getFactory }, -}; +using SubListAllocator = al::allocator>; EffectStateFactory *getFactoryByType(EffectSlotType 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; + switch(type) + { + case EffectSlotType::None: return NullStateFactory_getFactory(); + case EffectSlotType::Reverb: return ReverbStateFactory_getFactory(); + case EffectSlotType::Chorus: return ChorusStateFactory_getFactory(); + case EffectSlotType::Autowah: return AutowahStateFactory_getFactory(); + case EffectSlotType::Compressor: return CompressorStateFactory_getFactory(); + case EffectSlotType::Convolution: return ConvolutionStateFactory_getFactory(); + case EffectSlotType::Dedicated: return DedicatedStateFactory_getFactory(); + case EffectSlotType::Distortion: return DistortionStateFactory_getFactory(); + case EffectSlotType::Echo: return EchoStateFactory_getFactory(); + case EffectSlotType::Equalizer: return EqualizerStateFactory_getFactory(); + case EffectSlotType::Flanger: return ChorusStateFactory_getFactory(); + case EffectSlotType::FrequencyShifter: return FshifterStateFactory_getFactory(); + case EffectSlotType::RingModulator: return ModulatorStateFactory_getFactory(); + case EffectSlotType::PitchShifter: return PshifterStateFactory_getFactory(); + case EffectSlotType::VocalMorpher: return VmorpherStateFactory_getFactory(); + } + return nullptr; } -inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept +auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -95,10 +108,10 @@ inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept EffectSlotSubList &sublist{context->mEffectSlotList[lidx]}; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.EffectSlots + slidx; + return al::to_address(sublist.EffectSlots->begin() + slidx); } -inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) noexcept +inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -108,10 +121,10 @@ inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) noexcept EffectSubList &sublist = device->EffectList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Effects + slidx; + return al::to_address(sublist.Effects->begin() + slidx); } -inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept +inline auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -121,7 +134,7 @@ inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept BufferSubList &sublist = device->BufferList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Buffers + slidx; + return al::to_address(sublist.Buffers->begin() + slidx); } @@ -129,44 +142,44 @@ void AddActiveEffectSlots(const al::span auxslots, ALCcontext *co { if(auxslots.empty()) return; EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)}; - size_t newcount{curarray->size() + auxslots.size()}; + if((curarray->size()>>1) > std::numeric_limits::max()-auxslots.size()) + throw std::runtime_error{"Too many active effect slots"}; - /* Insert the new effect slots into the head of the array, followed by the - * existing ones. + size_t newcount{(curarray->size()>>1) + auxslots.size()}; + if(newcount > std::numeric_limits::max()>>1) + throw std::runtime_error{"Too many active effect slots"}; + + /* Insert the new effect slots into the head of the new array, followed by + * the existing ones. */ - EffectSlotArray *newarray = EffectSlot::CreatePtrArray(newcount); - auto slotiter = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(), - [](ALeffectslot *auxslot) noexcept { return auxslot->mSlot; }); - std::copy(curarray->begin(), curarray->end(), slotiter); + auto newarray = EffectSlot::CreatePtrArray(newcount<<1); + auto new_end = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(), + std::mem_fn(&ALeffectslot::mSlot)); + new_end = std::copy_n(curarray->begin(), curarray->size()>>1, new_end); /* 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; + new_end = std::remove(start, new_end, *(start-1)); + if(start == new_end) break; ++start; } - newcount = static_cast(std::distance(newarray->begin(), last)); + newcount = static_cast(std::distance(newarray->begin(), new_end)); /* Reallocate newarray if the new size ended up smaller from duplicate * removal. */ - if(newcount < newarray->size()) UNLIKELY + if(newcount < newarray->size()>>1) UNLIKELY { - curarray = newarray; - newarray = EffectSlot::CreatePtrArray(newcount); - std::copy_n(curarray->begin(), newcount, newarray->begin()); - delete curarray; - curarray = nullptr; + auto oldarray = std::move(newarray); + newarray = EffectSlot::CreatePtrArray(newcount<<1); + new_end = std::copy_n(oldarray->begin(), newcount, newarray->begin()); } - std::uninitialized_fill_n(newarray->end(), newcount, nullptr); + std::fill(new_end, newarray->end(), nullptr); - curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel); - context->mDevice->waitForMix(); - - al::destroy_n(curarray->end(), curarray->size()); - delete curarray; + auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray), + std::memory_order_acq_rel); + std::ignore = context->mDevice->waitForMix(); } void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext *context) @@ -177,9 +190,9 @@ void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext /* 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. */ - EffectSlotArray *newarray = EffectSlot::CreatePtrArray(curarray->size()); + auto newarray = EffectSlot::CreatePtrArray(curarray->size()); - auto new_end = std::copy(curarray->begin(), curarray->end(), newarray->begin()); + auto new_end = std::copy_n(curarray->begin(), curarray->size()>>1, newarray->begin()); /* Remove elements from newarray that match any ID in slotids. */ for(const ALeffectslot *auxslot : auxslots) { @@ -190,26 +203,21 @@ void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext /* Reallocate with the new size. */ auto newsize = static_cast(std::distance(newarray->begin(), new_end)); - if(newsize != newarray->size()) LIKELY + if(newsize < newarray->size()>>1) LIKELY { - curarray = newarray; - newarray = EffectSlot::CreatePtrArray(newsize); - std::copy_n(curarray->begin(), newsize, newarray->begin()); - - delete curarray; - curarray = nullptr; + auto oldarray = std::move(newarray); + newarray = EffectSlot::CreatePtrArray(newsize<<1); + new_end = std::copy_n(oldarray->begin(), newsize, newarray->begin()); } - std::uninitialized_fill_n(newarray->end(), newsize, nullptr); + std::fill(new_end, newarray->end(), nullptr); - curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel); - context->mDevice->waitForMix(); - - al::destroy_n(curarray->end(), curarray->size()); - delete curarray; + auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray), + std::memory_order_acq_rel); + std::ignore = context->mDevice->waitForMix(); } -EffectSlotType EffectSlotTypeFromEnum(ALenum type) +constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType { switch(type) { @@ -226,19 +234,19 @@ EffectSlotType EffectSlotTypeFromEnum(ALenum type) case AL_EFFECT_AUTOWAH: return EffectSlotType::Autowah; case AL_EFFECT_COMPRESSOR: return EffectSlotType::Compressor; case AL_EFFECT_EQUALIZER: return EffectSlotType::Equalizer; - case AL_EFFECT_EAXREVERB: return EffectSlotType::EAXReverb; - case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return EffectSlotType::DedicatedLFE; - case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::DedicatedDialog; - case AL_EFFECT_CONVOLUTION_REVERB_SOFT: return EffectSlotType::Convolution; + case AL_EFFECT_EAXREVERB: return EffectSlotType::Reverb; + case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return EffectSlotType::Dedicated; + case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::Dedicated; + case AL_EFFECT_CONVOLUTION_SOFT: return EffectSlotType::Convolution; } ERR("Unhandled effect enum: 0x%04x\n", type); return EffectSlotType::None; } -bool EnsureEffectSlots(ALCcontext *context, size_t needed) -{ +auto EnsureEffectSlots(ALCcontext *context, size_t needed) noexcept -> bool +try { size_t count{std::accumulate(context->mEffectSlotList.cbegin(), - context->mEffectSlotList.cend(), size_t{0}, + context->mEffectSlotList.cend(), 0_uz, [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; @@ -247,20 +255,17 @@ bool EnsureEffectSlots(ALCcontext *context, size_t needed) if(context->mEffectSlotList.size() >= 1<<25) UNLIKELY return false; - context->mEffectSlotList.emplace_back(); - auto sublist = context->mEffectSlotList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->EffectSlots = static_cast( - al_calloc(alignof(ALeffectslot), sizeof(ALeffectslot)*64)); - if(!sublist->EffectSlots) UNLIKELY - { - context->mEffectSlotList.pop_back(); - return false; - } - count += 64; + EffectSlotSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.EffectSlots = SubListAllocator{}.allocate(1); + context->mEffectSlotList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } return true; } +catch(...) { + return false; +} ALeffectslot *AllocEffectSlot(ALCcontext *context) { @@ -271,7 +276,8 @@ ALeffectslot *AllocEffectSlot(ALCcontext *context) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALeffectslot *slot{al::construct_at(sublist->EffectSlots + slidx, context)}; + ALeffectslot *slot{al::construct_at(al::to_address(sublist->EffectSlots->begin() + slidx), + context)}; aluInitEffectPanning(slot->mSlot, context); /* Add 1 to avoid ID 0. */ @@ -285,11 +291,13 @@ ALeffectslot *AllocEffectSlot(ALCcontext *context) void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot) { + context->mEffectSlotNames.erase(slot->id); + const ALuint id{slot->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; - al::destroy_at(slot); + std::destroy_at(slot); context->mEffectSlotList[lidx].FreeMask |= 1_u64 << slidx; context->mNumEffectSlots--; @@ -309,299 +317,213 @@ inline void UpdateProps(ALeffectslot *slot, ALCcontext *context) } // namespace -AL_API void AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Generating %d effect slots", n); +AL_API DECL_FUNC2(void, alGenAuxiliaryEffectSlots, ALsizei,n, ALuint*,effectslots) +FORCE_ALIGN void AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, + ALuint *effectslots) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Generating %d effect slots", n}; if(n <= 0) UNLIKELY return; - std::lock_guard _{context->mEffectSlotLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; ALCdevice *device{context->mALDevice.get()}; - if(static_cast(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(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())}; - effectslots[0] = slot->id; + const al::span eids{effectslots, static_cast(n)}; + if(eids.size() > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots) + throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)", + device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n}; + + if(!EnsureEffectSlots(context, eids.size())) + throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n, + (n == 1) ? "" : "s"}; + + std::vector slots; + try { + if(eids.size() == 1) + { + /* Special handling for the easy and normal case. */ + eids[0] = AllocEffectSlot(context)->id; + } + else + { + slots.reserve(eids.size()); + std::generate_n(std::back_inserter(slots), eids.size(), + [context]{ return AllocEffectSlot(context); }); + + std::transform(slots.cbegin(), slots.cend(), eids.begin(), + [](ALeffectslot *slot) -> ALuint { return slot->id; }); + } } - else - { - al::vector ids; - ALsizei count{n}; - ids.reserve(static_cast(count)); - do { - ALeffectslot *slot{AllocEffectSlot(context.get())}; - ids.emplace_back(slot->id); - } while(--count); - std::copy(ids.cbegin(), ids.cend(), effectslots); + catch(std::exception& e) { + ERR("Exception allocating effectslot %zu of %d: %s\n", slots.size()+1, n, e.what()); + auto delete_effectslot = [context](ALeffectslot *slot) -> void + { FreeEffectSlot(context, slot); }; + std::for_each(slots.begin(), slots.end(), delete_effectslot); + throw al::context_error{AL_INVALID_OPERATION, "Exception allocating %d effectslots: %s", n, + e.what()}; } } -END_API_FUNC - -AL_API void AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} +AL_API DECL_FUNC2(void, alDeleteAuxiliaryEffectSlots, ALsizei,n, const ALuint*,effectslots) +FORCE_ALIGN void AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, + const ALuint *effectslots) noexcept +try { if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Deleting %d effect slots", n); + throw al::context_error{AL_INVALID_VALUE, "Deleting %d effect slots", n}; if(n <= 0) UNLIKELY return; - std::lock_guard _{context->mEffectSlotLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; if(n == 1) { - ALeffectslot *slot{LookupEffectSlot(context.get(), effectslots[0])}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslots[0]); - return; - } - if(ReadRef(slot->ref) != 0) UNLIKELY - { - context->setError(AL_INVALID_OPERATION, "Deleting in-use effect slot %u", - effectslots[0]); - return; - } - RemoveActiveEffectSlots({&slot, 1u}, context.get()); - FreeEffectSlot(context.get(), slot); + ALeffectslot *slot{LookupEffectSlot(context, *effectslots)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", *effectslots}; + if(slot->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u", + *effectslots}; + + RemoveActiveEffectSlots({&slot, 1u}, context); + FreeEffectSlot(context, slot); } else { - auto slots = al::vector(static_cast(n)); - for(size_t i{0};i < slots.size();++i) + const al::span eids{effectslots, static_cast(n)}; + std::vector slots; + slots.reserve(eids.size()); + + auto lookupslot = [context](const ALuint eid) -> ALeffectslot* { - ALeffectslot *slot{LookupEffectSlot(context.get(), effectslots[i])}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslots[i]); - return; - } - if(ReadRef(slot->ref) != 0) UNLIKELY - { - context->setError(AL_INVALID_OPERATION, "Deleting in-use effect slot %u", - effectslots[i]); - return; - } - slots[i] = slot; - } - /* Remove any duplicates. */ - auto slots_end = slots.end(); - for(auto start=slots.begin()+1;start != slots_end;++start) - { - slots_end = std::remove(start, slots_end, *(start-1)); - if(start == slots_end) break; - } - slots.erase(slots_end, slots.end()); + ALeffectslot *slot{LookupEffectSlot(context, eid)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", eid}; + if(slot->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u", + eid}; + return slot; + }; + std::transform(eids.cbegin(), eids.cend(), std::back_inserter(slots), lookupslot); /* All effectslots are valid, remove and delete them */ - RemoveActiveEffectSlots(slots, context.get()); - for(ALeffectslot *slot : slots) - FreeEffectSlot(context.get(), slot); + RemoveActiveEffectSlots(slots, context); + + auto delete_effectslot = [context](const ALuint eid) -> void + { + if(ALeffectslot *slot{LookupEffectSlot(context, eid)}) + FreeEffectSlot(context, slot); + }; + std::for_each(eids.begin(), eids.end(), delete_effectslot); } } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} -AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsAuxiliaryEffectSlot, ALuint,effectslot) +FORCE_ALIGN ALboolean AL_APIENTRY alIsAuxiliaryEffectSlotDirect(ALCcontext *context, + ALuint effectslot) noexcept { - ContextRef context{GetContextRef()}; - if(context) LIKELY - { - std::lock_guard _{context->mEffectSlotLock}; - if(LookupEffectSlot(context.get(), effectslot) != nullptr) - return AL_TRUE; - } + std::lock_guard slotlock{context->mEffectSlotLock}; + if(LookupEffectSlot(context, effectslot) != nullptr) + return AL_TRUE; return AL_FALSE; } -END_API_FUNC -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) -START_API_FUNC +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)}; + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlaySOFT not supported"); +} + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei, const ALuint*) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return; + + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlayvSOFT not supported"); +} + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return; + + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopSOFT not supported"); +} + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei, const ALuint*) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return; + + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopvSOFT not supported"); +} + + +AL_API DECL_FUNC3(void, alAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, + ALenum param, ALint value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; + + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid); - return; - } - if(slot->mState == SlotState::Playing) - return; - - slot->mPropsDirty = false; - slot->updateProps(context.get()); - - AddActiveEffectSlots({&slot, 1}, context.get()); - slot->mState = SlotState::Playing; -} -END_API_FUNC - -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Playing %d effect slots", n); - if(n <= 0) UNLIKELY return; - - auto slots = al::vector(static_cast(n)); - std::lock_guard _{context->mEffectSlotLock}; - for(size_t i{0};i < slots.size();++i) - { - ALeffectslot *slot{LookupEffectSlot(context.get(), slotids[i])}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotids[i]); - return; - } - - if(slot->mState != SlotState::Playing) - { - slot->mPropsDirty = false; - slot->updateProps(context.get()); - } - slots[i] = slot; - }; - - AddActiveEffectSlots(slots, context.get()); - for(auto slot : slots) - slot->mState = SlotState::Playing; -} -END_API_FUNC - -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid); - return; - } - - RemoveActiveEffectSlots({&slot, 1}, context.get()); - slot->mState = SlotState::Stopped; -} -END_API_FUNC - -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Stopping %d effect slots", n); - if(n <= 0) UNLIKELY return; - - auto slots = al::vector(static_cast(n)); - std::lock_guard _{context->mEffectSlotLock}; - for(size_t i{0};i < slots.size();++i) - { - ALeffectslot *slot{LookupEffectSlot(context.get(), slotids[i])}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotids[i]); - return; - } - - slots[i] = slot; - }; - - RemoveActiveEffectSlots(slots, context.get()); - for(auto slot : slots) - slot->mState = SlotState::Stopped; -} -END_API_FUNC - - -AL_API void AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; ALeffectslot *target{}; - ALCdevice *device{}; ALenum err{}; switch(param) { case AL_EFFECTSLOT_EFFECT: - device = context->mALDevice.get(); - { - std::lock_guard ___{device->EffectLock}; + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard effectlock{device->EffectLock}; ALeffect *effect{value ? LookupEffect(device, static_cast(value)) : nullptr}; if(effect) - err = slot->initEffect(effect->type, effect->Props, context.get()); + err = slot->initEffect(effect->id, effect->type, effect->Props, context); else { if(value != 0) - return context->setError(AL_INVALID_VALUE, "Invalid effect ID %u", value); - err = slot->initEffect(AL_EFFECT_NULL, EffectProps{}, context.get()); + throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %u", value}; + err = slot->initEffect(0, AL_EFFECT_NULL, EffectProps{}, context); } } - if(err != AL_NO_ERROR) UNLIKELY - { - context->setError(err, "Effect initialization failed"); - return; - } + if(err != AL_NO_ERROR) + throw al::context_error{err, "Effect initialization failed"}; + if(slot->mState == SlotState::Initial) UNLIKELY { slot->mPropsDirty = false; - slot->updateProps(context.get()); + slot->updateProps(context); - AddActiveEffectSlots({&slot, 1}, context.get()); + AddActiveEffectSlots({&slot, 1}, context); slot->mState = SlotState::Playing; return; } - break; + UpdateProps(slot, context); + return; case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: if(!(value == AL_TRUE || value == AL_FALSE)) - return context->setError(AL_INVALID_VALUE, - "Effect slot auxiliary send auto out of range"); - if(slot->AuxSendAuto == !!value) UNLIKELY - return; - slot->AuxSendAuto = !!value; - break; + throw al::context_error{AL_INVALID_VALUE, + "Effect slot auxiliary send auto out of range"}; + if(!(slot->AuxSendAuto == !!value)) LIKELY + { + slot->AuxSendAuto = !!value; + UpdateProps(slot, context); + } + return; case AL_EFFECTSLOT_TARGET_SOFT: - target = LookupEffectSlot(context.get(), static_cast(value)); + target = LookupEffectSlot(context, static_cast(value)); if(value && !target) - return context->setError(AL_INVALID_VALUE, "Invalid effect slot target ID"); + throw al::context_error{AL_INVALID_VALUE, "Invalid effect slot target ID"}; if(slot->Target == target) UNLIKELY return; if(target) @@ -610,9 +532,9 @@ START_API_FUNC while(checker && checker != slot) checker = checker->Target; if(checker) - return context->setError(AL_INVALID_OPERATION, + throw al::context_error{AL_INVALID_OPERATION, "Setting target of effect slot ID %u to %u creates circular chain", slot->id, - target->id); + target->id}; } if(ALeffectslot *oldtarget{slot->Target}) @@ -623,39 +545,77 @@ START_API_FUNC if(target) IncrementRef(target->ref); DecrementRef(oldtarget->ref); slot->Target = target; - slot->updateProps(context.get()); + slot->updateProps(context); return; } if(target) IncrementRef(target->ref); slot->Target = target; - break; + UpdateProps(slot, context); + return; case AL_BUFFER: - device = context->mALDevice.get(); - - if(slot->mState == SlotState::Playing) - return context->setError(AL_INVALID_OPERATION, - "Setting buffer on playing effect slot %u", slot->id); - if(ALbuffer *buffer{slot->Buffer}) { - if(buffer->id == static_cast(value)) UNLIKELY + if(buffer->id == static_cast(value)) return; } - else if(value == 0) UNLIKELY + else if(value == 0) return; + if(slot->mState == SlotState::Playing) { - std::lock_guard ___{device->BufferLock}; + EffectStateFactory *factory{getFactoryByType(slot->Effect.Type)}; + assert(factory); + al::intrusive_ptr state{factory->create()}; + + ALCdevice *device{context->mALDevice.get()}; + auto bufferlock = std::unique_lock{device->BufferLock}; ALbuffer *buffer{}; if(value) { buffer = LookupBuffer(device, static_cast(value)); - if(!buffer) return context->setError(AL_INVALID_VALUE, "Invalid buffer ID"); + if(!buffer) + throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value}; if(buffer->mCallback) - return context->setError(AL_INVALID_OPERATION, - "Callback buffer not valid for effects"); + throw al::context_error{AL_INVALID_OPERATION, + "Callback buffer not valid for effects"}; + + IncrementRef(buffer->ref); + } + + /* Stop the effect slot from processing while we switch buffers. */ + RemoveActiveEffectSlots({&slot, 1}, context); + + if(ALbuffer *oldbuffer{slot->Buffer}) + DecrementRef(oldbuffer->ref); + slot->Buffer = buffer; + bufferlock.unlock(); + + state->mOutTarget = device->Dry.Buffer; + { + FPUCtl mixer_mode{}; + state->deviceUpdate(device, buffer); + } + slot->Effect.State = std::move(state); + + slot->mPropsDirty = false; + slot->updateProps(context); + AddActiveEffectSlots({&slot, 1}, context); + } + else + { + ALCdevice *device{context->mALDevice.get()}; + auto bufferlock = std::unique_lock{device->BufferLock}; + ALbuffer *buffer{}; + if(value) + { + buffer = LookupBuffer(device, static_cast(value)); + if(!buffer) + throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value}; + if(buffer->mCallback) + throw al::context_error{AL_INVALID_OPERATION, + "Callback buffer not valid for effects"}; IncrementRef(buffer->ref); } @@ -663,27 +623,29 @@ START_API_FUNC if(ALbuffer *oldbuffer{slot->Buffer}) DecrementRef(oldbuffer->ref); slot->Buffer = buffer; + bufferlock.unlock(); FPUCtl mixer_mode{}; auto *state = slot->Effect.State.get(); state->deviceUpdate(device, buffer); + slot->mPropsDirty = true; } - break; + return; case AL_EFFECTSLOT_STATE_SOFT: - return context->setError(AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"); - - default: - return context->setError(AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", - param); + throw al::context_error{AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"}; } - UpdateProps(slot, context.get()); -} -END_API_FUNC -AL_API void AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *values) -START_API_FUNC -{ + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, + ALenum param, const ALint *values) noexcept +try { switch(param) { case AL_EFFECTSLOT_EFFECT: @@ -691,129 +653,130 @@ START_API_FUNC case AL_EFFECTSLOT_TARGET_SOFT: case AL_EFFECTSLOT_STATE_SOFT: case AL_BUFFER: - alAuxiliaryEffectSloti(effectslot, param, values[0]); + alAuxiliaryEffectSlotiDirect(context, effectslot, param, *values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; switch(param) { - default: - return context->setError(AL_INVALID_ENUM, - "Invalid effect slot integer-vector property 0x%04x", param); } + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, + ALenum param, ALfloat value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; switch(param) { case AL_EFFECTSLOT_GAIN: if(!(value >= 0.0f && value <= 1.0f)) - return context->setError(AL_INVALID_VALUE, "Effect slot gain out of range"); - if(slot->Gain == value) UNLIKELY - return; - slot->Gain = value; - break; - - default: - return context->setError(AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", - param); - } - UpdateProps(slot, context.get()); -} -END_API_FUNC - -AL_API void AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECTSLOT_GAIN: - alAuxiliaryEffectSlotf(effectslot, param, values[0]); + throw al::context_error{AL_INVALID_VALUE, "Effect slot gain out of range"}; + if(!(slot->Gain == value)) LIKELY + { + slot->Gain = value; + UpdateProps(slot, context); + } return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); - - switch(param) - { - default: - return context->setError(AL_INVALID_ENUM, - "Invalid effect slot float-vector property 0x%04x", param); - } + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC +AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, + ALenum param, const ALfloat *values) noexcept +try { + switch(param) + { + case AL_EFFECTSLOT_GAIN: + alAuxiliaryEffectSlotfDirect(context, effectslot, param, *values); + return; + } -AL_API void AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; switch(param) { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotiDirect(ALCcontext *context, + ALuint effectslot, ALenum param, ALint *value) noexcept +try { + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + + switch(param) + { + case AL_EFFECTSLOT_EFFECT: + *value = static_cast(slot->EffectId); + return; + case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: *value = slot->AuxSendAuto ? AL_TRUE : AL_FALSE; - break; + return; case AL_EFFECTSLOT_TARGET_SOFT: if(auto *target = slot->Target) *value = static_cast(target->id); else *value = 0; - break; + return; case AL_EFFECTSLOT_STATE_SOFT: *value = static_cast(slot->mState); - break; + return; case AL_BUFFER: if(auto *buffer = slot->Buffer) *value = static_cast(buffer->id); else *value = 0; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param); + return; } -} -END_API_FUNC -AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *values) -START_API_FUNC -{ + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotivDirect(ALCcontext *context, + ALuint effectslot, ALenum param, ALint *values) noexcept +try { switch(param) { case AL_EFFECTSLOT_EFFECT: @@ -821,76 +784,72 @@ START_API_FUNC case AL_EFFECTSLOT_TARGET_SOFT: case AL_EFFECTSLOT_STATE_SOFT: case AL_BUFFER: - alGetAuxiliaryEffectSloti(effectslot, param, values); + alGetAuxiliaryEffectSlotiDirect(context, effectslot, param, values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot = LookupEffectSlot(context, effectslot); + if(!slot) + throw al::context_error{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); } + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); +AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotfDirect(ALCcontext *context, + ALuint effectslot, ALenum param, ALfloat *value) noexcept +try { + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{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 void 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(!context) UNLIKELY return; + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); +AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotfvDirect(ALCcontext *context, + ALuint effectslot, ALenum param, ALfloat *values) noexcept +try { + switch(param) + { + case AL_EFFECTSLOT_GAIN: + alGetAuxiliaryEffectSlotfDirect(context, effectslot, param, values); + return; + } + + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{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); } + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC ALeffectslot::ALeffectslot(ALCcontext *context) @@ -915,18 +874,14 @@ ALeffectslot::~ALeffectslot() DecrementRef(Buffer->ref); Buffer = nullptr; - if(EffectSlotProps *props{mSlot->Update.exchange(nullptr)}) - { - TRACE("Freed unapplied AuxiliaryEffectSlot update %p\n", - decltype(std::declval()){props}); - delete props; - } + if(auto *slot = mSlot->Update.exchange(nullptr, std::memory_order_relaxed)) + slot->State = nullptr; mSlot->mEffectState = nullptr; mSlot->InUse = false; } -ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProps, +ALenum ALeffectslot::initEffect(ALuint effectId, ALenum effectType, const EffectProps &effectProps, ALCcontext *context) { EffectSlotType newtype{EffectSlotTypeFromEnum(effectType)}; @@ -941,7 +896,6 @@ ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProp al::intrusive_ptr state{factory->create()}; ALCdevice *device{context->mALDevice.get()}; - std::unique_lock statelock{device->StateLock}; state->mOutTarget = device->Dry.Buffer; { FPUCtl mixer_mode{}; @@ -955,9 +909,10 @@ ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProp } else if(newtype != EffectSlotType::None) Effect.Props = effectProps; + EffectId = effectId; /* Remove state references from old effect slot property updates. */ - EffectSlotProps *props{context->mFreeEffectslotProps.load()}; + EffectSlotProps *props{context->mFreeEffectSlotProps.load()}; while(props) { props->State = nullptr; @@ -967,20 +922,20 @@ ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProp return AL_NO_ERROR; } -void ALeffectslot::updateProps(ALCcontext *context) +void ALeffectslot::updateProps(ALCcontext *context) const { /* Get an unused property container, or allocate a new one as needed. */ - EffectSlotProps *props{context->mFreeEffectslotProps.load(std::memory_order_relaxed)}; + EffectSlotProps *props{context->mFreeEffectSlotProps.load(std::memory_order_acquire)}; if(!props) - props = new EffectSlotProps{}; - else { - EffectSlotProps *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); + context->allocEffectSlotProps(); + props = context->mFreeEffectSlotProps.load(std::memory_order_acquire); } + EffectSlotProps *next; + do { + next = props->next.load(std::memory_order_relaxed); + } while(!context->mFreeEffectSlotProps.compare_exchange_weak(props, next, + std::memory_order_acq_rel, std::memory_order_acquire)); /* Copy in current property values. */ props->Gain = Gain; @@ -999,39 +954,53 @@ void ALeffectslot::updateProps(ALCcontext *context) * freelist. */ props->State = nullptr; - AtomicReplaceHead(context->mFreeEffectslotProps, props); + AtomicReplaceHead(context->mFreeEffectSlotProps, props); } } +void ALeffectslot::SetName(ALCcontext* context, ALuint id, std::string_view name) +{ + std::lock_guard slotlock{context->mEffectSlotLock}; + + auto slot = LookupEffectSlot(context, id); + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id}; + + context->mEffectSlotNames.insert_or_assign(id, name); +} + void UpdateAllEffectSlotProps(ALCcontext *context) { - std::lock_guard _{context->mEffectSlotLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; for(auto &sublist : context->mEffectSlotList) { uint64_t usemask{~sublist.FreeMask}; while(usemask) { - const int idx{al::countr_zero(usemask)}; + const auto idx = static_cast(al::countr_zero(usemask)); usemask &= ~(1_u64 << idx); - ALeffectslot *slot{sublist.EffectSlots + idx}; + auto &slot = (*sublist.EffectSlots)[idx]; - if(slot->mState != SlotState::Stopped && std::exchange(slot->mPropsDirty, false)) - slot->updateProps(context); + if(std::exchange(slot.mPropsDirty, false)) + slot.updateProps(context); } } } EffectSlotSubList::~EffectSlotSubList() { + if(!EffectSlots) + return; + uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; - al::destroy_at(EffectSlots+idx); + std::destroy_at(al::to_address(EffectSlots->begin() + idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; - al_free(EffectSlots); + SubListAllocator{}.deallocate(EffectSlots, 1); EffectSlots = nullptr; } @@ -1182,7 +1151,7 @@ void ALeffectslot::eax_fx_slot_set_defaults() eax_df_ = EaxDirtyFlags{}; } -void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) const +void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) { switch(call.get_property_id()) { @@ -1206,7 +1175,7 @@ void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) } } -void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) const +void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) { switch(call.get_property_id()) { @@ -1272,7 +1241,7 @@ void ALeffectslot::eax_fx_slot_load_effect(int version, ALenum altype) void ALeffectslot::eax_fx_slot_set_volume() { - const auto volume = clamp(eax_.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); + const auto volume = std::clamp(eax_.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); const auto gain = level_mb_to_gain(static_cast(volume)); eax_set_efx_slot_gain(gain); } @@ -1316,15 +1285,12 @@ void ALeffectslot::eax5_fx_slot_set_all(const EaxCall& call) bool ALeffectslot::eax_fx_slot_should_update_sources() const noexcept { - const auto dirty_bits = + static constexpr auto dirty_bits = eax_occlusion_dirty_bit | eax_occlusion_lf_ratio_dirty_bit | eax_flags_dirty_bit; - if((eax_df_ & dirty_bits) != EaxDirtyFlags{}) - return true; - - return false; + return (eax_df_ & dirty_bits) != EaxDirtyFlags{}; } // Returns `true` if all sources should be updated, or `false` otherwise. @@ -1470,7 +1436,8 @@ void ALeffectslot::eax_set_efx_slot_effect(EaxEffect &effect) { #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] " - const auto error = initEffect(effect.al_effect_type_, effect.al_effect_props_, eax_al_context_); + const auto error = initEffect(0, effect.al_effect_type_, effect.al_effect_props_, + eax_al_context_); if(error != AL_NO_ERROR) { ERR(EAX_PREFIX "%s\n", "Failed to initialize an effect."); @@ -1509,7 +1476,7 @@ void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain) if(gain < 0.0f || gain > 1.0f) ERR(EAX_PREFIX "Gain out of range (%f)\n", gain); - Gain = clampf(gain, 0.0f, 1.0f); + Gain = std::clamp(gain, 0.0f, 1.0f); mPropsDirty = true; #undef EAX_PREFIX @@ -1517,7 +1484,6 @@ void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain) void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot) { - assert(effect_slot); eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot); } @@ -1525,7 +1491,7 @@ EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context) { #define EAX_PREFIX "[EAX_MAKE_EFFECT_SLOT] " - std::unique_lock effect_slot_lock{context.mEffectSlotLock}; + std::lock_guard slotlock{context.mEffectSlotLock}; auto& device = *context.mALDevice; if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) { @@ -1547,9 +1513,10 @@ void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot) { #define EAX_PREFIX "[EAX_DELETE_EFFECT_SLOT] " - std::lock_guard effect_slot_lock{context.mEffectSlotLock}; + std::lock_guard slotlock{context.mEffectSlotLock}; - if(ReadRef(effect_slot.ref) != 0) { + if(effect_slot.ref.load(std::memory_order_relaxed) != 0) + { ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id); return; } diff --git a/Engine/lib/openal-soft/al/auxeffectslot.h b/Engine/lib/openal-soft/al/auxeffectslot.h index 3e9a2a4e5..5f9913a02 100644 --- a/Engine/lib/openal-soft/al/auxeffectslot.h +++ b/Engine/lib/openal-soft/al/auxeffectslot.h @@ -1,23 +1,24 @@ #ifndef AL_AUXEFFECTSLOT_H #define AL_AUXEFFECTSLOT_H +#include #include -#include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" -#include "AL/efx.h" -#include "alc/device.h" -#include "alc/effects/base.h" #include "almalloc.h" -#include "atomic.h" +#include "alnumeric.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "intrusive_ptr.h" -#include "vector.h" #ifdef ALSOFT_EAX #include +#include "eax/api.h" #include "eax/call.h" #include "eax/effect.h" #include "eax/exception.h" @@ -26,8 +27,6 @@ #endif // ALSOFT_EAX struct ALbuffer; -struct ALeffect; -struct WetBuffer; #ifdef ALSOFT_EAX class EaxFxSlotException : public EaxException { @@ -38,30 +37,30 @@ public: }; #endif // ALSOFT_EAX -enum class SlotState : ALenum { - Initial = AL_INITIAL, - Playing = AL_PLAYING, - Stopped = AL_STOPPED, +enum class SlotState : bool { + Initial, Playing, }; struct ALeffectslot { + ALuint EffectId{}; float Gain{1.0f}; bool AuxSendAuto{true}; ALeffectslot *Target{nullptr}; ALbuffer *Buffer{nullptr}; - struct { + struct EffectData { EffectSlotType Type{EffectSlotType::None}; EffectProps Props{}; al::intrusive_ptr State; - } Effect; + }; + EffectData Effect; bool mPropsDirty{true}; SlotState mState{SlotState::Initial}; - RefCount ref{0u}; + std::atomic ref{0u}; EffectSlot *mSlot{nullptr}; @@ -73,23 +72,23 @@ struct ALeffectslot { ALeffectslot& operator=(const ALeffectslot&) = delete; ~ALeffectslot(); - ALenum initEffect(ALenum effectType, const EffectProps &effectProps, ALCcontext *context); - void updateProps(ALCcontext *context); + ALenum initEffect(ALuint effectId, ALenum effectType, const EffectProps &effectProps, + ALCcontext *context); + void updateProps(ALCcontext *context) const; - /* This can be new'd for the context's default effect slot. */ - DEF_NEWDEL(ALeffectslot) + static void SetName(ALCcontext *context, ALuint id, std::string_view name); #ifdef ALSOFT_EAX public: void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index); - EaxFxSlotIndexValue eax_get_index() const noexcept { return eax_fx_slot_index_; } - const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept + [[nodiscard]] auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return eax_fx_slot_index_; } + [[nodiscard]] auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES& { return eax_; } // Returns `true` if all sources should be updated, or `false` otherwise. - bool eax_dispatch(const EaxCall& call) + [[nodiscard]] auto eax_dispatch(const EaxCall& call) -> bool { return call.is_get() ? eax_get(call) : eax_set(call); } void eax_commit(); @@ -193,6 +192,17 @@ private: } }; + struct Eax5FlagsValidator { + void operator()(unsigned long ulFlags) const + { + EaxRangeValidator{}( + "Flags", + ulFlags, + 0UL, + ~EAX50FXSLOTFLAGS_RESERVED); + } + }; + struct Eax5OcclusionValidator { void operator()(long lOcclusion) const { @@ -215,21 +225,13 @@ private: } }; - struct Eax5FlagsValidator { - void operator()(unsigned long ulFlags) const - { - EaxRangeValidator{}( - "Flags", - ulFlags, - 0UL, - ~EAX50FXSLOTFLAGS_RESERVED); - } - }; - struct Eax5AllValidator { void operator()(const EAX50FXSLOTPROPERTIES& all) const { - Eax4AllValidator{}(static_cast(all)); + Eax4GuidLoadEffectValidator{}(all.guidLoadEffect); + Eax4VolumeValidator{}(all.lVolume); + Eax4LockValidator{}(all.lLock); + Eax5FlagsValidator{}(all.ulFlags); Eax5OcclusionValidator{}(all.lOcclusion); Eax5OcclusionLfRatioValidator{}(all.flOcclusionLFRatio); } @@ -277,14 +279,14 @@ private: dst = src; } - constexpr bool eax4_fx_slot_is_legacy() const noexcept + [[nodiscard]] constexpr auto eax4_fx_slot_is_legacy() const noexcept -> bool { return eax_fx_slot_index_ < 2; } void eax4_fx_slot_ensure_unlocked() const; - static ALenum eax_get_efx_effect_type(const GUID& guid); - const GUID& eax_get_eax_default_effect_guid() const noexcept; - long eax_get_eax_default_lock() const noexcept; + [[nodiscard]] static auto eax_get_efx_effect_type(const GUID& guid) -> ALenum; + [[nodiscard]] auto eax_get_eax_default_effect_guid() const noexcept -> const GUID&; + [[nodiscard]] auto eax_get_eax_default_lock() const noexcept -> long; void eax4_fx_slot_set_defaults(Eax4Props& props) noexcept; void eax5_fx_slot_set_defaults(Eax5Props& props) noexcept; @@ -293,8 +295,8 @@ private: void eax_fx_slot_set_current_defaults(); void eax_fx_slot_set_defaults(); - void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) const; - void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) const; + static void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props); + static void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props); void eax_fx_slot_get(const EaxCall& call) const; // Returns `true` if all sources should be updated, or `false` otherwise. bool eax_get(const EaxCall& call); @@ -307,7 +309,7 @@ private: void eax4_fx_slot_set_all(const EaxCall& call); void eax5_fx_slot_set_all(const EaxCall& call); - bool eax_fx_slot_should_update_sources() const noexcept; + [[nodiscard]] auto eax_fx_slot_should_update_sources() const noexcept -> bool; // Returns `true` if all sources should be updated, or `false` otherwise. bool eax4_fx_slot_set(const EaxCall& call); @@ -365,4 +367,20 @@ EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context); void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot); #endif // ALSOFT_EAX +struct EffectSlotSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> EffectSlots{nullptr}; + + EffectSlotSubList() noexcept = default; + EffectSlotSubList(const EffectSlotSubList&) = delete; + EffectSlotSubList(EffectSlotSubList&& rhs) noexcept + : FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots} + { rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; } + ~EffectSlotSubList(); + + EffectSlotSubList& operator=(const EffectSlotSubList&) = delete; + EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; } +}; + #endif diff --git a/Engine/lib/openal-soft/al/buffer.cpp b/Engine/lib/openal-soft/al/buffer.cpp index ee5065968..4812e415a 100644 --- a/Engine/lib/openal-soft/al/buffer.cpp +++ b/Engine/lib/openal-soft/al/buffer.cpp @@ -26,37 +26,43 @@ #include #include #include +#include #include -#include #include #include #include #include #include -#include #include +#include #include +#include +#include #include +#include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "albit.h" -#include "albyte.h" #include "alc/context.h" #include "alc/device.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" -#include "aloptional.h" -#include "atomic.h" -#include "core/except.h" -#include "core/logging.h" +#include "alspan.h" +#include "core/device.h" +#include "core/resampler_limits.h" #include "core/voice.h" +#include "direct_defs.h" +#include "error.h" +#include "intrusive_ptr.h" #include "opthelpers.h" #ifdef ALSOFT_EAX +#include + #include "eax/globals.h" #include "eax/x_ram.h" #endif // ALSOFT_EAX @@ -64,16 +70,18 @@ namespace { -al::optional AmbiLayoutFromEnum(ALenum layout) +using SubListAllocator = al::allocator>; + +constexpr auto AmbiLayoutFromEnum(ALenum layout) noexcept -> std::optional { switch(layout) { case AL_FUMA_SOFT: return AmbiLayout::FuMa; case AL_ACN_SOFT: return AmbiLayout::ACN; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromAmbiLayout(AmbiLayout layout) +constexpr auto EnumFromAmbiLayout(AmbiLayout layout) -> ALenum { switch(layout) { @@ -83,7 +91,7 @@ ALenum EnumFromAmbiLayout(AmbiLayout layout) throw std::runtime_error{"Invalid AmbiLayout: "+std::to_string(int(layout))}; } -al::optional AmbiScalingFromEnum(ALenum scale) +constexpr auto AmbiScalingFromEnum(ALenum scale) noexcept -> std::optional { switch(scale) { @@ -91,9 +99,9 @@ al::optional AmbiScalingFromEnum(ALenum scale) case AL_SN3D_SOFT: return AmbiScaling::SN3D; case AL_N3D_SOFT: return AmbiScaling::N3D; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromAmbiScaling(AmbiScaling scale) +constexpr auto EnumFromAmbiScaling(AmbiScaling scale) -> ALenum { switch(scale) { @@ -106,7 +114,7 @@ ALenum EnumFromAmbiScaling(AmbiScaling scale) } #ifdef ALSOFT_EAX -al::optional EaxStorageFromEnum(ALenum scale) +constexpr auto EaxStorageFromEnum(ALenum scale) noexcept -> std::optional { switch(scale) { @@ -114,9 +122,9 @@ al::optional EaxStorageFromEnum(ALenum scale) case AL_STORAGE_ACCESSIBLE: return EaxStorage::Accessible; case AL_STORAGE_HARDWARE: return EaxStorage::Hardware; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromEaxStorage(EaxStorage storage) +constexpr auto EnumFromEaxStorage(EaxStorage storage) -> ALenum { switch(storage) { @@ -152,7 +160,7 @@ void eax_x_ram_apply(ALCdevice &device, ALbuffer &buffer) noexcept } } -void eax_x_ram_clear(ALCdevice& al_device, ALbuffer& al_buffer) +void eax_x_ram_clear(ALCdevice& al_device, ALbuffer& al_buffer) noexcept { if(al_buffer.eax_x_ram_is_hardware) al_device.eax_x_ram_free_size += al_buffer.OriginalSize; @@ -168,9 +176,9 @@ constexpr ALbitfieldSOFT INVALID_MAP_FLAGS{~unsigned(AL_MAP_READ_BIT_SOFT | AL_M 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}, +auto EnsureBuffers(ALCdevice *device, size_t needed) noexcept -> bool +try { + size_t count{std::accumulate(device->BufferList.cbegin(), device->BufferList.cend(), 0_uz, [](size_t cur, const BufferSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; @@ -179,21 +187,19 @@ bool EnsureBuffers(ALCdevice *device, size_t needed) if(device->BufferList.size() >= 1<<25) UNLIKELY return false; - device->BufferList.emplace_back(); - auto sublist = device->BufferList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Buffers = static_cast(al_calloc(alignof(ALbuffer), sizeof(ALbuffer)*64)); - if(!sublist->Buffers) UNLIKELY - { - device->BufferList.pop_back(); - return false; - } - count += 64; + BufferSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.Buffers = SubListAllocator{}.allocate(1); + device->BufferList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } return true; } +catch(...) { + return false; +} -ALbuffer *AllocBuffer(ALCdevice *device) +ALbuffer *AllocBuffer(ALCdevice *device) noexcept { auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(), [](const BufferSubList &entry) noexcept -> bool @@ -202,7 +208,7 @@ ALbuffer *AllocBuffer(ALCdevice *device) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALbuffer *buffer{al::construct_at(sublist->Buffers + slidx)}; + ALbuffer *buffer{al::construct_at(al::to_address(sublist->Buffers->begin() + slidx))}; /* Add 1 to avoid buffer ID 0. */ buffer->id = ((lidx<<6) | slidx) + 1; @@ -218,16 +224,18 @@ void FreeBuffer(ALCdevice *device, ALbuffer *buffer) eax_x_ram_clear(*device, *buffer); #endif // ALSOFT_EAX + device->mBufferNames.erase(buffer->id); + const ALuint id{buffer->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; - al::destroy_at(buffer); + std::destroy_at(buffer); device->BufferList[lidx].FreeMask |= 1_u64 << slidx; } -inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) +auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -237,11 +245,11 @@ inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) BufferSubList &sublist = device->BufferList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Buffers + slidx; + return al::to_address(sublist.Buffers->begin() + slidx); } -ALuint SanitizeAlignment(FmtType type, ALuint align) +constexpr auto SanitizeAlignment(FmtType type, ALuint align) noexcept -> ALuint { if(align == 0) { @@ -276,19 +284,19 @@ ALuint SanitizeAlignment(FmtType type, ALuint align) /** Loads the specified data into the buffer, using the specified format. */ -void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, - const FmtChannels DstChannels, const FmtType DstType, const al::byte *SrcData, +void LoadData(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, ALuint size, + const FmtChannels DstChannels, const FmtType DstType, const std::byte *SrcData, ALbitfieldSOFT access) { - if(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) UNLIKELY - return context->setError(AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", - ALBuf->id); + if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) + throw al::context_error{AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", + ALBuf->id}; const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; - if(align < 1) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", - unpackalign, NameFromFormat(DstType)); + if(align < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", + unpackalign, NameFromFormat(DstType)}; const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; @@ -296,12 +304,12 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, if((access&AL_PRESERVE_DATA_BIT_SOFT)) { /* Can only preserve data with the same format and alignment. */ - if(ALBuf->mChannels != DstChannels || ALBuf->mType != DstType) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched format"); - if(ALBuf->mBlockAlign != align) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched alignment"); - if(ALBuf->mAmbiOrder != ambiorder) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched order"); + if(ALBuf->mChannels != DstChannels || ALBuf->mType != DstType) + throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched format"}; + if(ALBuf->mBlockAlign != align) + throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched alignment"}; + if(ALBuf->mAmbiOrder != ambiorder) + throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched order"}; } /* Convert the size in bytes to blocks using the unpack block alignment. */ @@ -310,18 +318,18 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, ((DstType == FmtIMA4) ? (align-1)/2 + 4 : (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : (align * BytesFromFmt(DstType)))}; - if((size%BlockSize) != 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, + if((size%BlockSize) != 0) + throw al::context_error{AL_INVALID_VALUE, "Data size %d is not a multiple of frame size %d (%d unpack alignment)", - size, BlockSize, align); + size, BlockSize, align}; const ALuint blocks{size / BlockSize}; - if(blocks > std::numeric_limits::max()/align) UNLIKELY - return context->setError(AL_OUT_OF_MEMORY, - "Buffer size overflow, %d blocks x %d samples per block", blocks, align); - if(blocks > std::numeric_limits::max()/BlockSize) UNLIKELY - return context->setError(AL_OUT_OF_MEMORY, - "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize); + if(blocks > std::numeric_limits::max()/align) + throw al::context_error{AL_OUT_OF_MEMORY, + "Buffer size overflow, %d blocks x %d samples per block", blocks, align}; + if(blocks > std::numeric_limits::max()/BlockSize) + throw al::context_error{AL_OUT_OF_MEMORY, + "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize}; const size_t newsize{static_cast(blocks) * BlockSize}; @@ -330,8 +338,8 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, { ALCdevice &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, size)) - return context->setError(AL_OUT_OF_MEMORY, - "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size); + throw al::context_error{AL_OUT_OF_MEMORY, + "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size}; } #endif @@ -343,10 +351,10 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, */ if(newsize != ALBuf->mDataStorage.size()) { - auto newdata = al::vector(newsize, al::byte{}); + auto newdata = decltype(ALBuf->mDataStorage)(newsize, std::byte{}); if((access&AL_PRESERVE_DATA_BIT_SOFT)) { - const size_t tocopy{minz(newdata.size(), ALBuf->mDataStorage.size())}; + const size_t tocopy{std::min(newdata.size(), ALBuf->mDataStorage.size())}; std::copy_n(ALBuf->mDataStorage.begin(), tocopy, newdata.begin()); } newdata.swap(ALBuf->mDataStorage); @@ -383,19 +391,23 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, } /** Prepares the buffer to use the specified callback, using the specified format. */ -void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, +void PrepareCallback(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, const FmtChannels DstChannels, const FmtType DstType, ALBUFFERCALLBACKTYPESOFT callback, void *userptr) { - if(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) UNLIKELY - return context->setError(AL_INVALID_OPERATION, "Modifying callback for in-use buffer %u", - ALBuf->id); + if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) + throw al::context_error{AL_INVALID_OPERATION, "Modifying callback for in-use buffer %u", + ALBuf->id}; const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; + if(align < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", + unpackalign, NameFromFormat(DstType)}; + const ALuint BlockSize{ChannelsFromFmt(DstChannels, ambiorder) * ((DstType == FmtIMA4) ? (align-1)/2 + 4 : (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : @@ -436,18 +448,18 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, } /** Prepares the buffer to use caller-specified storage. */ -void PrepareUserPtr(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, - const FmtChannels DstChannels, const FmtType DstType, al::byte *sdata, const ALuint sdatalen) +void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, + const FmtChannels DstChannels, const FmtType DstType, std::byte *sdata, const ALuint sdatalen) { - if(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) UNLIKELY - return context->setError(AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", - ALBuf->id); + if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) + throw al::context_error{AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", + ALBuf->id}; const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; - if(align < 1) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", - unpackalign, NameFromFormat(DstType)); + if(align < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", + unpackalign, NameFromFormat(DstType)}; auto get_type_alignment = [](const FmtType type) noexcept -> ALuint { @@ -458,6 +470,7 @@ void PrepareUserPtr(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, { case FmtUByte: return alignof(ALubyte); case FmtShort: return alignof(ALshort); + case FmtInt: return alignof(ALint); case FmtFloat: return alignof(ALfloat); case FmtDouble: return alignof(ALdouble); case FmtMulaw: return alignof(ALubyte); @@ -469,8 +482,8 @@ void PrepareUserPtr(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, }; const auto typealign = get_type_alignment(DstType); if((reinterpret_cast(sdata) & (typealign-1)) != 0) - return context->setError(AL_INVALID_VALUE, "Pointer %p is misaligned for %s samples (%u)", - static_cast(sdata), NameFromFormat(DstType), typealign); + throw al::context_error{AL_INVALID_VALUE, "Pointer %p is misaligned for %s samples (%u)", + static_cast(sdata), NameFromFormat(DstType), typealign}; const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; @@ -481,32 +494,32 @@ void PrepareUserPtr(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ((DstType == FmtIMA4) ? (align-1)/2 + 4 : (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : (align * BytesFromFmt(DstType)))}; - if((sdatalen%BlockSize) != 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, + if((sdatalen%BlockSize) != 0) + throw al::context_error{AL_INVALID_VALUE, "Data size %u is not a multiple of frame size %u (%u unpack alignment)", - sdatalen, BlockSize, align); + sdatalen, BlockSize, align}; const ALuint blocks{sdatalen / BlockSize}; - if(blocks > std::numeric_limits::max()/align) UNLIKELY - return context->setError(AL_OUT_OF_MEMORY, - "Buffer size overflow, %d blocks x %d samples per block", blocks, align); - if(blocks > std::numeric_limits::max()/BlockSize) UNLIKELY - return context->setError(AL_OUT_OF_MEMORY, - "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize); + if(blocks > std::numeric_limits::max()/align) + throw al::context_error{AL_OUT_OF_MEMORY, + "Buffer size overflow, %d blocks x %d samples per block", blocks, align}; + if(blocks > std::numeric_limits::max()/BlockSize) + throw al::context_error{AL_OUT_OF_MEMORY, + "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize}; #ifdef ALSOFT_EAX if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) { ALCdevice &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, sdatalen)) - return context->setError(AL_OUT_OF_MEMORY, + throw al::context_error{AL_OUT_OF_MEMORY, "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, - sdatalen); + sdatalen}; } #endif decltype(ALBuf->mDataStorage){}.swap(ALBuf->mDataStorage); - ALBuf->mData = {static_cast(sdata), sdatalen}; + ALBuf->mData = {static_cast(sdata), sdatalen}; #ifdef ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); @@ -536,412 +549,381 @@ void PrepareUserPtr(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, struct DecompResult { FmtChannels channels; FmtType type; }; -al::optional DecomposeUserFormat(ALenum format) +auto DecomposeUserFormat(ALenum format) noexcept -> std::optional { struct FormatMap { ALenum format; - FmtChannels channels; - FmtType type; + DecompResult result; }; - static const std::array UserFmtList{{ - { AL_FORMAT_MONO8, FmtMono, FmtUByte }, - { AL_FORMAT_MONO16, FmtMono, FmtShort }, - { AL_FORMAT_MONO_FLOAT32, FmtMono, FmtFloat }, - { AL_FORMAT_MONO_DOUBLE_EXT, FmtMono, FmtDouble }, - { AL_FORMAT_MONO_IMA4, FmtMono, FmtIMA4 }, - { AL_FORMAT_MONO_MSADPCM_SOFT, FmtMono, FmtMSADPCM }, - { AL_FORMAT_MONO_MULAW, FmtMono, FmtMulaw }, - { AL_FORMAT_MONO_ALAW_EXT, FmtMono, FmtAlaw }, + static constexpr std::array UserFmtList{ + FormatMap{AL_FORMAT_MONO8, {FmtMono, FmtUByte} }, + FormatMap{AL_FORMAT_MONO16, {FmtMono, FmtShort} }, + FormatMap{AL_FORMAT_MONO_I32, {FmtMono, FmtInt} }, + FormatMap{AL_FORMAT_MONO_FLOAT32, {FmtMono, FmtFloat} }, + FormatMap{AL_FORMAT_MONO_DOUBLE_EXT, {FmtMono, FmtDouble} }, + FormatMap{AL_FORMAT_MONO_IMA4, {FmtMono, FmtIMA4} }, + FormatMap{AL_FORMAT_MONO_MSADPCM_SOFT, {FmtMono, FmtMSADPCM}}, + FormatMap{AL_FORMAT_MONO_MULAW, {FmtMono, FmtMulaw} }, + FormatMap{AL_FORMAT_MONO_ALAW_EXT, {FmtMono, FmtAlaw} }, - { AL_FORMAT_STEREO8, FmtStereo, FmtUByte }, - { AL_FORMAT_STEREO16, FmtStereo, FmtShort }, - { AL_FORMAT_STEREO_FLOAT32, FmtStereo, FmtFloat }, - { AL_FORMAT_STEREO_DOUBLE_EXT, FmtStereo, FmtDouble }, - { AL_FORMAT_STEREO_IMA4, FmtStereo, FmtIMA4 }, - { AL_FORMAT_STEREO_MSADPCM_SOFT, FmtStereo, FmtMSADPCM }, - { AL_FORMAT_STEREO_MULAW, FmtStereo, FmtMulaw }, - { AL_FORMAT_STEREO_ALAW_EXT, FmtStereo, FmtAlaw }, + FormatMap{AL_FORMAT_STEREO8, {FmtStereo, FmtUByte} }, + FormatMap{AL_FORMAT_STEREO16, {FmtStereo, FmtShort} }, + FormatMap{AL_FORMAT_STEREO_I32, {FmtStereo, FmtInt} }, + FormatMap{AL_FORMAT_STEREO_FLOAT32, {FmtStereo, FmtFloat} }, + FormatMap{AL_FORMAT_STEREO_DOUBLE_EXT, {FmtStereo, FmtDouble} }, + FormatMap{AL_FORMAT_STEREO_IMA4, {FmtStereo, FmtIMA4} }, + FormatMap{AL_FORMAT_STEREO_MSADPCM_SOFT, {FmtStereo, FmtMSADPCM}}, + FormatMap{AL_FORMAT_STEREO_MULAW, {FmtStereo, FmtMulaw} }, + FormatMap{AL_FORMAT_STEREO_ALAW_EXT, {FmtStereo, FmtAlaw} }, - { AL_FORMAT_REAR8, FmtRear, FmtUByte }, - { AL_FORMAT_REAR16, FmtRear, FmtShort }, - { AL_FORMAT_REAR32, FmtRear, FmtFloat }, - { AL_FORMAT_REAR_MULAW, FmtRear, FmtMulaw }, + FormatMap{AL_FORMAT_REAR8, {FmtRear, FmtUByte}}, + FormatMap{AL_FORMAT_REAR16, {FmtRear, FmtShort}}, + FormatMap{AL_FORMAT_REAR32, {FmtRear, FmtFloat}}, + FormatMap{AL_FORMAT_REAR_I32, {FmtRear, FmtInt} }, + FormatMap{AL_FORMAT_REAR_FLOAT32, {FmtRear, FmtFloat}}, + FormatMap{AL_FORMAT_REAR_MULAW, {FmtRear, FmtMulaw}}, - { AL_FORMAT_QUAD8_LOKI, FmtQuad, FmtUByte }, - { AL_FORMAT_QUAD16_LOKI, FmtQuad, FmtShort }, + FormatMap{AL_FORMAT_QUAD8_LOKI, {FmtQuad, FmtUByte}}, + FormatMap{AL_FORMAT_QUAD16_LOKI, {FmtQuad, FmtShort}}, - { AL_FORMAT_QUAD8, FmtQuad, FmtUByte }, - { AL_FORMAT_QUAD16, FmtQuad, FmtShort }, - { AL_FORMAT_QUAD32, FmtQuad, FmtFloat }, - { AL_FORMAT_QUAD_MULAW, FmtQuad, FmtMulaw }, + FormatMap{AL_FORMAT_QUAD8, {FmtQuad, FmtUByte}}, + FormatMap{AL_FORMAT_QUAD16, {FmtQuad, FmtShort}}, + FormatMap{AL_FORMAT_QUAD32, {FmtQuad, FmtFloat}}, + FormatMap{AL_FORMAT_QUAD_I32, {FmtQuad, FmtInt} }, + FormatMap{AL_FORMAT_QUAD_FLOAT32, {FmtQuad, FmtFloat}}, + FormatMap{AL_FORMAT_QUAD_MULAW, {FmtQuad, FmtMulaw}}, - { AL_FORMAT_51CHN8, FmtX51, FmtUByte }, - { AL_FORMAT_51CHN16, FmtX51, FmtShort }, - { AL_FORMAT_51CHN32, FmtX51, FmtFloat }, - { AL_FORMAT_51CHN_MULAW, FmtX51, FmtMulaw }, + FormatMap{AL_FORMAT_51CHN8, {FmtX51, FmtUByte}}, + FormatMap{AL_FORMAT_51CHN16, {FmtX51, FmtShort}}, + FormatMap{AL_FORMAT_51CHN32, {FmtX51, FmtFloat}}, + FormatMap{AL_FORMAT_51CHN_I32, {FmtX51, FmtInt} }, + FormatMap{AL_FORMAT_51CHN_FLOAT32, {FmtX51, FmtFloat}}, + FormatMap{AL_FORMAT_51CHN_MULAW, {FmtX51, FmtMulaw}}, - { AL_FORMAT_61CHN8, FmtX61, FmtUByte }, - { AL_FORMAT_61CHN16, FmtX61, FmtShort }, - { AL_FORMAT_61CHN32, FmtX61, FmtFloat }, - { AL_FORMAT_61CHN_MULAW, FmtX61, FmtMulaw }, + FormatMap{AL_FORMAT_61CHN8, {FmtX61, FmtUByte}}, + FormatMap{AL_FORMAT_61CHN16, {FmtX61, FmtShort}}, + FormatMap{AL_FORMAT_61CHN32, {FmtX61, FmtFloat}}, + FormatMap{AL_FORMAT_61CHN_I32, {FmtX61, FmtInt} }, + FormatMap{AL_FORMAT_61CHN_FLOAT32, {FmtX61, FmtFloat}}, + FormatMap{AL_FORMAT_61CHN_MULAW, {FmtX61, FmtMulaw}}, - { AL_FORMAT_71CHN8, FmtX71, FmtUByte }, - { AL_FORMAT_71CHN16, FmtX71, FmtShort }, - { AL_FORMAT_71CHN32, FmtX71, FmtFloat }, - { AL_FORMAT_71CHN_MULAW, FmtX71, FmtMulaw }, + FormatMap{AL_FORMAT_71CHN8, {FmtX71, FmtUByte}}, + FormatMap{AL_FORMAT_71CHN16, {FmtX71, FmtShort}}, + FormatMap{AL_FORMAT_71CHN32, {FmtX71, FmtFloat}}, + FormatMap{AL_FORMAT_71CHN_I32, {FmtX71, FmtInt} }, + FormatMap{AL_FORMAT_71CHN_FLOAT32, {FmtX71, FmtFloat}}, + FormatMap{AL_FORMAT_71CHN_MULAW, {FmtX71, FmtMulaw}}, - { AL_FORMAT_BFORMAT2D_8, FmtBFormat2D, FmtUByte }, - { AL_FORMAT_BFORMAT2D_16, FmtBFormat2D, FmtShort }, - { AL_FORMAT_BFORMAT2D_FLOAT32, FmtBFormat2D, FmtFloat }, - { AL_FORMAT_BFORMAT2D_MULAW, FmtBFormat2D, FmtMulaw }, + FormatMap{AL_FORMAT_BFORMAT2D_8, {FmtBFormat2D, FmtUByte}}, + FormatMap{AL_FORMAT_BFORMAT2D_16, {FmtBFormat2D, FmtShort}}, + FormatMap{AL_FORMAT_BFORMAT2D_FLOAT32, {FmtBFormat2D, FmtFloat}}, + FormatMap{AL_FORMAT_BFORMAT2D_MULAW, {FmtBFormat2D, FmtMulaw}}, - { AL_FORMAT_BFORMAT3D_8, FmtBFormat3D, FmtUByte }, - { AL_FORMAT_BFORMAT3D_16, FmtBFormat3D, FmtShort }, - { AL_FORMAT_BFORMAT3D_FLOAT32, FmtBFormat3D, FmtFloat }, - { AL_FORMAT_BFORMAT3D_MULAW, FmtBFormat3D, FmtMulaw }, + FormatMap{AL_FORMAT_BFORMAT3D_8, {FmtBFormat3D, FmtUByte}}, + FormatMap{AL_FORMAT_BFORMAT3D_16, {FmtBFormat3D, FmtShort}}, + FormatMap{AL_FORMAT_BFORMAT3D_FLOAT32, {FmtBFormat3D, FmtFloat}}, + FormatMap{AL_FORMAT_BFORMAT3D_MULAW, {FmtBFormat3D, FmtMulaw}}, - { AL_FORMAT_UHJ2CHN8_SOFT, FmtUHJ2, FmtUByte }, - { AL_FORMAT_UHJ2CHN16_SOFT, FmtUHJ2, FmtShort }, - { AL_FORMAT_UHJ2CHN_FLOAT32_SOFT, FmtUHJ2, FmtFloat }, - { AL_FORMAT_UHJ2CHN_MULAW_SOFT, FmtUHJ2, FmtMulaw }, - { AL_FORMAT_UHJ2CHN_ALAW_SOFT, FmtUHJ2, FmtAlaw }, - { AL_FORMAT_UHJ2CHN_IMA4_SOFT, FmtUHJ2, FmtIMA4 }, - { AL_FORMAT_UHJ2CHN_MSADPCM_SOFT, FmtUHJ2, FmtMSADPCM }, + FormatMap{AL_FORMAT_UHJ2CHN8_SOFT, {FmtUHJ2, FmtUByte} }, + FormatMap{AL_FORMAT_UHJ2CHN16_SOFT, {FmtUHJ2, FmtShort} }, + FormatMap{AL_FORMAT_UHJ2CHN_I32_SOFT, {FmtUHJ2, FmtInt} }, + FormatMap{AL_FORMAT_UHJ2CHN_FLOAT32_SOFT, {FmtUHJ2, FmtFloat} }, + FormatMap{AL_FORMAT_UHJ2CHN_MULAW_SOFT, {FmtUHJ2, FmtMulaw} }, + FormatMap{AL_FORMAT_UHJ2CHN_ALAW_SOFT, {FmtUHJ2, FmtAlaw} }, + FormatMap{AL_FORMAT_UHJ2CHN_IMA4_SOFT, {FmtUHJ2, FmtIMA4} }, + FormatMap{AL_FORMAT_UHJ2CHN_MSADPCM_SOFT, {FmtUHJ2, FmtMSADPCM}}, - { AL_FORMAT_UHJ3CHN8_SOFT, FmtUHJ3, FmtUByte }, - { AL_FORMAT_UHJ3CHN16_SOFT, FmtUHJ3, FmtShort }, - { AL_FORMAT_UHJ3CHN_FLOAT32_SOFT, FmtUHJ3, FmtFloat }, - { AL_FORMAT_UHJ3CHN_MULAW_SOFT, FmtUHJ3, FmtMulaw }, - { AL_FORMAT_UHJ3CHN_ALAW_SOFT, FmtUHJ3, FmtAlaw }, + FormatMap{AL_FORMAT_UHJ3CHN8_SOFT, {FmtUHJ3, FmtUByte}}, + FormatMap{AL_FORMAT_UHJ3CHN16_SOFT, {FmtUHJ3, FmtShort}}, + FormatMap{AL_FORMAT_UHJ3CHN_I32_SOFT, {FmtUHJ3, FmtInt} }, + FormatMap{AL_FORMAT_UHJ3CHN_FLOAT32_SOFT, {FmtUHJ3, FmtFloat}}, + FormatMap{AL_FORMAT_UHJ3CHN_MULAW_SOFT, {FmtUHJ3, FmtMulaw}}, + FormatMap{AL_FORMAT_UHJ3CHN_ALAW_SOFT, {FmtUHJ3, FmtAlaw} }, - { AL_FORMAT_UHJ4CHN8_SOFT, FmtUHJ4, FmtUByte }, - { AL_FORMAT_UHJ4CHN16_SOFT, FmtUHJ4, FmtShort }, - { AL_FORMAT_UHJ4CHN_FLOAT32_SOFT, FmtUHJ4, FmtFloat }, - { AL_FORMAT_UHJ4CHN_MULAW_SOFT, FmtUHJ4, FmtMulaw }, - { AL_FORMAT_UHJ4CHN_ALAW_SOFT, FmtUHJ4, FmtAlaw }, - }}; + FormatMap{AL_FORMAT_UHJ4CHN8_SOFT, {FmtUHJ4, FmtUByte}}, + FormatMap{AL_FORMAT_UHJ4CHN16_SOFT, {FmtUHJ4, FmtShort}}, + FormatMap{AL_FORMAT_UHJ4CHN_I32_SOFT, {FmtUHJ4, FmtInt} }, + FormatMap{AL_FORMAT_UHJ4CHN_FLOAT32_SOFT, {FmtUHJ4, FmtFloat}}, + FormatMap{AL_FORMAT_UHJ4CHN_MULAW_SOFT, {FmtUHJ4, FmtMulaw}}, + FormatMap{AL_FORMAT_UHJ4CHN_ALAW_SOFT, {FmtUHJ4, FmtAlaw} }, + }; - for(const auto &fmt : UserFmtList) - { - if(fmt.format == format) - return al::make_optional({fmt.channels, fmt.type}); - } - return al::nullopt; + auto iter = std::find_if(UserFmtList.cbegin(), UserFmtList.cend(), + [format](const FormatMap &fmt) noexcept { return fmt.format == format; }); + if(iter != UserFmtList.cend()) + return iter->result; + return std::nullopt; } } // namespace -AL_API void AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Generating %d buffers", n); +AL_API DECL_FUNC2(void, alGenBuffers, ALsizei,n, ALuint*,buffers) +FORCE_ALIGN void AL_APIENTRY alGenBuffersDirect(ALCcontext *context, ALsizei n, ALuint *buffers) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Generating %d buffers", n}; if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - if(!EnsureBuffers(device, static_cast(n))) - { - context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d buffer%s", n, (n==1)?"":"s"); - return; - } + std::lock_guard buflock{device->BufferLock}; - if(n == 1) LIKELY - { - /* 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 ids; - ids.reserve(static_cast(n)); - do { - ALbuffer *buffer{AllocBuffer(device)}; - ids.emplace_back(buffer->id); - } while(--n); - std::copy(ids.begin(), ids.end(), buffers); - } + const al::span bids{buffers, static_cast(n)}; + if(!EnsureBuffers(device, bids.size())) + throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d buffer%s", n, + (n == 1) ? "" : "s"}; + + std::generate(bids.begin(), bids.end(), [device]{ return AllocBuffer(device)->id; }); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n); +AL_API DECL_FUNC2(void, alDeleteBuffers, ALsizei,n, const ALuint*,buffers) +FORCE_ALIGN void AL_APIENTRY alDeleteBuffersDirect(ALCcontext *context, ALsizei n, + const ALuint *buffers) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Deleting %d buffers", n}; if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; /* First try to find any buffers that are invalid or in-use. */ - auto validate_buffer = [device, &context](const ALuint bid) -> bool + auto validate_buffer = [device](const ALuint bid) { - if(!bid) return true; + if(!bid) return; ALbuffer *ALBuf{LookupBuffer(device, bid)}; - if(!ALBuf) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", bid); - return false; - } - if(ReadRef(ALBuf->ref) != 0) UNLIKELY - { - context->setError(AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid); - return false; - } - return true; + if(!ALBuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bid}; + if(ALBuf->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid}; }; - const ALuint *buffers_end = buffers + n; - auto invbuf = std::find_if_not(buffers, buffers_end, validate_buffer); - if(invbuf != buffers_end) UNLIKELY return; + + const al::span bids{buffers, static_cast(n)}; + std::for_each(bids.begin(), bids.end(), validate_buffer); /* 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); + if(ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr}) + FreeBuffer(device, buffer); }; - std::for_each(buffers, buffers_end, delete_buffer); + std::for_each(bids.begin(), bids.end(), delete_buffer); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsBuffer, ALuint,buffer) +FORCE_ALIGN ALboolean AL_APIENTRY alIsBufferDirect(ALCcontext *context, ALuint buffer) noexcept { - ContextRef context{GetContextRef()}; - if(context) LIKELY - { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - if(!buffer || LookupBuffer(device, buffer)) - return AL_TRUE; - } + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + if(!buffer || LookupBuffer(device, buffer)) + return AL_TRUE; return AL_FALSE; } -END_API_FUNC -AL_API void 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 +AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq) noexcept { - ContextRef context{GetContextRef()}; + auto context = GetContextRef(); if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(size < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Negative storage size %d", size); - else if(freq < 1) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); - else if((flags&INVALID_STORAGE_MASK) != 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid storage flags 0x%x", - flags&INVALID_STORAGE_MASK); - else if((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS)) UNLIKELY - context->setError(AL_INVALID_VALUE, - "Declaring persistently mapped storage without read or write access"); - else - { - auto usrfmt = DecomposeUserFormat(format); - if(!usrfmt) UNLIKELY - context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); - else - { - LoadData(context.get(), albuf, freq, static_cast(size), usrfmt->channels, - usrfmt->type, static_cast(data), flags); - } - } + alBufferStorageDirectSOFT(context.get(), buffer, format, data, size, freq, 0); } -END_API_FUNC -void AL_APIENTRY alBufferDataStatic(const ALuint buffer, ALenum format, ALvoid *data, ALsizei size, - ALsizei freq) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +FORCE_ALIGN void AL_APIENTRY alBufferDataDirect(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq) noexcept +{ alBufferStorageDirectSOFT(context, buffer, format, data, size, freq, 0); } +AL_API DECL_FUNCEXT6(void, alBufferStorage,SOFT, ALuint,buffer, ALenum,format, const ALvoid*,data, ALsizei,size, ALsizei,freq, ALbitfieldSOFT,flags) +FORCE_ALIGN void AL_APIENTRY alBufferStorageDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - if(size < 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Negative storage size %d", size); - if(freq < 1) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(size < 0) + throw al::context_error{AL_INVALID_VALUE, "Negative storage size %d", size}; + if(freq < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + if((flags&INVALID_STORAGE_MASK) != 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid storage flags 0x%x", + flags&INVALID_STORAGE_MASK}; + if((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS)) + throw al::context_error{AL_INVALID_VALUE, + "Declaring persistently mapped storage without read or write access"}; auto usrfmt = DecomposeUserFormat(format); - if(!usrfmt) UNLIKELY - return context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); + if(!usrfmt) + throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; - PrepareUserPtr(context.get(), albuf, freq, usrfmt->channels, usrfmt->type, - static_cast(data), static_cast(size)); + LoadData(context, albuf, freq, static_cast(size), usrfmt->channels, usrfmt->type, + static_cast(data), flags); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return nullptr; +FORCE_ALIGN DECL_FUNC5(void, alBufferDataStatic, ALuint,buffer, ALenum,format, ALvoid*,data, ALsizei,size, ALsizei,freq) +FORCE_ALIGN void AL_APIENTRY alBufferDataStaticDirect(ALCcontext *context, const ALuint buffer, + ALenum format, ALvoid *data, ALsizei size, ALsizei freq) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if((access&INVALID_MAP_FLAGS) != 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid map flags 0x%x", access&INVALID_MAP_FLAGS); - else if(!(access&MAP_READ_WRITE_FLAGS)) UNLIKELY - context->setError(AL_INVALID_VALUE, "Mapping buffer %u without read or write access", - buffer); - else - { - ALbitfieldSOFT unavailable = (albuf->Access^access) & access; - if(ReadRef(albuf->ref) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) UNLIKELY - context->setError(AL_INVALID_OPERATION, - "Mapping in-use buffer %u without persistent mapping", buffer); - else if(albuf->MappedAccess != 0) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer); - else if((unavailable&AL_MAP_READ_BIT_SOFT)) UNLIKELY - context->setError(AL_INVALID_VALUE, - "Mapping buffer %u for reading without read access", buffer); - else if((unavailable&AL_MAP_WRITE_BIT_SOFT)) UNLIKELY - context->setError(AL_INVALID_VALUE, - "Mapping buffer %u for writing without write access", buffer); - else if((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) UNLIKELY - context->setError(AL_INVALID_VALUE, - "Mapping buffer %u persistently without persistent access", buffer); - else if(offset < 0 || length <= 0 - || static_cast(offset) >= albuf->OriginalSize - || static_cast(length) > albuf->OriginalSize - static_cast(offset)) - UNLIKELY - 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; - } - } + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(size < 0) + throw al::context_error{AL_INVALID_VALUE, "Negative storage size %d", size}; + if(freq < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + auto usrfmt = DecomposeUserFormat(format); + if(!usrfmt) + throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + + PrepareUserPtr(context, albuf, freq, usrfmt->channels, usrfmt->type, + static_cast(data), static_cast(size)); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT4(void*, alMapBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length, ALbitfieldSOFT,access) +FORCE_ALIGN void* AL_APIENTRY alMapBufferDirectSOFT(ALCcontext *context, ALuint buffer, + ALsizei offset, ALsizei length, ALbitfieldSOFT access) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if((access&INVALID_MAP_FLAGS) != 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid map flags 0x%x", + access&INVALID_MAP_FLAGS}; + if(!(access&MAP_READ_WRITE_FLAGS)) + throw al::context_error{AL_INVALID_VALUE, "Mapping buffer %u without read or write access", + buffer}; + + const ALbitfieldSOFT unavailable{(albuf->Access^access) & access}; + if(albuf->ref.load(std::memory_order_relaxed) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) + throw al::context_error{AL_INVALID_OPERATION, + "Mapping in-use buffer %u without persistent mapping", buffer}; + if(albuf->MappedAccess != 0) + throw al::context_error{AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer}; + if((unavailable&AL_MAP_READ_BIT_SOFT)) + throw al::context_error{AL_INVALID_VALUE, + "Mapping buffer %u for reading without read access", buffer}; + if((unavailable&AL_MAP_WRITE_BIT_SOFT)) + throw al::context_error{AL_INVALID_VALUE, + "Mapping buffer %u for writing without write access", buffer}; + if((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) + throw al::context_error{AL_INVALID_VALUE, + "Mapping buffer %u persistently without persistent access", buffer}; + if(offset < 0 || length <= 0 || static_cast(offset) >= albuf->OriginalSize + || static_cast(length) > albuf->OriginalSize - static_cast(offset)) + throw al::context_error{AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u", + offset, length, buffer}; + + void *retval{albuf->mData.data() + offset}; + albuf->MappedAccess = access; + albuf->MappedOffset = offset; + albuf->MappedSize = length; + return retval; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); return nullptr; } -END_API_FUNC - -AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT1(void, alUnmapBuffer,SOFT, ALuint,buffer) +FORCE_ALIGN void AL_APIENTRY alUnmapBufferDirectSOFT(ALCcontext *context, ALuint buffer) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(albuf->MappedAccess == 0) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer); - else - { - albuf->MappedAccess = 0; - albuf->MappedOffset = 0; - albuf->MappedSize = 0; - } + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(albuf->MappedAccess == 0) + throw al::context_error{AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer}; + + albuf->MappedAccess = 0; + albuf->MappedOffset = 0; + albuf->MappedSize = 0; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT3(void, alFlushMappedBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length) +FORCE_ALIGN void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffer, + ALsizei offset, ALsizei length) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Flushing buffer %u while not mapped for writing", - buffer); - else if(offset < albuf->MappedOffset || length <= 0 + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) + throw al::context_error{AL_INVALID_OPERATION, + "Flushing buffer %u while not mapped for writing", buffer}; + if(offset < albuf->MappedOffset || length <= 0 || offset >= albuf->MappedOffset+albuf->MappedSize - || length > albuf->MappedOffset+albuf->MappedSize-offset) UNLIKELY - 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); - } + || length > albuf->MappedOffset+albuf->MappedSize-offset) + throw al::context_error{AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u", + offset, length, buffer}; + + /* FIXME: Need to use some method of double-buffering for the mixer and app + * to hold separate memory, which can be safely transferred 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); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alBufferSubData,SOFT, ALuint,buffer, ALenum,format, const ALvoid*,data, ALsizei,offset, ALsizei,length) +FORCE_ALIGN void AL_APIENTRY alBufferSubDataDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; auto usrfmt = DecomposeUserFormat(format); - if(!usrfmt) UNLIKELY - return context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); + if(!usrfmt) + throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; const ALuint unpack_align{albuf->UnpackAlign}; const ALuint align{SanitizeAlignment(usrfmt->type, unpack_align)}; - if(align < 1) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u", unpack_align); - if(usrfmt->channels != albuf->mChannels || usrfmt->type != albuf->mType) UNLIKELY - return context->setError(AL_INVALID_ENUM, "Unpacking data with mismatched format"); - if(align != albuf->mBlockAlign) UNLIKELY - return context->setError(AL_INVALID_VALUE, + if(align < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u", unpack_align}; + if(usrfmt->channels != albuf->mChannels || usrfmt->type != albuf->mType) + throw al::context_error{AL_INVALID_ENUM, "Unpacking data with mismatched format"}; + if(align != albuf->mBlockAlign) + throw al::context_error{AL_INVALID_VALUE, "Unpacking data with alignment %u does not match original alignment %u", align, - albuf->mBlockAlign); - if(albuf->isBFormat() && albuf->UnpackAmbiOrder != albuf->mAmbiOrder) UNLIKELY - return context->setError(AL_INVALID_VALUE, - "Unpacking data with mismatched ambisonic order"); - if(albuf->MappedAccess != 0) UNLIKELY - return context->setError(AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u", - buffer); + albuf->mBlockAlign}; + if(albuf->isBFormat() && albuf->UnpackAmbiOrder != albuf->mAmbiOrder) + throw al::context_error{AL_INVALID_VALUE, + "Unpacking data with mismatched ambisonic order"}; + if(albuf->MappedAccess != 0) + throw al::context_error{AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u", + buffer}; const ALuint num_chans{albuf->channelsFromFmt()}; const ALuint byte_align{ @@ -951,431 +933,383 @@ START_API_FUNC if(offset < 0 || length < 0 || static_cast(offset) > albuf->OriginalSize || static_cast(length) > albuf->OriginalSize-static_cast(offset)) - UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u", - offset, length, buffer); - if((static_cast(offset)%byte_align) != 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, + throw al::context_error{AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u", + offset, length, buffer}; + if((static_cast(offset)%byte_align) != 0) + throw al::context_error{AL_INVALID_VALUE, "Sub-range offset %d is not a multiple of frame size %d (%d unpack alignment)", - offset, byte_align, align); - if((static_cast(length)%byte_align) != 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, + offset, byte_align, align}; + if((static_cast(length)%byte_align) != 0) + throw al::context_error{AL_INVALID_VALUE, "Sub-range length %d is not a multiple of frame size %d (%d unpack alignment)", - length, byte_align, align); + length, byte_align, align}; - assert(al::to_underlying(usrfmt->type) == al::to_underlying(albuf->mType)); - memcpy(albuf->mData.data()+offset, data, static_cast(length)); + std::memcpy(albuf->mData.data()+offset, data, static_cast(length)); } -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(!context) UNLIKELY return; - - context->setError(AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported"); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -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(!context) UNLIKELY 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(!context) UNLIKELY 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(!context) UNLIKELY 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(!context) UNLIKELY return; - +AL_API DECL_FUNC3(void, alBufferf, ALuint,buffer, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALfloat value [[maybe_unused]]) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + + switch(param) { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param); } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -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(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alBuffer3f, ALuint,buffer, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) +FORCE_ALIGN void AL_APIENTRY alBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALfloat value1 [[maybe_unused]], ALfloat value2 [[maybe_unused]], + ALfloat value3 [[maybe_unused]]) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + + switch(param) { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param); } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alBufferfv, ALuint,buffer, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, + const ALfloat *values) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param); } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - +AL_API DECL_FUNC3(void, alBufferi, ALuint,buffer, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALint value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + + switch(param) { case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: - if(value < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid unpack block alignment %d", value); - else - albuf->UnpackAlign = static_cast(value); - break; + if(value < 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack block alignment %d", value}; + albuf->UnpackAlign = static_cast(value); + return; case AL_PACK_BLOCK_ALIGNMENT_SOFT: - if(value < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid pack block alignment %d", value); - else - albuf->PackAlign = static_cast(value); - break; + if(value < 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid pack block alignment %d", value}; + albuf->PackAlign = static_cast(value); + return; case AL_AMBISONIC_LAYOUT_SOFT: - if(ReadRef(albuf->ref) != 0) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's ambisonic layout", - buffer); - else if(const auto layout = AmbiLayoutFromEnum(value)) - albuf->mAmbiLayout = layout.value(); - else UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic layout %04x", value); - break; - - case AL_AMBISONIC_SCALING_SOFT: - if(ReadRef(albuf->ref) != 0) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's ambisonic scaling", - buffer); - else if(const auto scaling = AmbiScalingFromEnum(value)) - albuf->mAmbiScaling = scaling.value(); - else UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic scaling %04x", value); - break; - - case AL_UNPACK_AMBISONIC_ORDER_SOFT: - if(value < 1 || value > 14) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic order %d", value); - else - albuf->UnpackAmbiOrder = static_cast(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(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - 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) + if(albuf->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, + "Modifying in-use buffer %u's ambisonic layout", buffer}; + if(const auto layout = AmbiLayoutFromEnum(value)) { - case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: - case AL_PACK_BLOCK_ALIGNMENT_SOFT: - case AL_AMBISONIC_LAYOUT_SOFT: - case AL_AMBISONIC_SCALING_SOFT: - case AL_UNPACK_AMBISONIC_ORDER_SOFT: - alBufferi(buffer, param, values[0]); + albuf->mAmbiLayout = layout.value(); return; } + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic layout %04x", value}; + + case AL_AMBISONIC_SCALING_SOFT: + if(albuf->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, + "Modifying in-use buffer %u's ambisonic scaling", buffer}; + if(const auto scaling = AmbiScalingFromEnum(value)) + { + albuf->mAmbiScaling = scaling.value(); + return; + } + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic scaling %04x", value}; + + case AL_UNPACK_AMBISONIC_ORDER_SOFT: + if(value < 1 || value > 14) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic order %d", value}; + albuf->UnpackAmbiOrder = static_cast(value); + return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC5(void, alBuffer3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) +FORCE_ALIGN void AL_APIENTRY alBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALint value1 [[maybe_unused]], ALint value2 [[maybe_unused]], ALint value3 [[maybe_unused]]) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alBufferiv, ALuint,buffer, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, + const ALint *values) noexcept +try { + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: + case AL_PACK_BLOCK_ALIGNMENT_SOFT: + case AL_AMBISONIC_LAYOUT_SOFT: + case AL_AMBISONIC_SCALING_SOFT: + case AL_UNPACK_AMBISONIC_ORDER_SOFT: + alBufferiDirect(context, buffer, param, *values); + return; + } ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + + switch(param) { case AL_LOOP_POINTS_SOFT: - if(ReadRef(albuf->ref) != 0) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's loop points", - buffer); - else if(values[0] < 0 || values[0] >= values[1] - || static_cast(values[1]) > albuf->mSampleLen) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid loop point range %d -> %d on buffer %u", - values[0], values[1], buffer); - else - { - albuf->mLoopStart = static_cast(values[0]); - albuf->mLoopEnd = static_cast(values[1]); - } - break; + auto vals = al::span{values, 2_uz}; + if(albuf->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, + "Modifying in-use buffer %u's loop points", buffer}; + if(vals[0] < 0 || vals[0] >= vals[1] || static_cast(vals[1]) > albuf->mSampleLen) + throw al::context_error{AL_INVALID_VALUE, + "Invalid loop point range %d -> %d on buffer %u", vals[0], vals[1], buffer}; - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", param); + albuf->mLoopStart = static_cast(vals[0]); + albuf->mLoopEnd = static_cast(vals[1]); + return; } + + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - +AL_API DECL_FUNC3(void, alGetBufferf, ALuint,buffer, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALfloat *value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { case AL_SEC_LENGTH_SOFT: *value = (albuf->mSampleRate < 1) ? 0.0f : (static_cast(albuf->mSampleLen) / static_cast(albuf->mSampleRate)); - break; - - 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(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value1 || !value2 || !value3) UNLIKELY - 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(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param); - } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC +AL_API DECL_FUNC5(void, alGetBuffer3f, ALuint,buffer, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) +FORCE_ALIGN void AL_APIENTRY alGetBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; -AL_API void AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value1 || !value2 || !value3) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetBufferfv, ALuint,buffer, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALfloat *values) noexcept +try { + switch(param) + { + case AL_SEC_LENGTH_SOFT: + alGetBufferfDirect(context, buffer, param, values); + return; + } ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard buflock{device->BufferLock}; + + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +AL_API DECL_FUNC3(void, alGetBufferi, ALuint,buffer, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALint *value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { case AL_FREQUENCY: *value = static_cast(albuf->mSampleRate); - break; + return; case AL_BITS: *value = (albuf->mType == FmtIMA4 || albuf->mType == FmtMSADPCM) ? 4 : static_cast(albuf->bytesFromFmt() * 8); - break; + return; case AL_CHANNELS: *value = static_cast(albuf->channelsFromFmt()); - break; + return; case AL_SIZE: *value = albuf->mCallback ? 0 : static_cast(albuf->mData.size()); - break; + return; case AL_BYTE_LENGTH_SOFT: *value = static_cast(albuf->mSampleLen / albuf->mBlockAlign * albuf->blockSizeFromFmt()); - break; + return; case AL_SAMPLE_LENGTH_SOFT: *value = static_cast(albuf->mSampleLen); - break; + return; case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: *value = static_cast(albuf->UnpackAlign); - break; + return; case AL_PACK_BLOCK_ALIGNMENT_SOFT: *value = static_cast(albuf->PackAlign); - break; + return; case AL_AMBISONIC_LAYOUT_SOFT: *value = EnumFromAmbiLayout(albuf->mAmbiLayout); - break; + return; case AL_AMBISONIC_SCALING_SOFT: *value = EnumFromAmbiScaling(albuf->mAmbiScaling); - break; + return; case AL_UNPACK_AMBISONIC_ORDER_SOFT: *value = static_cast(albuf->UnpackAmbiOrder); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param); + return; } + + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -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(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alGetBuffer3i, ALuint,buffer, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) +FORCE_ALIGN void AL_APIENTRY alGetBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALint *value1, ALint *value2, ALint *value3) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value1 || !value2 || !value3) UNLIKELY - 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 + std::lock_guard buflock{device->BufferLock}; -AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values) -START_API_FUNC -{ + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value1 || !value2 || !value3) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetBufferiv, ALuint,buffer, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALint *values) noexcept +try { switch(param) { case AL_FREQUENCY: @@ -1390,252 +1324,305 @@ START_API_FUNC case AL_AMBISONIC_LAYOUT_SOFT: case AL_AMBISONIC_SCALING_SOFT: case AL_UNPACK_AMBISONIC_ORDER_SOFT: - alGetBufferi(buffer, param, values); + alGetBufferiDirect(context, buffer, param, values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard buflock{device->BufferLock}; + + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { case AL_LOOP_POINTS_SOFT: - values[0] = static_cast(albuf->mLoopStart); - values[1] = static_cast(albuf->mLoopEnd); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", param); + auto vals = al::span{values, 2_uz}; + vals[0] = static_cast(albuf->mLoopStart); + vals[1] = static_cast(albuf->mLoopEnd); + return; } + + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", + param}; } -END_API_FUNC - - -AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, - ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(freq < 1) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); - else if(callback == nullptr) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL callback"); - else - { - auto usrfmt = DecomposeUserFormat(format); - if(!usrfmt) UNLIKELY - context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); - else - PrepareCallback(context.get(), albuf, freq, usrfmt->channels, usrfmt->type, callback, - userptr); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alBufferCallback,SOFT, ALuint,buffer, ALenum,format, ALsizei,freq, ALBUFFERCALLBACKTYPESOFT,callback, ALvoid*,userptr) +FORCE_ALIGN void AL_APIENTRY alBufferCallbackDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard buflock{device->BufferLock}; + + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(freq < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + if(callback == nullptr) + throw al::context_error{AL_INVALID_VALUE, "NULL callback"}; + + auto usrfmt = DecomposeUserFormat(format); + if(!usrfmt) + throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + + PrepareCallback(context, albuf, freq, usrfmt->channels, usrfmt->type, callback, userptr); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alGetBufferPtr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value) +FORCE_ALIGN void AL_APIENTRY alGetBufferPtrDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum param, ALvoid **value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { case AL_BUFFER_CALLBACK_FUNCTION_SOFT: *value = reinterpret_cast(albuf->mCallback); - break; + return; case AL_BUFFER_CALLBACK_USER_PARAM_SOFT: *value = albuf->mUserData; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer pointer property 0x%04x", param); + return; } + + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer pointer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alGetBuffer3Ptr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value1, ALvoid**,value2, ALvoid**,value3) +FORCE_ALIGN void AL_APIENTRY alGetBuffer3PtrDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value1 || !value2 || !value3) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-pointer property 0x%04x", param); - } -} -END_API_FUNC + std::lock_guard buflock{device->BufferLock}; -AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **values) -START_API_FUNC -{ + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value1 || !value2 || !value3) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-pointer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alGetBufferPtrv,SOFT, ALuint,buffer, ALenum,param, ALvoid**,values) +FORCE_ALIGN void AL_APIENTRY alGetBufferPtrvDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum param, ALvoid **values) noexcept +try { switch(param) { case AL_BUFFER_CALLBACK_FUNCTION_SOFT: case AL_BUFFER_CALLBACK_USER_PARAM_SOFT: - alGetBufferPtrSOFT(buffer, param, values); + alGetBufferPtrDirectSOFT(context, buffer, param, values); return; } + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer pointer-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint /*buffer*/, ALuint /*samplerate*/, + ALenum /*internalformat*/, ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, + const ALvoid* /*data*/) noexcept +{ ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer pointer-vector property 0x%04x", param); - } + context->setError(AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported"); +} + +AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/, + ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, const ALvoid* /*data*/) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return; + + context->setError(AL_INVALID_OPERATION, "alBufferSubSamplesSOFT not supported"); +} + +AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/, + ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, ALvoid* /*data*/) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return; + + context->setError(AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported"); +} + +AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return AL_FALSE; + + context->setError(AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported"); + return AL_FALSE; +} + + +void ALbuffer::SetName(ALCcontext *context, ALuint id, std::string_view name) +{ + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + auto buffer = LookupBuffer(device, id); + if(!buffer) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", id}; + + device->mBufferNames.insert_or_assign(id, name); } -END_API_FUNC BufferSubList::~BufferSubList() { + if(!Buffers) + return; + uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; - al::destroy_at(Buffers+idx); + std::destroy_at(al::to_address(Buffers->begin() + idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; - al_free(Buffers); + SubListAllocator{}.deallocate(Buffers, 1); Buffers = nullptr; } #ifdef ALSOFT_EAX -FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint* buffers, ALint value) -START_API_FUNC -{ -#define EAX_PREFIX "[EAXSetBufferMode] " - - const auto context = ContextRef{GetContextRef()}; - if(!context) - { - ERR(EAX_PREFIX "%s\n", "No current context."); - return ALC_FALSE; - } - +FORCE_ALIGN DECL_FUNC3(ALboolean, EAXSetBufferMode, ALsizei,n, const ALuint*,buffers, ALint,value) +FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferModeDirect(ALCcontext *context, ALsizei n, + const ALuint *buffers, ALint value) noexcept +try { if(!eax_g_is_enabled) - { - context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled."); - return ALC_FALSE; - } + throw al::context_error{AL_INVALID_OPERATION, "EAX not enabled"}; const auto storage = EaxStorageFromEnum(value); if(!storage) - { - context->setError(AL_INVALID_ENUM, EAX_PREFIX "Unsupported X-RAM mode 0x%x", value); - return ALC_FALSE; - } + throw al::context_error{AL_INVALID_ENUM, "Unsupported X-RAM mode 0x%x", value}; if(n == 0) - return ALC_TRUE; + return AL_TRUE; if(n < 0) - { - context->setError(AL_INVALID_VALUE, EAX_PREFIX "Buffer count %d out of range", n); - return ALC_FALSE; - } - + throw al::context_error{AL_INVALID_VALUE, "Buffer count %d out of range", n}; if(!buffers) - { - context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Null AL buffers"); - return ALC_FALSE; - } + throw al::context_error{AL_INVALID_VALUE, "Null AL buffers"}; auto device = context->mALDevice.get(); - std::lock_guard device_lock{device->BufferLock}; - size_t total_needed{0}; + std::lock_guard devlock{device->BufferLock}; - // Validate the buffers. - // - for(auto i = 0;i < n;++i) + /* Special-case setting a single buffer, to avoid extraneous allocations. */ + if(n == 1) { - const auto bufid = buffers[i]; + const auto bufid = *buffers; if(bufid == AL_NONE) - continue; + return AL_TRUE; const auto buffer = LookupBuffer(device, bufid); - if(!buffer) UNLIKELY - { - ERR(EAX_PREFIX "Invalid buffer ID %u.\n", bufid); - return ALC_FALSE; - } + if(!buffer) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bufid}; /* TODO: Is the store location allowed to change for in-use buffers, or * only when not set/queued on a source? */ - if(*storage == EaxStorage::Hardware && !buffer->eax_x_ram_is_hardware) + if(*storage == EaxStorage::Hardware) { - /* FIXME: This doesn't account for duplicate buffers. When the same - * buffer ID is specified multiple times in the provided list, it - * counts each instance as more memory that needs to fit in X-RAM. - */ - if(std::numeric_limits::max()-buffer->OriginalSize < total_needed) UNLIKELY - { - context->setError(AL_OUT_OF_MEMORY, EAX_PREFIX "Size overflow (%u + %zu)\n", - buffer->OriginalSize, total_needed); - return ALC_FALSE; - } - total_needed += buffer->OriginalSize; + if(!buffer->eax_x_ram_is_hardware + && buffer->OriginalSize > device->eax_x_ram_free_size) + throw al::context_error{AL_OUT_OF_MEMORY, + "Out of X-RAM memory (need: %u, avail: %u)", buffer->OriginalSize, + device->eax_x_ram_free_size}; + + eax_x_ram_apply(*device, *buffer); } - } - if(total_needed > device->eax_x_ram_free_size) - { - context->setError(AL_OUT_OF_MEMORY,EAX_PREFIX "Out of X-RAM memory (need: %zu, avail: %u)", - total_needed, device->eax_x_ram_free_size); - return ALC_FALSE; + else + eax_x_ram_clear(*device, *buffer); + buffer->eax_x_ram_mode = *storage; + return AL_TRUE; } - // Update the mode. - // - for(auto i = 0;i < n;++i) + /* Validate the buffers. */ + std::unordered_set buflist; + for(const ALuint bufid : al::span{buffers, static_cast(n)}) { - const auto bufid = buffers[i]; if(bufid == AL_NONE) continue; const auto buffer = LookupBuffer(device, bufid); - assert(buffer); + if(!buffer) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bufid}; + /* TODO: Is the store location allowed to change for in-use buffers, or + * only when not set/queued on a source? + */ + + buflist.emplace(buffer); + } + + if(*storage == EaxStorage::Hardware) + { + size_t total_needed{0}; + for(ALbuffer *buffer : buflist) + { + if(!buffer->eax_x_ram_is_hardware) + { + if(std::numeric_limits::max() - buffer->OriginalSize < total_needed) + throw al::context_error{AL_OUT_OF_MEMORY, "Size overflow (%u + %zu)", + buffer->OriginalSize, total_needed}; + + total_needed += buffer->OriginalSize; + } + } + if(total_needed > device->eax_x_ram_free_size) + throw al::context_error{AL_OUT_OF_MEMORY, "Out of X-RAM memory (need: %zu, avail: %u)", + total_needed, device->eax_x_ram_free_size}; + } + + /* Update the mode. */ + for(ALbuffer *buffer : buflist) + { if(*storage == EaxStorage::Hardware) eax_x_ram_apply(*device, *buffer); else @@ -1644,49 +1631,35 @@ START_API_FUNC } return AL_TRUE; - -#undef EAX_PREFIX } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "[EAXSetBufferMode] %s", e.what()); + return AL_FALSE; +} -FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint* pReserved) -START_API_FUNC -{ -#define EAX_PREFIX "[EAXGetBufferMode] " - - const auto context = ContextRef{GetContextRef()}; - if(!context) - { - ERR(EAX_PREFIX "%s\n", "No current context."); - return AL_NONE; - } +FORCE_ALIGN DECL_FUNC2(ALenum, EAXGetBufferMode, ALuint,buffer, ALint*,pReserved) +FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferModeDirect(ALCcontext *context, ALuint buffer, + ALint *pReserved) noexcept +try { if(!eax_g_is_enabled) - { - context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled."); - return AL_NONE; - } + throw al::context_error{AL_INVALID_OPERATION, "EAX not enabled."}; if(pReserved) - { - context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Non-null reserved parameter"); - return AL_NONE; - } + throw al::context_error{AL_INVALID_VALUE, "Non-null reserved parameter"}; auto device = context->mALDevice.get(); - std::lock_guard device_lock{device->BufferLock}; + std::lock_guard devlock{device->BufferLock}; const auto al_buffer = LookupBuffer(device, buffer); if(!al_buffer) - { - context->setError(AL_INVALID_NAME, EAX_PREFIX "Invalid buffer ID %u", buffer); - return AL_NONE; - } + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; return EnumFromEaxStorage(al_buffer->eax_x_ram_mode); - -#undef EAX_PREFIX } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "[EAXGetBufferMode] %s", e.what()); + return AL_NONE; +} #endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/buffer.h b/Engine/lib/openal-soft/al/buffer.h index 64ebe1f32..f7661ec88 100644 --- a/Engine/lib/openal-soft/al/buffer.h +++ b/Engine/lib/openal-soft/al/buffer.h @@ -1,20 +1,23 @@ #ifndef AL_BUFFER_H #define AL_BUFFER_H +#include #include +#include +#include +#include +#include #include "AL/al.h" +#include "AL/alc.h" -#include "albyte.h" #include "alc/inprogext.h" #include "almalloc.h" -#include "atomic.h" +#include "alnumeric.h" #include "core/buffer_storage.h" #include "vector.h" #ifdef ALSOFT_EAX -#include "eax/x_ram.h" - enum class EaxStorage : uint8_t { Automatic, Accessible, @@ -26,7 +29,7 @@ enum class EaxStorage : uint8_t { struct ALbuffer : public BufferStorage { ALbitfieldSOFT Access{0u}; - al::vector mDataStorage; + al::vector mDataStorage; ALuint OriginalSize{0}; @@ -42,12 +45,14 @@ struct ALbuffer : public BufferStorage { ALuint mLoopEnd{0u}; /* Number of times buffer was attached to a source (deletion can only occur when 0) */ - RefCount ref{0u}; + std::atomic ref{0u}; /* Self ID */ ALuint id{0}; - DISABLE_ALLOC() + static void SetName(ALCcontext *context, ALuint id, std::string_view name); + + DISABLE_ALLOC #ifdef ALSOFT_EAX EaxStorage eax_x_ram_mode{EaxStorage::Automatic}; @@ -55,4 +60,19 @@ struct ALbuffer : public BufferStorage { #endif // ALSOFT_EAX }; +struct BufferSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> Buffers{nullptr}; + + BufferSubList() noexcept = default; + BufferSubList(const BufferSubList&) = delete; + BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} + { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } + ~BufferSubList(); + + BufferSubList& operator=(const BufferSubList&) = delete; + BufferSubList& operator=(BufferSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } +}; + #endif diff --git a/Engine/lib/openal-soft/al/debug.cpp b/Engine/lib/openal-soft/al/debug.cpp new file mode 100644 index 000000000..e91dc29c2 --- /dev/null +++ b/Engine/lib/openal-soft/al/debug.cpp @@ -0,0 +1,618 @@ +#include "config.h" + +#include "debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alc/context.h" +#include "alc/device.h" +#include "alc/inprogext.h" +#include "alnumeric.h" +#include "alspan.h" +#include "alstring.h" +#include "auxeffectslot.h" +#include "buffer.h" +#include "core/logging.h" +#include "core/voice.h" +#include "direct_defs.h" +#include "effect.h" +#include "error.h" +#include "filter.h" +#include "intrusive_ptr.h" +#include "opthelpers.h" +#include "source.h" + + +/* Declared here to prevent compilers from thinking it should be inlined, which + * GCC warns about increasing code size. + */ +DebugGroup::~DebugGroup() = default; + +namespace { + +static_assert(DebugSeverityBase+DebugSeverityCount <= 32, "Too many debug bits"); + +template +constexpr auto make_array_sequence(std::integer_sequence) +{ return std::array{Vals...}; } + +template +constexpr auto make_array_sequence() +{ return make_array_sequence(std::make_integer_sequence{}); } + + +constexpr auto GetDebugSource(ALenum source) noexcept -> std::optional +{ + switch(source) + { + case AL_DEBUG_SOURCE_API_EXT: return DebugSource::API; + case AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT: return DebugSource::System; + case AL_DEBUG_SOURCE_THIRD_PARTY_EXT: return DebugSource::ThirdParty; + case AL_DEBUG_SOURCE_APPLICATION_EXT: return DebugSource::Application; + case AL_DEBUG_SOURCE_OTHER_EXT: return DebugSource::Other; + } + return std::nullopt; +} + +constexpr auto GetDebugType(ALenum type) noexcept -> std::optional +{ + switch(type) + { + case AL_DEBUG_TYPE_ERROR_EXT: return DebugType::Error; + case AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT: return DebugType::DeprecatedBehavior; + case AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT: return DebugType::UndefinedBehavior; + case AL_DEBUG_TYPE_PORTABILITY_EXT: return DebugType::Portability; + case AL_DEBUG_TYPE_PERFORMANCE_EXT: return DebugType::Performance; + case AL_DEBUG_TYPE_MARKER_EXT: return DebugType::Marker; + case AL_DEBUG_TYPE_PUSH_GROUP_EXT: return DebugType::PushGroup; + case AL_DEBUG_TYPE_POP_GROUP_EXT: return DebugType::PopGroup; + case AL_DEBUG_TYPE_OTHER_EXT: return DebugType::Other; + } + return std::nullopt; +} + +constexpr auto GetDebugSeverity(ALenum severity) noexcept -> std::optional +{ + switch(severity) + { + case AL_DEBUG_SEVERITY_HIGH_EXT: return DebugSeverity::High; + case AL_DEBUG_SEVERITY_MEDIUM_EXT: return DebugSeverity::Medium; + case AL_DEBUG_SEVERITY_LOW_EXT: return DebugSeverity::Low; + case AL_DEBUG_SEVERITY_NOTIFICATION_EXT: return DebugSeverity::Notification; + } + return std::nullopt; +} + + +constexpr auto GetDebugSourceEnum(DebugSource source) -> ALenum +{ + switch(source) + { + case DebugSource::API: return AL_DEBUG_SOURCE_API_EXT; + case DebugSource::System: return AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT; + case DebugSource::ThirdParty: return AL_DEBUG_SOURCE_THIRD_PARTY_EXT; + case DebugSource::Application: return AL_DEBUG_SOURCE_APPLICATION_EXT; + case DebugSource::Other: return AL_DEBUG_SOURCE_OTHER_EXT; + } + throw std::runtime_error{"Unexpected debug source value "+std::to_string(al::to_underlying(source))}; +} + +constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum +{ + switch(type) + { + case DebugType::Error: return AL_DEBUG_TYPE_ERROR_EXT; + case DebugType::DeprecatedBehavior: return AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT; + case DebugType::UndefinedBehavior: return AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT; + case DebugType::Portability: return AL_DEBUG_TYPE_PORTABILITY_EXT; + case DebugType::Performance: return AL_DEBUG_TYPE_PERFORMANCE_EXT; + case DebugType::Marker: return AL_DEBUG_TYPE_MARKER_EXT; + case DebugType::PushGroup: return AL_DEBUG_TYPE_PUSH_GROUP_EXT; + case DebugType::PopGroup: return AL_DEBUG_TYPE_POP_GROUP_EXT; + case DebugType::Other: return AL_DEBUG_TYPE_OTHER_EXT; + } + throw std::runtime_error{"Unexpected debug type value "+std::to_string(al::to_underlying(type))}; +} + +constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum +{ + switch(severity) + { + case DebugSeverity::High: return AL_DEBUG_SEVERITY_HIGH_EXT; + case DebugSeverity::Medium: return AL_DEBUG_SEVERITY_MEDIUM_EXT; + case DebugSeverity::Low: return AL_DEBUG_SEVERITY_LOW_EXT; + case DebugSeverity::Notification: return AL_DEBUG_SEVERITY_NOTIFICATION_EXT; + } + throw std::runtime_error{"Unexpected debug severity value "+std::to_string(al::to_underlying(severity))}; +} + + +constexpr auto GetDebugSourceName(DebugSource source) noexcept -> const char* +{ + switch(source) + { + case DebugSource::API: return "API"; + case DebugSource::System: return "Audio System"; + case DebugSource::ThirdParty: return "Third Party"; + case DebugSource::Application: return "Application"; + case DebugSource::Other: return "Other"; + } + return ""; +} + +constexpr auto GetDebugTypeName(DebugType type) noexcept -> const char* +{ + switch(type) + { + case DebugType::Error: return "Error"; + case DebugType::DeprecatedBehavior: return "Deprecated Behavior"; + case DebugType::UndefinedBehavior: return "Undefined Behavior"; + case DebugType::Portability: return "Portability"; + case DebugType::Performance: return "Performance"; + case DebugType::Marker: return "Marker"; + case DebugType::PushGroup: return "Push Group"; + case DebugType::PopGroup: return "Pop Group"; + case DebugType::Other: return "Other"; + } + return ""; +} + +constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> const char* +{ + switch(severity) + { + case DebugSeverity::High: return "High"; + case DebugSeverity::Medium: return "Medium"; + case DebugSeverity::Low: return "Low"; + case DebugSeverity::Notification: return "Notification"; + } + return ""; +} + +} // namespace + + +void ALCcontext::sendDebugMessage(std::unique_lock &debuglock, DebugSource source, + DebugType type, ALuint id, DebugSeverity severity, std::string_view message) +{ + if(!mDebugEnabled.load(std::memory_order_relaxed)) UNLIKELY + return; + + if(message.length() >= MaxDebugMessageLength) UNLIKELY + { + ERR("Debug message too long (%zu >= %d):\n-> %.*s\n", message.length(), + MaxDebugMessageLength, al::sizei(message), message.data()); + return; + } + + DebugGroup &debug = mDebugGroups.back(); + + const uint64_t idfilter{(1_u64 << (DebugSourceBase+al::to_underlying(source))) + | (1_u64 << (DebugTypeBase+al::to_underlying(type))) + | (uint64_t{id} << 32)}; + auto iditer = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(), idfilter); + if(iditer != debug.mIdFilters.cend() && *iditer == idfilter) + return; + + const uint filter{(1u << (DebugSourceBase+al::to_underlying(source))) + | (1u << (DebugTypeBase+al::to_underlying(type))) + | (1u << (DebugSeverityBase+al::to_underlying(severity)))}; + auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter); + if(iter != debug.mFilters.cend() && *iter == filter) + return; + + if(mDebugCb) + { + auto callback = mDebugCb; + auto param = mDebugParam; + debuglock.unlock(); + callback(GetDebugSourceEnum(source), GetDebugTypeEnum(type), id, + GetDebugSeverityEnum(severity), static_cast(message.length()), message.data(), + param); + } + else + { + if(mDebugLog.size() < MaxDebugLoggedMessages) + mDebugLog.emplace_back(source, type, id, severity, message); + else UNLIKELY + ERR("Debug message log overflow. Lost message:\n" + " Source: %s\n" + " Type: %s\n" + " ID: %u\n" + " Severity: %s\n" + " Message: \"%.*s\"\n", + GetDebugSourceName(source), GetDebugTypeName(type), id, + GetDebugSeverityName(severity), al::sizei(message), message.data()); + } +} + + +FORCE_ALIGN DECL_FUNCEXT2(void, alDebugMessageCallback,EXT, ALDEBUGPROCEXT,callback, void*,userParam) +FORCE_ALIGN void AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context, + ALDEBUGPROCEXT callback, void *userParam) noexcept +{ + std::lock_guard debuglock{context->mDebugCbLock}; + context->mDebugCb = callback; + context->mDebugParam = userParam; +} + + +FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageInsert,EXT, ALenum,source, ALenum,type, ALuint,id, ALenum,severity, ALsizei,length, const ALchar*,message) +FORCE_ALIGN void AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source, + ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) noexcept +try { + if(!context->mContextFlags.test(ContextFlags::DebugBit)) + return; + + if(!message) + throw al::context_error{AL_INVALID_VALUE, "Null message pointer"}; + + auto msgview = (length < 0) ? std::string_view{message} + : std::string_view{message, static_cast(length)}; + if(msgview.size() >= MaxDebugMessageLength) + throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", + msgview.size(), MaxDebugMessageLength}; + + auto dsource = GetDebugSource(source); + if(!dsource) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) + throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source}; + + auto dtype = GetDebugType(type); + if(!dtype) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type}; + + auto dseverity = GetDebugSeverity(severity); + if(!dseverity) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity}; + + context->debugMessage(*dsource, *dtype, id, *dseverity, msgview); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageControl,EXT, ALenum,source, ALenum,type, ALenum,severity, ALsizei,count, const ALuint*,ids, ALboolean,enable) +FORCE_ALIGN void AL_APIENTRY alDebugMessageControlDirectEXT(ALCcontext *context, ALenum source, + ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) noexcept +try { + if(count > 0) + { + if(!ids) + throw al::context_error{AL_INVALID_VALUE, "IDs is null with non-0 count"}; + if(source == AL_DONT_CARE_EXT) + throw al::context_error{AL_INVALID_OPERATION, + "Debug source cannot be AL_DONT_CARE_EXT with IDs"}; + if(type == AL_DONT_CARE_EXT) + throw al::context_error{AL_INVALID_OPERATION, + "Debug type cannot be AL_DONT_CARE_EXT with IDs"}; + if(severity != AL_DONT_CARE_EXT) + throw al::context_error{AL_INVALID_OPERATION, + "Debug severity must be AL_DONT_CARE_EXT with IDs"}; + } + + if(enable != AL_TRUE && enable != AL_FALSE) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug enable %d", enable}; + + static constexpr size_t ElemCount{DebugSourceCount + DebugTypeCount + DebugSeverityCount}; + static constexpr auto Values = make_array_sequence(); + + auto srcIndices = al::span{Values}.subspan(DebugSourceBase,DebugSourceCount); + if(source != AL_DONT_CARE_EXT) + { + auto dsource = GetDebugSource(source); + if(!dsource) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + srcIndices = srcIndices.subspan(al::to_underlying(*dsource), 1); + } + + auto typeIndices = al::span{Values}.subspan(DebugTypeBase,DebugTypeCount); + if(type != AL_DONT_CARE_EXT) + { + auto dtype = GetDebugType(type); + if(!dtype) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type}; + typeIndices = typeIndices.subspan(al::to_underlying(*dtype), 1); + } + + auto svrIndices = al::span{Values}.subspan(DebugSeverityBase,DebugSeverityCount); + if(severity != AL_DONT_CARE_EXT) + { + auto dseverity = GetDebugSeverity(severity); + if(!dseverity) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity}; + svrIndices = svrIndices.subspan(al::to_underlying(*dseverity), 1); + } + + std::lock_guard debuglock{context->mDebugCbLock}; + DebugGroup &debug = context->mDebugGroups.back(); + if(count > 0) + { + const uint filterbase{(1u<(count)}) + { + const uint64_t filter{filterbase | (uint64_t{id} << 32)}; + + auto iter = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(), + filter); + if(!enable && (iter == debug.mIdFilters.cend() || *iter != filter)) + debug.mIdFilters.insert(iter, filter); + else if(enable && iter != debug.mIdFilters.cend() && *iter == filter) + debug.mIdFilters.erase(iter); + } + } + else + { + auto apply_filter = [enable,&debug](const uint filter) + { + auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter); + if(!enable && (iter == debug.mFilters.cend() || *iter != filter)) + debug.mFilters.insert(iter, filter); + else if(enable && iter != debug.mFilters.cend() && *iter == filter) + debug.mFilters.erase(iter); + }; + auto apply_severity = [apply_filter,svrIndices](const uint filter) + { + std::for_each(svrIndices.cbegin(), svrIndices.cend(), + [apply_filter,filter](const uint idx){ apply_filter(filter | (1<setError(e.errorCode(), "%s", e.what()); +} + + +FORCE_ALIGN DECL_FUNCEXT4(void, alPushDebugGroup,EXT, ALenum,source, ALuint,id, ALsizei,length, const ALchar*,message) +FORCE_ALIGN void AL_APIENTRY alPushDebugGroupDirectEXT(ALCcontext *context, ALenum source, + ALuint id, ALsizei length, const ALchar *message) noexcept +try { + if(length < 0) + { + size_t newlen{std::strlen(message)}; + if(newlen >= MaxDebugMessageLength) + throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", newlen, + MaxDebugMessageLength}; + length = static_cast(newlen); + } + else if(length >= MaxDebugMessageLength) + throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%d >= %d)", length, + MaxDebugMessageLength}; + + auto dsource = GetDebugSource(source); + if(!dsource) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) + throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source}; + + std::unique_lock debuglock{context->mDebugCbLock}; + if(context->mDebugGroups.size() >= MaxDebugGroupDepth) + throw al::context_error{AL_STACK_OVERFLOW_EXT, "Pushing too many debug groups"}; + + context->mDebugGroups.emplace_back(*dsource, id, + std::string_view{message, static_cast(length)}); + auto &oldback = *(context->mDebugGroups.end()-2); + auto &newback = context->mDebugGroups.back(); + + newback.mFilters = oldback.mFilters; + newback.mIdFilters = oldback.mIdFilters; + + if(context->mContextFlags.test(ContextFlags::DebugBit)) + context->sendDebugMessage(debuglock, newback.mSource, DebugType::PushGroup, newback.mId, + DebugSeverity::Notification, newback.mMessage); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +FORCE_ALIGN DECL_FUNCEXT(void, alPopDebugGroup,EXT) +FORCE_ALIGN void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) noexcept +try { + std::unique_lock debuglock{context->mDebugCbLock}; + if(context->mDebugGroups.size() <= 1) + throw al::context_error{AL_STACK_UNDERFLOW_EXT, + "Attempting to pop the default debug group"}; + + DebugGroup &debug = context->mDebugGroups.back(); + const auto source = debug.mSource; + const auto id = debug.mId; + std::string message{std::move(debug.mMessage)}; + + context->mDebugGroups.pop_back(); + if(context->mContextFlags.test(ContextFlags::DebugBit)) + context->sendDebugMessage(debuglock, source, DebugType::PopGroup, id, + DebugSeverity::Notification, message); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +FORCE_ALIGN DECL_FUNCEXT8(ALuint, alGetDebugMessageLog,EXT, ALuint,count, ALsizei,logBufSize, ALenum*,sources, ALenum*,types, ALuint*,ids, ALenum*,severities, ALsizei*,lengths, ALchar*,logBuf) +FORCE_ALIGN ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context, ALuint count, + ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, + ALsizei *lengths, ALchar *logBuf) noexcept +try { + if(logBufSize < 0) + throw al::context_error{AL_INVALID_VALUE, "Negative debug log buffer size"}; + + auto sourcesOut = al::span{sources, sources ? count : 0u}; + auto typesOut = al::span{types, types ? count : 0u}; + auto idsOut = al::span{ids, ids ? count : 0u}; + auto severitiesOut = al::span{severities, severities ? count : 0u}; + auto lengthsOut = al::span{lengths, lengths ? count : 0u}; + auto logOut = al::span{logBuf, logBuf ? static_cast(logBufSize) : 0u}; + + std::lock_guard debuglock{context->mDebugCbLock}; + for(ALuint i{0};i < count;++i) + { + if(context->mDebugLog.empty()) + return i; + + auto &entry = context->mDebugLog.front(); + const size_t tocopy{entry.mMessage.size() + 1}; + if(logOut.data() != nullptr) + { + if(logOut.size() < tocopy) + return i; + auto oiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logOut.begin()); + *oiter = '\0'; + logOut = {oiter+1, logOut.end()}; + } + + if(!sourcesOut.empty()) + { + sourcesOut.front() = GetDebugSourceEnum(entry.mSource); + sourcesOut = sourcesOut.subspan<1>(); + } + if(!typesOut.empty()) + { + typesOut.front() = GetDebugTypeEnum(entry.mType); + typesOut = typesOut.subspan<1>(); + } + if(!idsOut.empty()) + { + idsOut.front() = entry.mId; + idsOut = idsOut.subspan<1>(); + } + if(!severitiesOut.empty()) + { + severitiesOut.front() = GetDebugSeverityEnum(entry.mSeverity); + severitiesOut = severitiesOut.subspan<1>(); + } + if(!lengthsOut.empty()) + { + lengthsOut.front() = static_cast(tocopy); + lengthsOut = lengthsOut.subspan<1>(); + } + + context->mDebugLog.pop_front(); + } + + return count; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); + return 0; +} + +FORCE_ALIGN DECL_FUNCEXT4(void, alObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,length, const ALchar*,label) +FORCE_ALIGN void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, + ALuint name, ALsizei length, const ALchar *label) noexcept +try { + if(!label && length != 0) + throw al::context_error{AL_INVALID_VALUE, "Null label pointer"}; + + auto objname = (length < 0) ? std::string_view{label} + : std::string_view{label, static_cast(length)}; + if(objname.size() >= MaxObjectLabelLength) + throw al::context_error{AL_INVALID_VALUE, "Object label length too long (%zu >= %d)", + objname.size(), MaxObjectLabelLength}; + + switch(identifier) + { + case AL_SOURCE_EXT: ALsource::SetName(context, name, objname); return; + case AL_BUFFER: ALbuffer::SetName(context, name, objname); return; + case AL_FILTER_EXT: ALfilter::SetName(context, name, objname); return; + case AL_EFFECT_EXT: ALeffect::SetName(context, name, objname); return; + case AL_AUXILIARY_EFFECT_SLOT_EXT: ALeffectslot::SetName(context, name, objname); return; + } + + throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label) +FORCE_ALIGN void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, + ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) noexcept +try { + if(bufSize < 0) + throw al::context_error{AL_INVALID_VALUE, "Negative label bufSize"}; + + if(!label && !length) + throw al::context_error{AL_INVALID_VALUE, "Null length and label"}; + if(label && bufSize == 0) + throw al::context_error{AL_INVALID_VALUE, "Zero label bufSize"}; + + const auto labelOut = al::span{label, label ? static_cast(bufSize) : 0u}; + auto copy_name = [name,length,labelOut](std::unordered_map &names) + { + std::string_view objname; + + auto iter = names.find(name); + if(iter != names.end()) + objname = iter->second; + + if(labelOut.empty()) + *length = static_cast(objname.size()); + else + { + const size_t tocopy{std::min(objname.size(), labelOut.size()-1)}; + auto oiter = std::copy_n(objname.cbegin(), tocopy, labelOut.begin()); + *oiter = '\0'; + if(length) + *length = static_cast(tocopy); + } + }; + + if(identifier == AL_SOURCE_EXT) + { + std::lock_guard srclock{context->mSourceLock}; + copy_name(context->mSourceNames); + } + else if(identifier == AL_BUFFER) + { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + copy_name(device->mBufferNames); + } + else if(identifier == AL_FILTER_EXT) + { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + copy_name(device->mFilterNames); + } + else if(identifier == AL_EFFECT_EXT) + { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard effectlock{device->EffectLock}; + copy_name(device->mEffectNames); + } + else if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT) + { + std::lock_guard slotlock{context->mEffectSlotLock}; + copy_name(context->mEffectSlotNames); + } + else + throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} diff --git a/Engine/lib/openal-soft/al/debug.h b/Engine/lib/openal-soft/al/debug.h new file mode 100644 index 000000000..d1792adb4 --- /dev/null +++ b/Engine/lib/openal-soft/al/debug.h @@ -0,0 +1,70 @@ +#ifndef AL_DEBUG_H +#define AL_DEBUG_H + +#include +#include +#include +#include + +using uint = unsigned int; + + +/* Somewhat arbitrary. Avoid letting it get out of control if the app enables + * logging but never reads it. + */ +inline constexpr std::uint8_t MaxDebugLoggedMessages{64}; +inline constexpr std::uint16_t MaxDebugMessageLength{1024}; +inline constexpr std::uint8_t MaxDebugGroupDepth{64}; +inline constexpr std::uint16_t MaxObjectLabelLength{1024}; + + +inline constexpr uint DebugSourceBase{0}; +enum class DebugSource : std::uint8_t { + API = 0, + System, + ThirdParty, + Application, + Other, +}; +inline constexpr uint DebugSourceCount{5}; + +inline constexpr uint DebugTypeBase{DebugSourceBase + DebugSourceCount}; +enum class DebugType : std::uint8_t { + Error = 0, + DeprecatedBehavior, + UndefinedBehavior, + Portability, + Performance, + Marker, + PushGroup, + PopGroup, + Other, +}; +inline constexpr uint DebugTypeCount{9}; + +inline constexpr uint DebugSeverityBase{DebugTypeBase + DebugTypeCount}; +enum class DebugSeverity : std::uint8_t { + High = 0, + Medium, + Low, + Notification, +}; +inline constexpr uint DebugSeverityCount{4}; + +struct DebugGroup { + const uint mId; + const DebugSource mSource; + std::string mMessage; + std::vector mFilters; + std::vector mIdFilters; + + template + DebugGroup(DebugSource source, uint id, T&& message) + : mId{id}, mSource{source}, mMessage{std::forward(message)} + { } + DebugGroup(const DebugGroup&) = default; + DebugGroup(DebugGroup&&) = default; + ~DebugGroup(); +}; + +#endif /* AL_DEBUG_H */ diff --git a/Engine/lib/openal-soft/al/direct_defs.h b/Engine/lib/openal-soft/al/direct_defs.h new file mode 100644 index 000000000..4119182f0 --- /dev/null +++ b/Engine/lib/openal-soft/al/direct_defs.h @@ -0,0 +1,127 @@ +#ifndef AL_DIRECT_DEFS_H +#define AL_DIRECT_DEFS_H + +namespace detail_ { + +template +constexpr T DefaultVal() noexcept { return T{}; } + +template<> +constexpr void DefaultVal() noexcept { } + +} // namespace detail_ + +#define DECL_FUNC(R, Name) \ +auto AL_APIENTRY Name() noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get()); \ +} + +#define DECL_FUNC1(R, Name, T1,n1) \ +auto AL_APIENTRY Name(T1 n1) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get(), n1); \ +} + +#define DECL_FUNC2(R, Name, T1,n1, T2,n2) \ +auto AL_APIENTRY Name(T1 n1, T2 n2) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get(), n1, n2); \ +} + +#define DECL_FUNC3(R, Name, T1,n1, T2,n2, T3,n3) \ +auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get(), n1, n2, n3); \ +} + +#define DECL_FUNC4(R, Name, T1,n1, T2,n2, T3,n3, T4,n4) \ +auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3, T4 n4) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get(), n1, n2, n3, n4); \ +} + +#define DECL_FUNC5(R, Name, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5) \ +auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get(), n1, n2, n3, n4, n5); \ +} + + +#define DECL_FUNCEXT(R, Name,Ext) \ +auto AL_APIENTRY Name##Ext() noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get()); \ +} + +#define DECL_FUNCEXT1(R, Name,Ext, T1,n1) \ +auto AL_APIENTRY Name##Ext(T1 n1) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1); \ +} + +#define DECL_FUNCEXT2(R, Name,Ext, T1,n1, T2,n2) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2); \ +} + +#define DECL_FUNCEXT3(R, Name,Ext, T1,n1, T2,n2, T3,n3) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2, n3); \ +} + +#define DECL_FUNCEXT4(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2, n3, n4); \ +} + +#define DECL_FUNCEXT5(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5); \ +} + +#define DECL_FUNCEXT6(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5, T6,n6) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5, T6 n6) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5, n6); \ +} + +#define DECL_FUNCEXT8(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5, T6,n6, T7,n7, T8,n8) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5, T6 n6, T7 n7, T8 n8) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5, n6, n7, n8); \ +} + +#endif /* AL_DIRECT_DEFS_H */ diff --git a/Engine/lib/openal-soft/al/eax/api.h b/Engine/lib/openal-soft/al/eax/api.h index d254da1f7..3e621d30f 100644 --- a/Engine/lib/openal-soft/al/eax/api.h +++ b/Engine/lib/openal-soft/al/eax/api.h @@ -10,34 +10,39 @@ // +#include #include #include #include - -#include +#include +#ifdef _WIN32 +#include +#endif #include "AL/al.h" -#ifndef GUID_DEFINED -#define GUID_DEFINED -typedef struct _GUID { +#ifndef _WIN32 +using GUID = struct _GUID { /* NOLINT(*-reserved-identifier) */ std::uint32_t Data1; std::uint16_t Data2; std::uint16_t Data3; - std::uint8_t Data4[8]; -} GUID; + std::array Data4; +}; -#ifndef _SYS_GUID_OPERATOR_EQ_ -#define _SYS_GUID_OPERATOR_EQ_ inline bool operator==(const GUID& lhs, const GUID& rhs) noexcept { return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0; } inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept { return !(lhs == rhs); } -#endif // _SYS_GUID_OPERATOR_EQ_ -#endif // GUID_DEFINED +#endif // _WIN32 +#define DECL_EQOP(T, ...) \ +[[nodiscard]] auto get_members() const noexcept { return std::forward_as_tuple(__VA_ARGS__); } \ +[[nodiscard]] friend bool operator==(const T &lhs, const T &rhs) noexcept \ +{ return lhs.get_members() == rhs.get_members(); } \ +[[nodiscard]] friend bool operator!=(const T &lhs, const T &rhs) noexcept \ +{ return !(lhs == rhs); } extern const GUID DSPROPSETID_EAX_ReverbProperties; @@ -276,11 +281,15 @@ struct EAXVECTOR { float x; float y; float z; + [[nodiscard]] + auto get_members() const noexcept { return std::forward_as_tuple(x, y, z); } }; // EAXVECTOR +[[nodiscard]] inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept -{ return std::memcmp(&lhs, &rhs, sizeof(EAXVECTOR)) == 0; } +{ return lhs.get_members() == rhs.get_members(); } +[[nodiscard]] inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept { return !(lhs == rhs); } @@ -361,6 +370,7 @@ constexpr auto EAXCONTEXT_MINMACROFXFACTOR = 0.0F; constexpr auto EAXCONTEXT_MAXMACROFXFACTOR = 1.0F; constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F; +constexpr auto EAXCONTEXT_DEFAULTLASTERROR = EAX_OK; extern const GUID EAXPROPERTYID_EAX40_FXSlot0; extern const GUID EAXPROPERTYID_EAX50_FXSlot0; @@ -613,7 +623,7 @@ struct EAX30SOURCEPROPERTIES { float flOcclusionLFRatio; // occlusion low-frequency level re. main control float flOcclusionRoomRatio; // relative occlusion control for room effect float flOcclusionDirectRatio; // relative occlusion control for direct path - long lExclusion; // main exlusion control (attenuation at high frequencies) + long lExclusion; // main exclusion control (attenuation at high frequencies) float flExclusionLFRatio; // exclusion low-frequency level re. main control long lOutsideVolumeHF; // outside sound cone level at high frequencies float flDopplerFactor; // like DS3D flDopplerFactor but per source @@ -653,11 +663,11 @@ struct EAXSPEAKERLEVELPROPERTIES { }; // EAXSPEAKERLEVELPROPERTIES struct EAX40ACTIVEFXSLOTS { - GUID guidActiveFXSlots[EAX40_MAX_ACTIVE_FXSLOTS]; + std::array guidActiveFXSlots; }; // EAX40ACTIVEFXSLOTS struct EAX50ACTIVEFXSLOTS { - GUID guidActiveFXSlots[EAX50_MAX_ACTIVE_FXSLOTS]; + std::array guidActiveFXSlots; }; // EAX50ACTIVEFXSLOTS // Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property. @@ -836,6 +846,11 @@ struct EAXREVERBPROPERTIES { float flLFReference; // reference low frequency float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect unsigned long ulFlags; // modifies the behavior of properties + DECL_EQOP(EAXREVERBPROPERTIES, ulEnvironment, flEnvironmentSize, flEnvironmentDiffusion, lRoom, + lRoomHF, lRoomLF, flDecayTime, flDecayHFRatio, flDecayLFRatio, lReflections, + flReflectionsDelay, vReflectionsPan, lReverb, flReverbDelay, vReverbPan, flEchoTime, + flEchoDepth, flModulationTime, flModulationDepth, flAirAbsorptionHF, flHFReference, + flLFReference, flRoomRolloffFactor, ulFlags) }; // EAXREVERBPROPERTIES @@ -965,6 +980,7 @@ enum EAXAGCCOMPRESSOR_PROPERTY : unsigned int { struct EAXAGCCOMPRESSORPROPERTIES { unsigned long ulOnOff; // Switch Compressor on or off + DECL_EQOP(EAXAGCCOMPRESSORPROPERTIES, ulOnOff) }; // EAXAGCCOMPRESSORPROPERTIES @@ -991,6 +1007,7 @@ struct EAXAUTOWAHPROPERTIES { float flReleaseTime; // Release time (seconds) long lResonance; // Resonance (mB) long lPeakLevel; // Peak level (mB) + DECL_EQOP(EAXAUTOWAHPROPERTIES, flAttackTime, flReleaseTime, lResonance, lPeakLevel) }; // EAXAUTOWAHPROPERTIES @@ -1038,6 +1055,7 @@ struct EAXCHORUSPROPERTIES { float flDepth; // Depth (0 to 1) float flFeedback; // Feedback (-1 to 1) float flDelay; // Delay (seconds) + DECL_EQOP(EAXCHORUSPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay) }; // EAXCHORUSPROPERTIES @@ -1086,6 +1104,7 @@ struct EAXDISTORTIONPROPERTIES { float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz) float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz) float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz) + DECL_EQOP(EAXDISTORTIONPROPERTIES, flEdge, lGain, flLowPassCutOff, flEQCenter, flEQBandwidth) }; // EAXDISTORTIONPROPERTIES @@ -1130,6 +1149,7 @@ struct EAXECHOPROPERTIES { float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1) float flFeedback; // Controls the duration of echo repetition (0 to 1) float flSpread; // Controls the left-right spread of the echoes + DECL_EQOP(EAXECHOPROPERTIES, flDelay, flLRDelay, flDamping, flFeedback, flSpread) }; // EAXECHOPROPERTIES @@ -1184,6 +1204,8 @@ struct EAXEQUALIZERPROPERTIES { float flMid2Width; // (octaves) long lHighGain; // (mB) float flHighCutOff; // (Hz) + DECL_EQOP(EAXEQUALIZERPROPERTIES, lLowGain, flLowCutOff, lMid1Gain, flMid1Center, flMid1Width, + lMid2Gain, flMid2Center, flMid2Width, lHighGain, flHighCutOff) }; // EAXEQUALIZERPROPERTIES @@ -1255,6 +1277,7 @@ struct EAXFLANGERPROPERTIES { float flDepth; // Depth (0 to 1) float flFeedback; // Feedback (0 to 1) float flDelay; // Delay (seconds) + DECL_EQOP(EAXFLANGERPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay) }; // EAXFLANGERPROPERTIES @@ -1305,6 +1328,7 @@ struct EAXFREQUENCYSHIFTERPROPERTIES { float flFrequency; // (Hz) unsigned long ulLeftDirection; // see enum above unsigned long ulRightDirection; // see enum above + DECL_EQOP(EAXFREQUENCYSHIFTERPROPERTIES, flFrequency, ulLeftDirection, ulRightDirection) }; // EAXFREQUENCYSHIFTERPROPERTIES @@ -1383,6 +1407,8 @@ struct EAXVOCALMORPHERPROPERTIES { long lPhonemeBCoarseTuning; // (semitones) unsigned long ulWaveform; // Waveform selector - see enum above float flRate; // (Hz) + DECL_EQOP(EAXVOCALMORPHERPROPERTIES, ulPhonemeA, lPhonemeACoarseTuning, ulPhonemeB, + lPhonemeBCoarseTuning, ulWaveform, flRate) }; // EAXVOCALMORPHERPROPERTIES @@ -1425,6 +1451,7 @@ enum EAXPITCHSHIFTER_PROPERTY : unsigned int { struct EAXPITCHSHIFTERPROPERTIES { long lCoarseTune; // Amount of pitch shift (semitones) long lFineTune; // Amount of pitch shift (cents) + DECL_EQOP(EAXPITCHSHIFTERPROPERTIES, lCoarseTune, lFineTune) }; // EAXPITCHSHIFTERPROPERTIES @@ -1460,6 +1487,7 @@ struct EAXRINGMODULATORPROPERTIES { float flFrequency; // Frequency of modulation (Hz) float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz) unsigned long ulWaveform; // Waveform selector - see enum above + DECL_EQOP(EAXRINGMODULATORPROPERTIES, flFrequency, flHighPassCutOff, ulWaveform) }; // EAXRINGMODULATORPROPERTIES @@ -1490,4 +1518,5 @@ using LPEAXGET = ALenum(AL_APIENTRY*)( ALvoid* property_buffer, ALuint property_size); +#undef DECL_EQOP #endif // !EAX_API_INCLUDED diff --git a/Engine/lib/openal-soft/al/eax/call.cpp b/Engine/lib/openal-soft/al/eax/call.cpp index 689d5cf1d..013a39928 100644 --- a/Engine/lib/openal-soft/al/eax/call.cpp +++ b/Engine/lib/openal-soft/al/eax/call.cpp @@ -22,8 +22,7 @@ EaxCall::EaxCall( ALuint property_source_id, ALvoid* property_buffer, ALuint property_size) - : mCallType{type}, mVersion{0}, mPropertySetId{EaxCallPropertySetId::none} - , mIsDeferred{(property_id & deferred_flag) != 0} + : mCallType{type}, mIsDeferred{(property_id & deferred_flag) != 0} , mPropertyId{property_id & ~deferred_flag}, mPropertySourceId{property_source_id} , mPropertyBuffer{property_buffer}, mPropertyBufferSize{property_size} { diff --git a/Engine/lib/openal-soft/al/eax/call.h b/Engine/lib/openal-soft/al/eax/call.h index 5ec33b0fa..72f96bbe4 100644 --- a/Engine/lib/openal-soft/al/eax/call.h +++ b/Engine/lib/openal-soft/al/eax/call.h @@ -31,16 +31,16 @@ public: ALvoid* property_buffer, ALuint property_size); - bool is_get() const noexcept { return mCallType == EaxCallType::get; } - bool is_deferred() const noexcept { return mIsDeferred; } - int get_version() const noexcept { return mVersion; } - EaxCallPropertySetId get_property_set_id() const noexcept { return mPropertySetId; } - ALuint get_property_id() const noexcept { return mPropertyId; } - ALuint get_property_al_name() const noexcept { return mPropertySourceId; } - EaxFxSlotIndex get_fx_slot_index() const noexcept { return mFxSlotIndex; } + [[nodiscard]] auto is_get() const noexcept -> bool { return mCallType == EaxCallType::get; } + [[nodiscard]] auto is_deferred() const noexcept -> bool { return mIsDeferred; } + [[nodiscard]] auto get_version() const noexcept -> int { return mVersion; } + [[nodiscard]] auto get_property_set_id() const noexcept -> EaxCallPropertySetId { return mPropertySetId; } + [[nodiscard]] auto get_property_id() const noexcept -> ALuint { return mPropertyId; } + [[nodiscard]] auto get_property_al_name() const noexcept -> ALuint { return mPropertySourceId; } + [[nodiscard]] auto get_fx_slot_index() const noexcept -> EaxFxSlotIndex { return mFxSlotIndex; } template - TValue& get_value() const + [[nodiscard]] auto get_value() const -> TValue& { if(mPropertyBufferSize < sizeof(TValue)) fail_too_small(); @@ -49,32 +49,32 @@ public: } template - al::span get_values(size_t max_count) const + [[nodiscard]] auto get_values(size_t max_count) const -> al::span { if(max_count == 0 || mPropertyBufferSize < sizeof(TValue)) fail_too_small(); - const auto count = minz(mPropertyBufferSize / sizeof(TValue), max_count); - return al::as_span(static_cast(mPropertyBuffer), count); + const auto count = std::min(mPropertyBufferSize/sizeof(TValue), max_count); + return {static_cast(mPropertyBuffer), count}; } template - al::span get_values() const + [[nodiscard]] auto get_values() const -> al::span { - return get_values(~size_t{}); + return get_values(~0_uz); } template - void set_value(const TValue& value) const + auto set_value(const TValue& value) const -> void { get_value() = value; } private: const EaxCallType mCallType; - int mVersion; - EaxFxSlotIndex mFxSlotIndex; - EaxCallPropertySetId mPropertySetId; + int mVersion{}; + EaxFxSlotIndex mFxSlotIndex{}; + EaxCallPropertySetId mPropertySetId{EaxCallPropertySetId::none}; bool mIsDeferred; const ALuint mPropertyId; diff --git a/Engine/lib/openal-soft/al/eax/effect.h b/Engine/lib/openal-soft/al/eax/effect.h index a0b4e71b2..9b1f8bd35 100644 --- a/Engine/lib/openal-soft/al/eax/effect.h +++ b/Engine/lib/openal-soft/al/eax/effect.h @@ -4,60 +4,56 @@ #include #include +#include #include "alnumeric.h" #include "AL/al.h" +#include "AL/alext.h" #include "core/effects/base.h" #include "call.h" -struct EaxEffectErrorMessages -{ +struct EaxEffectErrorMessages { static constexpr auto unknown_property_id() noexcept { return "Unknown property id."; } static constexpr auto unknown_version() noexcept { return "Unknown version."; } }; // EaxEffectErrorMessages -/* TODO: Use std::variant (C++17). */ -enum class EaxEffectType { - None, Reverb, Chorus, Autowah, Compressor, Distortion, Echo, Equalizer, Flanger, - FrequencyShifter, Modulator, PitchShifter, VocalMorpher -}; -struct EaxEffectProps { - EaxEffectType mType; - union { - EAXREVERBPROPERTIES mReverb; - EAXCHORUSPROPERTIES mChorus; - EAXAUTOWAHPROPERTIES mAutowah; - EAXAGCCOMPRESSORPROPERTIES mCompressor; - EAXDISTORTIONPROPERTIES mDistortion; - EAXECHOPROPERTIES mEcho; - EAXEQUALIZERPROPERTIES mEqualizer; - EAXFLANGERPROPERTIES mFlanger; - EAXFREQUENCYSHIFTERPROPERTIES mFrequencyShifter; - EAXRINGMODULATORPROPERTIES mModulator; - EAXPITCHSHIFTERPROPERTIES mPitchShifter; - EAXVOCALMORPHERPROPERTIES mVocalMorpher; - }; -}; +using EaxEffectProps = std::variant; + +template +struct overloaded : Ts... { using Ts::operator()...; }; + +template +overloaded(Ts...) -> overloaded; constexpr ALenum EnumFromEaxEffectType(const EaxEffectProps &props) { - switch(props.mType) - { - case EaxEffectType::None: break; - case EaxEffectType::Reverb: return AL_EFFECT_EAXREVERB; - case EaxEffectType::Chorus: return AL_EFFECT_CHORUS; - case EaxEffectType::Autowah: return AL_EFFECT_AUTOWAH; - case EaxEffectType::Compressor: return AL_EFFECT_COMPRESSOR; - case EaxEffectType::Distortion: return AL_EFFECT_DISTORTION; - case EaxEffectType::Echo: return AL_EFFECT_ECHO; - case EaxEffectType::Equalizer: return AL_EFFECT_EQUALIZER; - case EaxEffectType::Flanger: return AL_EFFECT_FLANGER; - case EaxEffectType::FrequencyShifter: return AL_EFFECT_FREQUENCY_SHIFTER; - case EaxEffectType::Modulator: return AL_EFFECT_RING_MODULATOR; - case EaxEffectType::PitchShifter: return AL_EFFECT_PITCH_SHIFTER; - case EaxEffectType::VocalMorpher: return AL_EFFECT_VOCAL_MORPHER; - } - return AL_EFFECT_NULL; + return std::visit(overloaded{ + [](const std::monostate&) noexcept { return AL_EFFECT_NULL; }, + [](const EAXREVERBPROPERTIES&) noexcept { return AL_EFFECT_EAXREVERB; }, + [](const EAXCHORUSPROPERTIES&) noexcept { return AL_EFFECT_CHORUS; }, + [](const EAXAUTOWAHPROPERTIES&) noexcept { return AL_EFFECT_AUTOWAH; }, + [](const EAXAGCCOMPRESSORPROPERTIES&) noexcept { return AL_EFFECT_COMPRESSOR; }, + [](const EAXDISTORTIONPROPERTIES&) noexcept { return AL_EFFECT_DISTORTION; }, + [](const EAXECHOPROPERTIES&) noexcept { return AL_EFFECT_ECHO; }, + [](const EAXEQUALIZERPROPERTIES&) noexcept { return AL_EFFECT_EQUALIZER; }, + [](const EAXFLANGERPROPERTIES&) noexcept { return AL_EFFECT_FLANGER; }, + [](const EAXFREQUENCYSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_FREQUENCY_SHIFTER; }, + [](const EAXRINGMODULATORPROPERTIES&) noexcept { return AL_EFFECT_RING_MODULATOR; }, + [](const EAXPITCHSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_PITCH_SHIFTER; }, + [](const EAXVOCALMORPHERPROPERTIES&) noexcept { return AL_EFFECT_VOCAL_MORPHER; } + }, props); } struct EaxReverbCommitter { @@ -105,7 +101,6 @@ struct EaxReverbCommitter { bool commit(const EAX_REVERBPROPERTIES &props); bool commit(const EAX20LISTENERPROPERTIES &props); bool commit(const EAXREVERBPROPERTIES &props); - bool commit(const EaxEffectProps &props); static void SetDefaults(EAX_REVERBPROPERTIES &props); static void SetDefaults(EAX20LISTENERPROPERTIES &props); @@ -115,16 +110,13 @@ struct EaxReverbCommitter { static void Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props); static void Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props); static void Get(const EaxCall &call, const EAXREVERBPROPERTIES &props); - static void Get(const EaxCall &call, const EaxEffectProps &props); static void Set(const EaxCall &call, EAX_REVERBPROPERTIES &props); static void Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props); static void Set(const EaxCall &call, EAXREVERBPROPERTIES &props); - static void Set(const EaxCall &call, EaxEffectProps &props); - static void translate(const EAX_REVERBPROPERTIES& src, EaxEffectProps& dst) noexcept; - static void translate(const EAX20LISTENERPROPERTIES& src, EaxEffectProps& dst) noexcept; - static void translate(const EAXREVERBPROPERTIES& src, EaxEffectProps& dst) noexcept; + static void translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept; + static void translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept; }; template @@ -149,51 +141,137 @@ struct EaxCommitter { [[noreturn]] static void fail(const char *message); [[noreturn]] static void fail_unknown_property_id() { fail(EaxEffectErrorMessages::unknown_property_id()); } - - bool commit(const EaxEffectProps &props); - - static void SetDefaults(EaxEffectProps &props); - static void Get(const EaxCall &call, const EaxEffectProps &props); - static void Set(const EaxCall &call, EaxEffectProps &props); }; struct EaxAutowahCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXAUTOWAHPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXAUTOWAHPROPERTIES &props); + static void Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props); }; struct EaxChorusCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXCHORUSPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props); + static void Set(const EaxCall &call, EAXCHORUSPROPERTIES &props); }; struct EaxCompressorCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXAGCCOMPRESSORPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props); + static void Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props); }; struct EaxDistortionCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXDISTORTIONPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props); + static void Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props); }; struct EaxEchoCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXECHOPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXECHOPROPERTIES &props); + static void Set(const EaxCall &call, EAXECHOPROPERTIES &props); }; struct EaxEqualizerCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXEQUALIZERPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props); + static void Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props); }; struct EaxFlangerCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXFLANGERPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props); + static void Set(const EaxCall &call, EAXFLANGERPROPERTIES &props); }; struct EaxFrequencyShifterCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXFREQUENCYSHIFTERPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXFREQUENCYSHIFTERPROPERTIES &props); + static void Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props); }; struct EaxModulatorCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXRINGMODULATORPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXRINGMODULATORPROPERTIES &props); + static void Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props); }; struct EaxPitchShifterCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXPITCHSHIFTERPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props); + static void Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props); }; struct EaxVocalMorpherCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXVOCALMORPHERPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props); + static void Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props); }; struct EaxNullCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const std::monostate &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const std::monostate &props); + static void Set(const EaxCall &call, std::monostate &props); }; +template +struct CommitterFromProps { }; + +template<> struct CommitterFromProps { using type = EaxNullCommitter; }; +template<> struct CommitterFromProps { using type = EaxReverbCommitter; }; +template<> struct CommitterFromProps { using type = EaxChorusCommitter; }; +template<> struct CommitterFromProps { using type = EaxCompressorCommitter; }; +template<> struct CommitterFromProps { using type = EaxAutowahCommitter; }; +template<> struct CommitterFromProps { using type = EaxDistortionCommitter; }; +template<> struct CommitterFromProps { using type = EaxEchoCommitter; }; +template<> struct CommitterFromProps { using type = EaxEqualizerCommitter; }; +template<> struct CommitterFromProps { using type = EaxFlangerCommitter; }; +template<> struct CommitterFromProps { using type = EaxFrequencyShifterCommitter; }; +template<> struct CommitterFromProps { using type = EaxModulatorCommitter; }; +template<> struct CommitterFromProps { using type = EaxPitchShifterCommitter; }; +template<> struct CommitterFromProps { using type = EaxVocalMorpherCommitter; }; + +template +using CommitterFor = typename CommitterFromProps>>::type; + class EaxEffect { public: @@ -238,51 +316,39 @@ public: State4 state5_{}; - template - void call_set_defaults(Args&& ...args) - { return T::SetDefaults(std::forward(args)...); } - - void call_set_defaults(const ALenum altype, EaxEffectProps &props) + static void call_set_defaults(const ALenum altype, EaxEffectProps &props) { - if(altype == AL_EFFECT_EAXREVERB) - return call_set_defaults(props); - if(altype == AL_EFFECT_CHORUS) - return call_set_defaults(props); - if(altype == AL_EFFECT_AUTOWAH) - return call_set_defaults(props); - if(altype == AL_EFFECT_COMPRESSOR) - return call_set_defaults(props); - if(altype == AL_EFFECT_DISTORTION) - return call_set_defaults(props); - if(altype == AL_EFFECT_ECHO) - return call_set_defaults(props); - if(altype == AL_EFFECT_EQUALIZER) - return call_set_defaults(props); - if(altype == AL_EFFECT_FLANGER) - return call_set_defaults(props); - if(altype == AL_EFFECT_FREQUENCY_SHIFTER) - return call_set_defaults(props); - if(altype == AL_EFFECT_RING_MODULATOR) - return call_set_defaults(props); - if(altype == AL_EFFECT_PITCH_SHIFTER) - return call_set_defaults(props); - if(altype == AL_EFFECT_VOCAL_MORPHER) - return call_set_defaults(props); - return call_set_defaults(props); + switch(altype) + { + case AL_EFFECT_EAXREVERB: return EaxReverbCommitter::SetDefaults(props); + case AL_EFFECT_CHORUS: return EaxChorusCommitter::SetDefaults(props); + case AL_EFFECT_AUTOWAH: return EaxAutowahCommitter::SetDefaults(props); + case AL_EFFECT_COMPRESSOR: return EaxCompressorCommitter::SetDefaults(props); + case AL_EFFECT_DISTORTION: return EaxDistortionCommitter::SetDefaults(props); + case AL_EFFECT_ECHO: return EaxEchoCommitter::SetDefaults(props); + case AL_EFFECT_EQUALIZER: return EaxEqualizerCommitter::SetDefaults(props); + case AL_EFFECT_FLANGER: return EaxFlangerCommitter::SetDefaults(props); + case AL_EFFECT_FREQUENCY_SHIFTER: return EaxFrequencyShifterCommitter::SetDefaults(props); + case AL_EFFECT_RING_MODULATOR: return EaxModulatorCommitter::SetDefaults(props); + case AL_EFFECT_PITCH_SHIFTER: return EaxPitchShifterCommitter::SetDefaults(props); + case AL_EFFECT_VOCAL_MORPHER: return EaxVocalMorpherCommitter::SetDefaults(props); + case AL_EFFECT_NULL: break; + } + return EaxNullCommitter::SetDefaults(props); } template void init() { - call_set_defaults(state1_.d); + EaxReverbCommitter::SetDefaults(state1_.d); state1_.i = state1_.d; - call_set_defaults(state2_.d); + EaxReverbCommitter::SetDefaults(state2_.d); state2_.i = state2_.d; - call_set_defaults(state3_.d); + EaxReverbCommitter::SetDefaults(state3_.d); state3_.i = state3_.d; - call_set_defaults(state4_.d); + T::SetDefaults(state4_.d); state4_.i = state4_.d; - call_set_defaults(state5_.d); + T::SetDefaults(state5_.d); state5_.i = state5_.d; } @@ -290,9 +356,9 @@ public: { switch(eax_version) { - case 1: call_set_defaults(state1_.d); break; - case 2: call_set_defaults(state2_.d); break; - case 3: call_set_defaults(state3_.d); break; + case 1: EaxReverbCommitter::SetDefaults(state1_.d); break; + case 2: EaxReverbCommitter::SetDefaults(state2_.d); break; + case 3: EaxReverbCommitter::SetDefaults(state3_.d); break; case 4: call_set_defaults(altype, state4_.d); break; case 5: call_set_defaults(altype, state5_.d); break; } @@ -300,47 +366,20 @@ public: } -#define EAXCALL(T, Callable, ...) \ - if(T == EaxEffectType::Reverb) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Chorus) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Autowah) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Compressor) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Distortion) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Echo) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Equalizer) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Flanger) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::FrequencyShifter) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Modulator) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::PitchShifter) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::VocalMorpher) \ - return Callable(__VA_ARGS__); \ - return Callable(__VA_ARGS__) - - template - static void call_set(Args&& ...args) - { return T::Set(std::forward(args)...); } - static void call_set(const EaxCall &call, EaxEffectProps &props) - { EAXCALL(props.mType, call_set, call, props); } + { + return std::visit([&](auto &arg) + { return CommitterFor::Set(call, arg); }, + props); + } void set(const EaxCall &call) { switch(call.get_version()) { - case 1: call_set(call, state1_.d); break; - case 2: call_set(call, state2_.d); break; - case 3: call_set(call, state3_.d); break; + case 1: EaxReverbCommitter::Set(call, state1_.d); break; + case 2: EaxReverbCommitter::Set(call, state2_.d); break; + case 3: EaxReverbCommitter::Set(call, state3_.d); break; case 4: call_set(call, state4_.d); break; case 5: call_set(call, state5_.d); break; } @@ -348,32 +387,32 @@ public: } - template - static void call_get(Args&& ...args) - { return T::Get(std::forward(args)...); } - static void call_get(const EaxCall &call, const EaxEffectProps &props) - { EAXCALL(props.mType, call_get, call, props); } + { + return std::visit([&](auto &arg) + { return CommitterFor::Get(call, arg); }, + props); + } - void get(const EaxCall &call) + void get(const EaxCall &call) const { switch(call.get_version()) { - case 1: call_get(call, state1_.d); break; - case 2: call_get(call, state2_.d); break; - case 3: call_get(call, state3_.d); break; + case 1: EaxReverbCommitter::Get(call, state1_.d); break; + case 2: EaxReverbCommitter::Get(call, state2_.d); break; + case 3: EaxReverbCommitter::Get(call, state3_.d); break; case 4: call_get(call, state4_.d); break; case 5: call_get(call, state5_.d); break; } } - template - bool call_commit(Args&& ...args) - { return T{props_, al_effect_props_}.commit(std::forward(args)...); } - bool call_commit(const EaxEffectProps &props) - { EAXCALL(props.mType, call_commit, props); } + { + return std::visit([&](auto &arg) + { return CommitterFor{props_, al_effect_props_}.commit(arg); }, + props); + } bool commit(int eax_version) { @@ -388,15 +427,15 @@ public: { case 1: state1_.i = state1_.d; - ret |= call_commit(state1_.d); + ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state1_.d); break; case 2: state2_.i = state2_.d; - ret |= call_commit(state2_.d); + ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state2_.d); break; case 3: state3_.i = state3_.d; - ret |= call_commit(state3_.d); + ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state3_.d); break; case 4: state4_.i = state4_.d; diff --git a/Engine/lib/openal-soft/al/eax/exception.cpp b/Engine/lib/openal-soft/al/eax/exception.cpp index 435e74426..e4945d88d 100644 --- a/Engine/lib/openal-soft/al/eax/exception.cpp +++ b/Engine/lib/openal-soft/al/eax/exception.cpp @@ -6,54 +6,27 @@ #include -EaxException::EaxException(const char *context, const char *message) +EaxException::EaxException(std::string_view context, std::string_view message) : std::runtime_error{make_message(context, message)} { } EaxException::~EaxException() = default; -std::string EaxException::make_message(const char *context, const char *message) +std::string EaxException::make_message(std::string_view context, std::string_view message) { - const auto context_size = (context ? std::string::traits_type::length(context) : 0); - const auto has_contex = (context_size > 0); - - const auto message_size = (message ? std::string::traits_type::length(message) : 0); - const auto has_message = (message_size > 0); - - if (!has_contex && !has_message) - { - return std::string{}; - } - - static constexpr char left_prefix[] = "["; - const auto left_prefix_size = std::string::traits_type::length(left_prefix); - - static constexpr char right_prefix[] = "] "; - const auto right_prefix_size = std::string::traits_type::length(right_prefix); - - const auto what_size = - ( - has_contex ? - left_prefix_size + context_size + right_prefix_size : - 0) + - message_size + - 1; - auto what = std::string{}; - what.reserve(what_size); + if(context.empty() && message.empty()) + return what; - if (has_contex) + what.reserve((!context.empty() ? context.size() + 3 : 0) + message.length() + 1); + if(!context.empty()) { - what.append(left_prefix, left_prefix_size); - what.append(context, context_size); - what.append(right_prefix, right_prefix_size); - } - - if (has_message) - { - what.append(message, message_size); + what += "["; + what += context; + what += "] "; } + what += message; return what; } diff --git a/Engine/lib/openal-soft/al/eax/exception.h b/Engine/lib/openal-soft/al/eax/exception.h index 3ae88cdc6..64cf7c49e 100644 --- a/Engine/lib/openal-soft/al/eax/exception.h +++ b/Engine/lib/openal-soft/al/eax/exception.h @@ -1,18 +1,23 @@ #ifndef EAX_EXCEPTION_INCLUDED #define EAX_EXCEPTION_INCLUDED - #include #include +#include class EaxException : public std::runtime_error { - static std::string make_message(const char *context, const char *message); + static std::string make_message(std::string_view context, std::string_view message); public: - EaxException(const char *context, const char *message); + EaxException() = delete; + EaxException(const EaxException&) = default; + EaxException(EaxException&&) = default; + EaxException(std::string_view context, std::string_view message); ~EaxException() override; -}; // EaxException + auto operator=(const EaxException&) -> EaxException& = default; + auto operator=(EaxException&&) -> EaxException& = default; +}; -#endif // !EAX_EXCEPTION_INCLUDED +#endif /* EAX_EXCEPTION_INCLUDED */ diff --git a/Engine/lib/openal-soft/al/eax/fx_slot_index.h b/Engine/lib/openal-soft/al/eax/fx_slot_index.h index 63dba0372..9f350d9b1 100644 --- a/Engine/lib/openal-soft/al/eax/fx_slot_index.h +++ b/Engine/lib/openal-soft/al/eax/fx_slot_index.h @@ -3,17 +3,16 @@ #include +#include -#include "aloptional.h" #include "api.h" using EaxFxSlotIndexValue = std::size_t; -class EaxFxSlotIndex : public al::optional -{ +class EaxFxSlotIndex : public std::optional { public: - using al::optional::optional; + using std::optional::optional; EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; } EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; } diff --git a/Engine/lib/openal-soft/al/eax/fx_slots.h b/Engine/lib/openal-soft/al/eax/fx_slots.h index 18b2d3ad4..d2d90b246 100644 --- a/Engine/lib/openal-soft/al/eax/fx_slots.h +++ b/Engine/lib/openal-soft/al/eax/fx_slots.h @@ -6,13 +6,10 @@ #include "al/auxeffectslot.h" -#include "api.h" -#include "call.h" #include "fx_slot_index.h" -class EaxFxSlots -{ +class EaxFxSlots { public: void initialize(ALCcontext& al_context); @@ -25,11 +22,9 @@ public: } - const ALeffectslot& get( - EaxFxSlotIndex index) const; + [[nodiscard]] auto get(EaxFxSlotIndex index) const -> const ALeffectslot&; - ALeffectslot& get( - EaxFxSlotIndex index); + [[nodiscard]] auto get(EaxFxSlotIndex index) -> ALeffectslot&; private: using Items = std::array; @@ -39,8 +34,7 @@ private: [[noreturn]] - static void fail( - const char* message); + static void fail(const char* message); void initialize_fx_slots(ALCcontext& al_context); }; // EaxFxSlots diff --git a/Engine/lib/openal-soft/al/eax/globals.cpp b/Engine/lib/openal-soft/al/eax/globals.cpp deleted file mode 100644 index 80e9dbfe5..000000000 --- a/Engine/lib/openal-soft/al/eax/globals.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "config.h" - -#include "globals.h" - - -bool eax_g_is_enabled = true; - - -const char eax1_ext_name[] = "EAX"; -const char eax2_ext_name[] = "EAX2.0"; -const char eax3_ext_name[] = "EAX3.0"; -const char eax4_ext_name[] = "EAX4.0"; -const char eax5_ext_name[] = "EAX5.0"; - -const char eax_x_ram_ext_name[] = "EAX-RAM"; - -const char eax_eax_set_func_name[] = "EAXSet"; -const char eax_eax_get_func_name[] = "EAXGet"; - -const char eax_eax_set_buffer_mode_func_name[] = "EAXSetBufferMode"; -const char eax_eax_get_buffer_mode_func_name[] = "EAXGetBufferMode"; diff --git a/Engine/lib/openal-soft/al/eax/globals.h b/Engine/lib/openal-soft/al/eax/globals.h index 1b4d63b85..6f3e9078f 100644 --- a/Engine/lib/openal-soft/al/eax/globals.h +++ b/Engine/lib/openal-soft/al/eax/globals.h @@ -1,22 +1,6 @@ #ifndef EAX_GLOBALS_INCLUDED #define EAX_GLOBALS_INCLUDED +inline bool eax_g_is_enabled{true}; -extern bool eax_g_is_enabled; - - -extern const char eax1_ext_name[]; -extern const char eax2_ext_name[]; -extern const char eax3_ext_name[]; -extern const char eax4_ext_name[]; -extern const char eax5_ext_name[]; - -extern const char eax_x_ram_ext_name[]; - -extern const char eax_eax_set_func_name[]; -extern const char eax_eax_get_func_name[]; - -extern const char eax_eax_set_buffer_mode_func_name[]; -extern const char eax_eax_get_buffer_mode_func_name[]; - -#endif // !EAX_GLOBALS_INCLUDED +#endif /* EAX_GLOBALS_INCLUDED */ diff --git a/Engine/lib/openal-soft/al/eax/utils.cpp b/Engine/lib/openal-soft/al/eax/utils.cpp index b3ed6ca14..871ab764b 100644 --- a/Engine/lib/openal-soft/al/eax/utils.cpp +++ b/Engine/lib/openal-soft/al/eax/utils.cpp @@ -5,10 +5,11 @@ #include #include +#include "alstring.h" #include "core/logging.h" -void eax_log_exception(const char *message) noexcept +void eax_log_exception(std::string_view message) noexcept { const auto exception_ptr = std::current_exception(); assert(exception_ptr); @@ -17,10 +18,9 @@ void eax_log_exception(const char *message) noexcept std::rethrow_exception(exception_ptr); } catch(const std::exception& ex) { - const auto ex_message = ex.what(); - ERR("%s %s\n", message ? message : "", ex_message); + ERR("%.*s %s\n", al::sizei(message), message.data(), ex.what()); } catch(...) { - ERR("%s %s\n", message ? message : "", "Generic exception."); + ERR("%.*s %s\n", al::sizei(message), message.data(), "Generic exception."); } } diff --git a/Engine/lib/openal-soft/al/eax/utils.h b/Engine/lib/openal-soft/al/eax/utils.h index 8ff75a18f..8e0f975f1 100644 --- a/Engine/lib/openal-soft/al/eax/utils.h +++ b/Engine/lib/openal-soft/al/eax/utils.h @@ -4,8 +4,11 @@ #include #include #include +#include #include +#include "opthelpers.h" + using EaxDirtyFlags = unsigned int; struct EaxAlLowPassParam { @@ -13,16 +16,13 @@ struct EaxAlLowPassParam { float gain_hf; }; -void eax_log_exception(const char *message) noexcept; +void eax_log_exception(std::string_view message) noexcept; template -void eax_validate_range( - const char* value_name, - const TValue& value, - const TValue& min_value, +void eax_validate_range(std::string_view value_name, const TValue& value, const TValue& min_value, const TValue& max_value) { - if (value >= min_value && value <= max_value) + if(value >= min_value && value <= max_value) LIKELY return; const auto message = diff --git a/Engine/lib/openal-soft/al/eax/x_ram.h b/Engine/lib/openal-soft/al/eax/x_ram.h index 438b99163..3616550d2 100644 --- a/Engine/lib/openal-soft/al/eax/x_ram.h +++ b/Engine/lib/openal-soft/al/eax/x_ram.h @@ -24,15 +24,7 @@ constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC"; constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE"; constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE"; - -ALboolean AL_APIENTRY EAXSetBufferMode( - ALsizei n, - const ALuint* buffers, - ALint value); - -ALenum AL_APIENTRY EAXGetBufferMode( - ALuint buffer, - ALint* pReserved); - +ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint *buffers, ALint value) noexcept; +ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint *pReserved) noexcept; #endif // !EAX_X_RAM_INCLUDED diff --git a/Engine/lib/openal-soft/al/effect.cpp b/Engine/lib/openal-soft/al/effect.cpp index bde89912a..6509d755d 100644 --- a/Engine/lib/openal-soft/al/effect.cpp +++ b/Engine/lib/openal-soft/al/effect.cpp @@ -28,9 +28,13 @@ #include #include #include -#include #include +#include +#include +#include #include +#include +#include #include "AL/al.h" #include "AL/alc.h" @@ -38,26 +42,23 @@ #include "AL/efx-presets.h" #include "AL/efx.h" +#include "al/effects/effects.h" #include "albit.h" #include "alc/context.h" #include "alc/device.h" -#include "alc/effects/base.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" +#include "alspan.h" #include "alstring.h" -#include "core/except.h" #include "core/logging.h" +#include "direct_defs.h" +#include "error.h" +#include "intrusive_ptr.h" #include "opthelpers.h" -#include "vector.h" -#ifdef ALSOFT_EAX -#include -#include "eax/exception.h" -#endif // ALSOFT_EAX - -const EffectList gEffectList[16]{ +const std::array gEffectList{{ { "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB }, { "reverb", REVERB_EFFECT, AL_EFFECT_REVERB }, { "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH }, @@ -73,95 +74,74 @@ const EffectList gEffectList[16]{ { "vmorpher", VMORPHER_EFFECT, AL_EFFECT_VOCAL_MORPHER }, { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT }, { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE }, - { "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_REVERB_SOFT }, -}; + { "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_SOFT }, +}}; -bool DisabledEffects[MAX_EFFECTS]; - - -effect_exception::effect_exception(ALenum code, const char *msg, ...) : mErrorCode{code} -{ - std::va_list args; - va_start(args, msg); - setMessage(msg, args); - va_end(args); -} -effect_exception::~effect_exception() = default; namespace { -struct EffectPropsItem { - ALenum Type; - const EffectProps &DefaultProps; - const EffectVtable &Vtable; -}; -constexpr EffectPropsItem EffectPropsList[] = { - { AL_EFFECT_NULL, NullEffectProps, NullEffectVtable }, - { AL_EFFECT_EAXREVERB, ReverbEffectProps, ReverbEffectVtable }, - { AL_EFFECT_REVERB, StdReverbEffectProps, StdReverbEffectVtable }, - { AL_EFFECT_AUTOWAH, AutowahEffectProps, AutowahEffectVtable }, - { AL_EFFECT_CHORUS, ChorusEffectProps, ChorusEffectVtable }, - { AL_EFFECT_COMPRESSOR, CompressorEffectProps, CompressorEffectVtable }, - { AL_EFFECT_DISTORTION, DistortionEffectProps, DistortionEffectVtable }, - { AL_EFFECT_ECHO, EchoEffectProps, EchoEffectVtable }, - { AL_EFFECT_EQUALIZER, EqualizerEffectProps, EqualizerEffectVtable }, - { AL_EFFECT_FLANGER, FlangerEffectProps, FlangerEffectVtable }, - { AL_EFFECT_FREQUENCY_SHIFTER, FshifterEffectProps, FshifterEffectVtable }, - { AL_EFFECT_RING_MODULATOR, ModulatorEffectProps, ModulatorEffectVtable }, - { AL_EFFECT_PITCH_SHIFTER, PshifterEffectProps, PshifterEffectVtable }, - { AL_EFFECT_VOCAL_MORPHER, VmorpherEffectProps, VmorpherEffectVtable }, - { AL_EFFECT_DEDICATED_DIALOGUE, DedicatedEffectProps, DedicatedEffectVtable }, - { AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedEffectProps, DedicatedEffectVtable }, - { AL_EFFECT_CONVOLUTION_REVERB_SOFT, ConvolutionEffectProps, ConvolutionEffectVtable }, -}; +using SubListAllocator = al::allocator>; - -void ALeffect_setParami(ALeffect *effect, ALenum param, int value) -{ effect->vtab->setParami(&effect->Props, param, value); } -void ALeffect_setParamiv(ALeffect *effect, ALenum param, const int *values) -{ effect->vtab->setParamiv(&effect->Props, param, values); } -void ALeffect_setParamf(ALeffect *effect, ALenum param, float value) -{ effect->vtab->setParamf(&effect->Props, param, value); } -void ALeffect_setParamfv(ALeffect *effect, ALenum param, const float *values) -{ effect->vtab->setParamfv(&effect->Props, param, values); } - -void ALeffect_getParami(const ALeffect *effect, ALenum param, int *value) -{ effect->vtab->getParami(&effect->Props, param, value); } -void ALeffect_getParamiv(const ALeffect *effect, ALenum param, int *values) -{ effect->vtab->getParamiv(&effect->Props, param, values); } -void ALeffect_getParamf(const ALeffect *effect, ALenum param, float *value) -{ effect->vtab->getParamf(&effect->Props, param, value); } -void ALeffect_getParamfv(const ALeffect *effect, ALenum param, float *values) -{ effect->vtab->getParamfv(&effect->Props, param, values); } - - -const EffectPropsItem *getEffectPropsItemByType(ALenum type) +constexpr auto GetDefaultProps(ALenum type) noexcept -> const EffectProps& { - auto iter = std::find_if(std::begin(EffectPropsList), std::end(EffectPropsList), - [type](const EffectPropsItem &item) noexcept -> bool - { return item.Type == type; }); - return (iter != std::end(EffectPropsList)) ? al::to_address(iter) : nullptr; + switch(type) + { + case AL_EFFECT_NULL: return NullEffectProps; + case AL_EFFECT_EAXREVERB: return ReverbEffectProps; + case AL_EFFECT_REVERB: return StdReverbEffectProps; + case AL_EFFECT_AUTOWAH: return AutowahEffectProps; + case AL_EFFECT_CHORUS: return ChorusEffectProps; + case AL_EFFECT_COMPRESSOR: return CompressorEffectProps; + case AL_EFFECT_DISTORTION: return DistortionEffectProps; + case AL_EFFECT_ECHO: return EchoEffectProps; + case AL_EFFECT_EQUALIZER: return EqualizerEffectProps; + case AL_EFFECT_FLANGER: return FlangerEffectProps; + case AL_EFFECT_FREQUENCY_SHIFTER: return FshifterEffectProps; + case AL_EFFECT_RING_MODULATOR: return ModulatorEffectProps; + case AL_EFFECT_PITCH_SHIFTER: return PshifterEffectProps; + case AL_EFFECT_VOCAL_MORPHER: return VmorpherEffectProps; + case AL_EFFECT_DEDICATED_DIALOGUE: return DedicatedDialogEffectProps; + case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return DedicatedLfeEffectProps; + case AL_EFFECT_CONVOLUTION_SOFT: return ConvolutionEffectProps; + } + return NullEffectProps; } -void InitEffectParams(ALeffect *effect, ALenum type) +void InitEffectParams(ALeffect *effect, ALenum type) noexcept { - const EffectPropsItem *item{getEffectPropsItemByType(type)}; - if(item) + switch(type) { - effect->Props = item->DefaultProps; - effect->vtab = &item->Vtable; - } - else - { - effect->Props = EffectProps{}; - effect->vtab = &NullEffectVtable; + case AL_EFFECT_NULL: effect->PropsVariant.emplace(); break; + case AL_EFFECT_EAXREVERB: effect->PropsVariant.emplace(); break; + case AL_EFFECT_REVERB: effect->PropsVariant.emplace(); break; + case AL_EFFECT_AUTOWAH: effect->PropsVariant.emplace(); break; + case AL_EFFECT_CHORUS: effect->PropsVariant.emplace(); break; + case AL_EFFECT_COMPRESSOR: effect->PropsVariant.emplace(); break; + case AL_EFFECT_DISTORTION: effect->PropsVariant.emplace(); break; + case AL_EFFECT_ECHO: effect->PropsVariant.emplace(); break; + case AL_EFFECT_EQUALIZER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_FLANGER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_FREQUENCY_SHIFTER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_RING_MODULATOR: effect->PropsVariant.emplace(); break; + case AL_EFFECT_PITCH_SHIFTER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_VOCAL_MORPHER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_DEDICATED_DIALOGUE: + effect->PropsVariant.emplace(); + break; + case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: + effect->PropsVariant.emplace(); + break; + case AL_EFFECT_CONVOLUTION_SOFT: + effect->PropsVariant.emplace(); + break; } + effect->Props = GetDefaultProps(type); effect->type = type; } -bool EnsureEffects(ALCdevice *device, size_t needed) -{ - size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), size_t{0}, +auto EnsureEffects(ALCdevice *device, size_t needed) noexcept -> bool +try { + size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz, [](size_t cur, const EffectSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; @@ -170,21 +150,19 @@ bool EnsureEffects(ALCdevice *device, size_t needed) if(device->EffectList.size() >= 1<<25) UNLIKELY return false; - device->EffectList.emplace_back(); - auto sublist = device->EffectList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Effects = static_cast(al_calloc(alignof(ALeffect), sizeof(ALeffect)*64)); - if(!sublist->Effects) UNLIKELY - { - device->EffectList.pop_back(); - return false; - } - count += 64; + EffectSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.Effects = SubListAllocator{}.allocate(1); + device->EffectList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } return true; } +catch(...) { + return false; +} -ALeffect *AllocEffect(ALCdevice *device) +ALeffect *AllocEffect(ALCdevice *device) noexcept { auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(), [](const EffectSubList &entry) noexcept -> bool @@ -193,7 +171,7 @@ ALeffect *AllocEffect(ALCdevice *device) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALeffect *effect{al::construct_at(sublist->Effects + slidx)}; + ALeffect *effect{al::construct_at(al::to_address(sublist->Effects->begin() + slidx))}; InitEffectParams(effect, AL_EFFECT_NULL); /* Add 1 to avoid effect ID 0. */ @@ -206,16 +184,18 @@ ALeffect *AllocEffect(ALCdevice *device) void FreeEffect(ALCdevice *device, ALeffect *effect) { + device->mEffectNames.erase(effect->id); + const ALuint id{effect->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; - al::destroy_at(effect); + std::destroy_at(effect); device->EffectList[lidx].FreeMask |= 1_u64 << slidx; } -inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) +inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -225,320 +205,294 @@ inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) EffectSubList &sublist = device->EffectList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Effects + slidx; + return al::to_address(sublist.Effects->begin() + slidx); } } // namespace -AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Generating %d effects", n); +AL_API DECL_FUNC2(void, alGenEffects, ALsizei,n, ALuint*,effects) +FORCE_ALIGN void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Generating %d effects", n}; if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; - if(!EnsureEffects(device, static_cast(n))) - { - context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, (n==1)?"":"s"); - return; - } + std::lock_guard effectlock{device->EffectLock}; - if(n == 1) LIKELY - { - /* 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 ids; - ids.reserve(static_cast(n)); - do { - ALeffect *effect{AllocEffect(device)}; - ids.emplace_back(effect->id); - } while(--n); - std::copy(ids.cbegin(), ids.cend(), effects); - } + const al::span eids{effects, static_cast(n)}; + if(!EnsureEffects(device, eids.size())) + throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, + (n == 1) ? "" : "s"}; + + std::generate(eids.begin(), eids.end(), [device]{ return AllocEffect(device)->id; }); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Deleting %d effects", n); +AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects) +FORCE_ALIGN void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei n, + const ALuint *effects) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Deleting %d effects", n}; if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; /* First try to find any effects that are invalid. */ auto validate_effect = [device](const ALuint eid) -> bool { return !eid || LookupEffect(device, eid) != nullptr; }; - const ALuint *effects_end = effects + n; - auto inveffect = std::find_if_not(effects, effects_end, validate_effect); - if(inveffect != effects_end) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", *inveffect); - return; - } + const al::span eids{effects, static_cast(n)}; + auto inveffect = std::find_if_not(eids.begin(), eids.end(), validate_effect); + if(inveffect != eids.end()) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", *inveffect}; /* All good. Delete non-0 effect IDs. */ auto delete_effect = [device](ALuint eid) -> void { - ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr}; - if(effect) FreeEffect(device, effect); + if(ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr}) + FreeEffect(device, effect); }; - std::for_each(effects, effects_end, delete_effect); + std::for_each(eids.begin(), eids.end(), delete_effect); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsEffect, ALuint,effect) +FORCE_ALIGN ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) noexcept { - ContextRef context{GetContextRef()}; - if(context) LIKELY - { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; - if(!effect || LookupEffect(device, effect)) - return AL_TRUE; - } + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard effectlock{device->EffectLock}; + if(!effect || LookupEffect(device, effect)) + return AL_TRUE; return AL_FALSE; } -END_API_FUNC - -AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alEffecti, ALuint,effect, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, + ALint value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else if(param == AL_EFFECT_TYPE) + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + switch(param) { - bool isOk{value == AL_EFFECT_NULL}; - if(!isOk) + case AL_EFFECT_TYPE: + if(value != AL_EFFECT_NULL) { - for(const EffectList &effectitem : gEffectList) - { - if(value == effectitem.val && !DisabledEffects[effectitem.type]) - { - isOk = true; - break; - } - } + auto check_effect = [value](const EffectList &item) -> bool + { return value == item.val && !DisabledEffects.test(item.type); }; + if(!std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect)) + throw al::context_error{AL_INVALID_VALUE, "Effect type 0x%04x not supported", + value}; } - if(isOk) - InitEffectParams(aleffect, value); - else - context->setError(AL_INVALID_VALUE, "Effect type 0x%04x not supported", value); - } - else try - { - /* Call the appropriate handler */ - ALeffect_setParami(aleffect, param, value); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECT_TYPE: - alEffecti(effect, param, values[0]); + InitEffectParams(aleffect, value); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) + { + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.SetParami(std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alEffectiv, ALuint,effect, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, + const ALint *values) noexcept +try { + switch(param) + { + case AL_EFFECT_TYPE: + alEffectiDirect(context, effect, param, *values); + return; + } ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; + + ALeffect *aleffect{LookupEffect(device, effect)}; + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) + { + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.SetParamiv(std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alEffectf, ALuint,effect, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, + ALfloat value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard effectlock{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) { - /* Call the appropriate handler */ - ALeffect_setParamiv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.SetParamf(std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alEffectfv, ALuint,effect, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, + const ALfloat *values) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) { - /* Call the appropriate handler */ - ALeffect_setParamf(aleffect, param, value); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.SetParamfv(std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); } -END_API_FUNC - -AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; - - ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try - { - /* Call the appropriate handler */ - ALeffect_setParamfv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alGetEffecti, ALuint,effect, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, + ALint *value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else if(param == AL_EFFECT_TYPE) + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + switch(param) + { + case AL_EFFECT_TYPE: *value = aleffect->type; - else try - { - /* Call the appropriate handler */ - ALeffect_getParami(aleffect, param, value); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECT_TYPE: - alGetEffecti(effect, param, values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) + { + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.GetParami(std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetEffectiv, ALuint,effect, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, + ALint *values) noexcept +try { + switch(param) + { + case AL_EFFECT_TYPE: + alGetEffectiDirect(context, effect, param, values); + return; + } ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) { - /* Call the appropriate handler */ - ALeffect_getParamiv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.GetParamiv(std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alGetEffectf, ALuint,effect, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, + ALfloat *value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) { - /* Call the appropriate handler */ - ALeffect_getParamf(aleffect, param, value); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.GetParamf(std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alGetEffectfv, ALuint,effect, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, + ALfloat *values) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) { - /* Call the appropriate handler */ - ALeffect_getParamfv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.GetParamfv(std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC void InitEffect(ALeffect *effect) @@ -546,26 +500,43 @@ void InitEffect(ALeffect *effect) InitEffectParams(effect, AL_EFFECT_NULL); } +void ALeffect::SetName(ALCcontext* context, ALuint id, std::string_view name) +{ + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard effectlock{device->EffectLock}; + + auto effect = LookupEffect(device, id); + if(!effect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", id}; + + device->mEffectNames.insert_or_assign(id, name); +} + + EffectSubList::~EffectSubList() { + if(!Effects) + return; + uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; - al::destroy_at(Effects+idx); + std::destroy_at(al::to_address(Effects->begin()+idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; - al_free(Effects); + SubListAllocator{}.deallocate(Effects, 1); Effects = nullptr; } -#define DECL(x) { #x, EFX_REVERB_PRESET_##x } -static const struct { - const char name[32]; +struct EffectPreset { + const char name[32]; /* NOLINT(*-avoid-c-arrays) */ EFXEAXREVERBPROPERTIES props; -} reverblist[] = { +}; +#define DECL(x) EffectPreset{#x, EFX_REVERB_PRESET_##x} +static constexpr std::array reverblist{ DECL(GENERIC), DECL(PADDEDCELL), DECL(ROOM), @@ -695,61 +666,62 @@ static const struct { }; #undef DECL -void LoadReverbPreset(const char *name, ALeffect *effect) +void LoadReverbPreset(const std::string_view name, ALeffect *effect) { - if(al::strcasecmp(name, "NONE") == 0) + using namespace std::string_view_literals; + + if(al::case_compare(name, "NONE"sv) == 0) { InitEffectParams(effect, AL_EFFECT_NULL); TRACE("Loading reverb '%s'\n", "NONE"); return; } - if(!DisabledEffects[EAXREVERB_EFFECT]) + if(!DisabledEffects.test(EAXREVERB_EFFECT)) InitEffectParams(effect, AL_EFFECT_EAXREVERB); - else if(!DisabledEffects[REVERB_EFFECT]) + else if(!DisabledEffects.test(REVERB_EFFECT)) InitEffectParams(effect, AL_EFFECT_REVERB); else InitEffectParams(effect, AL_EFFECT_NULL); for(const auto &reverbitem : reverblist) { - const EFXEAXREVERBPROPERTIES *props; - - if(al::strcasecmp(name, reverbitem.name) != 0) + if(al::case_compare(name, std::data(reverbitem.name)) != 0) continue; - TRACE("Loading reverb '%s'\n", reverbitem.name); - props = &reverbitem.props; - effect->Props.Reverb.Density = props->flDensity; - effect->Props.Reverb.Diffusion = props->flDiffusion; - effect->Props.Reverb.Gain = props->flGain; - effect->Props.Reverb.GainHF = props->flGainHF; - effect->Props.Reverb.GainLF = props->flGainLF; - effect->Props.Reverb.DecayTime = props->flDecayTime; - effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio; - effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio; - effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain; - effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay; - effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0]; - effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1]; - effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2]; - effect->Props.Reverb.LateReverbGain = props->flLateReverbGain; - effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay; - effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0]; - effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1]; - effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2]; - effect->Props.Reverb.EchoTime = props->flEchoTime; - effect->Props.Reverb.EchoDepth = props->flEchoDepth; - effect->Props.Reverb.ModulationTime = props->flModulationTime; - effect->Props.Reverb.ModulationDepth = props->flModulationDepth; - effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF; - effect->Props.Reverb.HFReference = props->flHFReference; - effect->Props.Reverb.LFReference = props->flLFReference; - effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor; - effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit ? AL_TRUE : AL_FALSE; + TRACE("Loading reverb '%s'\n", std::data(reverbitem.name)); + const auto &props = reverbitem.props; + auto &dst = std::get(effect->Props); + dst.Density = props.flDensity; + dst.Diffusion = props.flDiffusion; + dst.Gain = props.flGain; + dst.GainHF = props.flGainHF; + dst.GainLF = props.flGainLF; + dst.DecayTime = props.flDecayTime; + dst.DecayHFRatio = props.flDecayHFRatio; + dst.DecayLFRatio = props.flDecayLFRatio; + dst.ReflectionsGain = props.flReflectionsGain; + dst.ReflectionsDelay = props.flReflectionsDelay; + dst.ReflectionsPan[0] = props.flReflectionsPan[0]; + dst.ReflectionsPan[1] = props.flReflectionsPan[1]; + dst.ReflectionsPan[2] = props.flReflectionsPan[2]; + dst.LateReverbGain = props.flLateReverbGain; + dst.LateReverbDelay = props.flLateReverbDelay; + dst.LateReverbPan[0] = props.flLateReverbPan[0]; + dst.LateReverbPan[1] = props.flLateReverbPan[1]; + dst.LateReverbPan[2] = props.flLateReverbPan[2]; + dst.EchoTime = props.flEchoTime; + dst.EchoDepth = props.flEchoDepth; + dst.ModulationTime = props.flModulationTime; + dst.ModulationDepth = props.flModulationDepth; + dst.AirAbsorptionGainHF = props.flAirAbsorptionGainHF; + dst.HFReference = props.flHFReference; + dst.LFReference = props.flLFReference; + dst.RoomRolloffFactor = props.flRoomRolloffFactor; + dst.DecayHFLimit = props.iDecayHFLimit ? AL_TRUE : AL_FALSE; return; } - WARN("Reverb preset '%s' not found\n", name); + WARN("Reverb preset '%.*s' not found\n", al::sizei(name), name.data()); } bool IsValidEffectType(ALenum type) noexcept @@ -757,10 +729,7 @@ bool IsValidEffectType(ALenum type) noexcept if(type == AL_EFFECT_NULL) return true; - for(const auto &effect_item : gEffectList) - { - if(type == effect_item.val && !DisabledEffects[effect_item.type]) - return true; - } - return false; + auto check_effect = [type](const EffectList &item) noexcept -> bool + { return type == item.val && !DisabledEffects.test(item.type); }; + return std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect); } diff --git a/Engine/lib/openal-soft/al/effect.h b/Engine/lib/openal-soft/al/effect.h index a1d433133..842566578 100644 --- a/Engine/lib/openal-soft/al/effect.h +++ b/Engine/lib/openal-soft/al/effect.h @@ -1,11 +1,20 @@ #ifndef AL_EFFECT_H #define AL_EFFECT_H +#include +#include +#include +#include +#include + #include "AL/al.h" +#include "AL/alc.h" #include "AL/efx.h" -#include "al/effects/effects.h" -#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "core/effects/base.h" +#include "effects/effects.h" enum { @@ -27,36 +36,55 @@ enum { MAX_EFFECTS }; -extern bool DisabledEffects[MAX_EFFECTS]; - -extern float ReverbBoost; +inline std::bitset DisabledEffects; struct EffectList { - const char name[16]; - int type; + const char name[16]; /* NOLINT(*-avoid-c-arrays) */ + ALuint type; ALenum val; }; -extern const EffectList gEffectList[16]; +extern const std::array gEffectList; +using EffectHandlerVariant = std::variant; struct ALeffect { // Effect type (AL_EFFECT_NULL, ...) ALenum type{AL_EFFECT_NULL}; + EffectHandlerVariant PropsVariant; EffectProps Props{}; - const EffectVtable *vtab{nullptr}; - /* Self ID */ ALuint id{0u}; - DISABLE_ALLOC() + static void SetName(ALCcontext *context, ALuint id, std::string_view name); + + DISABLE_ALLOC }; void InitEffect(ALeffect *effect); -void LoadReverbPreset(const char *name, ALeffect *effect); +void LoadReverbPreset(const std::string_view name, ALeffect *effect); bool IsValidEffectType(ALenum type) noexcept; +struct EffectSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> Effects{nullptr}; /* 64 */ + + EffectSubList() noexcept = default; + EffectSubList(const EffectSubList&) = delete; + EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} + { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } + ~EffectSubList(); + + EffectSubList& operator=(const EffectSubList&) = delete; + EffectSubList& operator=(EffectSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } +}; + #endif diff --git a/Engine/lib/openal-soft/al/effects/autowah.cpp b/Engine/lib/openal-soft/al/effects/autowah.cpp index 129318f4e..fe52b8be4 100644 --- a/Engine/lib/openal-soft/al/effects/autowah.cpp +++ b/Engine/lib/openal-soft/al/effects/autowah.cpp @@ -13,6 +13,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -20,100 +21,87 @@ namespace { -void Autowah_setParamf(EffectProps *props, ALenum param, float val) +constexpr EffectProps genDefaultProps() noexcept +{ + AutowahProps props{}; + props.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME; + props.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME; + props.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE; + props.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN; + return props; +} + +} // namespace + +const EffectProps AutowahEffectProps{genDefaultProps()}; + +void AutowahEffectHandler::SetParami(AutowahProps&, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } +void AutowahEffectHandler::SetParamiv(AutowahProps&, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", + param}; +} + +void AutowahEffectHandler::SetParamf(AutowahProps &props, ALenum param, float val) { switch(param) { case AL_AUTOWAH_ATTACK_TIME: if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) throw effect_exception{AL_INVALID_VALUE, "Autowah attack time out of range"}; - props->Autowah.AttackTime = val; + props.AttackTime = val; break; case AL_AUTOWAH_RELEASE_TIME: if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) throw effect_exception{AL_INVALID_VALUE, "Autowah release time out of range"}; - props->Autowah.ReleaseTime = val; + props.ReleaseTime = val; break; case AL_AUTOWAH_RESONANCE: if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) throw effect_exception{AL_INVALID_VALUE, "Autowah resonance out of range"}; - props->Autowah.Resonance = val; + props.Resonance = val; break; case AL_AUTOWAH_PEAK_GAIN: if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Autowah peak gain out of range"}; - props->Autowah.PeakGain = val; + props.PeakGain = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; } } -void Autowah_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Autowah_setParamf(props, param, vals[0]); } +void AutowahEffectHandler::SetParamfv(AutowahProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Autowah_setParami(EffectProps*, ALenum param, int) +void AutowahEffectHandler::GetParami(const AutowahProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } -void Autowah_setParamiv(EffectProps*, ALenum param, const int*) +void AutowahEffectHandler::GetParamiv(const AutowahProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param}; } -void Autowah_getParamf(const EffectProps *props, ALenum param, float *val) +void AutowahEffectHandler::GetParamf(const AutowahProps &props, ALenum param, float *val) { switch(param) { - case AL_AUTOWAH_ATTACK_TIME: - *val = props->Autowah.AttackTime; - break; - - case AL_AUTOWAH_RELEASE_TIME: - *val = props->Autowah.ReleaseTime; - break; - - case AL_AUTOWAH_RESONANCE: - *val = props->Autowah.Resonance; - break; - - case AL_AUTOWAH_PEAK_GAIN: - *val = props->Autowah.PeakGain; - break; + case AL_AUTOWAH_ATTACK_TIME: *val = props.AttackTime; break; + case AL_AUTOWAH_RELEASE_TIME: *val = props.ReleaseTime; break; + case AL_AUTOWAH_RESONANCE: *val = props.Resonance; break; + case AL_AUTOWAH_PEAK_GAIN: *val = props.PeakGain; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; } } -void Autowah_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Autowah_getParamf(props, param, vals); } - -void Autowah_getParami(const EffectProps*, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } -void Autowah_getParamiv(const EffectProps*, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", - param}; -} - -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME; - props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME; - props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE; - props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Autowah); - -const EffectProps AutowahEffectProps{genDefaultProps()}; +void AutowahEffectHandler::GetParamfv(const AutowahProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } #ifdef ALSOFT_EAX namespace { @@ -189,62 +177,62 @@ template<> throw Exception{message}; } -template<> -bool AutowahCommitter::commit(const EaxEffectProps &props) +bool EaxAutowahCommitter::commit(const EAXAUTOWAHPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && mEaxProps.mAutowah.flAttackTime == props.mAutowah.flAttackTime - && mEaxProps.mAutowah.flReleaseTime == props.mAutowah.flReleaseTime - && mEaxProps.mAutowah.lResonance == props.mAutowah.lResonance - && mEaxProps.mAutowah.lPeakLevel == props.mAutowah.lPeakLevel) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - - mAlProps.Autowah.AttackTime = props.mAutowah.flAttackTime; - mAlProps.Autowah.ReleaseTime = props.mAutowah.flReleaseTime; - mAlProps.Autowah.Resonance = level_mb_to_gain(static_cast(props.mAutowah.lResonance)); - mAlProps.Autowah.PeakGain = level_mb_to_gain(static_cast(props.mAutowah.lPeakLevel)); + mAlProps = [&]{ + AutowahProps ret{}; + ret.AttackTime = props.flAttackTime; + ret.ReleaseTime = props.flReleaseTime; + ret.Resonance = level_mb_to_gain(static_cast(props.lResonance)); + ret.PeakGain = level_mb_to_gain(static_cast(props.lPeakLevel)); + return ret; + }(); return true; } -template<> -void AutowahCommitter::SetDefaults(EaxEffectProps &props) +void EaxAutowahCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Autowah; - props.mAutowah.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME; - props.mAutowah.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME; - props.mAutowah.lResonance = EAXAUTOWAH_DEFAULTRESONANCE; - props.mAutowah.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL; + static constexpr EAXAUTOWAHPROPERTIES defprops{[] + { + EAXAUTOWAHPROPERTIES ret{}; + ret.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME; + ret.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME; + ret.lResonance = EAXAUTOWAH_DEFAULTRESONANCE; + ret.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL; + return ret; + }()}; + props = defprops; } -template<> -void AutowahCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxAutowahCommitter::Get(const EaxCall &call, const EAXAUTOWAHPROPERTIES &props) { switch(call.get_property_id()) { case EAXAUTOWAH_NONE: break; - case EAXAUTOWAH_ALLPARAMETERS: call.set_value(props.mAutowah); break; - case EAXAUTOWAH_ATTACKTIME: call.set_value(props.mAutowah.flAttackTime); break; - case EAXAUTOWAH_RELEASETIME: call.set_value(props.mAutowah.flReleaseTime); break; - case EAXAUTOWAH_RESONANCE: call.set_value(props.mAutowah.lResonance); break; - case EAXAUTOWAH_PEAKLEVEL: call.set_value(props.mAutowah.lPeakLevel); break; + case EAXAUTOWAH_ALLPARAMETERS: call.set_value(props); break; + case EAXAUTOWAH_ATTACKTIME: call.set_value(props.flAttackTime); break; + case EAXAUTOWAH_RELEASETIME: call.set_value(props.flReleaseTime); break; + case EAXAUTOWAH_RESONANCE: call.set_value(props.lResonance); break; + case EAXAUTOWAH_PEAKLEVEL: call.set_value(props.lPeakLevel); break; default: fail_unknown_property_id(); } } -template<> -void AutowahCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxAutowahCommitter::Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props) { switch(call.get_property_id()) { case EAXAUTOWAH_NONE: break; - case EAXAUTOWAH_ALLPARAMETERS: defer(call, props.mAutowah); break; - case EAXAUTOWAH_ATTACKTIME: defer(call, props.mAutowah.flAttackTime); break; - case EAXAUTOWAH_RELEASETIME: defer(call, props.mAutowah.flReleaseTime); break; - case EAXAUTOWAH_RESONANCE: defer(call, props.mAutowah.lResonance); break; - case EAXAUTOWAH_PEAKLEVEL: defer(call, props.mAutowah.lPeakLevel); break; + case EAXAUTOWAH_ALLPARAMETERS: defer(call, props); break; + case EAXAUTOWAH_ATTACKTIME: defer(call, props.flAttackTime); break; + case EAXAUTOWAH_RELEASETIME: defer(call, props.flReleaseTime); break; + case EAXAUTOWAH_RESONANCE: defer(call, props.lResonance); break; + case EAXAUTOWAH_PEAKLEVEL: defer(call, props.lPeakLevel); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/chorus.cpp b/Engine/lib/openal-soft/al/effects/chorus.cpp index 305259a41..5a5013ce4 100644 --- a/Engine/lib/openal-soft/al/effects/chorus.cpp +++ b/Engine/lib/openal-soft/al/effects/chorus.cpp @@ -1,19 +1,17 @@ #include "config.h" +#include #include #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" -#include "aloptional.h" -#include "core/logging.h" #include "effects.h" #ifdef ALSOFT_EAX #include -#include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -27,16 +25,16 @@ static_assert(FlangerMaxDelay >= AL_FLANGER_MAX_DELAY, "Flanger max delay too sm static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch"); static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch"); -inline al::optional WaveformFromEnum(ALenum type) +constexpr std::optional WaveformFromEnum(ALenum type) noexcept { switch(type) { case AL_CHORUS_WAVEFORM_SINUSOID: return ChorusWaveform::Sinusoid; case AL_CHORUS_WAVEFORM_TRIANGLE: return ChorusWaveform::Triangle; } - return al::nullopt; + return std::nullopt; } -inline ALenum EnumFromWaveform(ChorusWaveform type) +constexpr ALenum EnumFromWaveform(ChorusWaveform type) { switch(type) { @@ -46,13 +44,41 @@ inline ALenum EnumFromWaveform(ChorusWaveform type) throw std::runtime_error{"Invalid chorus waveform: "+std::to_string(static_cast(type))}; } -void Chorus_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultChorusProps() noexcept +{ + ChorusProps props{}; + props.Waveform = WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM).value(); + props.Phase = AL_CHORUS_DEFAULT_PHASE; + props.Rate = AL_CHORUS_DEFAULT_RATE; + props.Depth = AL_CHORUS_DEFAULT_DEPTH; + props.Feedback = AL_CHORUS_DEFAULT_FEEDBACK; + props.Delay = AL_CHORUS_DEFAULT_DELAY; + return props; +} + +constexpr EffectProps genDefaultFlangerProps() noexcept +{ + ChorusProps props{}; + props.Waveform = WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM).value(); + props.Phase = AL_FLANGER_DEFAULT_PHASE; + props.Rate = AL_FLANGER_DEFAULT_RATE; + props.Depth = AL_FLANGER_DEFAULT_DEPTH; + props.Feedback = AL_FLANGER_DEFAULT_FEEDBACK; + props.Delay = AL_FLANGER_DEFAULT_DELAY; + return props; +} + +} // namespace + +const EffectProps ChorusEffectProps{genDefaultChorusProps()}; + +void ChorusEffectHandler::SetParami(ChorusProps &props, ALenum param, int val) { switch(param) { case AL_CHORUS_WAVEFORM: if(auto formopt = WaveformFromEnum(val)) - props->Chorus.Waveform = *formopt; + props.Waveform = *formopt; else throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val}; break; @@ -60,115 +86,89 @@ void Chorus_setParami(EffectProps *props, ALenum param, int val) case AL_CHORUS_PHASE: if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE)) throw effect_exception{AL_INVALID_VALUE, "Chorus phase out of range: %d", val}; - props->Chorus.Phase = val; + props.Phase = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; } } -void Chorus_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Chorus_setParami(props, param, vals[0]); } -void Chorus_setParamf(EffectProps *props, ALenum param, float val) +void ChorusEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void ChorusEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val) { switch(param) { case AL_CHORUS_RATE: if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) throw effect_exception{AL_INVALID_VALUE, "Chorus rate out of range: %f", val}; - props->Chorus.Rate = val; + props.Rate = val; break; case AL_CHORUS_DEPTH: if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "Chorus depth out of range: %f", val}; - props->Chorus.Depth = val; + props.Depth = val; break; case AL_CHORUS_FEEDBACK: if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) throw effect_exception{AL_INVALID_VALUE, "Chorus feedback out of range: %f", val}; - props->Chorus.Feedback = val; + props.Feedback = val; break; case AL_CHORUS_DELAY: if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) throw effect_exception{AL_INVALID_VALUE, "Chorus delay out of range: %f", val}; - props->Chorus.Delay = val; + props.Delay = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; } } -void Chorus_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Chorus_setParamf(props, param, vals[0]); } +void ChorusEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Chorus_getParami(const EffectProps *props, ALenum param, int *val) +void ChorusEffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val) { switch(param) { - case AL_CHORUS_WAVEFORM: - *val = EnumFromWaveform(props->Chorus.Waveform); - break; - - case AL_CHORUS_PHASE: - *val = props->Chorus.Phase; - break; + case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; + case AL_CHORUS_PHASE: *val = props.Phase; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; } } -void Chorus_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Chorus_getParami(props, param, vals); } -void Chorus_getParamf(const EffectProps *props, ALenum param, float *val) +void ChorusEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void ChorusEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val) { switch(param) { - case AL_CHORUS_RATE: - *val = props->Chorus.Rate; - break; - - case AL_CHORUS_DEPTH: - *val = props->Chorus.Depth; - break; - - case AL_CHORUS_FEEDBACK: - *val = props->Chorus.Feedback; - break; - - case AL_CHORUS_DELAY: - *val = props->Chorus.Delay; - break; + case AL_CHORUS_RATE: *val = props.Rate; break; + case AL_CHORUS_DEPTH: *val = props.Depth; break; + case AL_CHORUS_FEEDBACK: *val = props.Feedback; break; + case AL_CHORUS_DELAY: *val = props.Delay; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; } } -void Chorus_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Chorus_getParamf(props, param, vals); } - -EffectProps genDefaultChorusProps() noexcept -{ - EffectProps props{}; - props.Chorus.Waveform = *WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM); - props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE; - props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE; - props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH; - props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK; - props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY; - return props; -} +void ChorusEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -void Flanger_setParami(EffectProps *props, ALenum param, int val) +const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; + +void FlangerEffectHandler::SetParami(ChorusProps &props, ALenum param, int val) { switch(param) { case AL_FLANGER_WAVEFORM: if(auto formopt = WaveformFromEnum(val)) - props->Chorus.Waveform = *formopt; + props.Waveform = *formopt; else throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val}; break; @@ -176,127 +176,87 @@ void Flanger_setParami(EffectProps *props, ALenum param, int val) case AL_FLANGER_PHASE: if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE)) throw effect_exception{AL_INVALID_VALUE, "Flanger phase out of range: %d", val}; - props->Chorus.Phase = val; + props.Phase = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; } } -void Flanger_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Flanger_setParami(props, param, vals[0]); } -void Flanger_setParamf(EffectProps *props, ALenum param, float val) +void FlangerEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void FlangerEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val) { switch(param) { case AL_FLANGER_RATE: if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) throw effect_exception{AL_INVALID_VALUE, "Flanger rate out of range: %f", val}; - props->Chorus.Rate = val; + props.Rate = val; break; case AL_FLANGER_DEPTH: if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "Flanger depth out of range: %f", val}; - props->Chorus.Depth = val; + props.Depth = val; break; case AL_FLANGER_FEEDBACK: if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) throw effect_exception{AL_INVALID_VALUE, "Flanger feedback out of range: %f", val}; - props->Chorus.Feedback = val; + props.Feedback = val; break; case AL_FLANGER_DELAY: if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) throw effect_exception{AL_INVALID_VALUE, "Flanger delay out of range: %f", val}; - props->Chorus.Delay = val; + props.Delay = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; } } -void Flanger_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Flanger_setParamf(props, param, vals[0]); } +void FlangerEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Flanger_getParami(const EffectProps *props, ALenum param, int *val) +void FlangerEffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val) { switch(param) { - case AL_FLANGER_WAVEFORM: - *val = EnumFromWaveform(props->Chorus.Waveform); - break; - - case AL_FLANGER_PHASE: - *val = props->Chorus.Phase; - break; + case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; + case AL_FLANGER_PHASE: *val = props.Phase; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; } } -void Flanger_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Flanger_getParami(props, param, vals); } -void Flanger_getParamf(const EffectProps *props, ALenum param, float *val) +void FlangerEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void FlangerEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val) { switch(param) { - case AL_FLANGER_RATE: - *val = props->Chorus.Rate; - break; - - case AL_FLANGER_DEPTH: - *val = props->Chorus.Depth; - break; - - case AL_FLANGER_FEEDBACK: - *val = props->Chorus.Feedback; - break; - - case AL_FLANGER_DELAY: - *val = props->Chorus.Delay; - break; + case AL_FLANGER_RATE: *val = props.Rate; break; + case AL_FLANGER_DEPTH: *val = props.Depth; break; + case AL_FLANGER_FEEDBACK: *val = props.Feedback; break; + case AL_FLANGER_DELAY: *val = props.Delay; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; } } -void Flanger_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Flanger_getParamf(props, param, vals); } - -EffectProps genDefaultFlangerProps() noexcept -{ - EffectProps props{}; - props.Chorus.Waveform = *WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM); - props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE; - props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE; - props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH; - props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK; - props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Chorus); - -const EffectProps ChorusEffectProps{genDefaultChorusProps()}; - -DEFINE_ALEFFECT_VTABLE(Flanger); - -const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; +void FlangerEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } #ifdef ALSOFT_EAX namespace { struct EaxChorusTraits { - using Props = EAXCHORUSPROPERTIES; + using EaxProps = EAXCHORUSPROPERTIES; using Committer = EaxChorusCommitter; - static constexpr auto Field = &EaxEffectProps::mChorus; - static constexpr auto eax_effect_type() { return EaxEffectType::Chorus; } static constexpr auto efx_effect() { return AL_EFFECT_CHORUS; } static constexpr auto eax_none_param_id() { return EAXCHORUS_NONE; } @@ -359,11 +319,9 @@ struct EaxChorusTraits { }; // EaxChorusTraits struct EaxFlangerTraits { - using Props = EAXFLANGERPROPERTIES; + using EaxProps = EAXFLANGERPROPERTIES; using Committer = EaxFlangerCommitter; - static constexpr auto Field = &EaxEffectProps::mFlanger; - static constexpr auto eax_effect_type() { return EaxEffectType::Flanger; } static constexpr auto efx_effect() { return AL_EFFECT_FLANGER; } static constexpr auto eax_none_param_id() { return EAXFLANGER_NONE; } @@ -428,11 +386,10 @@ struct EaxFlangerTraits { template struct ChorusFlangerEffect { using Traits = TTraits; + using EaxProps = typename Traits::EaxProps; using Committer = typename Traits::Committer; using Exception = typename Committer::Exception; - static constexpr auto Field = Traits::Field; - struct WaveformValidator { void operator()(unsigned long ulWaveform) const { @@ -500,7 +457,7 @@ struct ChorusFlangerEffect { }; // DelayValidator struct AllValidator { - void operator()(const typename Traits::Props& all) const + void operator()(const EaxProps& all) const { WaveformValidator{}(all.ulWaveform); PhaseValidator{}(all.lPhase); @@ -514,8 +471,7 @@ struct ChorusFlangerEffect { public: static void SetDefaults(EaxEffectProps &props) { - auto&& all = props.*Field; - props.mType = Traits::eax_effect_type(); + auto&& all = props.emplace(); all.ulWaveform = Traits::eax_default_waveform(); all.lPhase = Traits::eax_default_phase(); all.flRate = Traits::eax_default_rate(); @@ -525,109 +481,83 @@ public: } - static void Get(const EaxCall &call, const EaxEffectProps &props) + static void Get(const EaxCall &call, const EaxProps &all) { - auto&& all = props.*Field; switch(call.get_property_id()) { case Traits::eax_none_param_id(): break; - case Traits::eax_allparameters_param_id(): call.template set_value(all); break; - case Traits::eax_waveform_param_id(): call.template set_value(all.ulWaveform); break; - case Traits::eax_phase_param_id(): call.template set_value(all.lPhase); break; - case Traits::eax_rate_param_id(): call.template set_value(all.flRate); break; - case Traits::eax_depth_param_id(): call.template set_value(all.flDepth); break; - case Traits::eax_feedback_param_id(): call.template set_value(all.flFeedback); break; - case Traits::eax_delay_param_id(): call.template set_value(all.flDelay); break; - default: Committer::fail_unknown_property_id(); } } - static void Set(const EaxCall &call, EaxEffectProps &props) + static void Set(const EaxCall &call, EaxProps &all) { - auto&& all = props.*Field; switch(call.get_property_id()) { case Traits::eax_none_param_id(): break; - case Traits::eax_allparameters_param_id(): Committer::template defer(call, all); break; - case Traits::eax_waveform_param_id(): Committer::template defer(call, all.ulWaveform); break; - case Traits::eax_phase_param_id(): Committer::template defer(call, all.lPhase); break; - case Traits::eax_rate_param_id(): Committer::template defer(call, all.flRate); break; - case Traits::eax_depth_param_id(): Committer::template defer(call, all.flDepth); break; - case Traits::eax_feedback_param_id(): Committer::template defer(call, all.flFeedback); break; - case Traits::eax_delay_param_id(): Committer::template defer(call, all.flDelay); break; - default: Committer::fail_unknown_property_id(); } } - static bool Commit(const EaxEffectProps &props, EaxEffectProps &props_, EffectProps &al_props_) + static bool Commit(const EaxProps &props, EaxEffectProps &props_, ChorusProps &al_props_) { - if(props.mType == props_.mType) - { - auto&& src = props_.*Field; - auto&& dst = props.*Field; - if(dst.ulWaveform == src.ulWaveform && dst.lPhase == src.lPhase - && dst.flRate == src.flRate && dst.flDepth == src.flDepth - && dst.flFeedback == src.flFeedback && dst.flDelay == src.flDelay) - return false; - } + if(auto *cur = std::get_if(&props_); cur && *cur == props) + return false; props_ = props; - auto&& dst = props.*Field; - al_props_.Chorus.Waveform = Traits::eax_waveform(dst.ulWaveform); - al_props_.Chorus.Phase = static_cast(dst.lPhase); - al_props_.Chorus.Rate = dst.flRate; - al_props_.Chorus.Depth = dst.flDepth; - al_props_.Chorus.Feedback = dst.flFeedback; - al_props_.Chorus.Delay = dst.flDelay; + al_props_.Waveform = Traits::eax_waveform(props.ulWaveform); + al_props_.Phase = static_cast(props.lPhase); + al_props_.Rate = props.flRate; + al_props_.Depth = props.flDepth; + al_props_.Feedback = props.flFeedback; + al_props_.Delay = props.flDelay; return true; } @@ -652,29 +582,25 @@ template<> throw Exception{message}; } -template<> -bool ChorusCommitter::commit(const EaxEffectProps &props) +bool EaxChorusCommitter::commit(const EAXCHORUSPROPERTIES &props) { using Committer = ChorusFlangerEffect; - return Committer::Commit(props, mEaxProps, mAlProps); + return Committer::Commit(props, mEaxProps, mAlProps.emplace()); } -template<> -void ChorusCommitter::SetDefaults(EaxEffectProps &props) +void EaxChorusCommitter::SetDefaults(EaxEffectProps &props) { using Committer = ChorusFlangerEffect; Committer::SetDefaults(props); } -template<> -void ChorusCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxChorusCommitter::Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Get(call, props); } -template<> -void ChorusCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxChorusCommitter::Set(const EaxCall &call, EAXCHORUSPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Set(call, props); @@ -693,29 +619,25 @@ template<> throw Exception{message}; } -template<> -bool FlangerCommitter::commit(const EaxEffectProps &props) +bool EaxFlangerCommitter::commit(const EAXFLANGERPROPERTIES &props) { using Committer = ChorusFlangerEffect; - return Committer::Commit(props, mEaxProps, mAlProps); + return Committer::Commit(props, mEaxProps, mAlProps.emplace()); } -template<> -void FlangerCommitter::SetDefaults(EaxEffectProps &props) +void EaxFlangerCommitter::SetDefaults(EaxEffectProps &props) { using Committer = ChorusFlangerEffect; Committer::SetDefaults(props); } -template<> -void FlangerCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxFlangerCommitter::Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Get(call, props); } -template<> -void FlangerCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxFlangerCommitter::Set(const EaxCall &call, EAXFLANGERPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Set(call, props); diff --git a/Engine/lib/openal-soft/al/effects/compressor.cpp b/Engine/lib/openal-soft/al/effects/compressor.cpp index a4aa8e77f..8c9f5b79a 100644 --- a/Engine/lib/openal-soft/al/effects/compressor.cpp +++ b/Engine/lib/openal-soft/al/effects/compressor.cpp @@ -9,6 +9,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -16,14 +17,25 @@ namespace { -void Compressor_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultProps() noexcept +{ + CompressorProps props{}; + props.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; + return props; +} + +} // namespace + +const EffectProps CompressorEffectProps{genDefaultProps()}; + +void CompressorEffectHandler::SetParami(CompressorProps &props, ALenum param, int val) { switch(param) { case AL_COMPRESSOR_ONOFF: if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) throw effect_exception{AL_INVALID_VALUE, "Compressor state out of range"}; - props->Compressor.OnOff = (val != AL_FALSE); + props.OnOff = (val != AL_FALSE); break; default: @@ -31,51 +43,36 @@ void Compressor_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Compressor_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Compressor_setParami(props, param, vals[0]); } -void Compressor_setParamf(EffectProps*, ALenum param, float) +void CompressorEffectHandler::SetParamiv(CompressorProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void CompressorEffectHandler::SetParamf(CompressorProps&, ALenum param, float) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } -void Compressor_setParamfv(EffectProps*, ALenum param, const float*) +void CompressorEffectHandler::SetParamfv(CompressorProps&, ALenum param, const float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param}; } -void Compressor_getParami(const EffectProps *props, ALenum param, int *val) +void CompressorEffectHandler::GetParami(const CompressorProps &props, ALenum param, int *val) { switch(param) { - case AL_COMPRESSOR_ONOFF: - *val = props->Compressor.OnOff; - break; - + case AL_COMPRESSOR_ONOFF: *val = props.OnOff; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", param}; } } -void Compressor_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Compressor_getParami(props, param, vals); } -void Compressor_getParamf(const EffectProps*, ALenum param, float*) +void CompressorEffectHandler::GetParamiv(const CompressorProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void CompressorEffectHandler::GetParamf(const CompressorProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } -void Compressor_getParamfv(const EffectProps*, ALenum param, float*) +void CompressorEffectHandler::GetParamfv(const CompressorProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param}; } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Compressor); - -const EffectProps CompressorEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -115,46 +112,40 @@ template<> throw Exception{message}; } -template<> -bool CompressorCommitter::commit(const EaxEffectProps &props) +bool EaxCompressorCommitter::commit(const EAXAGCCOMPRESSORPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && props.mCompressor.ulOnOff == mEaxProps.mCompressor.ulOnOff) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; + mAlProps = CompressorProps{props.ulOnOff != 0}; - mAlProps.Compressor.OnOff = (props.mCompressor.ulOnOff != 0); return true; } -template<> -void CompressorCommitter::SetDefaults(EaxEffectProps &props) +void EaxCompressorCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Compressor; - props.mCompressor.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF; + props = EAXAGCCOMPRESSORPROPERTIES{EAXAGCCOMPRESSOR_DEFAULTONOFF}; } -template<> -void CompressorCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxCompressorCommitter::Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props) { switch(call.get_property_id()) { case EAXAGCCOMPRESSOR_NONE: break; - case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value(props.mCompressor); break; - case EAXAGCCOMPRESSOR_ONOFF: call.set_value(props.mCompressor.ulOnOff); break; + case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value(props); break; + case EAXAGCCOMPRESSOR_ONOFF: call.set_value(props.ulOnOff); break; default: fail_unknown_property_id(); } } -template<> -void CompressorCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxCompressorCommitter::Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props) { switch(call.get_property_id()) { case EAXAGCCOMPRESSOR_NONE: break; - case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer(call, props.mCompressor); break; - case EAXAGCCOMPRESSOR_ONOFF: defer(call, props.mCompressor.ulOnOff); break; + case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer(call, props); break; + case EAXAGCCOMPRESSOR_ONOFF: defer(call, props.ulOnOff); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/convolution.cpp b/Engine/lib/openal-soft/al/effects/convolution.cpp index 8e850fd3e..618d5aac5 100644 --- a/Engine/lib/openal-soft/al/effects/convolution.cpp +++ b/Engine/lib/openal-soft/al/effects/convolution.cpp @@ -1,93 +1,117 @@ #include "config.h" -#include "AL/al.h" -#include "alc/inprogext.h" +#include +#include +#include -#include "alc/effects/base.h" +#include "AL/al.h" + +#include "alc/inprogext.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/effects/base.h" #include "effects.h" namespace { -void Convolution_setParami(EffectProps* /*props*/, ALenum param, int /*val*/) +constexpr EffectProps genDefaultProps() noexcept { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } -} -void Convolution_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ - switch(param) - { - default: - Convolution_setParami(props, param, vals[0]); - } -} -void Convolution_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } -} -void Convolution_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ - switch(param) - { - default: - Convolution_setParamf(props, param, vals[0]); - } -} - -void Convolution_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } -} -void Convolution_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ - switch(param) - { - default: - Convolution_getParami(props, param, vals); - } -} -void Convolution_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } -} -void Convolution_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ - switch(param) - { - default: - Convolution_getParamf(props, param, vals); - } -} - -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; + ConvolutionProps props{}; + props.OrientAt = {0.0f, 0.0f, -1.0f}; + props.OrientUp = {0.0f, 1.0f, 0.0f}; return props; } } // namespace -DEFINE_ALEFFECT_VTABLE(Convolution); - const EffectProps ConvolutionEffectProps{genDefaultProps()}; + +void ConvolutionEffectHandler::SetParami(ConvolutionProps& /*props*/, ALenum param, int /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x", + param}; + } +} +void ConvolutionEffectHandler::SetParamiv(ConvolutionProps &props, ALenum param, const int *vals) +{ + switch(param) + { + default: + SetParami(props, param, *vals); + } +} +void ConvolutionEffectHandler::SetParamf(ConvolutionProps& /*props*/, ALenum param, float /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x", + param}; + } +} +void ConvolutionEffectHandler::SetParamfv(ConvolutionProps &props, ALenum param, const float *values) +{ + static constexpr auto finite_checker = [](float val) -> bool { return std::isfinite(val); }; + al::span vals; + switch(param) + { + case AL_CONVOLUTION_ORIENTATION_SOFT: + vals = {values, 6_uz}; + if(!std::all_of(vals.cbegin(), vals.cend(), finite_checker)) + throw effect_exception{AL_INVALID_VALUE, "Property 0x%04x value out of range", param}; + + std::copy_n(vals.cbegin(), props.OrientAt.size(), props.OrientAt.begin()); + std::copy_n(vals.cbegin()+3, props.OrientUp.size(), props.OrientUp.begin()); + break; + + default: + SetParamf(props, param, *values); + } +} + +void ConvolutionEffectHandler::GetParami(const ConvolutionProps& /*props*/, ALenum param, int* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x", + param}; + } +} +void ConvolutionEffectHandler::GetParamiv(const ConvolutionProps &props, ALenum param, int *vals) +{ + switch(param) + { + default: + GetParami(props, param, vals); + } +} +void ConvolutionEffectHandler::GetParamf(const ConvolutionProps& /*props*/, ALenum param, float* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x", + param}; + } +} +void ConvolutionEffectHandler::GetParamfv(const ConvolutionProps &props, ALenum param, float *values) +{ + al::span vals; + switch(param) + { + case AL_CONVOLUTION_ORIENTATION_SOFT: + vals = {values, 6_uz}; + std::copy(props.OrientAt.cbegin(), props.OrientAt.cend(), vals.begin()); + std::copy(props.OrientUp.cbegin(), props.OrientUp.cend(), vals.begin()+3); + break; + + default: + GetParamf(props, param, values); + } +} diff --git a/Engine/lib/openal-soft/al/effects/dedicated.cpp b/Engine/lib/openal-soft/al/effects/dedicated.cpp index db57003c2..f032a72ec 100644 --- a/Engine/lib/openal-soft/al/effects/dedicated.cpp +++ b/Engine/lib/openal-soft/al/effects/dedicated.cpp @@ -12,61 +12,111 @@ namespace { -void Dedicated_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultDialogProps() noexcept +{ + DedicatedProps props{}; + props.Target = DedicatedProps::Dialog; + props.Gain = 1.0f; + return props; +} + +constexpr EffectProps genDefaultLfeProps() noexcept +{ + DedicatedProps props{}; + props.Target = DedicatedProps::Lfe; + props.Gain = 1.0f; + return props; +} + +} // namespace + +const EffectProps DedicatedDialogEffectProps{genDefaultDialogProps()}; + +void DedicatedDialogEffectHandler::SetParami(DedicatedProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void Dedicated_setParamiv(EffectProps*, ALenum param, const int*) +void DedicatedDialogEffectHandler::SetParamiv(DedicatedProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param}; } -void Dedicated_setParamf(EffectProps *props, ALenum param, float val) +void DedicatedDialogEffectHandler::SetParamf(DedicatedProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: if(!(val >= 0.0f && std::isfinite(val))) throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; - props->Dedicated.Gain = val; + props.Gain = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; } } -void Dedicated_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Dedicated_setParamf(props, param, vals[0]); } +void DedicatedDialogEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Dedicated_getParami(const EffectProps*, ALenum param, int*) +void DedicatedDialogEffectHandler::GetParami(const DedicatedProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void Dedicated_getParamiv(const EffectProps*, ALenum param, int*) +void DedicatedDialogEffectHandler::GetParamiv(const DedicatedProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param}; } -void Dedicated_getParamf(const EffectProps *props, ALenum param, float *val) +void DedicatedDialogEffectHandler::GetParamf(const DedicatedProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_DEDICATED_GAIN: *val = props.Gain; break; + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + } +} +void DedicatedDialogEffectHandler::GetParamfv(const DedicatedProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } + + +const EffectProps DedicatedLfeEffectProps{genDefaultLfeProps()}; + +void DedicatedLfeEffectHandler::SetParami(DedicatedProps&, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } +void DedicatedLfeEffectHandler::SetParamiv(DedicatedProps&, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", + param}; +} +void DedicatedLfeEffectHandler::SetParamf(DedicatedProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: - *val = props->Dedicated.Gain; + if(!(val >= 0.0f && std::isfinite(val))) + throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; + props.Gain = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; } } -void Dedicated_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Dedicated_getParamf(props, param, vals); } +void DedicatedLfeEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -EffectProps genDefaultProps() noexcept +void DedicatedLfeEffectHandler::GetParami(const DedicatedProps&, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } +void DedicatedLfeEffectHandler::GetParamiv(const DedicatedProps&, ALenum param, int*) { - EffectProps props{}; - props.Dedicated.Gain = 1.0f; - return props; + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", + param}; } - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Dedicated); - -const EffectProps DedicatedEffectProps{genDefaultProps()}; +void DedicatedLfeEffectHandler::GetParamf(const DedicatedProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_DEDICATED_GAIN: *val = props.Gain; break; + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + } +} +void DedicatedLfeEffectHandler::GetParamfv(const DedicatedProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } diff --git a/Engine/lib/openal-soft/al/effects/distortion.cpp b/Engine/lib/openal-soft/al/effects/distortion.cpp index ee298ddf7..7d47fef2d 100644 --- a/Engine/lib/openal-soft/al/effects/distortion.cpp +++ b/Engine/lib/openal-soft/al/effects/distortion.cpp @@ -9,6 +9,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -16,108 +17,93 @@ namespace { -void Distortion_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultProps() noexcept +{ + DistortionProps props{}; + props.Edge = AL_DISTORTION_DEFAULT_EDGE; + props.Gain = AL_DISTORTION_DEFAULT_GAIN; + props.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF; + props.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER; + props.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH; + return props; +} + +} // namespace + +const EffectProps DistortionEffectProps{genDefaultProps()}; + +void DistortionEffectHandler::SetParami(DistortionProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } -void Distortion_setParamiv(EffectProps*, ALenum param, const int*) +void DistortionEffectHandler::SetParamiv(DistortionProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param}; } -void Distortion_setParamf(EffectProps *props, ALenum param, float val) +void DistortionEffectHandler::SetParamf(DistortionProps &props, ALenum param, float val) { switch(param) { case AL_DISTORTION_EDGE: if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"}; - props->Distortion.Edge = val; + props.Edge = val; break; case AL_DISTORTION_GAIN: if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"}; - props->Distortion.Gain = val; + props.Gain = val; break; case AL_DISTORTION_LOWPASS_CUTOFF: if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) throw effect_exception{AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"}; - props->Distortion.LowpassCutoff = val; + props.LowpassCutoff = val; break; case AL_DISTORTION_EQCENTER: if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"}; - props->Distortion.EQCenter = val; + props.EQCenter = val; break; case AL_DISTORTION_EQBANDWIDTH: if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"}; - props->Distortion.EQBandwidth = val; + props.EQBandwidth = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; } } -void Distortion_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Distortion_setParamf(props, param, vals[0]); } +void DistortionEffectHandler::SetParamfv(DistortionProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Distortion_getParami(const EffectProps*, ALenum param, int*) +void DistortionEffectHandler::GetParami(const DistortionProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } -void Distortion_getParamiv(const EffectProps*, ALenum param, int*) +void DistortionEffectHandler::GetParamiv(const DistortionProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param}; } -void Distortion_getParamf(const EffectProps *props, ALenum param, float *val) +void DistortionEffectHandler::GetParamf(const DistortionProps &props, ALenum param, float *val) { switch(param) { - case AL_DISTORTION_EDGE: - *val = props->Distortion.Edge; - break; - - case AL_DISTORTION_GAIN: - *val = props->Distortion.Gain; - break; - - case AL_DISTORTION_LOWPASS_CUTOFF: - *val = props->Distortion.LowpassCutoff; - break; - - case AL_DISTORTION_EQCENTER: - *val = props->Distortion.EQCenter; - break; - - case AL_DISTORTION_EQBANDWIDTH: - *val = props->Distortion.EQBandwidth; - break; + case AL_DISTORTION_EDGE: *val = props.Edge; break; + case AL_DISTORTION_GAIN: *val = props.Gain; break; + case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; break; + case AL_DISTORTION_EQCENTER: *val = props.EQCenter; break; + case AL_DISTORTION_EQBANDWIDTH: *val = props.EQBandwidth; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; } } -void Distortion_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Distortion_getParamf(props, param, vals); } +void DistortionEffectHandler::GetParamfv(const DistortionProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE; - props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN; - props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF; - props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER; - props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Distortion); - -const EffectProps DistortionEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -204,66 +190,66 @@ template<> throw Exception{message}; } -template<> -bool DistortionCommitter::commit(const EaxEffectProps &props) +bool EaxDistortionCommitter::commit(const EAXDISTORTIONPROPERTIES &props) { - if(props.mType == mEaxProps.mType && mEaxProps.mDistortion.flEdge == props.mDistortion.flEdge - && mEaxProps.mDistortion.lGain == props.mDistortion.lGain - && mEaxProps.mDistortion.flLowPassCutOff == props.mDistortion.flLowPassCutOff - && mEaxProps.mDistortion.flEQCenter == props.mDistortion.flEQCenter - && mEaxProps.mDistortion.flEQBandwidth == props.mDistortion.flEQBandwidth) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - - mAlProps.Distortion.Edge = props.mDistortion.flEdge; - mAlProps.Distortion.Gain = level_mb_to_gain(static_cast(props.mDistortion.lGain)); - mAlProps.Distortion.LowpassCutoff = props.mDistortion.flLowPassCutOff; - mAlProps.Distortion.EQCenter = props.mDistortion.flEQCenter; - mAlProps.Distortion.EQBandwidth = props.mDistortion.flEdge; + mAlProps = [&]{ + DistortionProps ret{}; + ret.Edge = props.flEdge; + ret.Gain = level_mb_to_gain(static_cast(props.lGain)); + ret.LowpassCutoff = props.flLowPassCutOff; + ret.EQCenter = props.flEQCenter; + ret.EQBandwidth = props.flEdge; + return ret; + }(); return true; } -template<> -void DistortionCommitter::SetDefaults(EaxEffectProps &props) +void EaxDistortionCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Distortion; - props.mDistortion.flEdge = EAXDISTORTION_DEFAULTEDGE; - props.mDistortion.lGain = EAXDISTORTION_DEFAULTGAIN; - props.mDistortion.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF; - props.mDistortion.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER; - props.mDistortion.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH; + static constexpr EAXDISTORTIONPROPERTIES defprops{[] + { + EAXDISTORTIONPROPERTIES ret{}; + ret.flEdge = EAXDISTORTION_DEFAULTEDGE; + ret.lGain = EAXDISTORTION_DEFAULTGAIN; + ret.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF; + ret.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER; + ret.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH; + return ret; + }()}; + props = defprops; } -template<> -void DistortionCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxDistortionCommitter::Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props) { switch(call.get_property_id()) { case EAXDISTORTION_NONE: break; - case EAXDISTORTION_ALLPARAMETERS: call.set_value(props.mDistortion); break; - case EAXDISTORTION_EDGE: call.set_value(props.mDistortion.flEdge); break; - case EAXDISTORTION_GAIN: call.set_value(props.mDistortion.lGain); break; - case EAXDISTORTION_LOWPASSCUTOFF: call.set_value(props.mDistortion.flLowPassCutOff); break; - case EAXDISTORTION_EQCENTER: call.set_value(props.mDistortion.flEQCenter); break; - case EAXDISTORTION_EQBANDWIDTH: call.set_value(props.mDistortion.flEQBandwidth); break; + case EAXDISTORTION_ALLPARAMETERS: call.set_value(props); break; + case EAXDISTORTION_EDGE: call.set_value(props.flEdge); break; + case EAXDISTORTION_GAIN: call.set_value(props.lGain); break; + case EAXDISTORTION_LOWPASSCUTOFF: call.set_value(props.flLowPassCutOff); break; + case EAXDISTORTION_EQCENTER: call.set_value(props.flEQCenter); break; + case EAXDISTORTION_EQBANDWIDTH: call.set_value(props.flEQBandwidth); break; default: fail_unknown_property_id(); } } -template<> -void DistortionCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxDistortionCommitter::Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props) { switch(call.get_property_id()) { case EAXDISTORTION_NONE: break; - case EAXDISTORTION_ALLPARAMETERS: defer(call, props.mDistortion); break; - case EAXDISTORTION_EDGE: defer(call, props.mDistortion.flEdge); break; - case EAXDISTORTION_GAIN: defer(call, props.mDistortion.lGain); break; - case EAXDISTORTION_LOWPASSCUTOFF: defer(call, props.mDistortion.flLowPassCutOff); break; - case EAXDISTORTION_EQCENTER: defer(call, props.mDistortion.flEQCenter); break; - case EAXDISTORTION_EQBANDWIDTH: defer(call, props.mDistortion.flEQBandwidth); break; + case EAXDISTORTION_ALLPARAMETERS: defer(call, props); break; + case EAXDISTORTION_EDGE: defer(call, props.flEdge); break; + case EAXDISTORTION_GAIN: defer(call, props.lGain); break; + case EAXDISTORTION_LOWPASSCUTOFF: defer(call, props.flLowPassCutOff); break; + case EAXDISTORTION_EQCENTER: defer(call, props.flEQCenter); break; + case EAXDISTORTION_EQBANDWIDTH: defer(call, props.flEQBandwidth); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/echo.cpp b/Engine/lib/openal-soft/al/effects/echo.cpp index 2eb37603d..96ed7be27 100644 --- a/Engine/lib/openal-soft/al/effects/echo.cpp +++ b/Engine/lib/openal-soft/al/effects/echo.cpp @@ -9,6 +9,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -19,102 +20,87 @@ namespace { static_assert(EchoMaxDelay >= AL_ECHO_MAX_DELAY, "Echo max delay too short"); static_assert(EchoMaxLRDelay >= AL_ECHO_MAX_LRDELAY, "Echo max left-right delay too short"); -void Echo_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultProps() noexcept +{ + EchoProps props{}; + props.Delay = AL_ECHO_DEFAULT_DELAY; + props.LRDelay = AL_ECHO_DEFAULT_LRDELAY; + props.Damping = AL_ECHO_DEFAULT_DAMPING; + props.Feedback = AL_ECHO_DEFAULT_FEEDBACK; + props.Spread = AL_ECHO_DEFAULT_SPREAD; + return props; +} + +} // namespace + +const EffectProps EchoEffectProps{genDefaultProps()}; + +void EchoEffectHandler::SetParami(EchoProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } -void Echo_setParamiv(EffectProps*, ALenum param, const int*) +void EchoEffectHandler::SetParamiv(EchoProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } -void Echo_setParamf(EffectProps *props, ALenum param, float val) +void EchoEffectHandler::SetParamf(EchoProps &props, ALenum param, float val) { switch(param) { case AL_ECHO_DELAY: if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) throw effect_exception{AL_INVALID_VALUE, "Echo delay out of range"}; - props->Echo.Delay = val; + props.Delay = val; break; case AL_ECHO_LRDELAY: if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) throw effect_exception{AL_INVALID_VALUE, "Echo LR delay out of range"}; - props->Echo.LRDelay = val; + props.LRDelay = val; break; case AL_ECHO_DAMPING: if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) throw effect_exception{AL_INVALID_VALUE, "Echo damping out of range"}; - props->Echo.Damping = val; + props.Damping = val; break; case AL_ECHO_FEEDBACK: if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) throw effect_exception{AL_INVALID_VALUE, "Echo feedback out of range"}; - props->Echo.Feedback = val; + props.Feedback = val; break; case AL_ECHO_SPREAD: if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) throw effect_exception{AL_INVALID_VALUE, "Echo spread out of range"}; - props->Echo.Spread = val; + props.Spread = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; } } -void Echo_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Echo_setParamf(props, param, vals[0]); } +void EchoEffectHandler::SetParamfv(EchoProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Echo_getParami(const EffectProps*, ALenum param, int*) +void EchoEffectHandler::GetParami(const EchoProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } -void Echo_getParamiv(const EffectProps*, ALenum param, int*) +void EchoEffectHandler::GetParamiv(const EchoProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } -void Echo_getParamf(const EffectProps *props, ALenum param, float *val) +void EchoEffectHandler::GetParamf(const EchoProps &props, ALenum param, float *val) { switch(param) { - case AL_ECHO_DELAY: - *val = props->Echo.Delay; - break; - - case AL_ECHO_LRDELAY: - *val = props->Echo.LRDelay; - break; - - case AL_ECHO_DAMPING: - *val = props->Echo.Damping; - break; - - case AL_ECHO_FEEDBACK: - *val = props->Echo.Feedback; - break; - - case AL_ECHO_SPREAD: - *val = props->Echo.Spread; - break; + case AL_ECHO_DELAY: *val = props.Delay; break; + case AL_ECHO_LRDELAY: *val = props.LRDelay; break; + case AL_ECHO_DAMPING: *val = props.Damping; break; + case AL_ECHO_FEEDBACK: *val = props.Feedback; break; + case AL_ECHO_SPREAD: *val = props.Spread; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; } } -void Echo_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Echo_getParamf(props, param, vals); } +void EchoEffectHandler::GetParamfv(const EchoProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Echo.Delay = AL_ECHO_DEFAULT_DELAY; - props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY; - props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING; - props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK; - props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Echo); - -const EffectProps EchoEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -201,66 +187,66 @@ template<> throw Exception{message}; } -template<> -bool EchoCommitter::commit(const EaxEffectProps &props) +bool EaxEchoCommitter::commit(const EAXECHOPROPERTIES &props) { - if(props.mType == mEaxProps.mType && mEaxProps.mEcho.flDelay == props.mEcho.flDelay - && mEaxProps.mEcho.flLRDelay == props.mEcho.flLRDelay - && mEaxProps.mEcho.flDamping == props.mEcho.flDamping - && mEaxProps.mEcho.flFeedback == props.mEcho.flFeedback - && mEaxProps.mEcho.flSpread == props.mEcho.flSpread) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - - mAlProps.Echo.Delay = props.mEcho.flDelay; - mAlProps.Echo.LRDelay = props.mEcho.flLRDelay; - mAlProps.Echo.Damping = props.mEcho.flDamping; - mAlProps.Echo.Feedback = props.mEcho.flFeedback; - mAlProps.Echo.Spread = props.mEcho.flSpread; + mAlProps = [&]{ + EchoProps ret{}; + ret.Delay = props.flDelay; + ret.LRDelay = props.flLRDelay; + ret.Damping = props.flDamping; + ret.Feedback = props.flFeedback; + ret.Spread = props.flSpread; + return ret; + }(); return true; } -template<> -void EchoCommitter::SetDefaults(EaxEffectProps &props) +void EaxEchoCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Echo; - props.mEcho.flDelay = EAXECHO_DEFAULTDELAY; - props.mEcho.flLRDelay = EAXECHO_DEFAULTLRDELAY; - props.mEcho.flDamping = EAXECHO_DEFAULTDAMPING; - props.mEcho.flFeedback = EAXECHO_DEFAULTFEEDBACK; - props.mEcho.flSpread = EAXECHO_DEFAULTSPREAD; + static constexpr EAXECHOPROPERTIES defprops{[] + { + EAXECHOPROPERTIES ret{}; + ret.flDelay = EAXECHO_DEFAULTDELAY; + ret.flLRDelay = EAXECHO_DEFAULTLRDELAY; + ret.flDamping = EAXECHO_DEFAULTDAMPING; + ret.flFeedback = EAXECHO_DEFAULTFEEDBACK; + ret.flSpread = EAXECHO_DEFAULTSPREAD; + return ret; + }()}; + props = defprops; } -template<> -void EchoCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxEchoCommitter::Get(const EaxCall &call, const EAXECHOPROPERTIES &props) { switch(call.get_property_id()) { case EAXECHO_NONE: break; - case EAXECHO_ALLPARAMETERS: call.set_value(props.mEcho); break; - case EAXECHO_DELAY: call.set_value(props.mEcho.flDelay); break; - case EAXECHO_LRDELAY: call.set_value(props.mEcho.flLRDelay); break; - case EAXECHO_DAMPING: call.set_value(props.mEcho.flDamping); break; - case EAXECHO_FEEDBACK: call.set_value(props.mEcho.flFeedback); break; - case EAXECHO_SPREAD: call.set_value(props.mEcho.flSpread); break; + case EAXECHO_ALLPARAMETERS: call.set_value(props); break; + case EAXECHO_DELAY: call.set_value(props.flDelay); break; + case EAXECHO_LRDELAY: call.set_value(props.flLRDelay); break; + case EAXECHO_DAMPING: call.set_value(props.flDamping); break; + case EAXECHO_FEEDBACK: call.set_value(props.flFeedback); break; + case EAXECHO_SPREAD: call.set_value(props.flSpread); break; default: fail_unknown_property_id(); } } -template<> -void EchoCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxEchoCommitter::Set(const EaxCall &call, EAXECHOPROPERTIES &props) { switch(call.get_property_id()) { case EAXECHO_NONE: break; - case EAXECHO_ALLPARAMETERS: defer(call, props.mEcho); break; - case EAXECHO_DELAY: defer(call, props.mEcho.flDelay); break; - case EAXECHO_LRDELAY: defer(call, props.mEcho.flLRDelay); break; - case EAXECHO_DAMPING: defer(call, props.mEcho.flDamping); break; - case EAXECHO_FEEDBACK: defer(call, props.mEcho.flFeedback); break; - case EAXECHO_SPREAD: defer(call, props.mEcho.flSpread); break; + case EAXECHO_ALLPARAMETERS: defer(call, props); break; + case EAXECHO_DELAY: defer(call, props.flDelay); break; + case EAXECHO_LRDELAY: defer(call, props.flLRDelay); break; + case EAXECHO_DAMPING: defer(call, props.flDamping); break; + case EAXECHO_FEEDBACK: defer(call, props.flFeedback); break; + case EAXECHO_SPREAD: defer(call, props.flSpread); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/effects.cpp b/Engine/lib/openal-soft/al/effects/effects.cpp index 4a67b5ffe..ace4e7066 100644 --- a/Engine/lib/openal-soft/al/effects/effects.cpp +++ b/Engine/lib/openal-soft/al/effects/effects.cpp @@ -1,9 +1,3 @@ #include "config.h" -#ifdef ALSOFT_EAX - -#include -#include "AL/efx.h" #include "effects.h" - -#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/effects.h b/Engine/lib/openal-soft/al/effects/effects.h index 9d57dd820..3c4b8f93f 100644 --- a/Engine/lib/openal-soft/al/effects/effects.h +++ b/Engine/lib/openal-soft/al/effects/effects.h @@ -1,52 +1,47 @@ #ifndef AL_EFFECTS_EFFECTS_H #define AL_EFFECTS_EFFECTS_H +#include + #include "AL/al.h" -#include "core/except.h" +#include "al/error.h" +#include "core/effects/base.h" -#ifdef ALSOFT_EAX -#include "al/eax/effect.h" -#endif // ALSOFT_EAX - -union EffectProps; - - -class effect_exception final : public al::base_exception { - ALenum mErrorCode; - -public: -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf, 3, 4)]] -#else - [[gnu::format(printf, 3, 4)]] -#endif - effect_exception(ALenum code, const char *msg, ...); - ~effect_exception() override; - - ALenum errorCode() const noexcept { return mErrorCode; } +#define DECL_HANDLER(N, T) \ +struct N { \ + using prop_type = T; \ + \ + static void SetParami(prop_type &props, ALenum param, int val); \ + static void SetParamiv(prop_type &props, ALenum param, const int *vals); \ + static void SetParamf(prop_type &props, ALenum param, float val); \ + static void SetParamfv(prop_type &props, ALenum param, const float *vals);\ + static void GetParami(const prop_type &props, ALenum param, int *val); \ + static void GetParamiv(const prop_type &props, ALenum param, int *vals); \ + static void GetParamf(const prop_type &props, ALenum param, float *val); \ + static void GetParamfv(const prop_type &props, ALenum param, float *vals);\ }; +DECL_HANDLER(NullEffectHandler, std::monostate) +DECL_HANDLER(ReverbEffectHandler, ReverbProps) +DECL_HANDLER(StdReverbEffectHandler, ReverbProps) +DECL_HANDLER(AutowahEffectHandler, AutowahProps) +DECL_HANDLER(ChorusEffectHandler, ChorusProps) +DECL_HANDLER(CompressorEffectHandler, CompressorProps) +DECL_HANDLER(DistortionEffectHandler, DistortionProps) +DECL_HANDLER(EchoEffectHandler, EchoProps) +DECL_HANDLER(EqualizerEffectHandler, EqualizerProps) +DECL_HANDLER(FlangerEffectHandler, ChorusProps) +DECL_HANDLER(FshifterEffectHandler, FshifterProps) +DECL_HANDLER(ModulatorEffectHandler, ModulatorProps) +DECL_HANDLER(PshifterEffectHandler, PshifterProps) +DECL_HANDLER(VmorpherEffectHandler, VmorpherProps) +DECL_HANDLER(DedicatedDialogEffectHandler, DedicatedProps) +DECL_HANDLER(DedicatedLfeEffectHandler, DedicatedProps) +DECL_HANDLER(ConvolutionEffectHandler, ConvolutionProps) +#undef DECL_HANDLER -struct EffectVtable { - void (*const setParami)(EffectProps *props, ALenum param, int val); - void (*const setParamiv)(EffectProps *props, ALenum param, const int *vals); - void (*const setParamf)(EffectProps *props, ALenum param, float val); - void (*const setParamfv)(EffectProps *props, ALenum param, const float *vals); - - void (*const getParami)(const EffectProps *props, ALenum param, int *val); - void (*const getParamiv)(const EffectProps *props, ALenum param, int *vals); - void (*const getParamf)(const EffectProps *props, ALenum param, float *val); - void (*const getParamfv)(const EffectProps *props, ALenum param, float *vals); -}; - -#define DEFINE_ALEFFECT_VTABLE(T) \ -const EffectVtable T##EffectVtable = { \ - T##_setParami, T##_setParamiv, \ - T##_setParamf, T##_setParamfv, \ - T##_getParami, T##_getParamiv, \ - T##_getParamf, T##_getParamfv, \ -} +using effect_exception = al::context_error; /* Default properties for the given effect types. */ @@ -64,25 +59,8 @@ extern const EffectProps FshifterEffectProps; extern const EffectProps ModulatorEffectProps; extern const EffectProps PshifterEffectProps; extern const EffectProps VmorpherEffectProps; -extern const EffectProps DedicatedEffectProps; +extern const EffectProps DedicatedDialogEffectProps; +extern const EffectProps DedicatedLfeEffectProps; extern const EffectProps ConvolutionEffectProps; -/* Vtables to get/set properties for the given effect types. */ -extern const EffectVtable NullEffectVtable; -extern const EffectVtable ReverbEffectVtable; -extern const EffectVtable StdReverbEffectVtable; -extern const EffectVtable AutowahEffectVtable; -extern const EffectVtable ChorusEffectVtable; -extern const EffectVtable CompressorEffectVtable; -extern const EffectVtable DistortionEffectVtable; -extern const EffectVtable EchoEffectVtable; -extern const EffectVtable EqualizerEffectVtable; -extern const EffectVtable FlangerEffectVtable; -extern const EffectVtable FshifterEffectVtable; -extern const EffectVtable ModulatorEffectVtable; -extern const EffectVtable PshifterEffectVtable; -extern const EffectVtable VmorpherEffectVtable; -extern const EffectVtable DedicatedEffectVtable; -extern const EffectVtable ConvolutionEffectVtable; - #endif /* AL_EFFECTS_EFFECTS_H */ diff --git a/Engine/lib/openal-soft/al/effects/equalizer.cpp b/Engine/lib/openal-soft/al/effects/equalizer.cpp index 7dc703db6..ba7353078 100644 --- a/Engine/lib/openal-soft/al/effects/equalizer.cpp +++ b/Engine/lib/openal-soft/al/effects/equalizer.cpp @@ -9,6 +9,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -16,163 +17,133 @@ namespace { -void Equalizer_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultProps() noexcept +{ + EqualizerProps props{}; + props.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF; + props.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN; + props.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER; + props.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN; + props.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH; + props.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER; + props.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN; + props.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH; + props.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF; + props.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN; + return props; +} + +} // namespace + +const EffectProps EqualizerEffectProps{genDefaultProps()}; + +void EqualizerEffectHandler::SetParami(EqualizerProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } -void Equalizer_setParamiv(EffectProps*, ALenum param, const int*) +void EqualizerEffectHandler::SetParamiv(EqualizerProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param}; } -void Equalizer_setParamf(EffectProps *props, ALenum param, float val) +void EqualizerEffectHandler::SetParamf(EqualizerProps &props, ALenum param, float val) { switch(param) { case AL_EQUALIZER_LOW_GAIN: if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band gain out of range"}; - props->Equalizer.LowGain = val; + props.LowGain = val; break; case AL_EQUALIZER_LOW_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"}; - props->Equalizer.LowCutoff = val; + props.LowCutoff = val; break; case AL_EQUALIZER_MID1_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"}; - props->Equalizer.Mid1Gain = val; + props.Mid1Gain = val; break; case AL_EQUALIZER_MID1_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band center out of range"}; - props->Equalizer.Mid1Center = val; + props.Mid1Center = val; break; case AL_EQUALIZER_MID1_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band width out of range"}; - props->Equalizer.Mid1Width = val; + props.Mid1Width = val; break; case AL_EQUALIZER_MID2_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"}; - props->Equalizer.Mid2Gain = val; + props.Mid2Gain = val; break; case AL_EQUALIZER_MID2_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band center out of range"}; - props->Equalizer.Mid2Center = val; + props.Mid2Center = val; break; case AL_EQUALIZER_MID2_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band width out of range"}; - props->Equalizer.Mid2Width = val; + props.Mid2Width = val; break; case AL_EQUALIZER_HIGH_GAIN: if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band gain out of range"}; - props->Equalizer.HighGain = val; + props.HighGain = val; break; case AL_EQUALIZER_HIGH_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"}; - props->Equalizer.HighCutoff = val; + props.HighCutoff = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; } } -void Equalizer_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Equalizer_setParamf(props, param, vals[0]); } +void EqualizerEffectHandler::SetParamfv(EqualizerProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Equalizer_getParami(const EffectProps*, ALenum param, int*) +void EqualizerEffectHandler::GetParami(const EqualizerProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } -void Equalizer_getParamiv(const EffectProps*, ALenum param, int*) +void EqualizerEffectHandler::GetParamiv(const EqualizerProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param}; } -void Equalizer_getParamf(const EffectProps *props, ALenum param, float *val) +void EqualizerEffectHandler::GetParamf(const EqualizerProps &props, ALenum param, float *val) { switch(param) { - case AL_EQUALIZER_LOW_GAIN: - *val = props->Equalizer.LowGain; - break; - - case AL_EQUALIZER_LOW_CUTOFF: - *val = props->Equalizer.LowCutoff; - break; - - case AL_EQUALIZER_MID1_GAIN: - *val = props->Equalizer.Mid1Gain; - break; - - case AL_EQUALIZER_MID1_CENTER: - *val = props->Equalizer.Mid1Center; - break; - - case AL_EQUALIZER_MID1_WIDTH: - *val = props->Equalizer.Mid1Width; - break; - - case AL_EQUALIZER_MID2_GAIN: - *val = props->Equalizer.Mid2Gain; - break; - - case AL_EQUALIZER_MID2_CENTER: - *val = props->Equalizer.Mid2Center; - break; - - case AL_EQUALIZER_MID2_WIDTH: - *val = props->Equalizer.Mid2Width; - break; - - case AL_EQUALIZER_HIGH_GAIN: - *val = props->Equalizer.HighGain; - break; - - case AL_EQUALIZER_HIGH_CUTOFF: - *val = props->Equalizer.HighCutoff; - break; + case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; break; + case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; break; + case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; break; + case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; break; + case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; break; + case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; break; + case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; break; + case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; break; + case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; break; + case AL_EQUALIZER_HIGH_CUTOFF: *val = props.HighCutoff; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; } } -void Equalizer_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Equalizer_getParamf(props, param, vals); } +void EqualizerEffectHandler::GetParamfv(const EqualizerProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF; - props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN; - props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER; - props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN; - props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH; - props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER; - props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN; - props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH; - props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF; - props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Equalizer); - -const EffectProps EqualizerEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -319,91 +290,86 @@ template<> throw Exception{message}; } -template<> -bool EqualizerCommitter::commit(const EaxEffectProps &props) +bool EaxEqualizerCommitter::commit(const EAXEQUALIZERPROPERTIES &props) { - if(props.mType == mEaxProps.mType && mEaxProps.mEqualizer.lLowGain == props.mEqualizer.lLowGain - && mEaxProps.mEqualizer.flLowCutOff == props.mEqualizer.flLowCutOff - && mEaxProps.mEqualizer.lMid1Gain == props.mEqualizer.lMid1Gain - && mEaxProps.mEqualizer.flMid1Center == props.mEqualizer.flMid1Center - && mEaxProps.mEqualizer.flMid1Width == props.mEqualizer.flMid1Width - && mEaxProps.mEqualizer.lMid2Gain == props.mEqualizer.lMid2Gain - && mEaxProps.mEqualizer.flMid2Center == props.mEqualizer.flMid2Center - && mEaxProps.mEqualizer.flMid2Width == props.mEqualizer.flMid2Width - && mEaxProps.mEqualizer.lHighGain == props.mEqualizer.lHighGain - && mEaxProps.mEqualizer.flHighCutOff == props.mEqualizer.flHighCutOff) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - - mAlProps.Equalizer.LowGain = level_mb_to_gain(static_cast(props.mEqualizer.lLowGain)); - mAlProps.Equalizer.LowCutoff = props.mEqualizer.flLowCutOff; - mAlProps.Equalizer.Mid1Gain = level_mb_to_gain(static_cast(props.mEqualizer.lMid1Gain)); - mAlProps.Equalizer.Mid1Center = props.mEqualizer.flMid1Center; - mAlProps.Equalizer.Mid1Width = props.mEqualizer.flMid1Width; - mAlProps.Equalizer.Mid2Gain = level_mb_to_gain(static_cast(props.mEqualizer.lMid2Gain)); - mAlProps.Equalizer.Mid2Center = props.mEqualizer.flMid2Center; - mAlProps.Equalizer.Mid2Width = props.mEqualizer.flMid2Width; - mAlProps.Equalizer.HighGain = level_mb_to_gain(static_cast(props.mEqualizer.lHighGain)); - mAlProps.Equalizer.HighCutoff = props.mEqualizer.flHighCutOff; + mAlProps = [&]{ + EqualizerProps ret{}; + ret.LowGain = level_mb_to_gain(static_cast(props.lLowGain)); + ret.LowCutoff = props.flLowCutOff; + ret.Mid1Gain = level_mb_to_gain(static_cast(props.lMid1Gain)); + ret.Mid1Center = props.flMid1Center; + ret.Mid1Width = props.flMid1Width; + ret.Mid2Gain = level_mb_to_gain(static_cast(props.lMid2Gain)); + ret.Mid2Center = props.flMid2Center; + ret.Mid2Width = props.flMid2Width; + ret.HighGain = level_mb_to_gain(static_cast(props.lHighGain)); + ret.HighCutoff = props.flHighCutOff; + return ret; + }(); return true; } -template<> -void EqualizerCommitter::SetDefaults(EaxEffectProps &props) +void EaxEqualizerCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Equalizer; - props.mEqualizer.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN; - props.mEqualizer.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF; - props.mEqualizer.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN; - props.mEqualizer.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER; - props.mEqualizer.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH; - props.mEqualizer.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN; - props.mEqualizer.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER; - props.mEqualizer.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH; - props.mEqualizer.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN; - props.mEqualizer.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF; + static constexpr EAXEQUALIZERPROPERTIES defprops{[] + { + EAXEQUALIZERPROPERTIES ret{}; + ret.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN; + ret.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF; + ret.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN; + ret.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER; + ret.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH; + ret.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN; + ret.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER; + ret.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH; + ret.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN; + ret.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF; + return ret; + }()}; + props = defprops; } -template<> -void EqualizerCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxEqualizerCommitter::Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props) { switch(call.get_property_id()) { case EAXEQUALIZER_NONE: break; - case EAXEQUALIZER_ALLPARAMETERS: call.set_value(props.mEqualizer); break; - case EAXEQUALIZER_LOWGAIN: call.set_value(props.mEqualizer.lLowGain); break; - case EAXEQUALIZER_LOWCUTOFF: call.set_value(props.mEqualizer.flLowCutOff); break; - case EAXEQUALIZER_MID1GAIN: call.set_value(props.mEqualizer.lMid1Gain); break; - case EAXEQUALIZER_MID1CENTER: call.set_value(props.mEqualizer.flMid1Center); break; - case EAXEQUALIZER_MID1WIDTH: call.set_value(props.mEqualizer.flMid1Width); break; - case EAXEQUALIZER_MID2GAIN: call.set_value(props.mEqualizer.lMid2Gain); break; - case EAXEQUALIZER_MID2CENTER: call.set_value(props.mEqualizer.flMid2Center); break; - case EAXEQUALIZER_MID2WIDTH: call.set_value(props.mEqualizer.flMid2Width); break; - case EAXEQUALIZER_HIGHGAIN: call.set_value(props.mEqualizer.lHighGain); break; - case EAXEQUALIZER_HIGHCUTOFF: call.set_value(props.mEqualizer.flHighCutOff); break; + case EAXEQUALIZER_ALLPARAMETERS: call.set_value(props); break; + case EAXEQUALIZER_LOWGAIN: call.set_value(props.lLowGain); break; + case EAXEQUALIZER_LOWCUTOFF: call.set_value(props.flLowCutOff); break; + case EAXEQUALIZER_MID1GAIN: call.set_value(props.lMid1Gain); break; + case EAXEQUALIZER_MID1CENTER: call.set_value(props.flMid1Center); break; + case EAXEQUALIZER_MID1WIDTH: call.set_value(props.flMid1Width); break; + case EAXEQUALIZER_MID2GAIN: call.set_value(props.lMid2Gain); break; + case EAXEQUALIZER_MID2CENTER: call.set_value(props.flMid2Center); break; + case EAXEQUALIZER_MID2WIDTH: call.set_value(props.flMid2Width); break; + case EAXEQUALIZER_HIGHGAIN: call.set_value(props.lHighGain); break; + case EAXEQUALIZER_HIGHCUTOFF: call.set_value(props.flHighCutOff); break; default: fail_unknown_property_id(); } } -template<> -void EqualizerCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxEqualizerCommitter::Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props) { switch(call.get_property_id()) { case EAXEQUALIZER_NONE: break; - case EAXEQUALIZER_ALLPARAMETERS: defer(call, props.mEqualizer); break; - case EAXEQUALIZER_LOWGAIN: defer(call, props.mEqualizer.lLowGain); break; - case EAXEQUALIZER_LOWCUTOFF: defer(call, props.mEqualizer.flLowCutOff); break; - case EAXEQUALIZER_MID1GAIN: defer(call, props.mEqualizer.lMid1Gain); break; - case EAXEQUALIZER_MID1CENTER: defer(call, props.mEqualizer.flMid1Center); break; - case EAXEQUALIZER_MID1WIDTH: defer(call, props.mEqualizer.flMid1Width); break; - case EAXEQUALIZER_MID2GAIN: defer(call, props.mEqualizer.lMid2Gain); break; - case EAXEQUALIZER_MID2CENTER: defer(call, props.mEqualizer.flMid2Center); break; - case EAXEQUALIZER_MID2WIDTH: defer(call, props.mEqualizer.flMid2Width); break; - case EAXEQUALIZER_HIGHGAIN: defer(call, props.mEqualizer.lHighGain); break; - case EAXEQUALIZER_HIGHCUTOFF: defer(call, props.mEqualizer.flHighCutOff); break; + case EAXEQUALIZER_ALLPARAMETERS: defer(call, props); break; + case EAXEQUALIZER_LOWGAIN: defer(call, props.lLowGain); break; + case EAXEQUALIZER_LOWCUTOFF: defer(call, props.flLowCutOff); break; + case EAXEQUALIZER_MID1GAIN: defer(call, props.lMid1Gain); break; + case EAXEQUALIZER_MID1CENTER: defer(call, props.flMid1Center); break; + case EAXEQUALIZER_MID1WIDTH: defer(call, props.flMid1Width); break; + case EAXEQUALIZER_MID2GAIN: defer(call, props.lMid2Gain); break; + case EAXEQUALIZER_MID2CENTER: defer(call, props.flMid2Center); break; + case EAXEQUALIZER_MID2WIDTH: defer(call, props.flMid2Width); break; + case EAXEQUALIZER_HIGHGAIN: defer(call, props.lHighGain); break; + case EAXEQUALIZER_HIGHCUTOFF: defer(call, props.flHighCutOff); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/fshifter.cpp b/Engine/lib/openal-soft/al/effects/fshifter.cpp index 949db2035..a23213952 100644 --- a/Engine/lib/openal-soft/al/effects/fshifter.cpp +++ b/Engine/lib/openal-soft/al/effects/fshifter.cpp @@ -1,18 +1,19 @@ #include "config.h" +#include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/effects/base.h" -#include "aloptional.h" #include "effects.h" #ifdef ALSOFT_EAX #include #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -20,7 +21,7 @@ namespace { -al::optional DirectionFromEmum(ALenum value) +constexpr std::optional DirectionFromEmum(ALenum value) noexcept { switch(value) { @@ -28,9 +29,9 @@ al::optional DirectionFromEmum(ALenum value) case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return FShifterDirection::Up; case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return FShifterDirection::Off; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromDirection(FShifterDirection dir) +constexpr ALenum EnumFromDirection(FShifterDirection dir) { switch(dir) { @@ -41,31 +42,26 @@ ALenum EnumFromDirection(FShifterDirection dir) throw std::runtime_error{"Invalid direction: "+std::to_string(static_cast(dir))}; } -void Fshifter_setParamf(EffectProps *props, ALenum param, float val) +constexpr EffectProps genDefaultProps() noexcept { - switch(param) - { - case AL_FREQUENCY_SHIFTER_FREQUENCY: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) - throw effect_exception{AL_INVALID_VALUE, "Frequency shifter frequency out of range"}; - props->Fshifter.Frequency = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", - param}; - } + FshifterProps props{}; + props.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY; + props.LeftDirection = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION).value(); + props.RightDirection = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION).value(); + return props; } -void Fshifter_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Fshifter_setParamf(props, param, vals[0]); } -void Fshifter_setParami(EffectProps *props, ALenum param, int val) +} // namespace + +const EffectProps FshifterEffectProps{genDefaultProps()}; + +void FshifterEffectHandler::SetParami(FshifterProps &props, ALenum param, int val) { switch(param) { case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: if(auto diropt = DirectionFromEmum(val)) - props->Fshifter.LeftDirection = *diropt; + props.LeftDirection = *diropt; else throw effect_exception{AL_INVALID_VALUE, "Unsupported frequency shifter left direction: 0x%04x", val}; @@ -73,7 +69,7 @@ void Fshifter_setParami(EffectProps *props, ALenum param, int val) case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: if(auto diropt = DirectionFromEmum(val)) - props->Fshifter.RightDirection = *diropt; + props.RightDirection = *diropt; else throw effect_exception{AL_INVALID_VALUE, "Unsupported frequency shifter right direction: 0x%04x", val}; @@ -84,33 +80,17 @@ void Fshifter_setParami(EffectProps *props, ALenum param, int val) "Invalid frequency shifter integer property 0x%04x", param}; } } -void Fshifter_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Fshifter_setParami(props, param, vals[0]); } +void FshifterEffectHandler::SetParamiv(FshifterProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } -void Fshifter_getParami(const EffectProps *props, ALenum param, int *val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: - *val = EnumFromDirection(props->Fshifter.LeftDirection); - break; - case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: - *val = EnumFromDirection(props->Fshifter.RightDirection); - break; - default: - throw effect_exception{AL_INVALID_ENUM, - "Invalid frequency shifter integer property 0x%04x", param}; - } -} -void Fshifter_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Fshifter_getParami(props, param, vals); } - -void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val) +void FshifterEffectHandler::SetParamf(FshifterProps &props, ALenum param, float val) { switch(param) { case AL_FREQUENCY_SHIFTER_FREQUENCY: - *val = props->Fshifter.Frequency; + if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) + throw effect_exception{AL_INVALID_VALUE, "Frequency shifter frequency out of range"}; + props.Frequency = val; break; default: @@ -118,23 +98,44 @@ void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val) param}; } } -void Fshifter_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Fshifter_getParamf(props, param, vals); } +void FshifterEffectHandler::SetParamfv(FshifterProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -EffectProps genDefaultProps() noexcept +void FshifterEffectHandler::GetParami(const FshifterProps &props, ALenum param, int *val) { - EffectProps props{}; - props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY; - props.Fshifter.LeftDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION); - props.Fshifter.RightDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION); - return props; + switch(param) + { + case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: + *val = EnumFromDirection(props.LeftDirection); + break; + case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: + *val = EnumFromDirection(props.RightDirection); + break; + + default: + throw effect_exception{AL_INVALID_ENUM, + "Invalid frequency shifter integer property 0x%04x", param}; + } } +void FshifterEffectHandler::GetParamiv(const FshifterProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } -} // namespace +void FshifterEffectHandler::GetParamf(const FshifterProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_FREQUENCY_SHIFTER_FREQUENCY: + *val = props.Frequency; + break; -DEFINE_ALEFFECT_VTABLE(Fshifter); + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", + param}; + } +} +void FshifterEffectHandler::GetParamfv(const FshifterProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -const EffectProps FshifterEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -197,13 +198,9 @@ template<> throw Exception{message}; } -template<> -bool FrequencyShifterCommitter::commit(const EaxEffectProps &props) +bool EaxFrequencyShifterCommitter::commit(const EAXFREQUENCYSHIFTERPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && mEaxProps.mFrequencyShifter.flFrequency == props.mFrequencyShifter.flFrequency - && mEaxProps.mFrequencyShifter.ulLeftDirection == props.mFrequencyShifter.ulLeftDirection - && mEaxProps.mFrequencyShifter.ulRightDirection == props.mFrequencyShifter.ulRightDirection) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; @@ -217,46 +214,52 @@ bool FrequencyShifterCommitter::commit(const EaxEffectProps &props) return FShifterDirection::Off; }; - mAlProps.Fshifter.Frequency = props.mFrequencyShifter.flFrequency; - mAlProps.Fshifter.LeftDirection = get_direction(props.mFrequencyShifter.ulLeftDirection); - mAlProps.Fshifter.RightDirection = get_direction(props.mFrequencyShifter.ulRightDirection); + mAlProps = [&]{ + FshifterProps ret{}; + ret.Frequency = props.flFrequency; + ret.LeftDirection = get_direction(props.ulLeftDirection); + ret.RightDirection = get_direction(props.ulRightDirection); + return ret; + }(); return true; } -template<> -void FrequencyShifterCommitter::SetDefaults(EaxEffectProps &props) +void EaxFrequencyShifterCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::FrequencyShifter; - props.mFrequencyShifter.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY; - props.mFrequencyShifter.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION; - props.mFrequencyShifter.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION; + static constexpr EAXFREQUENCYSHIFTERPROPERTIES defprops{[] + { + EAXFREQUENCYSHIFTERPROPERTIES ret{}; + ret.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY; + ret.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION; + ret.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION; + return ret; + }()}; + props = defprops; } -template<> -void FrequencyShifterCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxFrequencyShifterCommitter::Get(const EaxCall &call, const EAXFREQUENCYSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXFREQUENCYSHIFTER_NONE: break; - case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.set_value(props.mFrequencyShifter); break; - case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value(props.mFrequencyShifter.flFrequency); break; - case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value(props.mFrequencyShifter.ulLeftDirection); break; - case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value(props.mFrequencyShifter.ulRightDirection); break; + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.set_value(props); break; + case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value(props.flFrequency); break; + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value(props.ulLeftDirection); break; + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value(props.ulRightDirection); break; default: fail_unknown_property_id(); } } -template<> -void FrequencyShifterCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxFrequencyShifterCommitter::Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXFREQUENCYSHIFTER_NONE: break; - case EAXFREQUENCYSHIFTER_ALLPARAMETERS: defer(call, props.mFrequencyShifter); break; - case EAXFREQUENCYSHIFTER_FREQUENCY: defer(call, props.mFrequencyShifter.flFrequency); break; - case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer(call, props.mFrequencyShifter.ulLeftDirection); break; - case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer(call, props.mFrequencyShifter.ulRightDirection); break; + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: defer(call, props); break; + case EAXFREQUENCYSHIFTER_FREQUENCY: defer(call, props.flFrequency); break; + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer(call, props.ulLeftDirection); break; + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer(call, props.ulRightDirection); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/modulator.cpp b/Engine/lib/openal-soft/al/effects/modulator.cpp index 5f37d08f3..856c85a97 100644 --- a/Engine/lib/openal-soft/al/effects/modulator.cpp +++ b/Engine/lib/openal-soft/al/effects/modulator.cpp @@ -1,18 +1,19 @@ #include "config.h" +#include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/effects/base.h" -#include "aloptional.h" #include "effects.h" #ifdef ALSOFT_EAX #include #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -20,7 +21,7 @@ namespace { -al::optional WaveformFromEmum(ALenum value) +constexpr std::optional WaveformFromEmum(ALenum value) noexcept { switch(value) { @@ -28,9 +29,9 @@ al::optional WaveformFromEmum(ALenum value) case AL_RING_MODULATOR_SAWTOOTH: return ModulatorWaveform::Sawtooth; case AL_RING_MODULATOR_SQUARE: return ModulatorWaveform::Square; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromWaveform(ModulatorWaveform type) +constexpr ALenum EnumFromWaveform(ModulatorWaveform type) { switch(type) { @@ -42,40 +43,31 @@ ALenum EnumFromWaveform(ModulatorWaveform type) std::to_string(static_cast(type))}; } -void Modulator_setParamf(EffectProps *props, ALenum param, float val) +constexpr EffectProps genDefaultProps() noexcept { - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) - throw effect_exception{AL_INVALID_VALUE, "Modulator frequency out of range: %f", val}; - props->Modulator.Frequency = val; - break; - - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) - throw effect_exception{AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: %f", val}; - props->Modulator.HighPassCutoff = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; - } + ModulatorProps props{}; + props.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY; + props.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF; + props.Waveform = WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM).value(); + return props; } -void Modulator_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Modulator_setParamf(props, param, vals[0]); } -void Modulator_setParami(EffectProps *props, ALenum param, int val) + +} // namespace + +const EffectProps ModulatorEffectProps{genDefaultProps()}; + +void ModulatorEffectHandler::SetParami(ModulatorProps &props, ALenum param, int val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - Modulator_setParamf(props, param, static_cast(val)); + SetParamf(props, param, static_cast(val)); break; case AL_RING_MODULATOR_WAVEFORM: if(auto formopt = WaveformFromEmum(val)) - props->Modulator.Waveform = *formopt; + props.Waveform = *formopt; else throw effect_exception{AL_INVALID_VALUE, "Invalid modulator waveform: 0x%04x", val}; break; @@ -85,62 +77,61 @@ void Modulator_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Modulator_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Modulator_setParami(props, param, vals[0]); } +void ModulatorEffectHandler::SetParamiv(ModulatorProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } -void Modulator_getParami(const EffectProps *props, ALenum param, int *val) +void ModulatorEffectHandler::SetParamf(ModulatorProps &props, ALenum param, float val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: - *val = static_cast(props->Modulator.Frequency); - break; - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - *val = static_cast(props->Modulator.HighPassCutoff); - break; - case AL_RING_MODULATOR_WAVEFORM: - *val = EnumFromWaveform(props->Modulator.Waveform); + if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) + throw effect_exception{AL_INVALID_VALUE, "Modulator frequency out of range: %f", val}; + props.Frequency = val; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", - param}; - } -} -void Modulator_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Modulator_getParami(props, param, vals); } -void Modulator_getParamf(const EffectProps *props, ALenum param, float *val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - *val = props->Modulator.Frequency; - break; case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - *val = props->Modulator.HighPassCutoff; + if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) + throw effect_exception{AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: %f", val}; + props.HighPassCutoff = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; } } -void Modulator_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Modulator_getParamf(props, param, vals); } +void ModulatorEffectHandler::SetParamfv(ModulatorProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -EffectProps genDefaultProps() noexcept +void ModulatorEffectHandler::GetParami(const ModulatorProps &props, ALenum param, int *val) { - EffectProps props{}; - props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY; - props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF; - props.Modulator.Waveform = *WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM); - return props; + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: *val = static_cast(props.Frequency); break; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast(props.HighPassCutoff); break; + case AL_RING_MODULATOR_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", + param}; + } } +void ModulatorEffectHandler::GetParamiv(const ModulatorProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void ModulatorEffectHandler::GetParamf(const ModulatorProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: *val = props.Frequency; break; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = props.HighPassCutoff; break; -} // namespace + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; + } +} +void ModulatorEffectHandler::GetParamfv(const ModulatorProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -DEFINE_ALEFFECT_VTABLE(Modulator); - -const EffectProps ModulatorEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -203,13 +194,9 @@ template<> throw Exception{message}; } -template<> -bool ModulatorCommitter::commit(const EaxEffectProps &props) +bool EaxModulatorCommitter::commit(const EAXRINGMODULATORPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && mEaxProps.mModulator.flFrequency == props.mModulator.flFrequency - && mEaxProps.mModulator.flHighPassCutOff == props.mModulator.flHighPassCutOff - && mEaxProps.mModulator.ulWaveform == props.mModulator.ulWaveform) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; @@ -225,46 +212,52 @@ bool ModulatorCommitter::commit(const EaxEffectProps &props) return ModulatorWaveform::Sinusoid; }; - mAlProps.Modulator.Frequency = props.mModulator.flFrequency; - mAlProps.Modulator.HighPassCutoff = props.mModulator.flHighPassCutOff; - mAlProps.Modulator.Waveform = get_waveform(props.mModulator.ulWaveform); + mAlProps = [&]{ + ModulatorProps ret{}; + ret.Frequency = props.flFrequency; + ret.HighPassCutoff = props.flHighPassCutOff; + ret.Waveform = get_waveform(props.ulWaveform); + return ret; + }(); return true; } -template<> -void ModulatorCommitter::SetDefaults(EaxEffectProps &props) +void EaxModulatorCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Modulator; - props.mModulator.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY; - props.mModulator.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF; - props.mModulator.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM; + static constexpr EAXRINGMODULATORPROPERTIES defprops{[] + { + EAXRINGMODULATORPROPERTIES ret{}; + ret.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY; + ret.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF; + ret.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM; + return ret; + }()}; + props = defprops; } -template<> -void ModulatorCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxModulatorCommitter::Get(const EaxCall &call, const EAXRINGMODULATORPROPERTIES &props) { switch(call.get_property_id()) { case EAXRINGMODULATOR_NONE: break; - case EAXRINGMODULATOR_ALLPARAMETERS: call.set_value(props.mModulator); break; - case EAXRINGMODULATOR_FREQUENCY: call.set_value(props.mModulator.flFrequency); break; - case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value(props.mModulator.flHighPassCutOff); break; - case EAXRINGMODULATOR_WAVEFORM: call.set_value(props.mModulator.ulWaveform); break; + case EAXRINGMODULATOR_ALLPARAMETERS: call.set_value(props); break; + case EAXRINGMODULATOR_FREQUENCY: call.set_value(props.flFrequency); break; + case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value(props.flHighPassCutOff); break; + case EAXRINGMODULATOR_WAVEFORM: call.set_value(props.ulWaveform); break; default: fail_unknown_property_id(); } } -template<> -void ModulatorCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxModulatorCommitter::Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props) { - switch (call.get_property_id()) + switch(call.get_property_id()) { case EAXRINGMODULATOR_NONE: break; - case EAXRINGMODULATOR_ALLPARAMETERS: defer(call, props.mModulator); break; - case EAXRINGMODULATOR_FREQUENCY: defer(call, props.mModulator.flFrequency); break; - case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer(call, props.mModulator.flHighPassCutOff); break; - case EAXRINGMODULATOR_WAVEFORM: defer(call, props.mModulator.ulWaveform); break; + case EAXRINGMODULATOR_ALLPARAMETERS: defer(call, props); break; + case EAXRINGMODULATOR_FREQUENCY: defer(call, props.flFrequency); break; + case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer(call, props.flHighPassCutOff); break; + case EAXRINGMODULATOR_WAVEFORM: defer(call, props.ulWaveform); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/null.cpp b/Engine/lib/openal-soft/al/effects/null.cpp index 0bbc183a3..2fa9cb6d4 100644 --- a/Engine/lib/openal-soft/al/effects/null.cpp +++ b/Engine/lib/openal-soft/al/effects/null.cpp @@ -8,94 +8,92 @@ #include "effects.h" #ifdef ALSOFT_EAX +#include "al/eax/effect.h" #include "al/eax/exception.h" #endif // ALSOFT_EAX namespace { -void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/) +constexpr EffectProps genDefaultProps() noexcept { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } -} -void Null_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ - switch(param) - { - default: - Null_setParami(props, param, vals[0]); - } -} -void Null_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } -} -void Null_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ - switch(param) - { - default: - Null_setParamf(props, param, vals[0]); - } -} - -void Null_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } -} -void Null_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ - switch(param) - { - default: - Null_getParami(props, param, vals); - } -} -void Null_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } -} -void Null_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ - switch(param) - { - default: - Null_getParamf(props, param, vals); - } -} - -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - return props; + return std::monostate{}; } } // namespace -DEFINE_ALEFFECT_VTABLE(Null); - const EffectProps NullEffectProps{genDefaultProps()}; +void NullEffectHandler::SetParami(std::monostate& /*props*/, ALenum param, int /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void NullEffectHandler::SetParamiv(std::monostate &props, ALenum param, const int *vals) +{ + switch(param) + { + default: + SetParami(props, param, *vals); + } +} +void NullEffectHandler::SetParamf(std::monostate& /*props*/, ALenum param, float /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void NullEffectHandler::SetParamfv(std::monostate &props, ALenum param, const float *vals) +{ + switch(param) + { + default: + SetParamf(props, param, *vals); + } +} + +void NullEffectHandler::GetParami(const std::monostate& /*props*/, ALenum param, int* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void NullEffectHandler::GetParamiv(const std::monostate &props, ALenum param, int *vals) +{ + switch(param) + { + default: + GetParami(props, param, vals); + } +} +void NullEffectHandler::GetParamf(const std::monostate& /*props*/, ALenum param, float* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void NullEffectHandler::GetParamfv(const std::monostate &props, ALenum param, float *vals) +{ + switch(param) + { + default: + GetParamf(props, param, vals); + } +} + #ifdef ALSOFT_EAX namespace { @@ -117,30 +115,26 @@ template<> throw Exception{message}; } -template<> -bool NullCommitter::commit(const EaxEffectProps &props) +bool EaxNullCommitter::commit(const std::monostate &props) { - const bool ret{props.mType != mEaxProps.mType}; + const bool ret{std::holds_alternative(mEaxProps)}; mEaxProps = props; + mAlProps = std::monostate{}; return ret; } -template<> -void NullCommitter::SetDefaults(EaxEffectProps &props) +void EaxNullCommitter::SetDefaults(EaxEffectProps &props) { - props = EaxEffectProps{}; - props.mType = EaxEffectType::None; + props = std::monostate{}; } -template<> -void NullCommitter::Get(const EaxCall &call, const EaxEffectProps&) +void EaxNullCommitter::Get(const EaxCall &call, const std::monostate&) { if(call.get_property_id() != 0) fail_unknown_property_id(); } -template<> -void NullCommitter::Set(const EaxCall &call, EaxEffectProps&) +void EaxNullCommitter::Set(const EaxCall &call, std::monostate&) { if(call.get_property_id() != 0) fail_unknown_property_id(); diff --git a/Engine/lib/openal-soft/al/effects/pshifter.cpp b/Engine/lib/openal-soft/al/effects/pshifter.cpp index 634eb1866..c408eddce 100644 --- a/Engine/lib/openal-soft/al/effects/pshifter.cpp +++ b/Engine/lib/openal-soft/al/effects/pshifter.cpp @@ -9,6 +9,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -16,28 +17,32 @@ namespace { -void Pshifter_setParamf(EffectProps*, ALenum param, float) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } -void Pshifter_setParamfv(EffectProps*, ALenum param, const float*) +constexpr EffectProps genDefaultProps() noexcept { - throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", - param}; + PshifterProps props{}; + props.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; + props.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE; + return props; } -void Pshifter_setParami(EffectProps *props, ALenum param, int val) +} // namespace + +const EffectProps PshifterEffectProps{genDefaultProps()}; + +void PshifterEffectHandler::SetParami(PshifterProps &props, ALenum param, int val) { switch(param) { case AL_PITCH_SHIFTER_COARSE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) throw effect_exception{AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"}; - props->Pshifter.CoarseTune = val; + props.CoarseTune = val; break; case AL_PITCH_SHIFTER_FINE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) throw effect_exception{AL_INVALID_VALUE, "Pitch shifter fine tune out of range"}; - props->Pshifter.FineTune = val; + props.FineTune = val; break; default: @@ -45,49 +50,40 @@ void Pshifter_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Pshifter_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Pshifter_setParami(props, param, vals[0]); } +void PshifterEffectHandler::SetParamiv(PshifterProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } -void Pshifter_getParami(const EffectProps *props, ALenum param, int *val) +void PshifterEffectHandler::SetParamf(PshifterProps&, ALenum param, float) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } +void PshifterEffectHandler::SetParamfv(PshifterProps&, ALenum param, const float*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", + param}; +} + +void PshifterEffectHandler::GetParami(const PshifterProps &props, ALenum param, int *val) { switch(param) { - case AL_PITCH_SHIFTER_COARSE_TUNE: - *val = props->Pshifter.CoarseTune; - break; - case AL_PITCH_SHIFTER_FINE_TUNE: - *val = props->Pshifter.FineTune; - break; + case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; break; + case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.FineTune; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param}; } } -void Pshifter_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Pshifter_getParami(props, param, vals); } +void PshifterEffectHandler::GetParamiv(const PshifterProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } -void Pshifter_getParamf(const EffectProps*, ALenum param, float*) +void PshifterEffectHandler::GetParamf(const PshifterProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } -void Pshifter_getParamfv(const EffectProps*, ALenum param, float*) +void PshifterEffectHandler::GetParamfv(const PshifterProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param}; } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; - props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Pshifter); - -const EffectProps PshifterEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -138,52 +134,48 @@ template<> throw Exception{message}; } -template<> -bool PitchShifterCommitter::commit(const EaxEffectProps &props) +bool EaxPitchShifterCommitter::commit(const EAXPITCHSHIFTERPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && mEaxProps.mPitchShifter.lCoarseTune == props.mPitchShifter.lCoarseTune - && mEaxProps.mPitchShifter.lFineTune == props.mPitchShifter.lFineTune) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - - mAlProps.Pshifter.CoarseTune = static_cast(mEaxProps.mPitchShifter.lCoarseTune); - mAlProps.Pshifter.FineTune = static_cast(mEaxProps.mPitchShifter.lFineTune); + mAlProps = [&]{ + PshifterProps ret{}; + ret.CoarseTune = static_cast(props.lCoarseTune); + ret.FineTune = static_cast(props.lFineTune); + return ret; + }(); return true; } -template<> -void PitchShifterCommitter::SetDefaults(EaxEffectProps &props) +void EaxPitchShifterCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::PitchShifter; - props.mPitchShifter.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE; - props.mPitchShifter.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE; + props = EAXPITCHSHIFTERPROPERTIES{EAXPITCHSHIFTER_DEFAULTCOARSETUNE, + EAXPITCHSHIFTER_DEFAULTFINETUNE}; } -template<> -void PitchShifterCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxPitchShifterCommitter::Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXPITCHSHIFTER_NONE: break; - case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value(props.mPitchShifter); break; - case EAXPITCHSHIFTER_COARSETUNE: call.set_value(props.mPitchShifter.lCoarseTune); break; - case EAXPITCHSHIFTER_FINETUNE: call.set_value(props.mPitchShifter.lFineTune); break; + case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value(props); break; + case EAXPITCHSHIFTER_COARSETUNE: call.set_value(props.lCoarseTune); break; + case EAXPITCHSHIFTER_FINETUNE: call.set_value(props.lFineTune); break; default: fail_unknown_property_id(); } } -template<> -void PitchShifterCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxPitchShifterCommitter::Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXPITCHSHIFTER_NONE: break; - case EAXPITCHSHIFTER_ALLPARAMETERS: defer(call, props.mPitchShifter); break; - case EAXPITCHSHIFTER_COARSETUNE: defer(call, props.mPitchShifter.lCoarseTune); break; - case EAXPITCHSHIFTER_FINETUNE: defer(call, props.mPitchShifter.lFineTune); break; + case EAXPITCHSHIFTER_ALLPARAMETERS: defer(call, props); break; + case EAXPITCHSHIFTER_COARSETUNE: defer(call, props.lCoarseTune); break; + case EAXPITCHSHIFTER_FINETUNE: defer(call, props.lFineTune); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/reverb.cpp b/Engine/lib/openal-soft/al/effects/reverb.cpp index 440d7b4e5..7954e6179 100644 --- a/Engine/lib/openal-soft/al/effects/reverb.cpp +++ b/Engine/lib/openal-soft/al/effects/reverb.cpp @@ -1,18 +1,24 @@ #include "config.h" +#include +#include #include +#include #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/effects/base.h" #include "effects.h" #ifdef ALSOFT_EAX #include -#include "alnumeric.h" -#include "AL/efx-presets.h" +#include "al/eax/api.h" +#include "al/eax/call.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -20,14 +26,80 @@ namespace { -void Reverb_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultProps() noexcept +{ + ReverbProps props{}; + props.Density = AL_EAXREVERB_DEFAULT_DENSITY; + props.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; + props.Gain = AL_EAXREVERB_DEFAULT_GAIN; + props.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; + props.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; + props.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; + props.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; + props.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; + props.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; + props.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; + props.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; + props.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; + props.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; + props.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; + props.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; + props.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; + props.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; + props.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; + props.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; + props.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; + props.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; + return props; +} + +constexpr EffectProps genDefaultStdProps() noexcept +{ + ReverbProps props{}; + props.Density = AL_REVERB_DEFAULT_DENSITY; + props.Diffusion = AL_REVERB_DEFAULT_DIFFUSION; + props.Gain = AL_REVERB_DEFAULT_GAIN; + props.GainHF = AL_REVERB_DEFAULT_GAINHF; + props.GainLF = 1.0f; + props.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME; + props.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO; + props.DecayLFRatio = 1.0f; + props.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN; + props.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY; + props.ReflectionsPan = {0.0f, 0.0f, 0.0f}; + props.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN; + props.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY; + props.LateReverbPan = {0.0f, 0.0f, 0.0f}; + props.EchoTime = 0.25f; + props.EchoDepth = 0.0f; + props.ModulationTime = 0.25f; + props.ModulationDepth = 0.0f; + props.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF; + props.HFReference = 5000.0f; + props.LFReference = 250.0f; + props.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; + props.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT; + return props; +} + +} // namespace + +const EffectProps ReverbEffectProps{genDefaultProps()}; + +void ReverbEffectHandler::SetParami(ReverbProps &props, ALenum param, int val) { switch(param) { case AL_EAXREVERB_DECAY_HFLIMIT: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; - props->Reverb.DecayHFLimit = val != AL_FALSE; + props.DecayHFLimit = val != AL_FALSE; break; default: @@ -35,167 +107,233 @@ void Reverb_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Reverb_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Reverb_setParami(props, param, vals[0]); } -void Reverb_setParamf(EffectProps *props, ALenum param, float val) +void ReverbEffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void ReverbEffectHandler::SetParamf(ReverbProps &props, ALenum param, float val) { switch(param) { case AL_EAXREVERB_DENSITY: if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; - props->Reverb.Density = val; + props.Density = val; break; case AL_EAXREVERB_DIFFUSION: if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; - props->Reverb.Diffusion = val; + props.Diffusion = val; break; case AL_EAXREVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; - props->Reverb.Gain = val; + props.Gain = val; break; case AL_EAXREVERB_GAINHF: if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; - props->Reverb.GainHF = val; + props.GainHF = val; break; case AL_EAXREVERB_GAINLF: if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainlf out of range"}; - props->Reverb.GainLF = val; + props.GainLF = val; break; case AL_EAXREVERB_DECAY_TIME: if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; - props->Reverb.DecayTime = val; + props.DecayTime = val; break; case AL_EAXREVERB_DECAY_HFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; - props->Reverb.DecayHFRatio = val; + props.DecayHFRatio = val; break; case AL_EAXREVERB_DECAY_LFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"}; - props->Reverb.DecayLFRatio = val; + props.DecayLFRatio = val; break; case AL_EAXREVERB_REFLECTIONS_GAIN: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; - props->Reverb.ReflectionsGain = val; + props.ReflectionsGain = val; break; case AL_EAXREVERB_REFLECTIONS_DELAY: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; - props->Reverb.ReflectionsDelay = val; + props.ReflectionsDelay = val; break; case AL_EAXREVERB_LATE_REVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; - props->Reverb.LateReverbGain = val; + props.LateReverbGain = val; break; case AL_EAXREVERB_LATE_REVERB_DELAY: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; - props->Reverb.LateReverbDelay = val; + props.LateReverbDelay = val; break; case AL_EAXREVERB_ECHO_TIME: if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo time out of range"}; - props->Reverb.EchoTime = val; + props.EchoTime = val; break; case AL_EAXREVERB_ECHO_DEPTH: if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo depth out of range"}; - props->Reverb.EchoDepth = val; + props.EchoDepth = val; break; case AL_EAXREVERB_MODULATION_TIME: if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation time out of range"}; - props->Reverb.ModulationTime = val; + props.ModulationTime = val; break; case AL_EAXREVERB_MODULATION_DEPTH: if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"}; - props->Reverb.ModulationDepth = val; + props.ModulationDepth = val; break; case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; - props->Reverb.AirAbsorptionGainHF = val; + props.AirAbsorptionGainHF = val; break; case AL_EAXREVERB_HFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb hfreference out of range"}; - props->Reverb.HFReference = val; + props.HFReference = val; break; case AL_EAXREVERB_LFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb lfreference out of range"}; - props->Reverb.LFReference = val; + props.LFReference = val; break; case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; - props->Reverb.RoomRolloffFactor = val; + props.RoomRolloffFactor = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; } } -void Reverb_setParamfv(EffectProps *props, ALenum param, const float *vals) +void ReverbEffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *vals) { + static constexpr auto finite_checker = [](float f) -> bool { return std::isfinite(f); }; + al::span values; switch(param) { case AL_EAXREVERB_REFLECTIONS_PAN: - if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) + values = {vals, 3_uz}; + if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"}; - props->Reverb.ReflectionsPan[0] = vals[0]; - props->Reverb.ReflectionsPan[1] = vals[1]; - props->Reverb.ReflectionsPan[2] = vals[2]; + std::copy(values.cbegin(), values.cend(), props.ReflectionsPan.begin()); break; case AL_EAXREVERB_LATE_REVERB_PAN: - if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) + values = {vals, 3_uz}; + if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"}; - props->Reverb.LateReverbPan[0] = vals[0]; - props->Reverb.LateReverbPan[1] = vals[1]; - props->Reverb.LateReverbPan[2] = vals[2]; + std::copy(values.cbegin(), values.cend(), props.LateReverbPan.begin()); break; default: - Reverb_setParamf(props, param, vals[0]); + SetParamf(props, param, *vals); break; } } -void Reverb_getParami(const EffectProps *props, ALenum param, int *val) +void ReverbEffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val) { switch(param) { - case AL_EAXREVERB_DECAY_HFLIMIT: - *val = props->Reverb.DecayHFLimit; + case AL_EAXREVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break; + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", + param}; + } +} +void ReverbEffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void ReverbEffectHandler::GetParamf(const ReverbProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_EAXREVERB_DENSITY: *val = props.Density; break; + case AL_EAXREVERB_DIFFUSION: *val = props.Diffusion; break; + case AL_EAXREVERB_GAIN: *val = props.Gain; break; + case AL_EAXREVERB_GAINHF: *val = props.GainHF; break; + case AL_EAXREVERB_GAINLF: *val = props.GainLF; break; + case AL_EAXREVERB_DECAY_TIME: *val = props.DecayTime; break; + case AL_EAXREVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break; + case AL_EAXREVERB_DECAY_LFRATIO: *val = props.DecayLFRatio; break; + case AL_EAXREVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break; + case AL_EAXREVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break; + case AL_EAXREVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break; + case AL_EAXREVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break; + case AL_EAXREVERB_ECHO_TIME: *val = props.EchoTime; break; + case AL_EAXREVERB_ECHO_DEPTH: *val = props.EchoDepth; break; + case AL_EAXREVERB_MODULATION_TIME: *val = props.ModulationTime; break; + case AL_EAXREVERB_MODULATION_DEPTH: *val = props.ModulationDepth; break; + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break; + case AL_EAXREVERB_HFREFERENCE: *val = props.HFReference; break; + case AL_EAXREVERB_LFREFERENCE: *val = props.LFReference; break; + case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + } +} +void ReverbEffectHandler::GetParamfv(const ReverbProps &props, ALenum param, float *vals) +{ + al::span values; + switch(param) + { + case AL_EAXREVERB_REFLECTIONS_PAN: + values = {vals, 3_uz}; + std::copy(props.ReflectionsPan.cbegin(), props.ReflectionsPan.cend(), values.begin()); + break; + case AL_EAXREVERB_LATE_REVERB_PAN: + values = {vals, 3_uz}; + std::copy(props.LateReverbPan.cbegin(), props.LateReverbPan.cend(), values.begin()); + break; + + default: + GetParamf(props, param, vals); + break; + } +} + + +const EffectProps StdReverbEffectProps{genDefaultStdProps()}; + +void StdReverbEffectHandler::SetParami(ReverbProps &props, ALenum param, int val) +{ + switch(param) + { + case AL_REVERB_DECAY_HFLIMIT: + if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; + props.DecayHFLimit = val != AL_FALSE; break; default: @@ -203,365 +341,127 @@ void Reverb_getParami(const EffectProps *props, ALenum param, int *val) param}; } } -void Reverb_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Reverb_getParami(props, param, vals); } -void Reverb_getParamf(const EffectProps *props, ALenum param, float *val) +void StdReverbEffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void StdReverbEffectHandler::SetParamf(ReverbProps &props, ALenum param, float val) { switch(param) { - case AL_EAXREVERB_DENSITY: - *val = props->Reverb.Density; + case AL_REVERB_DENSITY: + if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; + props.Density = val; break; - case AL_EAXREVERB_DIFFUSION: - *val = props->Reverb.Diffusion; + case AL_REVERB_DIFFUSION: + if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; + props.Diffusion = val; break; - case AL_EAXREVERB_GAIN: - *val = props->Reverb.Gain; + case AL_REVERB_GAIN: + if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; + props.Gain = val; break; - case AL_EAXREVERB_GAINHF: - *val = props->Reverb.GainHF; + case AL_REVERB_GAINHF: + if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; + props.GainHF = val; break; - case AL_EAXREVERB_GAINLF: - *val = props->Reverb.GainLF; + case AL_REVERB_DECAY_TIME: + if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; + props.DecayTime = val; break; - case AL_EAXREVERB_DECAY_TIME: - *val = props->Reverb.DecayTime; + case AL_REVERB_DECAY_HFRATIO: + if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; + props.DecayHFRatio = val; break; - case AL_EAXREVERB_DECAY_HFRATIO: - *val = props->Reverb.DecayHFRatio; + case AL_REVERB_REFLECTIONS_GAIN: + if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; + props.ReflectionsGain = val; break; - case AL_EAXREVERB_DECAY_LFRATIO: - *val = props->Reverb.DecayLFRatio; + case AL_REVERB_REFLECTIONS_DELAY: + if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; + props.ReflectionsDelay = val; break; - case AL_EAXREVERB_REFLECTIONS_GAIN: - *val = props->Reverb.ReflectionsGain; + case AL_REVERB_LATE_REVERB_GAIN: + if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; + props.LateReverbGain = val; break; - case AL_EAXREVERB_REFLECTIONS_DELAY: - *val = props->Reverb.ReflectionsDelay; + case AL_REVERB_LATE_REVERB_DELAY: + if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; + props.LateReverbDelay = val; break; - case AL_EAXREVERB_LATE_REVERB_GAIN: - *val = props->Reverb.LateReverbGain; + case AL_REVERB_AIR_ABSORPTION_GAINHF: + if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; + props.AirAbsorptionGainHF = val; break; - case AL_EAXREVERB_LATE_REVERB_DELAY: - *val = props->Reverb.LateReverbDelay; - 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_AIR_ABSORPTION_GAINHF: - *val = props->Reverb.AirAbsorptionGainHF; - 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; + case AL_REVERB_ROOM_ROLLOFF_FACTOR: + if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; + props.RoomRolloffFactor = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; } } -void Reverb_getParamfv(const EffectProps *props, ALenum param, float *vals) +void StdReverbEffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } + +void StdReverbEffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val) { 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; - + case AL_REVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break; default: - Reverb_getParamf(props, param, vals); - break; + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", + param}; } } - -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY; - props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; - props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN; - props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; - props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; - props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; - props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; - props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; - props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; - props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; - props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; - props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; - props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; - props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; - props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; - props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; - props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; - props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; - props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; - props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; - props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; - return props; -} - - -void StdReverb_setParami(EffectProps *props, ALenum param, int val) +void StdReverbEffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void StdReverbEffectHandler::GetParamf(const ReverbProps &props, ALenum param, float *val) { switch(param) { - case AL_REVERB_DECAY_HFLIMIT: - if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) - throw effect_exception{AL_INVALID_VALUE, "Reverb decay hflimit out of range"}; - props->Reverb.DecayHFLimit = val != AL_FALSE; - break; + case AL_REVERB_DENSITY: *val = props.Density; break; + case AL_REVERB_DIFFUSION: *val = props.Diffusion; break; + case AL_REVERB_GAIN: *val = props.Gain; break; + case AL_REVERB_GAINHF: *val = props.GainHF; break; + case AL_REVERB_DECAY_TIME: *val = props.DecayTime; break; + case AL_REVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break; + case AL_REVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break; + case AL_REVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break; + case AL_REVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break; + case AL_REVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break; + case AL_REVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break; + case AL_REVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; break; default: - throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param}; + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; } } -void StdReverb_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ StdReverb_setParami(props, param, vals[0]); } -void StdReverb_setParamf(EffectProps *props, ALenum param, float val) -{ - switch(param) - { - case AL_REVERB_DENSITY: - if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) - throw effect_exception{AL_INVALID_VALUE, "Reverb density out of range"}; - props->Reverb.Density = val; - break; +void StdReverbEffectHandler::GetParamfv(const ReverbProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } - case AL_REVERB_DIFFUSION: - if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) - throw effect_exception{AL_INVALID_VALUE, "Reverb diffusion out of range"}; - props->Reverb.Diffusion = val; - break; - - case AL_REVERB_GAIN: - if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Reverb gain out of range"}; - props->Reverb.Gain = val; - break; - - case AL_REVERB_GAINHF: - if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "Reverb gainhf out of range"}; - props->Reverb.GainHF = val; - break; - - case AL_REVERB_DECAY_TIME: - if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) - throw effect_exception{AL_INVALID_VALUE, "Reverb decay time out of range"}; - props->Reverb.DecayTime = val; - break; - - case AL_REVERB_DECAY_HFRATIO: - if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) - throw effect_exception{AL_INVALID_VALUE, "Reverb decay hfratio out of range"}; - props->Reverb.DecayHFRatio = val; - break; - - case AL_REVERB_REFLECTIONS_GAIN: - if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Reverb reflections gain out of range"}; - props->Reverb.ReflectionsGain = val; - break; - - case AL_REVERB_REFLECTIONS_DELAY: - if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "Reverb reflections delay out of range"}; - props->Reverb.ReflectionsDelay = val; - break; - - case AL_REVERB_LATE_REVERB_GAIN: - if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Reverb late reverb gain out of range"}; - props->Reverb.LateReverbGain = val; - break; - - case AL_REVERB_LATE_REVERB_DELAY: - if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "Reverb late reverb delay out of range"}; - props->Reverb.LateReverbDelay = val; - break; - - case AL_REVERB_AIR_ABSORPTION_GAINHF: - if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "Reverb air absorption gainhf out of range"}; - props->Reverb.AirAbsorptionGainHF = val; - break; - - case AL_REVERB_ROOM_ROLLOFF_FACTOR: - if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) - throw effect_exception{AL_INVALID_VALUE, "Reverb room rolloff factor out of range"}; - props->Reverb.RoomRolloffFactor = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param}; - } -} -void StdReverb_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ StdReverb_setParamf(props, param, vals[0]); } - -void StdReverb_getParami(const EffectProps *props, ALenum param, int *val) -{ - switch(param) - { - case AL_REVERB_DECAY_HFLIMIT: - *val = props->Reverb.DecayHFLimit; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param}; - } -} -void StdReverb_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ StdReverb_getParami(props, param, vals); } -void StdReverb_getParamf(const EffectProps *props, ALenum param, float *val) -{ - 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: - throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param}; - } -} -void StdReverb_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ StdReverb_getParamf(props, param, vals); } - -EffectProps genDefaultStdProps() noexcept -{ - EffectProps props{}; - props.Reverb.Density = AL_REVERB_DEFAULT_DENSITY; - props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION; - props.Reverb.Gain = AL_REVERB_DEFAULT_GAIN; - props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF; - props.Reverb.GainLF = 1.0f; - props.Reverb.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME; - props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO; - props.Reverb.DecayLFRatio = 1.0f; - props.Reverb.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN; - props.Reverb.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY; - props.Reverb.ReflectionsPan[0] = 0.0f; - props.Reverb.ReflectionsPan[1] = 0.0f; - props.Reverb.ReflectionsPan[2] = 0.0f; - props.Reverb.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN; - props.Reverb.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY; - props.Reverb.LateReverbPan[0] = 0.0f; - props.Reverb.LateReverbPan[1] = 0.0f; - props.Reverb.LateReverbPan[2] = 0.0f; - props.Reverb.EchoTime = 0.25f; - props.Reverb.EchoDepth = 0.0f; - props.Reverb.ModulationTime = 0.25f; - props.Reverb.ModulationDepth = 0.0f; - props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF; - props.Reverb.HFReference = 5000.0f; - props.Reverb.LFReference = 250.0f; - props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; - props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Reverb); - -const EffectProps ReverbEffectProps{genDefaultProps()}; - -DEFINE_ALEFFECT_VTABLE(StdReverb); - -const EffectProps StdReverbEffectProps{genDefaultStdProps()}; #ifdef ALSOFT_EAX namespace { @@ -945,7 +845,7 @@ struct EnvironmentSizeDeferrer2 { if ((props.dwFlags & EAX2LISTENERFLAGS_DECAYTIMESCALE) != 0) { - props.flDecayTime = clamp( + props.flDecayTime = std::clamp( props.flDecayTime * scale, EAXREVERB_MINDECAYTIME, EAXREVERB_MAXDECAYTIME); @@ -954,7 +854,7 @@ struct EnvironmentSizeDeferrer2 { if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSSCALE) != 0 && (props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0) { - props.lReflections = clamp( + props.lReflections = std::clamp( props.lReflections - static_cast(gain_to_level_mb(scale)), EAXREVERB_MINREFLECTIONS, EAXREVERB_MAXREFLECTIONS); @@ -962,7 +862,7 @@ struct EnvironmentSizeDeferrer2 { if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0) { - props.flReflectionsDelay = clamp( + props.flReflectionsDelay = std::clamp( props.flReflectionsDelay * scale, EAXREVERB_MINREFLECTIONSDELAY, EAXREVERB_MAXREFLECTIONSDELAY); @@ -972,7 +872,7 @@ struct EnvironmentSizeDeferrer2 { { const auto log_scalar = ((props.dwFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; - props.lReverb = clamp( + props.lReverb = std::clamp( props.lReverb - static_cast(std::log10(scale) * log_scalar), EAXREVERB_MINREVERB, EAXREVERB_MAXREVERB); @@ -980,7 +880,7 @@ struct EnvironmentSizeDeferrer2 { if ((props.dwFlags & EAX2LISTENERFLAGS_REVERBDELAYSCALE) != 0) { - props.flReverbDelay = clamp( + props.flReverbDelay = std::clamp( props.flReverbDelay * scale, EAXREVERB_MINREVERBDELAY, EAXREVERB_MAXREVERBDELAY); @@ -1015,7 +915,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) { - props.flDecayTime = clamp( + props.flDecayTime = std::clamp( props.flDecayTime * scale, EAXREVERB_MINDECAYTIME, EAXREVERB_MAXDECAYTIME); @@ -1024,7 +924,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0 && (props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) { - props.lReflections = clamp( + props.lReflections = std::clamp( props.lReflections - static_cast(gain_to_level_mb(scale)), EAXREVERB_MINREFLECTIONS, EAXREVERB_MAXREFLECTIONS); @@ -1032,7 +932,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) { - props.flReflectionsDelay = clamp( + props.flReflectionsDelay = std::clamp( props.flReflectionsDelay * scale, EAXREVERB_MINREFLECTIONSDELAY, EAXREVERB_MAXREFLECTIONSDELAY); @@ -1041,7 +941,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0) { const auto log_scalar = ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; - props.lReverb = clamp( + props.lReverb = std::clamp( props.lReverb - static_cast(std::log10(scale) * log_scalar), EAXREVERB_MINREVERB, EAXREVERB_MAXREVERB); @@ -1049,7 +949,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0) { - props.flReverbDelay = clamp( + props.flReverbDelay = std::clamp( props.flReverbDelay * scale, EAXREVERB_MINREVERBDELAY, EAXREVERB_MAXREVERBDELAY); @@ -1057,7 +957,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0) { - props.flEchoTime = clamp( + props.flEchoTime = std::clamp( props.flEchoTime * scale, EAXREVERB_MINECHOTIME, EAXREVERB_MAXECHOTIME); @@ -1065,7 +965,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0) { - props.flModulationTime = clamp( + props.flModulationTime = std::clamp( props.flModulationTime * scale, EAXREVERB_MINMODULATIONTIME, EAXREVERB_MAXMODULATIONTIME); @@ -1086,111 +986,87 @@ struct EaxReverbCommitter::Exception : public EaxReverbEffectException throw Exception{message}; } -void EaxReverbCommitter::translate(const EAX_REVERBPROPERTIES& src, EaxEffectProps& dst) noexcept +void EaxReverbCommitter::translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept { assert(src.environment <= EAX1REVERB_MAXENVIRONMENT); - dst.mType = EaxEffectType::Reverb; - dst.mReverb = EAXREVERB_PRESETS[src.environment]; - dst.mReverb.flDecayTime = src.fDecayTime_sec; - dst.mReverb.flDecayHFRatio = src.fDamping; - dst.mReverb.lReverb = mini(static_cast(gain_to_level_mb(src.fVolume)), 0); + dst = EAXREVERB_PRESETS[src.environment]; + dst.flDecayTime = src.fDecayTime_sec; + dst.flDecayHFRatio = src.fDamping; + dst.lReverb = static_cast(std::min(gain_to_level_mb(src.fVolume), 0.0f)); } -void EaxReverbCommitter::translate(const EAX20LISTENERPROPERTIES& src, EaxEffectProps& dst) noexcept +void EaxReverbCommitter::translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept { assert(src.dwEnvironment <= EAX1REVERB_MAXENVIRONMENT); - const auto& env = EAXREVERB_PRESETS[src.dwEnvironment]; - dst.mType = EaxEffectType::Reverb; - dst.mReverb.ulEnvironment = src.dwEnvironment; - dst.mReverb.flEnvironmentSize = src.flEnvironmentSize; - dst.mReverb.flEnvironmentDiffusion = src.flEnvironmentDiffusion; - dst.mReverb.lRoom = src.lRoom; - dst.mReverb.lRoomHF = src.lRoomHF; - dst.mReverb.lRoomLF = env.lRoomLF; - dst.mReverb.flDecayTime = src.flDecayTime; - dst.mReverb.flDecayHFRatio = src.flDecayHFRatio; - dst.mReverb.flDecayLFRatio = env.flDecayLFRatio; - dst.mReverb.lReflections = src.lReflections; - dst.mReverb.flReflectionsDelay = src.flReflectionsDelay; - dst.mReverb.vReflectionsPan = env.vReflectionsPan; - dst.mReverb.lReverb = src.lReverb; - dst.mReverb.flReverbDelay = src.flReverbDelay; - dst.mReverb.vReverbPan = env.vReverbPan; - dst.mReverb.flEchoTime = env.flEchoTime; - dst.mReverb.flEchoDepth = env.flEchoDepth; - dst.mReverb.flModulationTime = env.flModulationTime; - dst.mReverb.flModulationDepth = env.flModulationDepth; - dst.mReverb.flAirAbsorptionHF = src.flAirAbsorptionHF; - dst.mReverb.flHFReference = env.flHFReference; - dst.mReverb.flLFReference = env.flLFReference; - dst.mReverb.flRoomRolloffFactor = src.flRoomRolloffFactor; - dst.mReverb.ulFlags = src.dwFlags; -} - -void EaxReverbCommitter::translate(const EAXREVERBPROPERTIES& src, EaxEffectProps& dst) noexcept -{ - dst.mType = EaxEffectType::Reverb; - dst.mReverb = src; + dst = EAXREVERB_PRESETS[src.dwEnvironment]; + dst.ulEnvironment = src.dwEnvironment; + dst.flEnvironmentSize = src.flEnvironmentSize; + dst.flEnvironmentDiffusion = src.flEnvironmentDiffusion; + dst.lRoom = src.lRoom; + dst.lRoomHF = src.lRoomHF; + dst.flDecayTime = src.flDecayTime; + dst.flDecayHFRatio = src.flDecayHFRatio; + dst.lReflections = src.lReflections; + dst.flReflectionsDelay = src.flReflectionsDelay; + dst.lReverb = src.lReverb; + dst.flReverbDelay = src.flReverbDelay; + dst.flAirAbsorptionHF = src.flAirAbsorptionHF; + dst.flRoomRolloffFactor = src.flRoomRolloffFactor; + dst.ulFlags = src.dwFlags; } bool EaxReverbCommitter::commit(const EAX_REVERBPROPERTIES &props) { - EaxEffectProps dst{}; + EAXREVERBPROPERTIES dst{}; translate(props, dst); return commit(dst); } bool EaxReverbCommitter::commit(const EAX20LISTENERPROPERTIES &props) { - EaxEffectProps dst{}; + EAXREVERBPROPERTIES dst{}; translate(props, dst); return commit(dst); } bool EaxReverbCommitter::commit(const EAXREVERBPROPERTIES &props) { - EaxEffectProps dst{}; - translate(props, dst); - return commit(dst); -} - -bool EaxReverbCommitter::commit(const EaxEffectProps &props) -{ - if(props.mType == mEaxProps.mType - && memcmp(&props.mReverb, &mEaxProps.mReverb, sizeof(mEaxProps.mReverb)) == 0) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - const auto size = props.mReverb.flEnvironmentSize; - const auto density = (size * size * size) / 16.0F; - mAlProps.Reverb.Density = std::min(density, AL_EAXREVERB_MAX_DENSITY); - mAlProps.Reverb.Diffusion = props.mReverb.flEnvironmentDiffusion; - mAlProps.Reverb.Gain = level_mb_to_gain(static_cast(props.mReverb.lRoom)); - mAlProps.Reverb.GainHF = level_mb_to_gain(static_cast(props.mReverb.lRoomHF)); - mAlProps.Reverb.GainLF = level_mb_to_gain(static_cast(props.mReverb.lRoomLF)); - mAlProps.Reverb.DecayTime = props.mReverb.flDecayTime; - mAlProps.Reverb.DecayHFRatio = props.mReverb.flDecayHFRatio; - mAlProps.Reverb.DecayLFRatio = mEaxProps.mReverb.flDecayLFRatio; - mAlProps.Reverb.ReflectionsGain = level_mb_to_gain(static_cast(props.mReverb.lReflections)); - mAlProps.Reverb.ReflectionsDelay = props.mReverb.flReflectionsDelay; - mAlProps.Reverb.ReflectionsPan[0] = props.mReverb.vReflectionsPan.x; - mAlProps.Reverb.ReflectionsPan[1] = props.mReverb.vReflectionsPan.y; - mAlProps.Reverb.ReflectionsPan[2] = props.mReverb.vReflectionsPan.z; - mAlProps.Reverb.LateReverbGain = level_mb_to_gain(static_cast(props.mReverb.lReverb)); - mAlProps.Reverb.LateReverbDelay = props.mReverb.flReverbDelay; - mAlProps.Reverb.LateReverbPan[0] = props.mReverb.vReverbPan.x; - mAlProps.Reverb.LateReverbPan[1] = props.mReverb.vReverbPan.y; - mAlProps.Reverb.LateReverbPan[2] = props.mReverb.vReverbPan.z; - mAlProps.Reverb.EchoTime = props.mReverb.flEchoTime; - mAlProps.Reverb.EchoDepth = props.mReverb.flEchoDepth; - mAlProps.Reverb.ModulationTime = props.mReverb.flModulationTime; - mAlProps.Reverb.ModulationDepth = props.mReverb.flModulationDepth; - mAlProps.Reverb.AirAbsorptionGainHF = level_mb_to_gain(props.mReverb.flAirAbsorptionHF); - mAlProps.Reverb.HFReference = props.mReverb.flHFReference; - mAlProps.Reverb.LFReference = props.mReverb.flLFReference; - mAlProps.Reverb.RoomRolloffFactor = props.mReverb.flRoomRolloffFactor; - mAlProps.Reverb.DecayHFLimit = ((props.mReverb.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); + const auto size = props.flEnvironmentSize; + const auto density = (size * size * size) / 16.0f; + mAlProps = [&]{ + ReverbProps ret{}; + ret.Density = std::min(density, AL_EAXREVERB_MAX_DENSITY); + ret.Diffusion = props.flEnvironmentDiffusion; + ret.Gain = level_mb_to_gain(static_cast(props.lRoom)); + ret.GainHF = level_mb_to_gain(static_cast(props.lRoomHF)); + ret.GainLF = level_mb_to_gain(static_cast(props.lRoomLF)); + ret.DecayTime = props.flDecayTime; + ret.DecayHFRatio = props.flDecayHFRatio; + ret.DecayLFRatio = props.flDecayLFRatio; + ret.ReflectionsGain = level_mb_to_gain(static_cast(props.lReflections)); + ret.ReflectionsDelay = props.flReflectionsDelay; + ret.ReflectionsPan = {props.vReflectionsPan.x, props.vReflectionsPan.y, + props.vReflectionsPan.z}; + ret.LateReverbGain = level_mb_to_gain(static_cast(props.lReverb)); + ret.LateReverbDelay = props.flReverbDelay; + ret.LateReverbPan = {props.vReverbPan.x, props.vReverbPan.y, props.vReverbPan.z}; + ret.EchoTime = props.flEchoTime; + ret.EchoDepth = props.flEchoDepth; + ret.ModulationTime = props.flModulationTime; + ret.ModulationDepth = props.flModulationDepth; + ret.AirAbsorptionGainHF = level_mb_to_gain(props.flAirAbsorptionHF); + ret.HFReference = props.flHFReference; + ret.LFReference = props.flLFReference; + ret.RoomRolloffFactor = props.flRoomRolloffFactor; + ret.DecayHFLimit = ((props.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); + return ret; + }(); + return true; } @@ -1212,8 +1088,7 @@ void EaxReverbCommitter::SetDefaults(EAXREVERBPROPERTIES &props) void EaxReverbCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Reverb; - SetDefaults(props.mReverb); + SetDefaults(props.emplace()); } @@ -1288,11 +1163,6 @@ void EaxReverbCommitter::Get(const EaxCall &call, const EAXREVERBPROPERTIES &pro } } -void EaxReverbCommitter::Get(const EaxCall &call, const EaxEffectProps &props) -{ - Get(call, props.mReverb); -} - void EaxReverbCommitter::Set(const EaxCall &call, EAX_REVERBPROPERTIES &props) { @@ -1311,71 +1181,23 @@ void EaxReverbCommitter::Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props { switch(call.get_property_id()) { - case DSPROPERTY_EAX20LISTENER_NONE: - break; - - case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: - defer(call, props); - break; - - case DSPROPERTY_EAX20LISTENER_ROOM: - defer(call, props.lRoom); - break; - - case DSPROPERTY_EAX20LISTENER_ROOMHF: - defer(call, props.lRoomHF); - break; - - case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: - defer(call, props.flRoomRolloffFactor); - break; - - case DSPROPERTY_EAX20LISTENER_DECAYTIME: - defer(call, props.flDecayTime); - break; - - case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: - defer(call, props.flDecayHFRatio); - break; - - case DSPROPERTY_EAX20LISTENER_REFLECTIONS: - defer(call, props.lReflections); - break; - - case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: - defer(call, props.flReverbDelay); - break; - - case DSPROPERTY_EAX20LISTENER_REVERB: - defer(call, props.lReverb); - break; - - case DSPROPERTY_EAX20LISTENER_REVERBDELAY: - defer(call, props.flReverbDelay); - break; - - case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: - defer(call, props, props.dwEnvironment); - break; - - case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: - defer(call, props, props.flEnvironmentSize); - break; - - case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: - defer(call, props.flEnvironmentDiffusion); - break; - - case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: - defer(call, props.flAirAbsorptionHF); - break; - - case DSPROPERTY_EAX20LISTENER_FLAGS: - defer(call, props.dwFlags); - break; - - default: - fail_unknown_property_id(); + case DSPROPERTY_EAX20LISTENER_NONE: break; + case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: defer(call, props); break; + case DSPROPERTY_EAX20LISTENER_ROOM: defer(call, props.lRoom); break; + case DSPROPERTY_EAX20LISTENER_ROOMHF: defer(call, props.lRoomHF); break; + case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: defer(call, props.flRoomRolloffFactor); break; + case DSPROPERTY_EAX20LISTENER_DECAYTIME: defer(call, props.flDecayTime); break; + case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: defer(call, props.flDecayHFRatio); break; + case DSPROPERTY_EAX20LISTENER_REFLECTIONS: defer(call, props.lReflections); break; + case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: defer(call, props.flReverbDelay); break; + case DSPROPERTY_EAX20LISTENER_REVERB: defer(call, props.lReverb); break; + case DSPROPERTY_EAX20LISTENER_REVERBDELAY: defer(call, props.flReverbDelay); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: defer(call, props, props.dwEnvironment); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: defer(call, props, props.flEnvironmentSize); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: defer(call, props.flEnvironmentDiffusion); break; + case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: defer(call, props.flAirAbsorptionHF); break; + case DSPROPERTY_EAX20LISTENER_FLAGS: defer(call, props.dwFlags); break; + default: fail_unknown_property_id(); } } @@ -1383,117 +1205,34 @@ void EaxReverbCommitter::Set(const EaxCall &call, EAXREVERBPROPERTIES &props) { switch(call.get_property_id()) { - case EAXREVERB_NONE: - break; - - case EAXREVERB_ALLPARAMETERS: - defer(call, props); - break; - - case EAXREVERB_ENVIRONMENT: - defer(call, props, props.ulEnvironment); - break; - - case EAXREVERB_ENVIRONMENTSIZE: - defer(call, props, props.flEnvironmentSize); - break; - - case EAXREVERB_ENVIRONMENTDIFFUSION: - defer3(call, props, props.flEnvironmentDiffusion); - break; - - case EAXREVERB_ROOM: - defer3(call, props, props.lRoom); - break; - - case EAXREVERB_ROOMHF: - defer3(call, props, props.lRoomHF); - break; - - case EAXREVERB_ROOMLF: - defer3(call, props, props.lRoomLF); - break; - - case EAXREVERB_DECAYTIME: - defer3(call, props, props.flDecayTime); - break; - - case EAXREVERB_DECAYHFRATIO: - defer3(call, props, props.flDecayHFRatio); - break; - - case EAXREVERB_DECAYLFRATIO: - defer3(call, props, props.flDecayLFRatio); - break; - - case EAXREVERB_REFLECTIONS: - defer3(call, props, props.lReflections); - break; - - case EAXREVERB_REFLECTIONSDELAY: - defer3(call, props, props.flReflectionsDelay); - break; - - case EAXREVERB_REFLECTIONSPAN: - defer3(call, props, props.vReflectionsPan); - break; - - case EAXREVERB_REVERB: - defer3(call, props, props.lReverb); - break; - - case EAXREVERB_REVERBDELAY: - defer3(call, props, props.flReverbDelay); - break; - - case EAXREVERB_REVERBPAN: - defer3(call, props, props.vReverbPan); - break; - - case EAXREVERB_ECHOTIME: - defer3(call, props, props.flEchoTime); - break; - - case EAXREVERB_ECHODEPTH: - defer3(call, props, props.flEchoDepth); - break; - - case EAXREVERB_MODULATIONTIME: - defer3(call, props, props.flModulationTime); - break; - - case EAXREVERB_MODULATIONDEPTH: - defer3(call, props, props.flModulationDepth); - break; - - case EAXREVERB_AIRABSORPTIONHF: - defer3(call, props, props.flAirAbsorptionHF); - break; - - case EAXREVERB_HFREFERENCE: - defer3(call, props, props.flHFReference); - break; - - case EAXREVERB_LFREFERENCE: - defer3(call, props, props.flLFReference); - break; - - case EAXREVERB_ROOMROLLOFFFACTOR: - defer3(call, props, props.flRoomRolloffFactor); - break; - - case EAXREVERB_FLAGS: - defer3(call, props, props.ulFlags); - break; - - default: - fail_unknown_property_id(); + case EAXREVERB_NONE: break; + case EAXREVERB_ALLPARAMETERS: defer(call, props); break; + case EAXREVERB_ENVIRONMENT: defer(call, props, props.ulEnvironment); break; + case EAXREVERB_ENVIRONMENTSIZE: defer(call, props, props.flEnvironmentSize); break; + case EAXREVERB_ENVIRONMENTDIFFUSION: defer3(call, props, props.flEnvironmentDiffusion); break; + case EAXREVERB_ROOM: defer3(call, props, props.lRoom); break; + case EAXREVERB_ROOMHF: defer3(call, props, props.lRoomHF); break; + case EAXREVERB_ROOMLF: defer3(call, props, props.lRoomLF); break; + case EAXREVERB_DECAYTIME: defer3(call, props, props.flDecayTime); break; + case EAXREVERB_DECAYHFRATIO: defer3(call, props, props.flDecayHFRatio); break; + case EAXREVERB_DECAYLFRATIO: defer3(call, props, props.flDecayLFRatio); break; + case EAXREVERB_REFLECTIONS: defer3(call, props, props.lReflections); break; + case EAXREVERB_REFLECTIONSDELAY: defer3(call, props, props.flReflectionsDelay); break; + case EAXREVERB_REFLECTIONSPAN: defer3(call, props, props.vReflectionsPan); break; + case EAXREVERB_REVERB: defer3(call, props, props.lReverb); break; + case EAXREVERB_REVERBDELAY: defer3(call, props, props.flReverbDelay); break; + case EAXREVERB_REVERBPAN: defer3(call, props, props.vReverbPan); break; + case EAXREVERB_ECHOTIME: defer3(call, props, props.flEchoTime); break; + case EAXREVERB_ECHODEPTH: defer3(call, props, props.flEchoDepth); break; + case EAXREVERB_MODULATIONTIME: defer3(call, props, props.flModulationTime); break; + case EAXREVERB_MODULATIONDEPTH: defer3(call, props, props.flModulationDepth); break; + case EAXREVERB_AIRABSORPTIONHF: defer3(call, props, props.flAirAbsorptionHF); break; + case EAXREVERB_HFREFERENCE: defer3(call, props, props.flHFReference); break; + case EAXREVERB_LFREFERENCE: defer3(call, props, props.flLFReference); break; + case EAXREVERB_ROOMROLLOFFFACTOR: defer3(call, props, props.flRoomRolloffFactor); break; + case EAXREVERB_FLAGS: defer3(call, props, props.ulFlags); break; + default: fail_unknown_property_id(); } } -void EaxReverbCommitter::Set(const EaxCall &call, EaxEffectProps &props) -{ - Set(call, props.mReverb); -} - #endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/vmorpher.cpp b/Engine/lib/openal-soft/al/effects/vmorpher.cpp index 21ea3680c..8c66957be 100644 --- a/Engine/lib/openal-soft/al/effects/vmorpher.cpp +++ b/Engine/lib/openal-soft/al/effects/vmorpher.cpp @@ -1,18 +1,18 @@ #include "config.h" +#include #include #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" -#include "aloptional.h" +#include "core/effects/base.h" #include "effects.h" #ifdef ALSOFT_EAX #include -#include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -20,7 +20,7 @@ namespace { -al::optional PhenomeFromEnum(ALenum val) +constexpr std::optional PhenomeFromEnum(ALenum val) noexcept { #define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \ return VMorpherPhenome::x @@ -57,10 +57,10 @@ al::optional PhenomeFromEnum(ALenum val) HANDLE_PHENOME(V); HANDLE_PHENOME(Z); } - return al::nullopt; + return std::nullopt; #undef HANDLE_PHENOME } -ALenum EnumFromPhenome(VMorpherPhenome phenome) +constexpr ALenum EnumFromPhenome(VMorpherPhenome phenome) { #define HANDLE_PHENOME(x) case VMorpherPhenome::x: return AL_VOCAL_MORPHER_PHONEME_ ## x switch(phenome) @@ -100,7 +100,7 @@ ALenum EnumFromPhenome(VMorpherPhenome phenome) #undef HANDLE_PHENOME } -al::optional WaveformFromEmum(ALenum value) +constexpr std::optional WaveformFromEmum(ALenum value) noexcept { switch(value) { @@ -108,9 +108,9 @@ al::optional WaveformFromEmum(ALenum value) case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return VMorpherWaveform::Triangle; case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return VMorpherWaveform::Sawtooth; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromWaveform(VMorpherWaveform type) +constexpr ALenum EnumFromWaveform(VMorpherWaveform type) { switch(type) { @@ -122,13 +122,29 @@ ALenum EnumFromWaveform(VMorpherWaveform type) std::to_string(static_cast(type))}; } -void Vmorpher_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultProps() noexcept +{ + VmorpherProps props{}; + props.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE; + props.PhonemeA = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA).value(); + props.PhonemeB = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB).value(); + props.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING; + props.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING; + props.Waveform = WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM).value(); + return props; +} + +} // namespace + +const EffectProps VmorpherEffectProps{genDefaultProps()}; + +void VmorpherEffectHandler::SetParami(VmorpherProps &props, ALenum param, int val) { switch(param) { case AL_VOCAL_MORPHER_PHONEMEA: if(auto phenomeopt = PhenomeFromEnum(val)) - props->Vmorpher.PhonemeA = *phenomeopt; + props.PhonemeA = *phenomeopt; else throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val}; break; @@ -136,12 +152,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val) case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING)) throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"}; - props->Vmorpher.PhonemeACoarseTuning = val; + props.PhonemeACoarseTuning = val; break; case AL_VOCAL_MORPHER_PHONEMEB: if(auto phenomeopt = PhenomeFromEnum(val)) - props->Vmorpher.PhonemeB = *phenomeopt; + props.PhonemeB = *phenomeopt; else throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val}; break; @@ -149,12 +165,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val) case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING)) throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"}; - props->Vmorpher.PhonemeBCoarseTuning = val; + props.PhonemeBCoarseTuning = val; break; case AL_VOCAL_MORPHER_WAVEFORM: if(auto formopt = WaveformFromEmum(val)) - props->Vmorpher.Waveform = *formopt; + props.Waveform = *formopt; else throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val}; break; @@ -164,19 +180,19 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Vmorpher_setParamiv(EffectProps*, ALenum param, const int*) +void VmorpherEffectHandler::SetParamiv(VmorpherProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param}; } -void Vmorpher_setParamf(EffectProps *props, ALenum param, float val) +void VmorpherEffectHandler::SetParamf(VmorpherProps &props, ALenum param, float val) { switch(param) { case AL_VOCAL_MORPHER_RATE: if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE)) throw effect_exception{AL_INVALID_VALUE, "Vocal morpher rate out of range"}; - props->Vmorpher.Rate = val; + props.Rate = val; break; default: @@ -184,49 +200,35 @@ void Vmorpher_setParamf(EffectProps *props, ALenum param, float val) param}; } } -void Vmorpher_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Vmorpher_setParamf(props, param, vals[0]); } +void VmorpherEffectHandler::SetParamfv(VmorpherProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Vmorpher_getParami(const EffectProps *props, ALenum param, int* val) +void VmorpherEffectHandler::GetParami(const VmorpherProps &props, ALenum param, int* val) { switch(param) { - case AL_VOCAL_MORPHER_PHONEMEA: - *val = EnumFromPhenome(props->Vmorpher.PhonemeA); - break; - - case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: - *val = props->Vmorpher.PhonemeACoarseTuning; - break; - - case AL_VOCAL_MORPHER_PHONEMEB: - *val = EnumFromPhenome(props->Vmorpher.PhonemeB); - break; - - case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: - *val = props->Vmorpher.PhonemeBCoarseTuning; - break; - - case AL_VOCAL_MORPHER_WAVEFORM: - *val = EnumFromWaveform(props->Vmorpher.Waveform); - break; + case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); break; + case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; break; + case AL_VOCAL_MORPHER_PHONEMEB: *val = EnumFromPhenome(props.PhonemeB); break; + case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: *val = props.PhonemeBCoarseTuning; break; + case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", param}; } } -void Vmorpher_getParamiv(const EffectProps*, ALenum param, int*) +void VmorpherEffectHandler::GetParamiv(const VmorpherProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param}; } -void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val) +void VmorpherEffectHandler::GetParamf(const VmorpherProps &props, ALenum param, float *val) { switch(param) { case AL_VOCAL_MORPHER_RATE: - *val = props->Vmorpher.Rate; + *val = props.Rate; break; default: @@ -234,26 +236,9 @@ void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val) param}; } } -void Vmorpher_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Vmorpher_getParamf(props, param, vals); } +void VmorpherEffectHandler::GetParamfv(const VmorpherProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Vmorpher.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE; - props.Vmorpher.PhonemeA = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA); - props.Vmorpher.PhonemeB = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB); - props.Vmorpher.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING; - props.Vmorpher.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING; - props.Vmorpher.Waveform = *WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM); - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Vmorpher); - -const EffectProps VmorpherEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -352,16 +337,9 @@ template<> throw Exception{message}; } -template<> -bool VocalMorpherCommitter::commit(const EaxEffectProps &props) +bool EaxVocalMorpherCommitter::commit(const EAXVOCALMORPHERPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && mEaxProps.mVocalMorpher.ulPhonemeA == props.mVocalMorpher.ulPhonemeA - && mEaxProps.mVocalMorpher.lPhonemeACoarseTuning == props.mVocalMorpher.lPhonemeACoarseTuning - && mEaxProps.mVocalMorpher.ulPhonemeB == props.mVocalMorpher.ulPhonemeB - && mEaxProps.mVocalMorpher.lPhonemeBCoarseTuning == props.mVocalMorpher.lPhonemeBCoarseTuning - && mEaxProps.mVocalMorpher.ulWaveform == props.mVocalMorpher.ulWaveform - && mEaxProps.mVocalMorpher.flRate == props.mVocalMorpher.flRate) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; @@ -413,107 +391,65 @@ bool VocalMorpherCommitter::commit(const EaxEffectProps &props) return VMorpherWaveform::Sinusoid; }; - mAlProps.Vmorpher.PhonemeA = get_phoneme(props.mVocalMorpher.ulPhonemeA); - mAlProps.Vmorpher.PhonemeACoarseTuning = static_cast(props.mVocalMorpher.lPhonemeACoarseTuning); - mAlProps.Vmorpher.PhonemeB = get_phoneme(props.mVocalMorpher.ulPhonemeB); - mAlProps.Vmorpher.PhonemeBCoarseTuning = static_cast(props.mVocalMorpher.lPhonemeBCoarseTuning); - mAlProps.Vmorpher.Waveform = get_waveform(props.mVocalMorpher.ulWaveform); - mAlProps.Vmorpher.Rate = props.mVocalMorpher.flRate; + mAlProps = [&]{ + VmorpherProps ret{}; + ret.PhonemeA = get_phoneme(props.ulPhonemeA); + ret.PhonemeACoarseTuning = static_cast(props.lPhonemeACoarseTuning); + ret.PhonemeB = get_phoneme(props.ulPhonemeB); + ret.PhonemeBCoarseTuning = static_cast(props.lPhonemeBCoarseTuning); + ret.Waveform = get_waveform(props.ulWaveform); + ret.Rate = props.flRate; + return ret; + }(); return true; } -template<> -void VocalMorpherCommitter::SetDefaults(EaxEffectProps &props) +void EaxVocalMorpherCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::VocalMorpher; - props.mVocalMorpher.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA; - props.mVocalMorpher.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING; - props.mVocalMorpher.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB; - props.mVocalMorpher.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING; - props.mVocalMorpher.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM; - props.mVocalMorpher.flRate = EAXVOCALMORPHER_DEFAULTRATE; + static constexpr EAXVOCALMORPHERPROPERTIES defprops{[] + { + EAXVOCALMORPHERPROPERTIES ret{}; + ret.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA; + ret.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING; + ret.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB; + ret.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING; + ret.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM; + ret.flRate = EAXVOCALMORPHER_DEFAULTRATE; + return ret; + }()}; + props = defprops; } -template<> -void VocalMorpherCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxVocalMorpherCommitter::Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props) { switch(call.get_property_id()) { - case EAXVOCALMORPHER_NONE: - break; - - case EAXVOCALMORPHER_ALLPARAMETERS: - call.set_value(props.mVocalMorpher); - break; - - case EAXVOCALMORPHER_PHONEMEA: - call.set_value(props.mVocalMorpher.ulPhonemeA); - break; - - case EAXVOCALMORPHER_PHONEMEACOARSETUNING: - call.set_value(props.mVocalMorpher.lPhonemeACoarseTuning); - break; - - case EAXVOCALMORPHER_PHONEMEB: - call.set_value(props.mVocalMorpher.ulPhonemeB); - break; - - case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: - call.set_value(props.mVocalMorpher.lPhonemeBCoarseTuning); - break; - - case EAXVOCALMORPHER_WAVEFORM: - call.set_value(props.mVocalMorpher.ulWaveform); - break; - - case EAXVOCALMORPHER_RATE: - call.set_value(props.mVocalMorpher.flRate); - break; - - default: - fail_unknown_property_id(); + case EAXVOCALMORPHER_NONE: break; + case EAXVOCALMORPHER_ALLPARAMETERS: call.set_value(props); break; + case EAXVOCALMORPHER_PHONEMEA: call.set_value(props.ulPhonemeA); break; + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: call.set_value(props.lPhonemeACoarseTuning); break; + case EAXVOCALMORPHER_PHONEMEB: call.set_value(props.ulPhonemeB); break; + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: call.set_value(props.lPhonemeBCoarseTuning); break; + case EAXVOCALMORPHER_WAVEFORM: call.set_value(props.ulWaveform); break; + case EAXVOCALMORPHER_RATE: call.set_value(props.flRate); break; + default: fail_unknown_property_id(); } } -template<> -void VocalMorpherCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxVocalMorpherCommitter::Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props) { switch(call.get_property_id()) { - case EAXVOCALMORPHER_NONE: - break; - - case EAXVOCALMORPHER_ALLPARAMETERS: - defer(call, props.mVocalMorpher); - break; - - case EAXVOCALMORPHER_PHONEMEA: - defer(call, props.mVocalMorpher.ulPhonemeA); - break; - - case EAXVOCALMORPHER_PHONEMEACOARSETUNING: - defer(call, props.mVocalMorpher.lPhonemeACoarseTuning); - break; - - case EAXVOCALMORPHER_PHONEMEB: - defer(call, props.mVocalMorpher.ulPhonemeB); - break; - - case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: - defer(call, props.mVocalMorpher.lPhonemeBCoarseTuning); - break; - - case EAXVOCALMORPHER_WAVEFORM: - defer(call, props.mVocalMorpher.ulWaveform); - break; - - case EAXVOCALMORPHER_RATE: - defer(call, props.mVocalMorpher.flRate); - break; - - default: - fail_unknown_property_id(); + case EAXVOCALMORPHER_NONE: break; + case EAXVOCALMORPHER_ALLPARAMETERS: defer(call, props); break; + case EAXVOCALMORPHER_PHONEMEA: defer(call, props.ulPhonemeA); break; + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: defer(call, props.lPhonemeACoarseTuning); break; + case EAXVOCALMORPHER_PHONEMEB: defer(call, props.ulPhonemeB); break; + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: defer(call, props.lPhonemeBCoarseTuning); break; + case EAXVOCALMORPHER_WAVEFORM: defer(call, props.ulWaveform); break; + case EAXVOCALMORPHER_RATE: defer(call, props.flRate); break; + default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/error.cpp b/Engine/lib/openal-soft/al/error.cpp index afa7019a9..24cc51dca 100644 --- a/Engine/lib/openal-soft/al/error.cpp +++ b/Engine/lib/openal-soft/al/error.cpp @@ -20,6 +20,8 @@ #include "config.h" +#include "error.h" + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -29,27 +31,45 @@ #include #include #include +#include #include -#include +#include +#include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" +#include "al/debug.h" +#include "alc/alconfig.h" #include "alc/context.h" -#include "almalloc.h" -#include "core/except.h" +#include "alc/inprogext.h" #include "core/logging.h" #include "opthelpers.h" -#include "vector.h" +#include "strutils.h" -bool TrapALError{false}; +namespace al { +context_error::context_error(ALenum code, const char *msg, ...) : mErrorCode{code} +{ + /* NOLINTBEGIN(*-array-to-pointer-decay) */ + std::va_list args; + va_start(args, msg); + setMessage(msg, args); + va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ +} +context_error::~context_error() = default; +} /* namespace al */ void ALCcontext::setError(ALenum errorCode, const char *msg, ...) { - auto message = al::vector(256); + auto message = std::vector(256); - va_list args, args2; + /* NOLINTBEGIN(*-array-to-pointer-decay) */ + std::va_list args, args2; va_start(args, msg); va_copy(args2, args); int msglen{std::vsnprintf(message.data(), message.size(), msg, args)}; @@ -60,9 +80,15 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...) } va_end(args2); va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ - if(msglen >= 0) msg = message.data(); - else msg = ""; + if(msglen >= 0) + msg = message.data(); + else + { + msg = ""; + msglen = static_cast(strlen(msg)); + } WARN("Error generated on context %p, code 0x%04x, \"%s\"\n", decltype(std::declval()){this}, errorCode, msg); @@ -77,30 +103,55 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...) #endif } - ALenum curerr{AL_NO_ERROR}; - mLastError.compare_exchange_strong(curerr, errorCode); + if(mLastThreadError.get() == AL_NO_ERROR) + mLastThreadError.set(errorCode); + + debugMessage(DebugSource::API, DebugType::Error, 0, DebugSeverity::High, + {msg, static_cast(msglen)}); } -AL_API ALenum AL_APIENTRY alGetError(void) -START_API_FUNC +/* Special-case alGetError since it (potentially) raises a debug signal and + * returns a non-default value for a null context. + */ +AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY - { - static constexpr ALenum deferror{AL_INVALID_OPERATION}; - WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror); - if(TrapALError) - { -#ifdef _WIN32 - if(IsDebuggerPresent()) - DebugBreak(); -#elif defined(SIGTRAP) - raise(SIGTRAP); -#endif - } - return deferror; - } + if(auto context = GetContextRef()) LIKELY + return alGetErrorDirect(context.get()); - return context->mLastError.exchange(AL_NO_ERROR); + auto get_value = [](const char *envname, const char *optname) -> ALenum + { + auto optstr = al::getenv(envname); + if(!optstr) + optstr = ConfigValueStr({}, "game_compat", optname); + if(optstr) + { + char *end{}; + auto value = std::strtoul(optstr->c_str(), &end, 0); + if(end && *end == '\0' && value <= std::numeric_limits::max()) + return static_cast(value); + ERR("Invalid default error value: \"%s\"", optstr->c_str()); + } + return AL_INVALID_OPERATION; + }; + static const ALenum deferror{get_value("__ALSOFT_DEFAULT_ERROR", "default-error")}; + + WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror); + if(TrapALError) + { +#ifdef _WIN32 + if(IsDebuggerPresent()) + DebugBreak(); +#elif defined(SIGTRAP) + raise(SIGTRAP); +#endif + } + return deferror; +} + +FORCE_ALIGN ALenum AL_APIENTRY alGetErrorDirect(ALCcontext *context) noexcept +{ + ALenum ret{context->mLastThreadError.get()}; + if(ret != AL_NO_ERROR) UNLIKELY + context->mLastThreadError.set(AL_NO_ERROR); + return ret; } -END_API_FUNC diff --git a/Engine/lib/openal-soft/al/error.h b/Engine/lib/openal-soft/al/error.h new file mode 100644 index 000000000..c50011a54 --- /dev/null +++ b/Engine/lib/openal-soft/al/error.h @@ -0,0 +1,27 @@ +#ifndef AL_ERROR_H +#define AL_ERROR_H + +#include "AL/al.h" + +#include "core/except.h" + +namespace al { + +class context_error final : public al::base_exception { + ALenum mErrorCode{}; + +public: +#ifdef __MINGW32__ + [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] +#else + [[gnu::format(printf, 3, 4)]] +#endif + context_error(ALenum code, const char *msg, ...); + ~context_error() final; + + [[nodiscard]] auto errorCode() const noexcept -> ALenum { return mErrorCode; } +}; + +} /* namespace al */ + +#endif /* AL_ERROR_H */ diff --git a/Engine/lib/openal-soft/al/event.cpp b/Engine/lib/openal-soft/al/event.cpp index 1bc39d1ea..5d6a1c38c 100644 --- a/Engine/lib/openal-soft/al/event.cpp +++ b/Engine/lib/openal-soft/al/event.cpp @@ -3,35 +3,49 @@ #include "event.h" -#include +#include #include -#include +#include #include #include #include #include +#include #include +#include #include +#include #include +#include #include "AL/al.h" #include "AL/alc.h" +#include "AL/alext.h" -#include "albyte.h" #include "alc/context.h" -#include "alc/effects/base.h" -#include "alc/inprogext.h" -#include "almalloc.h" +#include "alsem.h" +#include "alspan.h" #include "core/async_event.h" -#include "core/except.h" +#include "core/context.h" +#include "core/effects/base.h" #include "core/logging.h" -#include "core/voice_change.h" +#include "debug.h" +#include "direct_defs.h" +#include "error.h" +#include "intrusive_ptr.h" #include "opthelpers.h" #include "ringbuffer.h" -#include "threads.h" -static int EventThread(ALCcontext *context) +namespace { + +template +struct overloaded : Ts... { using Ts::operator()...; }; + +template +overloaded(Ts...) -> overloaded; + +int EventThread(ALCcontext *context) { RingBuffer *ring{context->mAsyncEvents.get()}; bool quitnow{false}; @@ -44,76 +58,98 @@ static int EventThread(ALCcontext *context) continue; } - std::lock_guard _{context->mEventCbLock}; - do { - auto *evt_ptr = reinterpret_cast(evt_data.buf); - evt_data.buf += sizeof(AsyncEvent); - evt_data.len -= 1; - - AsyncEvent evt{*evt_ptr}; - al::destroy_at(evt_ptr); - ring->readAdvance(1); - - quitnow = evt.EnumType == AsyncEvent::KillThread; + std::lock_guard eventlock{context->mEventCbLock}; + auto evt_span = al::span{std::launder(reinterpret_cast(evt_data.buf)), + evt_data.len}; + for(auto &event : evt_span) + { + quitnow = std::holds_alternative(event); if(quitnow) UNLIKELY break; - if(evt.EnumType == AsyncEvent::ReleaseEffectState) - { - al::intrusive_ptr{evt.u.mEffectState}; - continue; - } - auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire); - if(!context->mEventCb || !enabledevts.test(evt.EnumType)) - continue; - - if(evt.EnumType == AsyncEvent::SourceStateChange) + auto proc_killthread = [](AsyncKillThread&) { }; + auto proc_release = [](AsyncEffectReleaseEvent &evt) { + al::intrusive_ptr{evt.mEffectState}; + }; + auto proc_srcstate = [context,enabledevts](AsyncSourceStateEvent &evt) + { + if(!context->mEventCb + || !enabledevts.test(al::to_underlying(AsyncEnableBits::SourceState))) + return; + ALuint state{}; - std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)}; + std::string msg{"Source ID " + std::to_string(evt.mId)}; msg += " state has changed to "; - switch(evt.u.srcstate.state) + switch(evt.mState) { - case AsyncEvent::SrcState::Reset: + case AsyncSrcState::Reset: msg += "AL_INITIAL"; state = AL_INITIAL; break; - case AsyncEvent::SrcState::Stop: + case AsyncSrcState::Stop: msg += "AL_STOPPED"; state = AL_STOPPED; break; - case AsyncEvent::SrcState::Play: + case AsyncSrcState::Play: msg += "AL_PLAYING"; state = AL_PLAYING; break; - case AsyncEvent::SrcState::Pause: + case AsyncSrcState::Pause: msg += "AL_PAUSED"; state = AL_PAUSED; break; } - context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id, - state, static_cast(msg.length()), msg.c_str(), context->mEventParam); - } - else if(evt.EnumType == AsyncEvent::BufferCompleted) + context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.mId, state, + static_cast(msg.length()), msg.c_str(), context->mEventParam); + }; + auto proc_buffercomp = [context,enabledevts](AsyncBufferCompleteEvent &evt) { - std::string msg{std::to_string(evt.u.bufcomp.count)}; - if(evt.u.bufcomp.count == 1) msg += " buffer completed"; + if(!context->mEventCb + || !enabledevts.test(al::to_underlying(AsyncEnableBits::BufferCompleted))) + return; + + std::string msg{std::to_string(evt.mCount)}; + if(evt.mCount == 1) msg += " buffer completed"; else msg += " buffers completed"; - context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id, - evt.u.bufcomp.count, static_cast(msg.length()), msg.c_str(), - context->mEventParam); - } - else if(evt.EnumType == AsyncEvent::Disconnected) + context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.mId, evt.mCount, + static_cast(msg.length()), msg.c_str(), context->mEventParam); + }; + auto proc_disconnect = [context,enabledevts](AsyncDisconnectEvent &evt) { - context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, - static_cast(strlen(evt.u.disconnect.msg)), evt.u.disconnect.msg, - context->mEventParam); - } - } while(evt_data.len != 0); + context->debugMessage(DebugSource::System, DebugType::Error, 0, + DebugSeverity::High, evt.msg); + + if(context->mEventCb + && enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected))) + context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, + static_cast(evt.msg.length()), evt.msg.c_str(), + context->mEventParam); + }; + + std::visit(overloaded{proc_srcstate, proc_buffercomp, proc_release, proc_disconnect, + proc_killthread}, event); + } + std::destroy(evt_span.begin(), evt_span.end()); + ring->readAdvance(evt_span.size()); } return 0; } +constexpr std::optional GetEventType(ALenum etype) noexcept +{ + switch(etype) + { + case AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT: return AsyncEnableBits::BufferCompleted; + case AL_EVENT_TYPE_DISCONNECTED_SOFT: return AsyncEnableBits::Disconnected; + case AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT: return AsyncEnableBits::SourceState; + } + return std::nullopt; +} + +} // namespace + + void StartEventThrd(ALCcontext *ctx) { try { @@ -138,7 +174,7 @@ void StopEventThrd(ALCcontext *ctx) evt_data = ring->getWriteVector().first; } while(evt_data.len == 0); } - al::construct_at(reinterpret_cast(evt_data.buf), AsyncEvent::KillThread); + std::ignore = InitAsyncEvent(evt_data.buf); ring->writeAdvance(1); ctx->mEventSem.post(); @@ -146,34 +182,25 @@ void StopEventThrd(ALCcontext *ctx) ctx->mEventThread.join(); } -AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT3(void, alEventControl,SOFT, ALsizei,count, const ALenum*,types, ALboolean,enable) +FORCE_ALIGN void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsizei count, + const ALenum *types, ALboolean enable) noexcept +try { + if(count < 0) + throw al::context_error{AL_INVALID_VALUE, "Controlling %d events", count}; + if(count <= 0) UNLIKELY return; - if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count); - if(count <= 0) return; - if(!types) return context->setError(AL_INVALID_VALUE, "NULL pointer"); + if(!types) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; ContextBase::AsyncEventBitset flags{}; - 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.set(AsyncEvent::BufferCompleted); - else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT) - flags.set(AsyncEvent::SourceStateChange); - else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT) - flags.set(AsyncEvent::Disconnected); - else - return false; - return true; - } - ); - if(bad_type != types_end) - return context->setError(AL_INVALID_ENUM, "Invalid event type 0x%04x", *bad_type); + for(ALenum evttype : al::span{types, static_cast(count)}) + { + auto etype = GetEventType(evttype); + if(!etype) + throw al::context_error{AL_INVALID_ENUM, "Invalid event type 0x%04x", evttype}; + flags.set(al::to_underlying(*etype)); + } if(enable) { @@ -196,20 +223,18 @@ START_API_FUNC /* Wait to ensure the event handler sees the changed flags before * returning. */ - std::lock_guard _{context->mEventCbLock}; + std::lock_guard eventlock{context->mEventCbLock}; } } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} -AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam) -START_API_FUNC +AL_API DECL_FUNCEXT2(void, alEventCallback,SOFT, ALEVENTPROCSOFT,callback, void*,userParam) +FORCE_ALIGN void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context, + ALEVENTPROCSOFT callback, void *userParam) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mEventCbLock}; + std::lock_guard eventlock{context->mEventCbLock}; context->mEventCb = callback; context->mEventParam = userParam; } -END_API_FUNC diff --git a/Engine/lib/openal-soft/al/extension.cpp b/Engine/lib/openal-soft/al/extension.cpp index 3ead0af82..16dc015aa 100644 --- a/Engine/lib/openal-soft/al/extension.cpp +++ b/Engine/lib/openal-soft/al/extension.cpp @@ -20,63 +20,59 @@ #include "config.h" -#include -#include -#include +#include +#include #include "AL/al.h" #include "AL/alc.h" #include "alc/context.h" +#include "alc/inprogext.h" #include "alstring.h" -#include "core/except.h" +#include "direct_defs.h" #include "opthelpers.h" -AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsExtensionPresent, const ALchar*,extName) +FORCE_ALIGN ALboolean AL_APIENTRY alIsExtensionPresentDirect(ALCcontext *context, const ALchar *extName) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return AL_FALSE; - if(!extName) UNLIKELY { context->setError(AL_INVALID_VALUE, "NULL pointer"); return AL_FALSE; } - size_t len{strlen(extName)}; - const char *ptr{context->mExtensionList}; - while(ptr && *ptr) + const std::string_view tofind{extName}; + for(std::string_view ext : context->mExtensions) { - if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) + if(al::case_compare(ext, tofind) == 0) return AL_TRUE; - - if((ptr=strchr(ptr, ' ')) != nullptr) - { - do { - ++ptr; - } while(isspace(*ptr)); - } } return AL_FALSE; } -END_API_FUNC -AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName) -START_API_FUNC +AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName) noexcept { if(!funcName) return nullptr; return alcGetProcAddress(nullptr, funcName); } -END_API_FUNC -AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName) -START_API_FUNC +FORCE_ALIGN ALvoid* AL_APIENTRY alGetProcAddressDirect(ALCcontext*, const ALchar *funcName) noexcept { - if(!enumName) return static_cast(0); + if(!funcName) return nullptr; + return alcGetProcAddress(nullptr, funcName); +} + +AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName) noexcept +{ + if(!enumName) return ALenum{0}; + return alcGetEnumValue(nullptr, enumName); +} + +FORCE_ALIGN ALenum AL_APIENTRY alGetEnumValueDirect(ALCcontext*, const ALchar *enumName) noexcept +{ + if(!enumName) return ALenum{0}; return alcGetEnumValue(nullptr, enumName); } -END_API_FUNC diff --git a/Engine/lib/openal-soft/al/filter.cpp b/Engine/lib/openal-soft/al/filter.cpp index 73efa01f8..5e56c6f04 100644 --- a/Engine/lib/openal-soft/al/filter.cpp +++ b/Engine/lib/openal-soft/al/filter.cpp @@ -29,8 +29,10 @@ #include #include #include -#include #include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" @@ -39,252 +41,19 @@ #include "albit.h" #include "alc/context.h" #include "alc/device.h" +#include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" -#include "core/except.h" +#include "alspan.h" +#include "direct_defs.h" +#include "error.h" +#include "intrusive_ptr.h" #include "opthelpers.h" -#include "vector.h" namespace { -class filter_exception final : public al::base_exception { - ALenum mErrorCode; - -public: -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf, 3, 4)]] -#else - [[gnu::format(printf, 3, 4)]] -#endif - filter_exception(ALenum code, const char *msg, ...); - ~filter_exception() override; - - ALenum errorCode() const noexcept { return mErrorCode; } -}; - -filter_exception::filter_exception(ALenum code, const char* msg, ...) : mErrorCode{code} -{ - std::va_list args; - va_start(args, msg); - setMessage(msg, args); - va_end(args); -} -filter_exception::~filter_exception() = default; - - -#define DEFINE_ALFILTER_VTABLE(T) \ -const ALfilter::Vtable T##_vtable = { \ - T##_setParami, T##_setParamiv, T##_setParamf, T##_setParamfv, \ - T##_getParami, T##_getParamiv, T##_getParamf, T##_getParamfv, \ -} - -void ALlowpass_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } -void ALlowpass_setParamiv(ALfilter*, ALenum param, const int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", - param}; -} -void ALlowpass_setParamf(ALfilter *filter, ALenum param, float val) -{ - switch(param) - { - case AL_LOWPASS_GAIN: - if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN)) - throw filter_exception{AL_INVALID_VALUE, "Low-pass gain %f out of range", val}; - filter->Gain = val; - break; - - case AL_LOWPASS_GAINHF: - if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF)) - throw filter_exception{AL_INVALID_VALUE, "Low-pass gainhf %f out of range", val}; - filter->GainHF = val; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; - } -} -void ALlowpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ ALlowpass_setParamf(filter, param, vals[0]); } - -void ALlowpass_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } -void ALlowpass_getParamiv(const ALfilter*, ALenum param, int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", - param}; -} -void ALlowpass_getParamf(const ALfilter *filter, ALenum param, float *val) -{ - switch(param) - { - case AL_LOWPASS_GAIN: - *val = filter->Gain; - break; - - case AL_LOWPASS_GAINHF: - *val = filter->GainHF; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; - } -} -void ALlowpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ ALlowpass_getParamf(filter, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALlowpass); - - -void ALhighpass_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } -void ALhighpass_setParamiv(ALfilter*, ALenum param, const int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", - param}; -} -void ALhighpass_setParamf(ALfilter *filter, ALenum param, float val) -{ - switch(param) - { - case AL_HIGHPASS_GAIN: - if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN)) - throw filter_exception{AL_INVALID_VALUE, "High-pass gain %f out of range", val}; - filter->Gain = val; - break; - - case AL_HIGHPASS_GAINLF: - if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF)) - throw filter_exception{AL_INVALID_VALUE, "High-pass gainlf %f out of range", val}; - filter->GainLF = val; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; - } -} -void ALhighpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ ALhighpass_setParamf(filter, param, vals[0]); } - -void ALhighpass_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } -void ALhighpass_getParamiv(const ALfilter*, ALenum param, int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", - param}; -} -void ALhighpass_getParamf(const ALfilter *filter, ALenum param, float *val) -{ - switch(param) - { - case AL_HIGHPASS_GAIN: - *val = filter->Gain; - break; - - case AL_HIGHPASS_GAINLF: - *val = filter->GainLF; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; - } -} -void ALhighpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ ALhighpass_getParamf(filter, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALhighpass); - - -void ALbandpass_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } -void ALbandpass_setParamiv(ALfilter*, ALenum param, const int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", - param}; -} -void ALbandpass_setParamf(ALfilter *filter, ALenum param, float val) -{ - switch(param) - { - case AL_BANDPASS_GAIN: - if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN)) - throw filter_exception{AL_INVALID_VALUE, "Band-pass gain %f out of range", val}; - filter->Gain = val; - break; - - case AL_BANDPASS_GAINHF: - if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF)) - throw filter_exception{AL_INVALID_VALUE, "Band-pass gainhf %f out of range", val}; - filter->GainHF = val; - break; - - case AL_BANDPASS_GAINLF: - if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF)) - throw filter_exception{AL_INVALID_VALUE, "Band-pass gainlf %f out of range", val}; - filter->GainLF = val; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; - } -} -void ALbandpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ ALbandpass_setParamf(filter, param, vals[0]); } - -void ALbandpass_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } -void ALbandpass_getParamiv(const ALfilter*, ALenum param, int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", - param}; -} -void ALbandpass_getParamf(const ALfilter *filter, ALenum param, float *val) -{ - switch(param) - { - case AL_BANDPASS_GAIN: - *val = filter->Gain; - break; - - case AL_BANDPASS_GAINHF: - *val = filter->GainHF; - break; - - case AL_BANDPASS_GAINLF: - *val = filter->GainLF; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; - } -} -void ALbandpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ ALbandpass_getParamf(filter, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALbandpass); - - -void ALnullfilter_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_setParamiv(ALfilter*, ALenum param, const int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_setParamf(ALfilter*, ALenum param, float) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_setParamfv(ALfilter*, ALenum param, const float*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } - -void ALnullfilter_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_getParamiv(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_getParamf(const ALfilter*, ALenum param, float*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_getParamfv(const ALfilter*, ALenum param, float*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } - -DEFINE_ALFILTER_VTABLE(ALnullfilter); +using SubListAllocator = al::allocator>; void InitFilterParams(ALfilter *filter, ALenum type) @@ -293,44 +62,44 @@ void InitFilterParams(ALfilter *filter, ALenum type) { filter->Gain = AL_LOWPASS_DEFAULT_GAIN; filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF; - filter->HFReference = LOWPASSFREQREF; + filter->HFReference = LowPassFreqRef; filter->GainLF = 1.0f; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALlowpass_vtable; + filter->LFReference = HighPassFreqRef; + filter->mTypeVariant.emplace(); } else if(type == AL_FILTER_HIGHPASS) { filter->Gain = AL_HIGHPASS_DEFAULT_GAIN; filter->GainHF = 1.0f; - filter->HFReference = LOWPASSFREQREF; + filter->HFReference = LowPassFreqRef; filter->GainLF = AL_HIGHPASS_DEFAULT_GAINLF; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALhighpass_vtable; + filter->LFReference = HighPassFreqRef; + filter->mTypeVariant.emplace(); } else if(type == AL_FILTER_BANDPASS) { filter->Gain = AL_BANDPASS_DEFAULT_GAIN; filter->GainHF = AL_BANDPASS_DEFAULT_GAINHF; - filter->HFReference = LOWPASSFREQREF; + filter->HFReference = LowPassFreqRef; filter->GainLF = AL_BANDPASS_DEFAULT_GAINLF; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALbandpass_vtable; + filter->LFReference = HighPassFreqRef; + filter->mTypeVariant.emplace(); } else { filter->Gain = 1.0f; filter->GainHF = 1.0f; - filter->HFReference = LOWPASSFREQREF; + filter->HFReference = LowPassFreqRef; filter->GainLF = 1.0f; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALnullfilter_vtable; + filter->LFReference = HighPassFreqRef; + filter->mTypeVariant.emplace(); } filter->type = type; } -bool EnsureFilters(ALCdevice *device, size_t needed) -{ - size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), size_t{0}, +auto EnsureFilters(ALCdevice *device, size_t needed) noexcept -> bool +try { + size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), 0_uz, [](size_t cur, const FilterSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; @@ -339,22 +108,20 @@ bool EnsureFilters(ALCdevice *device, size_t needed) if(device->FilterList.size() >= 1<<25) UNLIKELY return false; - device->FilterList.emplace_back(); - auto sublist = device->FilterList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Filters = static_cast(al_calloc(alignof(ALfilter), sizeof(ALfilter)*64)); - if(!sublist->Filters) UNLIKELY - { - device->FilterList.pop_back(); - return false; - } - count += 64; + FilterSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.Filters = SubListAllocator{}.allocate(1); + device->FilterList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } return true; } +catch(...) { + return false; +} -ALfilter *AllocFilter(ALCdevice *device) +ALfilter *AllocFilter(ALCdevice *device) noexcept { auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(), [](const FilterSubList &entry) noexcept -> bool @@ -363,7 +130,7 @@ ALfilter *AllocFilter(ALCdevice *device) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALfilter *filter{al::construct_at(sublist->Filters + slidx)}; + ALfilter *filter{al::construct_at(al::to_address(sublist->Filters->begin() + slidx))}; InitFilterParams(filter, AL_FILTER_NULL); /* Add 1 to avoid filter ID 0. */ @@ -376,17 +143,19 @@ ALfilter *AllocFilter(ALCdevice *device) void FreeFilter(ALCdevice *device, ALfilter *filter) { + device->mFilterNames.erase(filter->id); + const ALuint id{filter->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; - al::destroy_at(filter); + std::destroy_at(filter); device->FilterList[lidx].FreeMask |= 1_u64 << slidx; } -inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) +auto LookupFilter(ALCdevice *device, ALuint id) noexcept -> ALfilter* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -396,327 +165,466 @@ inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) FilterSubList &sublist = device->FilterList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Filters + slidx; + return al::to_address(sublist.Filters->begin() + slidx); } } // namespace -AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters) -START_API_FUNC +/* Null filter parameter handlers */ +template<> +void FilterTable::setParami(ALfilter*, ALenum param, int) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::setParamiv(ALfilter*, ALenum param, const int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::setParamf(ALfilter*, ALenum param, float) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::setParamfv(ALfilter*, ALenum param, const float*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::getParami(const ALfilter*, ALenum param, int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::getParamiv(const ALfilter*, ALenum param, int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::getParamf(const ALfilter*, ALenum param, float*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::getParamfv(const ALfilter*, ALenum param, float*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } + +/* Lowpass parameter handlers */ +template<> +void FilterTable::setParami(ALfilter*, ALenum param, int) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } +template<> +void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) +{ setParami(filter, param, *values); } +template<> +void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Generating %d filters", n); - if(n <= 0) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - if(!EnsureFilters(device, static_cast(n))) + switch(param) { - context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, (n==1)?"":"s"); + case AL_LOWPASS_GAIN: + if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN)) + throw al::context_error{AL_INVALID_VALUE, "Low-pass gain %f out of range", val}; + filter->Gain = val; + return; + + case AL_LOWPASS_GAINHF: + if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF)) + throw al::context_error{AL_INVALID_VALUE, "Low-pass gainhf %f out of range", val}; + filter->GainHF = val; return; } - - if(n == 1) LIKELY - { - /* 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 ids; - ids.reserve(static_cast(n)); - do { - ALfilter *filter{AllocFilter(device)}; - ids.emplace_back(filter->id); - } while(--n); - std::copy(ids.begin(), ids.end(), filters); - } + throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; } -END_API_FUNC - -AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters) -START_API_FUNC +template<> +void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) +{ setParamf(filter, param, *vals); } +template<> +void FilterTable::getParami(const ALfilter*, ALenum param, int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } +template<> +void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) +{ getParami(filter, param, values); } +template<> +void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + switch(param) + { + case AL_LOWPASS_GAIN: *val = filter->Gain; return; + case AL_LOWPASS_GAINHF: *val = filter->GainHF; return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; +} +template<> +void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) +{ getParamf(filter, param, vals); } - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Deleting %d filters", n); +/* Highpass parameter handlers */ +template<> +void FilterTable::setParami(ALfilter*, ALenum param, int) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } +template<> +void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) +{ setParami(filter, param, *values); } +template<> +void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) +{ + switch(param) + { + case AL_HIGHPASS_GAIN: + if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN)) + throw al::context_error{AL_INVALID_VALUE, "High-pass gain %f out of range", val}; + filter->Gain = val; + return; + + case AL_HIGHPASS_GAINLF: + if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF)) + throw al::context_error{AL_INVALID_VALUE, "High-pass gainlf %f out of range", val}; + filter->GainLF = val; + return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; +} +template<> +void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) +{ setParamf(filter, param, *vals); } +template<> +void FilterTable::getParami(const ALfilter*, ALenum param, int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } +template<> +void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) +{ getParami(filter, param, values); } +template<> +void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) +{ + switch(param) + { + case AL_HIGHPASS_GAIN: *val = filter->Gain; return; + case AL_HIGHPASS_GAINLF: *val = filter->GainLF; return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; +} +template<> +void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) +{ getParamf(filter, param, vals); } + +/* Bandpass parameter handlers */ +template<> +void FilterTable::setParami(ALfilter*, ALenum param, int) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } +template<> +void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) +{ setParami(filter, param, *values); } +template<> +void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) +{ + switch(param) + { + case AL_BANDPASS_GAIN: + if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN)) + throw al::context_error{AL_INVALID_VALUE, "Band-pass gain %f out of range", val}; + filter->Gain = val; + return; + + case AL_BANDPASS_GAINHF: + if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF)) + throw al::context_error{AL_INVALID_VALUE, "Band-pass gainhf %f out of range", val}; + filter->GainHF = val; + return; + + case AL_BANDPASS_GAINLF: + if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF)) + throw al::context_error{AL_INVALID_VALUE, "Band-pass gainlf %f out of range", val}; + filter->GainLF = val; + return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; +} +template<> +void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) +{ setParamf(filter, param, *vals); } +template<> +void FilterTable::getParami(const ALfilter*, ALenum param, int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } +template<> +void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) +{ getParami(filter, param, values); } +template<> +void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) +{ + switch(param) + { + case AL_BANDPASS_GAIN: *val = filter->Gain; return; + case AL_BANDPASS_GAINHF: *val = filter->GainHF; return; + case AL_BANDPASS_GAINLF: *val = filter->GainLF; return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; +} +template<> +void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) +{ getParamf(filter, param, vals); } + + +AL_API DECL_FUNC2(void, alGenFilters, ALsizei,n, ALuint*,filters) +FORCE_ALIGN void AL_APIENTRY alGenFiltersDirect(ALCcontext *context, ALsizei n, ALuint *filters) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Generating %d filters", n}; if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; + std::lock_guard filterlock{device->FilterLock}; + + const al::span fids{filters, static_cast(n)}; + if(!EnsureFilters(device, fids.size())) + throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, + (n == 1) ? "" : "s"}; + + std::generate(fids.begin(), fids.end(), [device]{ return AllocFilter(device)->id; }); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC2(void, alDeleteFilters, ALsizei,n, const ALuint*,filters) +FORCE_ALIGN void AL_APIENTRY alDeleteFiltersDirect(ALCcontext *context, ALsizei n, + const ALuint *filters) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Deleting %d filters", n}; + if(n <= 0) UNLIKELY return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; /* First try to find any filters that are invalid. */ auto validate_filter = [device](const ALuint fid) -> bool { return !fid || LookupFilter(device, fid) != nullptr; }; - const ALuint *filters_end = filters + n; - auto invflt = std::find_if_not(filters, filters_end, validate_filter); - if(invflt != filters_end) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", *invflt); - return; - } + const al::span fids{filters, static_cast(n)}; + auto invflt = std::find_if_not(fids.begin(), fids.end(), validate_filter); + if(invflt != fids.end()) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", *invflt}; /* All good. Delete non-0 filter IDs. */ auto delete_filter = [device](const ALuint fid) -> void { - ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr}; - if(filter) FreeFilter(device, filter); + if(ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr}) + FreeFilter(device, filter); }; - std::for_each(filters, filters_end, delete_filter); + std::for_each(fids.begin(), fids.end(), delete_filter); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsFilter, ALuint,filter) +FORCE_ALIGN ALboolean AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) noexcept { - ContextRef context{GetContextRef()}; - if(context) LIKELY - { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - if(!filter || LookupFilter(device, filter)) - return AL_TRUE; - } + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + if(!filter || LookupFilter(device, filter)) + return AL_TRUE; return AL_FALSE; } -END_API_FUNC -AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - +AL_API DECL_FUNC3(void, alFilteri, ALuint,filter, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, + ALint value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; + std::lock_guard filterlock{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - 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 try - { - /* Call the appropriate handler */ - alfilt->setParami(param, value); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } - } -} -END_API_FUNC + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; -AL_API void AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *values) -START_API_FUNC -{ switch(param) { case AL_FILTER_TYPE: - alFilteri(filter, param, values[0]); + if(!(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS + || value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)) + throw al::context_error{AL_INVALID_VALUE, "Invalid filter type 0x%04x", value}; + InitFilterParams(alfilt, value); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->setParamiv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + /* Call the appropriate handler */ + std::visit([alfilt,param,value](auto&& thunk){thunk.setParami(alfilt, param, value);}, + alfilt->mTypeVariant); } -END_API_FUNC - -AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->setParamf(param, value); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->setParamfv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - const ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - if(param == AL_FILTER_TYPE) - *value = alfilt->type; - else try - { - /* Call the appropriate handler */ - alfilt->getParami(param, value); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *values) -START_API_FUNC -{ +AL_API DECL_FUNC3(void, alFilteriv, ALuint,filter, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, + const ALint *values) noexcept +try { switch(param) { case AL_FILTER_TYPE: - alGetFilteri(filter, param, values); + alFilteriDirect(context, filter, param, *values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + + ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + /* Call the appropriate handler */ + std::visit([alfilt,param,values](auto&& thunk){thunk.setParamiv(alfilt, param, values);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alFilterf, ALuint,filter, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, + ALfloat value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + + ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + /* Call the appropriate handler */ + std::visit([alfilt,param,value](auto&& thunk){thunk.setParamf(alfilt, param, value);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alFilterfv, ALuint,filter, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, + const ALfloat *values) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + + ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + /* Call the appropriate handler */ + std::visit([alfilt,param,values](auto&& thunk){thunk.setParamfv(alfilt, param, values);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetFilteri, ALuint,filter, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, + ALint *value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + + const ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + switch(param) + { + case AL_FILTER_TYPE: + *value = alfilt->type; + return; + } + + /* Call the appropriate handler */ + std::visit([alfilt,param,value](auto&& thunk){thunk.getParami(alfilt, param, value);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetFilteriv, ALuint,filter, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, + ALint *values) noexcept +try { + switch(param) + { + case AL_FILTER_TYPE: + alGetFilteriDirect(context, filter, param, values); + return; + } ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; + std::lock_guard filterlock{device->FilterLock}; + + const ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + /* Call the appropriate handler */ + std::visit([alfilt,param,values](auto&& thunk){thunk.getParamiv(alfilt, param, values);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetFilterf, ALuint,filter, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, + ALfloat *value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->getParamiv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; -AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value) -START_API_FUNC + /* Call the appropriate handler */ + std::visit([alfilt,param,value](auto&& thunk){thunk.getParamf(alfilt, param, value);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetFilterfv, ALuint,filter, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, + ALfloat *values) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + + const ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) UNLIKELY + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + /* Call the appropriate handler */ + std::visit([alfilt,param,values](auto&& thunk){thunk.getParamfv(alfilt, param, values);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +void ALfilter::SetName(ALCcontext *context, ALuint id, std::string_view name) { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; + std::lock_guard filterlock{device->FilterLock}; - const ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->getParamf(param, value); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + auto filter = LookupFilter(device, id); + if(!filter) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", id}; + + device->mFilterNames.insert_or_assign(id, name); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - const ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->getParamfv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC FilterSubList::~FilterSubList() { + if(!Filters) + return; + uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; - al::destroy_at(Filters+idx); + std::destroy_at(al::to_address(Filters->begin() + idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; - al_free(Filters); + SubListAllocator{}.deallocate(Filters, 1); Filters = nullptr; } diff --git a/Engine/lib/openal-soft/al/filter.h b/Engine/lib/openal-soft/al/filter.h index 65a9e30fd..562ec52e2 100644 --- a/Engine/lib/openal-soft/al/filter.h +++ b/Engine/lib/openal-soft/al/filter.h @@ -1,15 +1,40 @@ #ifndef AL_FILTER_H #define AL_FILTER_H +#include +#include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" -#include "AL/alext.h" +#include "AL/efx.h" #include "almalloc.h" +#include "alnumeric.h" -#define LOWPASSFREQREF 5000.0f -#define HIGHPASSFREQREF 250.0f + +inline constexpr float LowPassFreqRef{5000.0f}; +inline constexpr float HighPassFreqRef{250.0f}; + +template +struct FilterTable { + static void setParami(struct ALfilter*, ALenum, int); + static void setParamiv(struct ALfilter*, ALenum, const int*); + static void setParamf(struct ALfilter*, ALenum, float); + static void setParamfv(struct ALfilter*, ALenum, const float*); + + static void getParami(const struct ALfilter*, ALenum, int*); + static void getParamiv(const struct ALfilter*, ALenum, int*); + static void getParamf(const struct ALfilter*, ALenum, float*); + static void getParamfv(const struct ALfilter*, ALenum, float*); +}; + +struct NullFilterTable : public FilterTable { }; +struct LowpassFilterTable : public FilterTable { }; +struct HighpassFilterTable : public FilterTable { }; +struct BandpassFilterTable : public FilterTable { }; struct ALfilter { @@ -17,36 +42,35 @@ struct ALfilter { float Gain{1.0f}; float GainHF{1.0f}; - float HFReference{LOWPASSFREQREF}; + float HFReference{LowPassFreqRef}; float GainLF{1.0f}; - float LFReference{HIGHPASSFREQREF}; + float LFReference{HighPassFreqRef}; - struct Vtable { - void (*const setParami )(ALfilter *filter, ALenum param, int val); - void (*const setParamiv)(ALfilter *filter, ALenum param, const int *vals); - void (*const setParamf )(ALfilter *filter, ALenum param, float val); - void (*const setParamfv)(ALfilter *filter, ALenum param, const float *vals); - - void (*const getParami )(const ALfilter *filter, ALenum param, int *val); - void (*const getParamiv)(const ALfilter *filter, ALenum param, int *vals); - void (*const getParamf )(const ALfilter *filter, ALenum param, float *val); - void (*const getParamfv)(const ALfilter *filter, ALenum param, float *vals); - }; - const Vtable *vtab{nullptr}; + using TableTypes = std::variant; + TableTypes mTypeVariant; /* Self ID */ ALuint id{0}; - void setParami(ALenum param, int value) { vtab->setParami(this, param, value); } - void setParamiv(ALenum param, const int *values) { vtab->setParamiv(this, param, values); } - void setParamf(ALenum param, float value) { vtab->setParamf(this, param, value); } - void setParamfv(ALenum param, const float *values) { vtab->setParamfv(this, param, values); } - void getParami(ALenum param, int *value) const { vtab->getParami(this, param, value); } - void getParamiv(ALenum param, int *values) const { vtab->getParamiv(this, param, values); } - void getParamf(ALenum param, float *value) const { vtab->getParamf(this, param, value); } - void getParamfv(ALenum param, float *values) const { vtab->getParamfv(this, param, values); } + static void SetName(ALCcontext *context, ALuint id, std::string_view name); - DISABLE_ALLOC() + DISABLE_ALLOC +}; + +struct FilterSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> Filters{nullptr}; + + FilterSubList() noexcept = default; + FilterSubList(const FilterSubList&) = delete; + FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} + { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } + ~FilterSubList(); + + FilterSubList& operator=(const FilterSubList&) = delete; + FilterSubList& operator=(FilterSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } }; #endif diff --git a/Engine/lib/openal-soft/al/listener.cpp b/Engine/lib/openal-soft/al/listener.cpp index 06d7c370d..30ceed040 100644 --- a/Engine/lib/openal-soft/al/listener.cpp +++ b/Engine/lib/openal-soft/al/listener.cpp @@ -22,6 +22,7 @@ #include "listener.h" +#include #include #include @@ -30,9 +31,10 @@ #include "AL/efx.h" #include "alc/context.h" -#include "almalloc.h" -#include "atomic.h" -#include "core/except.h" +#include "alc/inprogext.h" +#include "alspan.h" +#include "direct_defs.h" +#include "error.h" #include "opthelpers.h" @@ -68,377 +70,333 @@ inline void CommitAndUpdateProps(ALCcontext *context) } // namespace -AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - +AL_API DECL_FUNC2(void, alListenerf, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alListenerfDirect(ALCcontext *context, ALenum param, ALfloat value) noexcept +try { ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_GAIN: if(!(value >= 0.0f && std::isfinite(value))) - return context->setError(AL_INVALID_VALUE, "Listener gain out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener gain out of range"}; listener.Gain = value; - UpdateProps(context.get()); - break; + UpdateProps(context); + return; case AL_METERS_PER_UNIT: if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT)) - return context->setError(AL_INVALID_VALUE, "Listener meters per unit out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener meters per unit out of range"}; listener.mMetersPerUnit = value; - UpdateProps(context.get()); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener float property"); + UpdateProps(context); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC4(void, alListener3f, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) +FORCE_ALIGN void AL_APIENTRY alListener3fDirect(ALCcontext *context, ALenum param, ALfloat value1, + ALfloat value2, ALfloat value3) noexcept +try { ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_POSITION: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - return context->setError(AL_INVALID_VALUE, "Listener position out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener position out of range"}; listener.Position[0] = value1; listener.Position[1] = value2; listener.Position[2] = value3; - CommitAndUpdateProps(context.get()); - break; + CommitAndUpdateProps(context); + return; case AL_VELOCITY: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - return context->setError(AL_INVALID_VALUE, "Listener velocity out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener velocity out of range"}; listener.Velocity[0] = value1; listener.Velocity[1] = value2; listener.Velocity[2] = value3; - CommitAndUpdateProps(context.get()); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property"); + CommitAndUpdateProps(context); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values) -START_API_FUNC -{ - if(values) +AL_API DECL_FUNC2(void, alListenerfv, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum param, + const ALfloat *values) noexcept +try { + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { - switch(param) - { - case AL_GAIN: - case AL_METERS_PER_UNIT: - alListenerf(param, values[0]); - return; + case AL_GAIN: + case AL_METERS_PER_UNIT: + alListenerfDirect(context, param, *values); + return; - case AL_POSITION: - case AL_VELOCITY: - alListener3f(param, values[0], values[1], values[2]); - return; - } + case AL_POSITION: + case AL_VELOCITY: + auto vals = al::span{values, 3_uz}; + alListener3fDirect(context, param, vals[0], vals[1], vals[2]); + return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); - ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_ORIENTATION: - if(!(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) && - std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5]))) + auto vals = al::span{values, 6_uz}; + if(!std::all_of(vals.cbegin(), vals.cend(), [](float f) { return std::isfinite(f); })) return context->setError(AL_INVALID_VALUE, "Listener orientation out of range"); /* AT then UP */ - listener.OrientAt[0] = values[0]; - listener.OrientAt[1] = values[1]; - listener.OrientAt[2] = values[2]; - listener.OrientUp[0] = values[3]; - listener.OrientUp[1] = values[4]; - listener.OrientUp[2] = values[5]; - CommitAndUpdateProps(context.get()); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property"); + std::copy_n(vals.cbegin(), 3, listener.OrientAt.begin()); + std::copy_n(vals.cbegin()+3, 3, listener.OrientUp.begin()); + CommitAndUpdateProps(context); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param}; } -END_API_FUNC - - -AL_API void AL_APIENTRY alListeneri(ALenum param, ALint /*value*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid listener integer property"); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3) -START_API_FUNC -{ + +AL_API DECL_FUNC2(void, alListeneri, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint /*value*/) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC4(void, alListener3i, ALenum,param, ALint,value1, ALint,value2, ALint,value3) +FORCE_ALIGN void AL_APIENTRY alListener3iDirect(ALCcontext *context, ALenum param, ALint value1, + ALint value2, ALint value3) noexcept +try { switch(param) { case AL_POSITION: case AL_VELOCITY: - alListener3f(param, static_cast(value1), static_cast(value2), - static_cast(value3)); + alListener3fDirect(context, param, static_cast(value1), + static_cast(value2), static_cast(value3)); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + std::lock_guard proplock{context->mPropLock}; + throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} - std::lock_guard _{context->mPropLock}; +AL_API DECL_FUNC2(void, alListeneriv, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum param, + const ALint *values) noexcept +try { + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + al::span vals; switch(param) { - default: - context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property"); + case AL_POSITION: + case AL_VELOCITY: + vals = {values, 3_uz}; + alListener3fDirect(context, param, static_cast(vals[0]), + static_cast(vals[1]), static_cast(vals[2])); + return; + + case AL_ORIENTATION: + vals = {values, 6_uz}; + const std::array fvals{static_cast(vals[0]), static_cast(vals[1]), + static_cast(vals[2]), static_cast(vals[3]), + static_cast(vals[4]), static_cast(vals[5]), + }; + alListenerfvDirect(context, param, fvals.data()); + return; } + + std::lock_guard proplock{context->mPropLock}; + throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x", + param}; } -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(values[0]), static_cast(values[1]), - static_cast(values[2])); - return; - - case AL_ORIENTATION: - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - fvals[3] = static_cast(values[3]); - fvals[4] = static_cast(values[4]); - fvals[5] = static_cast(values[5]); - alListenerfv(param, fvals); - return; - } - } - - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property"); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; +AL_API DECL_FUNC2(void, alGetListenerf, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum param, + ALfloat *value) noexcept +try { if(!value) - 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 void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; + switch(param) + { + case AL_GAIN: *value = listener.Gain; return; + case AL_METERS_PER_UNIT: *value = listener.mMetersPerUnit; return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC4(void, alGetListener3f, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) +FORCE_ALIGN void AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum param, + ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept +try { if(!value1 || !value2 || !value3) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + ALlistener &listener = context->mListener; + std::lock_guard proplock{context->mPropLock}; + switch(param) { case AL_POSITION: *value1 = listener.Position[0]; *value2 = listener.Position[1]; *value3 = listener.Position[2]; - break; + return; case AL_VELOCITY: *value1 = listener.Velocity[0]; *value2 = listener.Velocity[1]; *value3 = listener.Velocity[2]; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property"); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values) -START_API_FUNC -{ +AL_API DECL_FUNC2(void, alGetListenerfv, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum param, + ALfloat *values) noexcept +try { + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + switch(param) { case AL_GAIN: case AL_METERS_PER_UNIT: - alGetListenerf(param, values); + alGetListenerfDirect(context, param, values); return; case AL_POSITION: case AL_VELOCITY: - alGetListener3f(param, values+0, values+1, values+2); + auto vals = al::span{values, 3_uz}; + alGetListener3fDirect(context, param, &vals[0], &vals[1], &vals[2]); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard proplock{context->mPropLock}; + switch(param) { case AL_ORIENTATION: + al::span vals{values, 6_uz}; // AT then UP - values[0] = listener.OrientAt[0]; - values[1] = listener.OrientAt[1]; - values[2] = listener.OrientAt[2]; - values[3] = listener.OrientUp[0]; - values[4] = listener.OrientUp[1]; - values[5] = listener.OrientUp[2]; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property"); + std::copy_n(listener.OrientAt.cbegin(), 3, vals.begin()); + std::copy_n(listener.OrientUp.cbegin(), 3, vals.begin()+3); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param}; } -END_API_FUNC - - -AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - if(!value) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid listener integer property"); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + +AL_API DECL_FUNC2(void, alGetListeneri, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) noexcept +try { + if(!value) throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + std::lock_guard proplock{context->mPropLock}; + throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC4(void, alGetListener3i, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) +FORCE_ALIGN void AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum param, + ALint *value1, ALint *value2, ALint *value3) noexcept +try { + if(!value1 || !value2 || !value3) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; - if(!value1 || !value2 || !value3) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard proplock{context->mPropLock}; + switch(param) { case AL_POSITION: *value1 = static_cast(listener.Position[0]); *value2 = static_cast(listener.Position[1]); *value3 = static_cast(listener.Position[2]); - break; + return; case AL_VELOCITY: *value1 = static_cast(listener.Velocity[0]); *value2 = static_cast(listener.Velocity[1]); *value3 = static_cast(listener.Velocity[2]); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property"); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint* values) -START_API_FUNC -{ +AL_API DECL_FUNC2(void, alGetListeneriv, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum param, + ALint *values) noexcept +try { + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + switch(param) { case AL_POSITION: case AL_VELOCITY: - alGetListener3i(param, values+0, values+1, values+2); + auto vals = al::span{values, 3_uz}; + alGetListener3iDirect(context, param, &vals[0], &vals[1], &vals[2]); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard proplock{context->mPropLock}; + + static constexpr auto f2i = [](const float val) noexcept { return static_cast(val); }; + switch(param) { case AL_ORIENTATION: + auto vals = al::span{values, 6_uz}; // AT then UP - values[0] = static_cast(listener.OrientAt[0]); - values[1] = static_cast(listener.OrientAt[1]); - values[2] = static_cast(listener.OrientAt[2]); - values[3] = static_cast(listener.OrientUp[0]); - values[4] = static_cast(listener.OrientUp[1]); - values[5] = static_cast(listener.OrientUp[2]); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property"); + std::transform(listener.OrientAt.cbegin(), listener.OrientAt.cend(), vals.begin(), f2i); + std::transform(listener.OrientUp.cbegin(), listener.OrientUp.cend(), vals.begin()+3, f2i); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC diff --git a/Engine/lib/openal-soft/al/listener.h b/Engine/lib/openal-soft/al/listener.h index 815328771..7a4ff530f 100644 --- a/Engine/lib/openal-soft/al/listener.h +++ b/Engine/lib/openal-soft/al/listener.h @@ -3,8 +3,6 @@ #include -#include "AL/al.h" -#include "AL/alc.h" #include "AL/efx.h" #include "almalloc.h" @@ -18,7 +16,7 @@ struct ALlistener { float Gain{1.0f}; float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT}; - DISABLE_ALLOC() + DISABLE_ALLOC }; #endif diff --git a/Engine/lib/openal-soft/al/source.cpp b/Engine/lib/openal-soft/al/source.cpp index cba33862e..514d4f7fe 100644 --- a/Engine/lib/openal-soft/al/source.cpp +++ b/Engine/lib/openal-soft/al/source.cpp @@ -25,22 +25,28 @@ #include #include #include +#include #include #include -#include +#include #include #include -#include -#include +#include +#include #include #include #include #include -#include #include +#include +#include #include -#include +#include +#include +#include +#include #include +#include #include "AL/al.h" #include "AL/alc.h" @@ -48,41 +54,43 @@ #include "AL/efx.h" #include "albit.h" -#include "alc/alu.h" #include "alc/backends/base.h" #include "alc/context.h" #include "alc/device.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "atomic.h" #include "auxeffectslot.h" #include "buffer.h" -#include "core/ambidefs.h" -#include "core/bformatdec.h" -#include "core/except.h" -#include "core/filters/nfc.h" -#include "core/filters/splitter.h" +#include "core/buffer_storage.h" #include "core/logging.h" +#include "core/mixer/defs.h" #include "core/voice_change.h" -#include "event.h" +#include "direct_defs.h" +#include "error.h" #include "filter.h" +#include "flexarray.h" +#include "intrusive_ptr.h" #include "opthelpers.h" -#include "ringbuffer.h" -#include "threads.h" #ifdef ALSOFT_EAX -#include -#endif // ALSOFT_EAX - -bool sBufferSubDataCompat{false}; +#include "eax/api.h" +#include "eax/call.h" +#include "eax/fx_slot_index.h" +#include "eax/utils.h" +#endif namespace { -using namespace std::placeholders; +using SubListAllocator = al::allocator>; using std::chrono::nanoseconds; +using seconds_d = std::chrono::duration; +using source_store_array = std::array; +using source_store_vector = std::vector; +using source_store_variant = std::variant; + Voice *GetSourceVoice(ALsource *source, ALCcontext *context) { @@ -95,7 +103,7 @@ Voice *GetSourceVoice(ALsource *source, ALCcontext *context) if(voice->mSourceID.load(std::memory_order_acquire) == sid) return voice; } - source->VoiceIdx = INVALID_VOICE_IDX; + source->VoiceIdx = InvalidVoiceIndex; return nullptr; } @@ -153,6 +161,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context props->Radius = source->Radius; props->EnhWidth = source->EnhWidth; + props->Panning = source->mPanningEnabled ? source->mPan : 0.0f; props->Direct.Gain = source->Direct.Gain; props->Direct.GainHF = source->Direct.GainHF; @@ -171,7 +180,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context ret.LFReference = srcsend.LFReference; return ret; }; - std::transform(source->Send.cbegin(), source->Send.cend(), props->Send, copy_send); + std::transform(source->Send.cbegin(), source->Send.cend(), props->Send.begin(), copy_send); if(!props->Send[0].Slot && context->mDefaultSlot) props->Send[0].Slot = context->mDefaultSlot->mSlot; @@ -202,7 +211,7 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds do { refcount = device->waitForMix(); - *clocktime = GetDeviceClockTime(device); + *clocktime = device->getClockTime(); voice = GetSourceVoice(Source, context); if(voice) { @@ -212,7 +221,7 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds 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)); + } while(refcount != device->mMixCount.load(std::memory_order_relaxed)); if(!voice) return 0; @@ -242,7 +251,7 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl do { refcount = device->waitForMix(); - *clocktime = GetDeviceClockTime(device); + *clocktime = device->getClockTime(); voice = GetSourceVoice(Source, context); if(voice) { @@ -252,7 +261,7 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl 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)); + } while(refcount != device->mMixCount.load(std::memory_order_relaxed)); if(!voice) return 0.0f; @@ -281,7 +290,8 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl * (Bytes, Samples or Seconds). The offset is relative to the start of the * queue (not the start of the current buffer). */ -double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) +template +NOINLINE T GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) { ALCdevice *device{context->mALDevice.get()}; const VoiceBufferItem *Current{}; @@ -301,10 +311,10 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) 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)); + } while(refcount != device->mMixCount.load(std::memory_order_relaxed)); if(!voice) - return 0.0; + return T{0}; const ALbuffer *BufferFmt{nullptr}; auto BufferList = Source->mQueue.cbegin(); @@ -321,24 +331,48 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) } ASSUME(BufferFmt != nullptr); - double offset{}; + T offset{}; switch(name) { case AL_SEC_OFFSET: - offset = static_cast(readPos) + readPosFrac/double{MixerFracOne}; - offset /= BufferFmt->mSampleRate; + if constexpr(std::is_floating_point_v) + { + offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; + offset /= static_cast(BufferFmt->mSampleRate); + } + else + { + readPos /= BufferFmt->mSampleRate; + offset = static_cast(std::clamp(readPos, std::numeric_limits::min(), + std::numeric_limits::max())); + } break; case AL_SAMPLE_OFFSET: - offset = static_cast(readPos) + readPosFrac/double{MixerFracOne}; + if constexpr(std::is_floating_point_v) + offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; + else + offset = static_cast(std::clamp(readPos, std::numeric_limits::min(), + std::numeric_limits::max())); break; case AL_BYTE_OFFSET: const ALuint BlockSamples{BufferFmt->mBlockAlign}; const ALuint BlockSize{BufferFmt->blockSizeFromFmt()}; - /* Round down to the block boundary. */ - offset = static_cast(readPos / BlockSamples) * BlockSize; + readPos = readPos / BlockSamples * BlockSize; + + if constexpr(std::is_floating_point_v) + offset = static_cast(readPos); + else + { + if(readPos > std::numeric_limits::max()) + offset = RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); + else if(readPos < std::numeric_limits::min()) + offset = RoundUp(std::numeric_limits::min(), static_cast(BlockSize)); + else + offset = static_cast(readPos); + } break; } return offset; @@ -349,7 +383,8 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) * Gets the length of the given Source's buffer queue, in the appropriate * format (Bytes, Samples or Seconds). */ -double GetSourceLength(const ALsource *source, ALenum name) +template +NOINLINE T GetSourceLength(const ALsource *source, ALenum name) { uint64_t length{0}; const ALbuffer *BufferFmt{nullptr}; @@ -360,25 +395,40 @@ double GetSourceLength(const ALsource *source, ALenum name) length += listitem.mSampleLen; } if(length == 0) - return 0.0; + return T{0}; ASSUME(BufferFmt != nullptr); switch(name) { case AL_SEC_LENGTH_SOFT: - return static_cast(length) / BufferFmt->mSampleRate; + if constexpr(std::is_floating_point_v) + return static_cast(length) / static_cast(BufferFmt->mSampleRate); + else + return static_cast(std::min(length/BufferFmt->mSampleRate, + std::numeric_limits::max())); case AL_SAMPLE_LENGTH_SOFT: - return static_cast(length); + if constexpr(std::is_floating_point_v) + return static_cast(length); + else + return static_cast(std::min(length, std::numeric_limits::max())); case AL_BYTE_LENGTH_SOFT: const ALuint BlockSamples{BufferFmt->mBlockAlign}; const ALuint BlockSize{BufferFmt->blockSizeFromFmt()}; - /* Round down to the block boundary. */ - return static_cast(length / BlockSamples) * BlockSize; + length = length / BlockSamples * BlockSize; + + if constexpr(std::is_floating_point_v) + return static_cast(length); + else + { + if(length > uint64_t{std::numeric_limits::max()}) + return RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); + return static_cast(length); + } } - return 0.0; + return T{0}; } @@ -392,11 +442,11 @@ struct VoicePos { * GetSampleOffset * * Retrieves the voice position, fixed-point fraction, and bufferlist item - * using the givem offset type and offset. If the offset is out of range, + * using the given offset type and offset. If the offset is out of range, * returns an empty optional. */ -al::optional GetSampleOffset(al::deque &BufferList, ALenum OffsetType, - double Offset) +std::optional GetSampleOffset(std::deque &BufferList, + ALenum OffsetType, double Offset) { /* Find the first valid Buffer in the Queue */ const ALbuffer *BufferFmt{nullptr}; @@ -406,7 +456,7 @@ al::optional GetSampleOffset(al::deque &BufferList, if(BufferFmt) break; } if(!BufferFmt) UNLIKELY - return al::nullopt; + return std::nullopt; /* Get sample frame offset */ int64_t offset{}; @@ -426,7 +476,7 @@ al::optional GetSampleOffset(al::deque &BufferList, dblfrac += 1.0; } offset = static_cast(dbloff); - frac = static_cast(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); + frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); break; case AL_SAMPLE_OFFSET: @@ -437,7 +487,7 @@ al::optional GetSampleOffset(al::deque &BufferList, dblfrac += 1.0; } offset = static_cast(dbloff); - frac = static_cast(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); + frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); break; case AL_BYTE_OFFSET: @@ -452,12 +502,12 @@ al::optional GetSampleOffset(al::deque &BufferList, if(offset < 0) { if(offset < std::numeric_limits::min()) - return al::nullopt; + return std::nullopt; return VoicePos{static_cast(offset), frac, &BufferList.front()}; } if(BufferFmt->mCallback) - return al::nullopt; + return std::nullopt; int64_t totalBufferLen{0}; for(auto &item : BufferList) @@ -473,7 +523,7 @@ al::optional GetSampleOffset(al::deque &BufferList, } /* Offset is out of range of the queue */ - return al::nullopt; + return std::nullopt; } @@ -485,9 +535,12 @@ void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, AL ALbuffer *buffer{BufferList->mBuffer}; voice->mFrequency = buffer->mSampleRate; - voice->mFmtChannels = - (buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced) ? - FmtSuperStereo : buffer->mChannels; + if(buffer->mChannels == FmtMono && source->mPanningEnabled) + voice->mFmtChannels = FmtMonoDup; + else if(buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced) + voice->mFmtChannels = FmtSuperStereo; + else + voice->mFmtChannels = buffer->mChannels; voice->mFmtType = buffer->mType; voice->mFrameStep = buffer->channelsFromFmt(); voice->mBytesPerBlock = buffer->blockSizeFromFmt(); @@ -534,7 +587,7 @@ void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) oldhead->mNext.store(tail, std::memory_order_release); const bool connected{device->Connected.load(std::memory_order_acquire)}; - device->waitForMix(); + std::ignore = device->waitForMix(); if(!connected) UNLIKELY { if(ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) @@ -640,7 +693,7 @@ bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALC return true; /* Otherwise, wait for any current mix to finish and check one last time. */ - device->waitForMix(); + std::ignore = device->waitForMix(); if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending) return true; /* The change-over failed because the old voice stopped before the new @@ -676,31 +729,30 @@ inline ALenum GetSourceState(ALsource *source, Voice *voice) bool EnsureSources(ALCcontext *context, size_t needed) { - size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(), - size_t{0}, + size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(), 0_uz, [](size_t cur, const SourceSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; - while(needed > count) - { - if(context->mSourceList.size() >= 1<<25) UNLIKELY - return false; - - context->mSourceList.emplace_back(); - auto sublist = context->mSourceList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Sources = static_cast(al_calloc(alignof(ALsource), sizeof(ALsource)*64)); - if(!sublist->Sources) UNLIKELY + try { + while(needed > count) { - context->mSourceList.pop_back(); - return false; + if(context->mSourceList.size() >= 1<<25) UNLIKELY + return false; + + SourceSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.Sources = SubListAllocator{}.allocate(1); + context->mSourceList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } - count += 64; + } + catch(...) { + return false; } return true; } -ALsource *AllocSource(ALCcontext *context) +ALsource *AllocSource(ALCcontext *context) noexcept { auto sublist = std::find_if(context->mSourceList.begin(), context->mSourceList.end(), [](const SourceSubList &entry) noexcept -> bool @@ -709,7 +761,10 @@ ALsource *AllocSource(ALCcontext *context) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALsource *source{al::construct_at(sublist->Sources + slidx)}; + ALsource *source{al::construct_at(al::to_address(sublist->Sources->begin() + slidx))}; +#ifdef ALSOFT_EAX + source->eaxInitialize(context); +#endif // ALSOFT_EAX /* Add 1 to avoid source ID 0. */ source->id = ((lidx<<6) | slidx) + 1; @@ -722,6 +777,8 @@ ALsource *AllocSource(ALCcontext *context) void FreeSource(ALCcontext *context, ALsource *source) { + context->mSourceNames.erase(source->id); + const ALuint id{source->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; @@ -738,7 +795,7 @@ void FreeSource(ALCcontext *context, ALsource *source) SendVoiceChanges(context, vchg); } - al::destroy_at(source); + std::destroy_at(source); context->mSourceList[lidx].FreeMask |= 1_u64 << slidx; context->mNumSources--; @@ -755,59 +812,58 @@ inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept SourceSubList &sublist{context->mSourceList[lidx]}; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Sources + slidx; + return al::to_address(sublist.Sources->begin() + slidx); } -inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept +auto LookupBuffer = [](ALCdevice *device, auto id) noexcept -> ALbuffer* { - const size_t lidx{(id-1) >> 6}; - const ALuint slidx{(id-1) & 0x3f}; + const auto lidx{(id-1) >> 6}; + const auto slidx{(id-1) & 0x3f}; if(lidx >= device->BufferList.size()) UNLIKELY return nullptr; - BufferSubList &sublist = device->BufferList[lidx]; + BufferSubList &sublist = device->BufferList[static_cast(lidx)]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Buffers + slidx; -} + return al::to_address(sublist.Buffers->begin() + static_cast(slidx)); +}; -inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) noexcept +auto LookupFilter = [](ALCdevice *device, auto id) noexcept -> ALfilter* { - const size_t lidx{(id-1) >> 6}; - const ALuint slidx{(id-1) & 0x3f}; + const auto lidx{(id-1) >> 6}; + const auto slidx{(id-1) & 0x3f}; if(lidx >= device->FilterList.size()) UNLIKELY return nullptr; - FilterSubList &sublist = device->FilterList[lidx]; + FilterSubList &sublist = device->FilterList[static_cast(lidx)]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Filters + slidx; -} + return al::to_address(sublist.Filters->begin() + static_cast(slidx)); +}; -inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept +auto LookupEffectSlot = [](ALCcontext *context, auto id) noexcept -> ALeffectslot* { - const size_t lidx{(id-1) >> 6}; - const ALuint slidx{(id-1) & 0x3f}; + const auto lidx{(id-1) >> 6}; + const auto slidx{(id-1) & 0x3f}; if(lidx >= context->mEffectSlotList.size()) UNLIKELY return nullptr; - EffectSlotSubList &sublist{context->mEffectSlotList[lidx]}; + EffectSlotSubList &sublist{context->mEffectSlotList[static_cast(lidx)]}; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.EffectSlots + slidx; -} + return al::to_address(sublist.EffectSlots->begin() + static_cast(slidx)); +}; -al::optional StereoModeFromEnum(ALenum mode) +auto StereoModeFromEnum = [](auto mode) noexcept -> std::optional { switch(mode) { case AL_NORMAL_SOFT: return SourceStereo::Normal; case AL_SUPER_STEREO_SOFT: return SourceStereo::Enhanced; } - WARN("Unsupported stereo mode: 0x%04x\n", mode); - return al::nullopt; -} + return std::nullopt; +}; ALenum EnumFromStereoMode(SourceStereo mode) { switch(mode) @@ -818,7 +874,7 @@ ALenum EnumFromStereoMode(SourceStereo mode) throw std::runtime_error{"Invalid SourceStereo: "+std::to_string(int(mode))}; } -al::optional SpatializeModeFromEnum(ALenum mode) +auto SpatializeModeFromEnum = [](auto mode) noexcept -> std::optional { switch(mode) { @@ -826,9 +882,8 @@ al::optional SpatializeModeFromEnum(ALenum mode) case AL_TRUE: return SpatializeMode::On; case AL_AUTO_SOFT: return SpatializeMode::Auto; } - WARN("Unsupported spatialize mode: 0x%04x\n", mode); - return al::nullopt; -} + return std::nullopt; +}; ALenum EnumFromSpatializeMode(SpatializeMode mode) { switch(mode) @@ -840,7 +895,7 @@ ALenum EnumFromSpatializeMode(SpatializeMode mode) throw std::runtime_error{"Invalid SpatializeMode: "+std::to_string(int(mode))}; } -al::optional DirectModeFromEnum(ALenum mode) +auto DirectModeFromEnum = [](auto mode) noexcept -> std::optional { switch(mode) { @@ -848,9 +903,8 @@ al::optional DirectModeFromEnum(ALenum mode) case AL_DROP_UNMATCHED_SOFT: return DirectMode::DropMismatch; case AL_REMIX_UNMATCHED_SOFT: return DirectMode::RemixMismatch; } - WARN("Unsupported direct mode: 0x%04x\n", mode); - return al::nullopt; -} + return std::nullopt; +}; ALenum EnumFromDirectMode(DirectMode mode) { switch(mode) @@ -862,7 +916,7 @@ ALenum EnumFromDirectMode(DirectMode mode) throw std::runtime_error{"Invalid DirectMode: "+std::to_string(int(mode))}; } -al::optional DistanceModelFromALenum(ALenum model) +auto DistanceModelFromALenum = [](auto model) noexcept -> std::optional { switch(model) { @@ -874,8 +928,8 @@ al::optional DistanceModelFromALenum(ALenum model) case AL_EXPONENT_DISTANCE: return DistanceModel::Exponent; case AL_EXPONENT_DISTANCE_CLAMPED: return DistanceModel::ExponentClamped; } - return al::nullopt; -} + return std::nullopt; +}; ALenum ALenumFromDistanceModel(DistanceModel model) { switch(model) @@ -970,11 +1024,13 @@ enum SourceProp : ALenum { /* AL_SOFT_buffer_sub_data */ srcByteRWOffsetsSOFT = AL_BYTE_RW_OFFSETS_SOFT, srcSampleRWOffsetsSOFT = AL_SAMPLE_RW_OFFSETS_SOFT, + + /* AL_SOFT_source_panning */ + srcPanningEnabledSOFT = AL_PANNING_ENABLED_SOFT, + srcPanSOFT = AL_PAN_SOFT, }; -constexpr size_t MaxValues{6u}; - constexpr ALuint IntValsByProp(ALenum prop) { switch(static_cast(prop)) @@ -999,6 +1055,8 @@ constexpr ALuint IntValsByProp(ALenum prop) case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_STEREO_MODE_SOFT: + case AL_PANNING_ENABLED_SOFT: + case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ @@ -1076,6 +1134,8 @@ constexpr ALuint Int64ValsByProp(ALenum prop) case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_STEREO_MODE_SOFT: + case AL_PANNING_ENABLED_SOFT: + case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ @@ -1169,6 +1229,8 @@ constexpr ALuint FloatValsByProp(ALenum prop) case AL_SEC_LENGTH_SOFT: case AL_STEREO_MODE_SOFT: case AL_SUPER_STEREO_WIDTH_SOFT: + case AL_PANNING_ENABLED_SOFT: + case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ @@ -1242,6 +1304,8 @@ constexpr ALuint DoubleValsByProp(ALenum prop) case AL_SEC_LENGTH_SOFT: case AL_STEREO_MODE_SOFT: case AL_SUPER_STEREO_WIDTH_SOFT: + case AL_PANNING_ENABLED_SOFT: + case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ @@ -1276,22 +1340,6 @@ constexpr ALuint DoubleValsByProp(ALenum prop) } -void SetSourcefv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); -void SetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); -void SetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); - -struct check_exception : std::exception { -}; -struct check_size_exception final : check_exception { - const char *what() const noexcept override - { return "check_size_exception"; } -}; -struct check_value_exception final : check_exception { - const char *what() const noexcept override - { return "check_value_exception"; } -}; - - void UpdateSourceProps(ALsource *source, ALCcontext *context) { if(!context->mDeferUpdates) @@ -1327,10 +1375,40 @@ inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) #endif +template +struct PropType { }; +template<> +struct PropType { static const char *Name() { return "integer"; } }; +template<> +struct PropType { static const char *Name() { return "int64"; } }; +template<> +struct PropType { static const char *Name() { return "float"; } }; +template<> +struct PropType { static const char *Name() { return "double"; } }; + +struct HexPrinter { + std::array mStr{}; + + template + HexPrinter(T value) + { + using ST = std::make_signed_t>; + if constexpr(std::is_same_v) + std::snprintf(mStr.data(), mStr.size(), "0x%x", value); + else if constexpr(std::is_same_v) + std::snprintf(mStr.data(), mStr.size(), "0x%lx", value); + else if constexpr(std::is_same_v) + std::snprintf(mStr.data(), mStr.size(), "0x%llx", value); + } + + [[nodiscard]] auto c_str() const noexcept -> const char* { return mStr.data(); } +}; + + /** - * Returns a pair of lambdas to check the following setters and getters. + * Returns a pair of lambdas to check the following setter. * - * The first lambda checks the size of the span is valid for its given size, + * The first lambda checks the size of the span is valid for the required size, * setting the proper context error and throwing a check_size_exception if it * fails. * @@ -1338,1007 +1416,918 @@ inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) * context error and throwing a check_value_exception if it failed. */ template -auto GetCheckers(ALCcontext *const Context, const SourceProp prop, const al::span values) +auto GetCheckers(const SourceProp prop, const al::span values) { return std::make_pair( [=](size_t expect) -> void { - if(values.size() == expect) LIKELY return; - Context->setError(AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu", - prop, expect, values.size()); - throw check_size_exception{}; + if(values.size() == expect) return; + throw al::context_error{AL_INVALID_ENUM, + "Property 0x%04x expects %zu value(s), got %zu", prop, expect, values.size()}; }, - [Context](bool passed) -> void + [](bool passed) -> void { - if(passed) LIKELY return; - Context->setError(AL_INVALID_VALUE, "Value out of range"); - throw check_value_exception{}; + if(passed) return; + throw al::context_error{AL_INVALID_VALUE, "Value out of range"}; } ); } -void SetSourcefv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - /* Structured bindings would be nice (C++17). */ - auto Checkers = GetCheckers(Context, prop, values); - auto &CheckSize = Checkers.first; - auto &CheckValue = Checkers.second; - int ival; +template +NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, + const al::span values) +{ + auto [CheckSize, CheckValue] = GetCheckers(prop, values); + ALCdevice *device{Context->mALDevice.get()}; switch(prop) { + case AL_SOURCE_STATE: + case AL_SOURCE_TYPE: + case AL_BUFFERS_QUEUED: + case AL_BUFFERS_PROCESSED: + if constexpr(std::is_integral_v) + { + /* Query only */ + throw al::context_error{AL_INVALID_OPERATION, + "Setting read-only source property 0x%04x", prop}; + } + break; + + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: case AL_SEC_LENGTH_SOFT: + case AL_SAMPLE_OFFSET_LATENCY_SOFT: case AL_SEC_OFFSET_LATENCY_SOFT: + case AL_SAMPLE_OFFSET_CLOCK_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: /* Query only */ - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); + throw al::context_error{AL_INVALID_OPERATION, "Setting read-only source property 0x%04x", + prop}; case AL_PITCH: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->Pitch = values[0]; + Source->Pitch = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_INNER_ANGLE: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 360.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{360}); - Source->InnerAngle = values[0]; + Source->InnerAngle = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_CONE_OUTER_ANGLE: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 360.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{360}); - Source->OuterAngle = values[0]; + Source->OuterAngle = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_GAIN: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->Gain = values[0]; + Source->Gain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_DISTANCE: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->MaxDistance = values[0]; + Source->MaxDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_ROLLOFF_FACTOR: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->RolloffFactor = values[0]; + Source->RolloffFactor = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_REFERENCE_DISTANCE: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->RefDistance = values[0]; + Source->RefDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_MIN_GAIN: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->MinGain = values[0]; + Source->MinGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_GAIN: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->MaxGain = values[0]; + Source->MaxGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_OUTER_GAIN: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 1.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{1}); - Source->OuterGain = values[0]; + Source->OuterGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_OUTER_GAINHF: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 1.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{1}); - Source->OuterGainHF = values[0]; + Source->OuterGainHF = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_AIR_ABSORPTION_FACTOR: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 10.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{10}); - Source->AirAbsorptionFactor = values[0]; + Source->AirAbsorptionFactor = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_ROOM_ROLLOFF_FACTOR: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 10.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{1}); - Source->RoomRolloffFactor = values[0]; + Source->RoomRolloffFactor = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_DOPPLER_FACTOR: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 1.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{1}); - Source->DopplerFactor = values[0]; + Source->DopplerFactor = static_cast(values[0]); return UpdateSourceProps(Source, Context); + + case AL_SOURCE_RELATIVE: + if constexpr(std::is_integral_v) + { + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->HeadRelative = values[0] != AL_FALSE; + return CommitAndUpdateSourceProps(Source, Context); + } + break; + + case AL_LOOPING: + if constexpr(std::is_integral_v) + { + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->Looping = values[0] != AL_FALSE; + if(Voice *voice{GetSourceVoice(Source, Context)}) + { + if(Source->Looping) + voice->mLoopBuffer.store(&Source->mQueue.front(), 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. + */ + std::ignore = device->waitForMix(); + } + return; + } + break; + + case AL_BUFFER: + if constexpr(std::is_integral_v) + { + CheckSize(1); + if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; + state == AL_PLAYING || state == AL_PAUSED) + throw al::context_error{AL_INVALID_OPERATION, + "Setting buffer on playing or paused source %u", Source->id}; + + std::deque oldlist; + if(values[0]) + { + using UT = std::make_unsigned_t; + std::lock_guard buflock{device->BufferLock}; + ALbuffer *buffer{LookupBuffer(device, static_cast(values[0]))}; + if(!buffer) + throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %s", + std::to_string(values[0]).c_str()}; + if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) + throw al::context_error{AL_INVALID_OPERATION, + "Setting non-persistently mapped buffer %u", buffer->id}; + if(buffer->mCallback && buffer->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, + "Setting already-set callback buffer %u", buffer->id}; + + /* Add the selected buffer to a one-item queue */ + std::deque newlist; + newlist.emplace_back(); + newlist.back().mCallback = buffer->mCallback; + newlist.back().mUserData = buffer->mUserData; + newlist.back().mBlockAlign = buffer->mBlockAlign; + newlist.back().mSampleLen = buffer->mSampleLen; + newlist.back().mLoopStart = buffer->mLoopStart; + newlist.back().mLoopEnd = buffer->mLoopEnd; + newlist.back().mSamples = buffer->mData; + newlist.back().mBuffer = buffer; + IncrementRef(buffer->ref); + + /* Source is now Static */ + Source->SourceType = AL_STATIC; + Source->mQueue.swap(oldlist); + Source->mQueue.swap(newlist); + } + else + { + /* Source is now Undetermined */ + Source->SourceType = AL_UNDETERMINED; + Source->mQueue.swap(oldlist); + } + + /* Delete all elements in the previous queue */ + for(auto &item : oldlist) + { + if(ALbuffer *buffer{item.mBuffer}) + DecrementRef(buffer->ref); + } + return; + } + break; + + case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: CheckSize(1); - CheckValue(std::isfinite(values[0])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(values[0])); if(Voice *voice{GetSourceVoice(Source, Context)}) { - auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]); - if(!vpos) return Context->setError(AL_INVALID_VALUE, "Invalid offset"); + auto vpos = GetSampleOffset(Source->mQueue, prop, static_cast(values[0])); + if(!vpos) throw al::context_error{AL_INVALID_VALUE, "Invalid offset"}; if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get())) return; } Source->OffsetType = prop; - Source->Offset = values[0]; + Source->Offset = static_cast(values[0]); return; case AL_SAMPLE_RW_OFFSETS_SOFT: + if(sBufferSubDataCompat) + { + if constexpr(std::is_integral_v) + { + /* Query only */ + throw al::context_error{AL_INVALID_OPERATION, + "Setting read-only source property 0x%04x", prop}; + } + } break; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ if(sBufferSubDataCompat) + { + if constexpr(std::is_integral_v) + { + /* Query only */ + throw al::context_error{AL_INVALID_OPERATION, + "Setting read-only source property 0x%04x", prop}; + } break; + } CheckSize(1); - CheckValue(values[0] >= 0.0f && std::isfinite(values[0])); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); - Source->Radius = values[0]; + Source->Radius = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_SUPER_STEREO_WIDTH_SOFT: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 1.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{1}); - Source->EnhWidth = values[0]; + Source->EnhWidth = static_cast(values[0]); + return UpdateSourceProps(Source, Context); + + case AL_PANNING_ENABLED_SOFT: + CheckSize(1); + if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; + state == AL_PLAYING || state == AL_PAUSED) + throw al::context_error{AL_INVALID_OPERATION, + "Modifying panning enabled on playing or paused source %u", Source->id}; + + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->mPanningEnabled = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); + + case AL_PAN_SOFT: + CheckSize(1); + CheckValue(values[0] >= T{-1} && values[0] <= T{1}); + + Source->mPan = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_STEREO_ANGLES: CheckSize(2); - CheckValue(std::isfinite(values[0]) && std::isfinite(values[1])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(static_cast(values[0])) + && std::isfinite(static_cast(values[1]))); - Source->StereoPan[0] = values[0]; - Source->StereoPan[1] = values[1]; + Source->StereoPan[0] = static_cast(values[0]); + Source->StereoPan[1] = static_cast(values[1]); return UpdateSourceProps(Source, Context); case AL_POSITION: CheckSize(3); - CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(static_cast(values[0])) + && std::isfinite(static_cast(values[1])) + && std::isfinite(static_cast(values[2]))); - Source->Position[0] = values[0]; - Source->Position[1] = values[1]; - Source->Position[2] = values[2]; + Source->Position[0] = static_cast(values[0]); + Source->Position[1] = static_cast(values[1]); + Source->Position[2] = static_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_VELOCITY: CheckSize(3); - CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(static_cast(values[0])) + && std::isfinite(static_cast(values[1])) + && std::isfinite(static_cast(values[2]))); - Source->Velocity[0] = values[0]; - Source->Velocity[1] = values[1]; - Source->Velocity[2] = values[2]; + Source->Velocity[0] = static_cast(values[0]); + Source->Velocity[1] = static_cast(values[1]); + Source->Velocity[2] = static_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_DIRECTION: CheckSize(3); - CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(static_cast(values[0])) + && std::isfinite(static_cast(values[1])) + && std::isfinite(static_cast(values[2]))); - Source->Direction[0] = values[0]; - Source->Direction[1] = values[1]; - Source->Direction[2] = values[2]; + Source->Direction[0] = static_cast(values[0]); + Source->Direction[1] = static_cast(values[1]); + Source->Direction[2] = static_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_ORIENTATION: CheckSize(6); - CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) - && std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(static_cast(values[0])) + && std::isfinite(static_cast(values[1])) + && std::isfinite(static_cast(values[2])) + && std::isfinite(static_cast(values[3])) + && std::isfinite(static_cast(values[4])) + && std::isfinite(static_cast(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]; + Source->OrientAt[0] = static_cast(values[0]); + Source->OrientAt[1] = static_cast(values[1]); + Source->OrientAt[2] = static_cast(values[2]); + Source->OrientUp[0] = static_cast(values[3]); + Source->OrientUp[1] = static_cast(values[4]); + Source->OrientUp[2] = static_cast(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: - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_STEREO_MODE_SOFT: - CheckSize(1); - ival = static_cast(values[0]); - return SetSourceiv(Source, Context, prop, {&ival, 1u}); - - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - CheckSize(1); - ival = static_cast(static_cast(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); -} -catch(check_exception&) { -} - -void SetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - auto Checkers = GetCheckers(Context, prop, values); - auto &CheckSize = Checkers.first; - auto &CheckValue = Checkers.second; - ALCdevice *device{Context->mALDevice.get()}; - ALeffectslot *slot{nullptr}; - al::deque oldlist; - std::unique_lock slotlock; - float fvals[6]; - - switch(prop) - { - case AL_SOURCE_STATE: - case AL_SOURCE_TYPE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - /* Query only */ - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); - - case AL_SOURCE_RELATIVE: - CheckSize(1); - CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->HeadRelative = values[0] != AL_FALSE; - return CommitAndUpdateSourceProps(Source, Context); - - case AL_LOOPING: - CheckSize(1); - CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->Looping = values[0] != AL_FALSE; - if(Voice *voice{GetSourceVoice(Source, Context)}) + if constexpr(std::is_integral_v) { - if(Source->Looping) - voice->mLoopBuffer.store(&Source->mQueue.front(), std::memory_order_release); + CheckSize(1); + const auto filterid = static_cast>(values[0]); + if(values[0]) + { + std::lock_guard filterlock{device->FilterLock}; + ALfilter *filter{LookupFilter(device, filterid)}; + if(!filter) + throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s", + std::to_string(filterid).c_str()}; + 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; + } 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. - */ - device->waitForMix(); + { + Source->Direct.Gain = 1.0f; + Source->Direct.GainHF = 1.0f; + Source->Direct.HFReference = LowPassFreqRef; + Source->Direct.GainLF = 1.0f; + Source->Direct.LFReference = HighPassFreqRef; + } + return UpdateSourceProps(Source, Context); } - return; + break; - case AL_BUFFER: - CheckSize(1); + case AL_DIRECT_FILTER_GAINHF_AUTO: + if constexpr(std::is_integral_v) { - const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; - if(state == AL_PLAYING || state == AL_PAUSED) - return Context->setError(AL_INVALID_OPERATION, - "Setting buffer on playing or paused source %u", Source->id); + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->DryGainHFAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); } - if(values[0]) + break; + + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + if constexpr(std::is_integral_v) { - std::lock_guard _{device->BufferLock}; - ALbuffer *buffer{LookupBuffer(device, static_cast(values[0]))}; - if(!buffer) - return Context->setError(AL_INVALID_VALUE, "Invalid buffer ID %u", - static_cast(values[0])); - if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - return Context->setError(AL_INVALID_OPERATION, - "Setting non-persistently mapped buffer %u", buffer->id); - if(buffer->mCallback && ReadRef(buffer->ref) != 0) - return Context->setError(AL_INVALID_OPERATION, - "Setting already-set callback buffer %u", buffer->id); + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); - /* Add the selected buffer to a one-item queue */ - al::deque newlist; - newlist.emplace_back(); - newlist.back().mCallback = buffer->mCallback; - newlist.back().mUserData = buffer->mUserData; - newlist.back().mBlockAlign = buffer->mBlockAlign; - newlist.back().mSampleLen = buffer->mSampleLen; - newlist.back().mLoopStart = buffer->mLoopStart; - newlist.back().mLoopEnd = buffer->mLoopEnd; - newlist.back().mSamples = buffer->mData.data(); - newlist.back().mBuffer = buffer; - IncrementRef(buffer->ref); - - /* Source is now Static */ - Source->SourceType = AL_STATIC; - Source->mQueue.swap(oldlist); - Source->mQueue.swap(newlist); + Source->WetGainAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); } - else + break; + + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + if constexpr(std::is_integral_v) { - /* Source is now Undetermined */ - Source->SourceType = AL_UNDETERMINED; - Source->mQueue.swap(oldlist); + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->WetGainHFAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); } + break; - /* Delete all elements in the previous queue */ - for(auto &item : oldlist) + case AL_DIRECT_CHANNELS_SOFT: + if constexpr(std::is_integral_v) { - if(ALbuffer *buffer{item.mBuffer}) - DecrementRef(buffer->ref); + CheckSize(1); + if(auto mode = DirectModeFromEnum(values[0])) + { + Source->DirectChannels = *mode; + return UpdateSourceProps(Source, Context); + } + throw al::context_error{AL_INVALID_VALUE, "Invalid direct channels mode: %s\n", + HexPrinter{values[0]}.c_str()}; } - return; + break; - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - CheckSize(1); - - if(Voice *voice{GetSourceVoice(Source, Context)}) + case AL_DISTANCE_MODEL: + if constexpr(std::is_integral_v) { - auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]); - if(!vpos) return Context->setError(AL_INVALID_VALUE, "Invalid source offset"); - - if(SetVoiceOffset(voice, *vpos, Source, Context, device)) + CheckSize(1); + if(auto model = DistanceModelFromALenum(values[0])) + { + Source->mDistanceModel = *model; + if(Context->mSourceDistanceModel) + UpdateSourceProps(Source, Context); return; + } + throw al::context_error{AL_INVALID_VALUE, "Invalid distance model: %s\n", + HexPrinter{values[0]}.c_str()}; } - Source->OffsetType = prop; - Source->Offset = values[0]; - return; + break; - case AL_DIRECT_FILTER: - CheckSize(1); - if(values[0]) + case AL_SOURCE_RESAMPLER_SOFT: + if constexpr(std::is_integral_v) { - std::lock_guard _{device->FilterLock}; - ALfilter *filter{LookupFilter(device, static_cast(values[0]))}; - if(!filter) - return Context->setError(AL_INVALID_VALUE, "Invalid filter ID %u", - static_cast(values[0])); - 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; - } - else - { - Source->Direct.Gain = 1.0f; - Source->Direct.GainHF = 1.0f; - Source->Direct.HFReference = LOWPASSFREQREF; - Source->Direct.GainLF = 1.0f; - Source->Direct.LFReference = HIGHPASSFREQREF; - } - return UpdateSourceProps(Source, Context); + CheckSize(1); + CheckValue(values[0] >= 0 && values[0] <= static_cast(Resampler::Max)); - case AL_DIRECT_FILTER_GAINHF_AUTO: - CheckSize(1); - CheckValue(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(1); - CheckValue(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(1); - CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->WetGainHFAuto = values[0] != AL_FALSE; - return UpdateSourceProps(Source, Context); - - case AL_DIRECT_CHANNELS_SOFT: - CheckSize(1); - if(auto mode = DirectModeFromEnum(values[0])) - { - Source->DirectChannels = *mode; + Source->mResampler = static_cast(values[0]); return UpdateSourceProps(Source, Context); } - Context->setError(AL_INVALID_VALUE, "Unsupported AL_DIRECT_CHANNELS_SOFT: 0x%04x\n", - values[0]); - return; + break; - case AL_DISTANCE_MODEL: - CheckSize(1); - if(auto model = DistanceModelFromALenum(values[0])) + case AL_SOURCE_SPATIALIZE_SOFT: + if constexpr(std::is_integral_v) { - Source->mDistanceModel = *model; - if(Context->mSourceDistanceModel) + CheckSize(1); + if(auto mode = SpatializeModeFromEnum(values[0])) + { + Source->mSpatialize = *mode; + return UpdateSourceProps(Source, Context); + } + throw al::context_error{AL_INVALID_VALUE, "Invalid source spatialize mode: %s\n", + HexPrinter{values[0]}.c_str()}; + } + break; + + case AL_STEREO_MODE_SOFT: + if constexpr(std::is_integral_v) + { + CheckSize(1); + if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; + state == AL_PLAYING || state == AL_PAUSED) + throw al::context_error{AL_INVALID_OPERATION, + "Modifying stereo mode on playing or paused source %u", Source->id}; + + if(auto mode = StereoModeFromEnum(values[0])) + { + Source->mStereoMode = *mode; + return; + } + throw al::context_error{AL_INVALID_VALUE, "Invalid stereo mode: %s\n", + HexPrinter{values[0]}.c_str()}; + } + break; + + case AL_AUXILIARY_SEND_FILTER: + if constexpr(std::is_integral_v) + { + CheckSize(3); + const auto slotid = static_cast>(values[0]); + const auto sendidx = static_cast>(values[1]); + const auto filterid = static_cast>(values[2]); + + std::unique_lock slotlock{Context->mEffectSlotLock}; + ALeffectslot *slot{}; + if(values[0]) + { + slot = LookupEffectSlot(Context, slotid); + if(!slot) + throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %s", + std::to_string(slotid).c_str()}; + } + + if(sendidx >= device->NumAuxSends) + throw al::context_error{AL_INVALID_VALUE, "Invalid send %s", + std::to_string(sendidx).c_str()}; + auto &send = Source->Send[static_cast(sendidx)]; + + if(values[2]) + { + std::lock_guard filterlock{device->FilterLock}; + ALfilter *filter{LookupFilter(device, filterid)}; + if(!filter) + throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s", + std::to_string(filterid).c_str()}; + + send.Gain = filter->Gain; + send.GainHF = filter->GainHF; + send.HFReference = filter->HFReference; + send.GainLF = filter->GainLF; + send.LFReference = filter->LFReference; + } + else + { + /* Disable filter */ + send.Gain = 1.0f; + send.GainHF = 1.0f; + send.HFReference = LowPassFreqRef; + send.GainLF = 1.0f; + send.LFReference = HighPassFreqRef; + } + + /* We must force an update if the current auxiliary slot is valid + * and about to be changed on an active source, in case the old + * slot is about to be deleted. + */ + if(send.Slot && slot != send.Slot && IsPlayingOrPaused(Source)) + { + /* Add refcount on the new slot, and release the previous slot */ + if(slot) IncrementRef(slot->ref); + if(auto *oldslot = send.Slot) + DecrementRef(oldslot->ref); + send.Slot = slot; + + Voice *voice{GetSourceVoice(Source, Context)}; + if(voice) UpdateSourceProps(Source, voice, Context); + else Source->mPropsDirty = true; + } + else + { + if(slot) IncrementRef(slot->ref); + if(auto *oldslot = send.Slot) + DecrementRef(oldslot->ref); + send.Slot = slot; UpdateSourceProps(Source, Context); + } return; } - Context->setError(AL_INVALID_VALUE, "Distance model out of range: 0x%04x", values[0]); - return; - - case AL_SOURCE_RESAMPLER_SOFT: - CheckSize(1); - CheckValue(values[0] >= 0 && values[0] <= static_cast(Resampler::Max)); - - Source->mResampler = static_cast(values[0]); - return UpdateSourceProps(Source, Context); - - case AL_SOURCE_SPATIALIZE_SOFT: - CheckSize(1); - if(auto mode = SpatializeModeFromEnum(values[0])) - { - Source->mSpatialize = *mode; - return UpdateSourceProps(Source, Context); - } - Context->setError(AL_INVALID_VALUE, "Unsupported AL_SOURCE_SPATIALIZE_SOFT: 0x%04x\n", - values[0]); - return; - - case AL_STEREO_MODE_SOFT: - CheckSize(1); - { - const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; - if(state == AL_PLAYING || state == AL_PAUSED) - return Context->setError(AL_INVALID_OPERATION, - "Modifying stereo mode on playing or paused source %u", Source->id); - } - if(auto mode = StereoModeFromEnum(values[0])) - { - Source->mStereoMode = *mode; - return; - } - Context->setError(AL_INVALID_VALUE, "Unsupported AL_STEREO_MODE_SOFT: 0x%04x\n", - values[0]); - return; - - case AL_AUXILIARY_SEND_FILTER: - CheckSize(3); - slotlock = std::unique_lock{Context->mEffectSlotLock}; - if(values[0] && (slot=LookupEffectSlot(Context, static_cast(values[0]))) == nullptr) - return Context->setError(AL_INVALID_VALUE, "Invalid effect ID %u", values[0]); - if(static_cast(values[1]) >= device->NumAuxSends) - return Context->setError(AL_INVALID_VALUE, "Invalid send %u", values[1]); - - if(values[2]) - { - std::lock_guard _{device->FilterLock}; - ALfilter *filter{LookupFilter(device, static_cast(values[2]))}; - if(!filter) - return Context->setError(AL_INVALID_VALUE, "Invalid filter ID %u", values[2]); - - auto &send = Source->Send[static_cast(values[1])]; - send.Gain = filter->Gain; - send.GainHF = filter->GainHF; - send.HFReference = filter->HFReference; - send.GainLF = filter->GainLF; - send.LFReference = filter->LFReference; - } - else - { - /* Disable filter */ - auto &send = Source->Send[static_cast(values[1])]; - send.Gain = 1.0f; - send.GainHF = 1.0f; - send.HFReference = LOWPASSFREQREF; - send.GainLF = 1.0f; - send.LFReference = HIGHPASSFREQREF; - } - - /* We must force an update if the current auxiliary slot is valid and - * about to be changed on an active source, in case the old slot is - * about to be deleted. - */ - if(Source->Send[static_cast(values[1])].Slot - && slot != Source->Send[static_cast(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(values[1])].Slot) - DecrementRef(oldslot->ref); - Source->Send[static_cast(values[1])].Slot = slot; - - Voice *voice{GetSourceVoice(Source, Context)}; - if(voice) UpdateSourceProps(Source, voice, Context); - else Source->mPropsDirty = true; - } - else - { - if(slot) IncrementRef(slot->ref); - if(auto *oldslot = Source->Send[static_cast(values[1])].Slot) - DecrementRef(oldslot->ref); - Source->Send[static_cast(values[1])].Slot = slot; - UpdateSourceProps(Source, Context); - } - return; - - - case AL_SAMPLE_RW_OFFSETS_SOFT: - if(sBufferSubDataCompat) - /* Query only */ - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); - break; - - case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ - if(sBufferSubDataCompat) - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); - /*fall-through*/ - - /* 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_SEC_LENGTH_SOFT: - case AL_SUPER_STEREO_WIDTH_SOFT: - CheckSize(1); - fvals[0] = static_cast(values[0]); - return SetSourcefv(Source, Context, prop, {fvals, 1u}); - - /* 3x float */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - CheckSize(3); - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - return SetSourcefv(Source, Context, prop, {fvals, 3u}); - - /* 6x float */ - case AL_ORIENTATION: - CheckSize(6); - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - fvals[3] = static_cast(values[3]); - fvals[4] = static_cast(values[4]); - fvals[5] = static_cast(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); -} -catch(check_exception&) { -} - -void SetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - auto Checkers = GetCheckers(Context, prop, values); - auto &CheckSize = Checkers.first; - auto &CheckValue = Checkers.second; - float fvals[MaxValues]; - int ivals[MaxValues]; - - switch(prop) - { - case AL_SOURCE_TYPE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_STATE: - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - /* Query only */ - return Context->setError(AL_INVALID_OPERATION, - "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: - case AL_STEREO_MODE_SOFT: - CheckSize(1); - CheckValue(values[0] <= INT_MAX && values[0] >= INT_MIN); - - ivals[0] = static_cast(values[0]); - return SetSourceiv(Source, Context, prop, {ivals, 1u}); - - /* 1x uint */ - case AL_BUFFER: - case AL_DIRECT_FILTER: - CheckSize(1); - CheckValue(values[0] <= UINT_MAX && values[0] >= 0); - - ivals[0] = static_cast(values[0]); - return SetSourceiv(Source, Context, prop, {ivals, 1u}); - - /* 3x uint */ - case AL_AUXILIARY_SEND_FILTER: - CheckSize(3); - CheckValue(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(values[0]); - ivals[1] = static_cast(values[1]); - ivals[2] = static_cast(values[2]); - return SetSourceiv(Source, Context, prop, {ivals, 3u}); - - case AL_SAMPLE_RW_OFFSETS_SOFT: - if(sBufferSubDataCompat) - { - /* Query only */ - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); - } - break; - - case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ - if(sBufferSubDataCompat) - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); - /*fall-through*/ - - /* 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_SEC_LENGTH_SOFT: - case AL_SUPER_STEREO_WIDTH_SOFT: - CheckSize(1); - fvals[0] = static_cast(values[0]); - return SetSourcefv(Source, Context, prop, {fvals, 1u}); - - /* 3x float */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - CheckSize(3); - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - return SetSourcefv(Source, Context, prop, {fvals, 3u}); - - /* 6x float */ - case AL_ORIENTATION: - CheckSize(6); - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - fvals[3] = static_cast(values[3]); - fvals[4] = static_cast(values[4]); - fvals[5] = static_cast(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); -} -catch(check_exception&) { + ERR("Unexpected %s property: 0x%04x\n", PropType::Name(), prop); + throw al::context_error{AL_INVALID_ENUM, "Invalid source %s property 0x%04x", + PropType::Name(), prop}; } template -auto GetSizeChecker(ALCcontext *const Context, const SourceProp prop, const al::span values) +auto GetSizeChecker(const SourceProp prop, const al::span values) { return [=](size_t expect) -> void { if(values.size() == expect) LIKELY return; - Context->setError(AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu", - prop, expect, values.size()); - throw check_size_exception{}; + throw al::context_error{AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu", + prop, expect, values.size()}; }; } -bool GetSourcedv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); -bool GetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); -bool GetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); - -bool GetSourcedv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - auto CheckSize = GetSizeChecker(Context, prop, values); +template +NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, + const al::span values) +{ + using std::chrono::duration_cast; + auto CheckSize = GetSizeChecker(prop, values); ALCdevice *device{Context->mALDevice.get()}; - ClockLatency clocktime; - nanoseconds srcclock; - int ivals[MaxValues]; - bool err; switch(prop) { case AL_GAIN: CheckSize(1); - values[0] = Source->Gain; - return true; + values[0] = static_cast(Source->Gain); + return; case AL_PITCH: CheckSize(1); - values[0] = Source->Pitch; - return true; + values[0] = static_cast(Source->Pitch); + return; case AL_MAX_DISTANCE: CheckSize(1); - values[0] = Source->MaxDistance; - return true; + values[0] = static_cast(Source->MaxDistance); + return; case AL_ROLLOFF_FACTOR: CheckSize(1); - values[0] = Source->RolloffFactor; - return true; + values[0] = static_cast(Source->RolloffFactor); + return; case AL_REFERENCE_DISTANCE: CheckSize(1); - values[0] = Source->RefDistance; - return true; + values[0] = static_cast(Source->RefDistance); + return; case AL_CONE_INNER_ANGLE: CheckSize(1); - values[0] = Source->InnerAngle; - return true; + values[0] = static_cast(Source->InnerAngle); + return; case AL_CONE_OUTER_ANGLE: CheckSize(1); - values[0] = Source->OuterAngle; - return true; + values[0] = static_cast(Source->OuterAngle); + return; case AL_MIN_GAIN: CheckSize(1); - values[0] = Source->MinGain; - return true; + values[0] = static_cast(Source->MinGain); + return; case AL_MAX_GAIN: CheckSize(1); - values[0] = Source->MaxGain; - return true; + values[0] = static_cast(Source->MaxGain); + return; case AL_CONE_OUTER_GAIN: CheckSize(1); - values[0] = Source->OuterGain; - return true; + values[0] = static_cast(Source->OuterGain); + return; case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: CheckSize(1); - values[0] = GetSourceOffset(Source, prop, Context); - return true; + values[0] = GetSourceOffset(Source, prop, Context); + return; case AL_CONE_OUTER_GAINHF: CheckSize(1); - values[0] = Source->OuterGainHF; - return true; + values[0] = static_cast(Source->OuterGainHF); + return; case AL_AIR_ABSORPTION_FACTOR: CheckSize(1); - values[0] = Source->AirAbsorptionFactor; - return true; + values[0] = static_cast(Source->AirAbsorptionFactor); + return; case AL_ROOM_ROLLOFF_FACTOR: CheckSize(1); - values[0] = Source->RoomRolloffFactor; - return true; + values[0] = static_cast(Source->RoomRolloffFactor); + return; case AL_DOPPLER_FACTOR: CheckSize(1); - values[0] = Source->DopplerFactor; - return true; + values[0] = static_cast(Source->DopplerFactor); + return; case AL_SAMPLE_RW_OFFSETS_SOFT: + if constexpr(std::is_integral_v) + { + if(sBufferSubDataCompat) + { + CheckSize(2); + values[0] = GetSourceOffset(Source, AL_SAMPLE_OFFSET, Context); + /* FIXME: values[1] should be ahead of values[0] by the device + * update time. It needs to clamp or wrap the length of the + * buffer queue. + */ + values[1] = values[0]; + return; + } + } break; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ - if(sBufferSubDataCompat) - break; + if constexpr(std::is_floating_point_v) + { + if(sBufferSubDataCompat) + break; - CheckSize(1); - values[0] = Source->Radius; - return true; + CheckSize(1); + values[0] = static_cast(Source->Radius); + return; + } + else + { + if(sBufferSubDataCompat) + { + CheckSize(2); + values[0] = GetSourceOffset(Source, AL_BYTE_OFFSET, Context); + /* FIXME: values[1] should be ahead of values[0] by the device + * update time. It needs to clamp or wrap the length of the + * buffer queue. + */ + values[1] = values[0]; + return; + } + break; + } case AL_SUPER_STEREO_WIDTH_SOFT: CheckSize(1); - values[0] = Source->EnhWidth; - return true; + values[0] = static_cast(Source->EnhWidth); + return; case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_SEC_LENGTH_SOFT: CheckSize(1); - values[0] = GetSourceLength(Source, prop); - return true; + values[0] = GetSourceLength(Source, prop); + return; + + case AL_PANNING_ENABLED_SOFT: + CheckSize(1); + values[0] = Source->mPanningEnabled; + return; + + case AL_PAN_SOFT: + CheckSize(1); + values[0] = static_cast(Source->mPan); + return; case AL_STEREO_ANGLES: - CheckSize(2); - values[0] = Source->StereoPan[0]; - values[1] = Source->StereoPan[1]; - return true; + if constexpr(std::is_floating_point_v) + { + CheckSize(2); + values[0] = static_cast(Source->StereoPan[0]); + values[1] = static_cast(Source->StereoPan[1]); + return; + } + break; + + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + if constexpr(std::is_same_v) + { + CheckSize(2); + /* Get the source offset with the clock time first. Then get the + * clock time with the device latency. Order is important. + */ + ClockLatency clocktime{}; + nanoseconds srcclock{}; + values[0] = GetSourceSampleOffset(Source, Context, &srcclock); + { + std::lock_guard statelock{device->StateLock}; + clocktime = GetClockLatency(device, device->Backend.get()); + } + if(srcclock == clocktime.ClockTime) + values[1] = nanoseconds{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 auto diff = std::min(clocktime.Latency, clocktime.ClockTime-srcclock); + values[1] = nanoseconds{clocktime.Latency - diff}.count(); + } + return; + } + break; + + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + if constexpr(std::is_same_v) + { + CheckSize(2); + nanoseconds srcclock{}; + values[0] = GetSourceSampleOffset(Source, Context, &srcclock); + values[1] = srcclock.count(); + return; + } + break; case AL_SEC_OFFSET_LATENCY_SOFT: - CheckSize(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); + if constexpr(std::is_same_v) { - std::lock_guard _{device->StateLock}; - clocktime = GetClockLatency(device, device->Backend.get()); - } - if(srcclock == clocktime.ClockTime) - values[1] = static_cast(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. + CheckSize(2); + /* Get the source offset with the clock time first. Then get the + * clock time with the device latency. Order is important. */ - const nanoseconds diff{clocktime.ClockTime - srcclock}; - const nanoseconds latency{clocktime.Latency - std::min(clocktime.Latency, diff)}; - values[1] = static_cast(latency.count()) / 1000000000.0; + ClockLatency clocktime{}; + nanoseconds srcclock{}; + values[0] = GetSourceSecOffset(Source, Context, &srcclock); + { + std::lock_guard statelock{device->StateLock}; + clocktime = GetClockLatency(device, device->Backend.get()); + } + if(srcclock == clocktime.ClockTime) + values[1] = duration_cast(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 auto diff = std::min(clocktime.Latency, clocktime.ClockTime-srcclock); + values[1] = duration_cast(clocktime.Latency - diff).count(); + } + return; } - return true; + break; case AL_SEC_OFFSET_CLOCK_SOFT: - CheckSize(2); - values[0] = GetSourceSecOffset(Source, Context, &srcclock); - values[1] = static_cast(srcclock.count()) / 1000000000.0; - return true; + if constexpr(std::is_same_v) + { + CheckSize(2); + nanoseconds srcclock{}; + values[0] = GetSourceSecOffset(Source, Context, &srcclock); + values[1] = duration_cast(srcclock).count(); + return; + } + break; case AL_POSITION: CheckSize(3); - values[0] = Source->Position[0]; - values[1] = Source->Position[1]; - values[2] = Source->Position[2]; - return true; + values[0] = static_cast(Source->Position[0]); + values[1] = static_cast(Source->Position[1]); + values[2] = static_cast(Source->Position[2]); + return; case AL_VELOCITY: CheckSize(3); - values[0] = Source->Velocity[0]; - values[1] = Source->Velocity[1]; - values[2] = Source->Velocity[2]; - return true; + values[0] = static_cast(Source->Velocity[0]); + values[1] = static_cast(Source->Velocity[1]); + values[2] = static_cast(Source->Velocity[2]); + return; case AL_DIRECTION: CheckSize(3); - values[0] = Source->Direction[0]; - values[1] = Source->Direction[1]; - values[2] = Source->Direction[2]; - return true; + values[0] = static_cast(Source->Direction[0]); + values[1] = static_cast(Source->Direction[1]); + values[2] = static_cast(Source->Direction[2]); + return; case AL_ORIENTATION: CheckSize(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; + values[0] = static_cast(Source->OrientAt[0]); + values[1] = static_cast(Source->OrientAt[1]); + values[2] = static_cast(Source->OrientAt[2]); + values[3] = static_cast(Source->OrientUp[0]); + values[4] = static_cast(Source->OrientUp[1]); + values[5] = static_cast(Source->OrientUp[2]); + return; + - /* 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: - case AL_STEREO_MODE_SOFT: - CheckSize(1); - if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) - values[0] = static_cast(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; -} -catch(check_exception&) { - return false; -} - -bool GetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - auto CheckSize = GetSizeChecker(Context, prop, values); - double dvals[MaxValues]; - bool err; - - switch(prop) - { - case AL_SOURCE_RELATIVE: - CheckSize(1); - values[0] = Source->HeadRelative; - return true; - - case AL_LOOPING: - CheckSize(1); - values[0] = Source->Looping; - return true; - - case AL_BUFFER: - CheckSize(1); + if constexpr(std::is_integral_v) { + CheckSize(1); + values[0] = Source->HeadRelative; + return; + } + break; + + case AL_LOOPING: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = Source->Looping; + return; + } + break; + + case AL_BUFFER: + if constexpr(std::is_integral_v) + { + CheckSize(1); ALbufferQueueItem *BufferList{}; /* HACK: This query should technically only return the buffer set * on a static source. However, some apps had used it to detect @@ -2356,381 +2345,150 @@ try { BufferList = static_cast(Current); } ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr}; - values[0] = buffer ? static_cast(buffer->id) : 0; + values[0] = buffer ? static_cast(buffer->id) : T{0}; + return; } - return true; + break; case AL_SOURCE_STATE: - CheckSize(1); - values[0] = GetSourceState(Source, GetSourceVoice(Source, Context)); - return true; + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = GetSourceState(Source, GetSourceVoice(Source, Context)); + return; + } + break; case AL_BUFFERS_QUEUED: - CheckSize(1); - values[0] = static_cast(Source->mQueue.size()); - return true; + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = static_cast(Source->mQueue.size()); + return; + } + break; case AL_BUFFERS_PROCESSED: - CheckSize(1); - if(Source->Looping || Source->SourceType != AL_STREAMING) + if constexpr(std::is_integral_v) { - /* Buffers on a looping source are in a perpetual state of PENDING, - * so don't report any as PROCESSED - */ - values[0] = 0; - } - else - { - int played{0}; - if(Source->state != AL_INITIAL) + CheckSize(1); + if(Source->Looping || Source->SourceType != AL_STREAMING) { - const VoiceBufferItem *Current{nullptr}; - if(Voice *voice{GetSourceVoice(Source, Context)}) - Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); - for(auto &item : Source->mQueue) - { - if(&item == Current) - break; - ++played; - } + /* Buffers on a looping source are in a perpetual state of + * PENDING, so don't report any as PROCESSED + */ + values[0] = 0; } - values[0] = played; - } - return true; - - case AL_SOURCE_TYPE: - CheckSize(1); - values[0] = Source->SourceType; - return true; - - case AL_DIRECT_FILTER_GAINHF_AUTO: - CheckSize(1); - values[0] = Source->DryGainHFAuto; - return true; - - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - CheckSize(1); - values[0] = Source->WetGainAuto; - return true; - - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - CheckSize(1); - values[0] = Source->WetGainHFAuto; - return true; - - case AL_DIRECT_CHANNELS_SOFT: - CheckSize(1); - values[0] = EnumFromDirectMode(Source->DirectChannels); - return true; - - case AL_DISTANCE_MODEL: - CheckSize(1); - values[0] = ALenumFromDistanceModel(Source->mDistanceModel); - return true; - - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_SEC_LENGTH_SOFT: - CheckSize(1); - values[0] = static_cast(mind(GetSourceLength(Source, prop), - std::numeric_limits::max())); - return true; - - case AL_SOURCE_RESAMPLER_SOFT: - CheckSize(1); - values[0] = static_cast(Source->mResampler); - return true; - - case AL_SOURCE_SPATIALIZE_SOFT: - CheckSize(1); - values[0] = EnumFromSpatializeMode(Source->mSpatialize); - return true; - - case AL_STEREO_MODE_SOFT: - CheckSize(1); - values[0] = EnumFromStereoMode(Source->mStereoMode); - return true; - - case AL_SAMPLE_RW_OFFSETS_SOFT: - if(sBufferSubDataCompat) - { - CheckSize(2); - const auto offset = GetSourceOffset(Source, AL_SAMPLE_OFFSET, Context); - /* FIXME: values[1] should be ahead of values[0] by the device - * update time. It needs to clamp or wrap the length of the buffer - * queue. - */ - values[0] = static_cast(mind(offset, std::numeric_limits::max())); - values[1] = values[0]; - return true; + else + { + int played{0}; + if(Source->state != AL_INITIAL) + { + const VoiceBufferItem *Current{nullptr}; + if(Voice *voice{GetSourceVoice(Source, Context)}) + Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); + for(auto &item : Source->mQueue) + { + if(&item == Current) + break; + ++played; + } + } + values[0] = played; + } + return; } break; - case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ - if(sBufferSubDataCompat) + + case AL_SOURCE_TYPE: + if constexpr(std::is_integral_v) { - CheckSize(2); - const auto offset = GetSourceOffset(Source, AL_BYTE_OFFSET, Context); - /* FIXME: values[1] should be ahead of values[0] by the device - * update time. It needs to clamp or wrap the length of the buffer - * queue. - */ - values[0] = static_cast(mind(offset, std::numeric_limits::max())); - values[1] = values[0]; - return true; + CheckSize(1); + values[0] = Source->SourceType; + return; } - /*fall-through*/ + break; - /* 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_SUPER_STEREO_WIDTH_SOFT: - CheckSize(1); - if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) - values[0] = static_cast(dvals[0]); - return err; - - /* 3x float/double */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - CheckSize(3); - if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false) + case AL_DIRECT_FILTER_GAINHF_AUTO: + if constexpr(std::is_integral_v) { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); + CheckSize(1); + values[0] = Source->DryGainHFAuto; + return; } - return err; + break; - /* 6x float/double */ - case AL_ORIENTATION: - CheckSize(6); - if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false) + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + if constexpr(std::is_integral_v) { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); - values[3] = static_cast(dvals[3]); - values[4] = static_cast(dvals[4]); - values[5] = static_cast(dvals[5]); + CheckSize(1); + values[0] = Source->WetGainAuto; + return; } - return err; + break; - 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_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = Source->WetGainHFAuto; + return; + } + break; + + case AL_DIRECT_CHANNELS_SOFT: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = EnumFromDirectMode(Source->DirectChannels); + return; + } + break; + + case AL_DISTANCE_MODEL: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = ALenumFromDistanceModel(Source->mDistanceModel); + return; + } + break; + + case AL_SOURCE_RESAMPLER_SOFT: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = static_cast(Source->mResampler); + return; + } + break; + + case AL_SOURCE_SPATIALIZE_SOFT: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = EnumFromSpatializeMode(Source->mSpatialize); + return; + } + break; + + case AL_STEREO_MODE_SOFT: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = EnumFromStereoMode(Source->mStereoMode); + return; + } + break; 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; -} -catch(check_exception&) { - return false; -} - -bool GetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - auto CheckSize = GetSizeChecker(Context, prop, values); - ALCdevice *device{Context->mALDevice.get()}; - ClockLatency clocktime; - nanoseconds srcclock; - double dvals[MaxValues]; - int ivals[MaxValues]; - bool err; - - switch(prop) - { - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_SEC_LENGTH_SOFT: - CheckSize(1); - values[0] = static_cast(GetSourceLength(Source, prop)); - return true; - - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - CheckSize(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 _{device->StateLock}; - clocktime = GetClockLatency(device, device->Backend.get()); - } - 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(2); - values[0] = GetSourceSampleOffset(Source, Context, &srcclock); - values[1] = srcclock.count(); - return true; - - case AL_SAMPLE_RW_OFFSETS_SOFT: - if(sBufferSubDataCompat) - { - CheckSize(2); - /* FIXME: values[1] should be ahead of values[0] by the device - * update time. It needs to clamp or wrap the length of the buffer - * queue. - */ - values[0] = static_cast(GetSourceOffset(Source, AL_SAMPLE_OFFSET, Context)); - values[1] = values[0]; - return true; - } break; - case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ - if(sBufferSubDataCompat) - { - CheckSize(2); - /* FIXME: values[1] should be ahead of values[0] by the device - * update time. It needs to clamp or wrap the length of the buffer - * queue. - */ - values[0] = static_cast(GetSourceOffset(Source, AL_BYTE_OFFSET, Context)); - values[1] = values[0]; - return true; - } - /*fall-through*/ - - /* 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_SUPER_STEREO_WIDTH_SOFT: - CheckSize(1); - if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) - values[0] = static_cast(dvals[0]); - return err; - - /* 3x float/double */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - CheckSize(3); - if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false) - { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); - } - return err; - - /* 6x float/double */ - case AL_ORIENTATION: - CheckSize(6); - if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false) - { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); - values[3] = static_cast(dvals[3]); - values[4] = static_cast(dvals[4]); - values[5] = static_cast(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: - case AL_STEREO_MODE_SOFT: - CheckSize(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(1); - if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) - values[0] = static_cast(ivals[0]); - return err; - - /* 3x uint */ - case AL_AUXILIARY_SEND_FILTER: - CheckSize(3); - if((err=GetSourceiv(Source, Context, prop, {ivals, 3u})) != false) - { - values[0] = static_cast(ivals[0]); - values[1] = static_cast(ivals[1]); - values[2] = static_cast(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; -} -catch(check_exception&) { - return false; + ERR("Unexpected %s query property: 0x%04x\n", PropType::Name(), prop); + throw al::context_error{AL_INVALID_ENUM, "Invalid source %s query property 0x%04x", + PropType::Name(), prop}; } @@ -2905,717 +2663,639 @@ void StartSources(ALCcontext *const context, const al::span srchandle } // namespace -AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Generating %d sources", n); +AL_API DECL_FUNC2(void, alGenSources, ALsizei,n, ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alGenSourcesDirect(ALCcontext *context, ALsizei n, ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Generating %d sources", n}; if(n <= 0) UNLIKELY return; std::unique_lock srclock{context->mSourceLock}; ALCdevice *device{context->mALDevice.get()}; - if(static_cast(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(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())}; - sources[0] = source->id; + const al::span sids{sources, static_cast(n)}; + if(sids.size() > device->SourcesMax-context->mNumSources) + throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u source limit (%u + %d)", + device->SourcesMax, context->mNumSources, n}; + if(!EnsureSources(context, sids.size())) + throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n, + (n == 1) ? "" : "s"}; -#ifdef ALSOFT_EAX - source->eaxInitialize(context.get()); -#endif // ALSOFT_EAX - } - else - { - al::vector ids; - ids.reserve(static_cast(n)); - do { - ALsource *source{AllocSource(context.get())}; - ids.emplace_back(source->id); - -#ifdef ALSOFT_EAX - source->eaxInitialize(context.get()); -#endif // ALSOFT_EAX - } while(--n); - std::copy(ids.cbegin(), ids.cend(), sources); - } + std::generate(sids.begin(), sids.end(), [context]{ return AllocSource(context)->id; }); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Deleting %d sources", n); +AL_API DECL_FUNC2(void, alDeleteSources, ALsizei,n, const ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alDeleteSourcesDirect(ALCcontext *context, ALsizei n, + const ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Deleting %d sources", n}; if(n <= 0) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; + std::lock_guard srclock{context->mSourceLock}; /* Check that all Sources are valid */ - auto validate_source = [&context](const ALuint sid) -> bool - { return LookupSource(context.get(), sid) != nullptr; }; + auto validate_source = [context](const ALuint sid) -> bool + { return LookupSource(context, sid) != nullptr; }; - const ALuint *sources_end = sources + n; - auto invsrc = std::find_if_not(sources, sources_end, validate_source); - if(invsrc != sources_end) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *invsrc); + const al::span sids{sources, static_cast(n)}; + auto invsrc = std::find_if_not(sids.begin(), sids.end(), validate_source); + if(invsrc != sids.end()) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", *invsrc}; /* 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); + if(ALsource *src{LookupSource(context, sid)}) + FreeSource(context, src); }; - std::for_each(sources, sources_end, delete_source); + std::for_each(sids.begin(), sids.end(), delete_source); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API ALboolean AL_APIENTRY alIsSource(ALuint source) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsSource, ALuint,source) +FORCE_ALIGN ALboolean AL_APIENTRY alIsSourceDirect(ALCcontext *context, ALuint source) noexcept { - ContextRef context{GetContextRef()}; - if(context) LIKELY - { - std::lock_guard _{context->mSourceLock}; - if(LookupSource(context.get(), source) != nullptr) - return AL_TRUE; - } + std::lock_guard srclock{context->mSourceLock}; + if(LookupSource(context, source) != nullptr) + return AL_TRUE; return AL_FALSE; } -END_API_FUNC -AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alSourcef, ALuint,source, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alSourcefDirect(ALCcontext *context, ALuint source, ALenum param, + ALfloat value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - SetSourcefv(Source, context.get(), static_cast(param), {&value, 1u}); + SetProperty(Source, context, static_cast(param), {&value, 1u}); } -END_API_FUNC - -AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - { - const float fvals[3]{ value1, value2, value3 }; - SetSourcefv(Source, context.get(), static_cast(param), fvals); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alSource3f, ALuint,source, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) +FORCE_ALIGN void AL_APIENTRY alSource3fDirect(ALCcontext *context, ALuint source, ALenum param, + ALfloat value1, ALfloat value2, ALfloat value3) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + const std::array fvals{value1, value2, value3}; + SetProperty(Source, context, static_cast(param), fvals); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alSourcefv, ALuint,source, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, + const ALfloat *values) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{FloatValsByProp(param)}; - SetSourcefv(Source, context.get(), static_cast(param), {values, count}); + SetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - { - const float fval[1]{static_cast(value)}; - SetSourcefv(Source, context.get(), static_cast(param), fval); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - { - const float fvals[3]{static_cast(value1), static_cast(value2), - static_cast(value3)}; - SetSourcefv(Source, context.get(), static_cast(param), fvals); - } +AL_API DECL_FUNCEXT3(void, alSourced,SOFT, ALuint,source, ALenum,param, ALdouble,value) +FORCE_ALIGN void AL_APIENTRY alSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, + ALdouble value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + + SetProperty(Source, context, static_cast(param), {&value, 1}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alSource3d,SOFT, ALuint,source, ALenum,param, ALdouble,value1, ALdouble,value2, ALdouble,value3) +FORCE_ALIGN void AL_APIENTRY alSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, + ALdouble value1, ALdouble value2, ALdouble value3) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + const std::array dvals{value1, value2, value3}; + SetProperty(Source, context, static_cast(param), dvals); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alSourcedv,SOFT, ALuint,source, ALenum,param, const ALdouble*,values) +FORCE_ALIGN void AL_APIENTRY alSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, + const ALdouble *values) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{DoubleValsByProp(param)}; - float fvals[MaxValues]; - std::copy_n(values, count, fvals); - SetSourcefv(Source, context.get(), static_cast(param), {fvals, count}); + SetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - SetSourceiv(Source, context.get(), static_cast(param), {&value, 1u}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -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(!context) UNLIKELY return; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - { - const int ivals[3]{ value1, value2, value3 }; - SetSourceiv(Source, context.get(), static_cast(param), ivals); - } +AL_API DECL_FUNC3(void, alSourcei, ALuint,source, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alSourceiDirect(ALCcontext *context, ALuint source, ALenum param, + ALint value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + + SetProperty(Source, context, static_cast(param), {&value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alSource3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) +FORCE_ALIGN void AL_APIENTRY alSource3iDirect(ALCcontext *context, ALuint source, ALenum param, + ALint value1, ALint value2, ALint value3) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + const std::array ivals{value1, value2, value3}; + SetProperty(Source, context, static_cast(param), ivals); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alSourceiv, ALuint,source, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alSourceivDirect(ALCcontext *context, ALuint source, ALenum param, + const ALint *values) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{IntValsByProp(param)}; - SetSourceiv(Source, context.get(), static_cast(param), {values, count}); + SetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - SetSourcei64v(Source, context.get(), static_cast(param), {&value, 1u}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -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(!context) UNLIKELY return; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - { - const int64_t i64vals[3]{ value1, value2, value3 }; - SetSourcei64v(Source, context.get(), static_cast(param), i64vals); - } +AL_API DECL_FUNCEXT3(void, alSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value) +FORCE_ALIGN void AL_APIENTRY alSourcei64DirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALint64SOFT value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + + SetProperty(Source, context, static_cast(param), {&value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value1, ALint64SOFT,value2, ALint64SOFT,value3) +FORCE_ALIGN void AL_APIENTRY alSource3i64DirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + const std::array i64vals{value1, value2, value3}; + SetProperty(Source, context, static_cast(param), i64vals); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alSourcei64v,SOFT, ALuint,source, ALenum,param, const ALint64SOFT*,values) +FORCE_ALIGN void AL_APIENTRY alSourcei64vDirectSOFT(ALCcontext *context, ALuint source, + ALenum param, const ALint64SOFT *values) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{Int64ValsByProp(param)}; - SetSourcei64v(Source, context.get(), static_cast(param), {values, count}); + SetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - double dval[1]; - if(GetSourcedv(Source, context.get(), static_cast(param), dval)) - *value = static_cast(dval[0]); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - double dvals[3]; - if(GetSourcedv(Source, context.get(), static_cast(param), dvals)) - { - *value1 = static_cast(dvals[0]); - *value2 = static_cast(dvals[1]); - *value3 = static_cast(dvals[2]); - } - } +AL_API DECL_FUNC3(void, alGetSourcef, ALuint,source, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetSourcefDirect(ALCcontext *context, ALuint source, ALenum param, + ALfloat *value) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + GetProperty(Source, context, static_cast(param), al::span{value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alGetSource3f, ALuint,source, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) +FORCE_ALIGN void AL_APIENTRY alGetSource3fDirect(ALCcontext *context, ALuint source, ALenum param, + ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!(value1 && value2 && value3)) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + std::array fvals{}; + GetProperty(Source, context, static_cast(param), fvals); + *value1 = fvals[0]; + *value2 = fvals[1]; + *value3 = fvals[2]; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetSourcefv, ALuint,source, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, + ALfloat *values) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{FloatValsByProp(param)}; - double dvals[MaxValues]; - if(GetSourcedv(Source, context.get(), static_cast(param), {dvals, count})) - std::copy_n(dvals, count, values); + GetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourcedv(Source, context.get(), static_cast(param), {value, 1u}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -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(!context) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - double dvals[3]; - if(GetSourcedv(Source, context.get(), static_cast(param), dvals)) - { - *value1 = dvals[0]; - *value2 = dvals[1]; - *value3 = dvals[2]; - } - } +AL_API DECL_FUNCEXT3(void, alGetSourced,SOFT, ALuint,source, ALenum,param, ALdouble*,value) +FORCE_ALIGN void AL_APIENTRY alGetSourcedDirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALdouble *value) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + GetProperty(Source, context, static_cast(param), al::span{value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alGetSource3d,SOFT, ALuint,source, ALenum,param, ALdouble*,value1, ALdouble*,value2, ALdouble*,value3) +FORCE_ALIGN void AL_APIENTRY alGetSource3dDirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!(value1 && value2 && value3)) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + std::array dvals{}; + GetProperty(Source, context, static_cast(param), dvals); + *value1 = dvals[0]; + *value2 = dvals[1]; + *value3 = dvals[2]; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alGetSourcedv,SOFT, ALuint,source, ALenum,param, ALdouble*,values) +FORCE_ALIGN void AL_APIENTRY alGetSourcedvDirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALdouble *values) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{DoubleValsByProp(param)}; - GetSourcedv(Source, context.get(), static_cast(param), {values, count}); + GetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourceiv(Source, context.get(), static_cast(param), {value, 1u}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -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(!context) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - int ivals[3]; - if(GetSourceiv(Source, context.get(), static_cast(param), ivals)) - { - *value1 = ivals[0]; - *value2 = ivals[1]; - *value3 = ivals[2]; - } - } +AL_API DECL_FUNC3(void, alGetSourcei, ALuint,source, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetSourceiDirect(ALCcontext *context, ALuint source, ALenum param, + ALint *value) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + GetProperty(Source, context, static_cast(param), al::span{value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alGetSource3i, ALuint,source, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) +FORCE_ALIGN void AL_APIENTRY alGetSource3iDirect(ALCcontext *context, ALuint source, ALenum param, + ALint *value1, ALint *value2, ALint *value3) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!(value1 && value2 && value3)) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + std::array ivals{}; + GetProperty(Source, context, static_cast(param), ivals); + *value1 = ivals[0]; + *value2 = ivals[1]; + *value3 = ivals[2]; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); +AL_API DECL_FUNC3(void, alGetSourceiv, ALuint,source, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetSourceivDirect(ALCcontext *context, ALuint source, ALenum param, + ALint *values) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{IntValsByProp(param)}; - GetSourceiv(Source, context.get(), static_cast(param), {values, count}); + GetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourcei64v(Source, context.get(), static_cast(param), {value, 1u}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -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(!context) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - int64_t i64vals[3]; - if(GetSourcei64v(Source, context.get(), static_cast(param), i64vals)) - { - *value1 = i64vals[0]; - *value2 = i64vals[1]; - *value3 = i64vals[2]; - } - } +AL_API DECL_FUNCEXT3(void, alGetSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value) +FORCE_ALIGN void AL_APIENTRY alGetSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + GetProperty(Source, context, static_cast(param), al::span{value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alGetSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value1, ALint64SOFT*,value2, ALint64SOFT*,value3) +FORCE_ALIGN void AL_APIENTRY alGetSource3i64DirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!(value1 && value2 && value3)) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + std::array i64vals{}; + GetProperty(Source, context, static_cast(param), i64vals); + *value1 = i64vals[0]; + *value2 = i64vals[1]; + *value3 = i64vals[2]; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alGetSourcei64v,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,values) +FORCE_ALIGN void AL_APIENTRY alGetSourcei64vDirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALint64SOFT *values) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{Int64ValsByProp(param)}; - GetSourcei64v(Source, context.get(), static_cast(param), {values, count}); + GetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcePlay(ALuint source) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mSourceLock}; - ALsource *srchandle{LookupSource(context.get(), source)}; - if(!srchandle) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - - StartSources(context.get(), {&srchandle, 1}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - if(start_time < 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time); +AL_API DECL_FUNC1(void, alSourcePlay, ALuint,source) +FORCE_ALIGN void AL_APIENTRY alSourcePlayDirect(ALCcontext *context, ALuint source) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mSourceLock}; - ALsource *srchandle{LookupSource(context.get(), source)}; - if(!srchandle) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - - StartSources(context.get(), {&srchandle, 1}, nanoseconds{start_time}); + StartSources(context, {&Source, 1}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +FORCE_ALIGN DECL_FUNCEXT2(void, alSourcePlayAtTime,SOFT, ALuint,source, ALint64SOFT,start_time) +FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimeDirectSOFT(ALCcontext *context, ALuint source, + ALint64SOFT start_time) noexcept +try { + if(start_time < 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time}; - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Playing %d sources", n); + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + + StartSources(context, {&Source, 1}, nanoseconds{start_time}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC2(void, alSourcePlayv, ALsizei,n, const ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alSourcePlayvDirect(ALCcontext *context, ALsizei n, + const ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n}; if(n <= 0) UNLIKELY return; - al::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(static_cast(n) <= source_storage.size()) LIKELY - srchandles = {source_storage.data(), static_cast(n)}; - else + al::span sids{sources, static_cast(n)}; + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) + std::lock_guard sourcelock{context->mSourceLock}; + auto lookup_src = [context](const ALuint sid) -> ALsource* { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); - ++sources; - } + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); - StartSources(context.get(), srchandles); + StartSources(context, srchandles); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Playing %d sources", n); +FORCE_ALIGN DECL_FUNCEXT3(void, alSourcePlayAtTimev,SOFT, ALsizei,n, const ALuint*,sources, ALint64SOFT,start_time) +FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimevDirectSOFT(ALCcontext *context, ALsizei n, + const ALuint *sources, ALint64SOFT start_time) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n}; if(n <= 0) UNLIKELY return; - if(start_time < 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time); + if(start_time < 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time}; - al::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(static_cast(n) <= source_storage.size()) LIKELY - srchandles = {source_storage.data(), static_cast(n)}; - else + al::span sids{sources, static_cast(n)}; + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) + std::lock_guard sourcelock{context->mSourceLock}; + auto lookup_src = [context](const ALuint sid) -> ALsource* { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); - ++sources; - } + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); - StartSources(context.get(), srchandles, nanoseconds{start_time}); + StartSources(context, srchandles, nanoseconds{start_time}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourcePause(ALuint source) -START_API_FUNC -{ alSourcePausev(1, &source); } -END_API_FUNC +AL_API DECL_FUNC1(void, alSourcePause, ALuint,source) +FORCE_ALIGN void AL_APIENTRY alSourcePauseDirect(ALCcontext *context, ALuint source) noexcept +{ alSourcePausevDirect(context, 1, &source); } -AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Pausing %d sources", n); +AL_API DECL_FUNC2(void, alSourcePausev, ALsizei,n, const ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alSourcePausevDirect(ALCcontext *context, ALsizei n, + const ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Pausing %d sources", n}; if(n <= 0) UNLIKELY return; - al::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(static_cast(n) <= source_storage.size()) LIKELY - srchandles = {source_storage.data(), static_cast(n)}; - else + al::span sids{sources, static_cast(n)}; + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) + std::lock_guard sourcelock{context->mSourceLock}; + auto lookup_src = [context](const ALuint sid) -> ALsource* { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); - ++sources; - } + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); /* Pausing has to be done in two steps. First, for each source that's * detected to be playing, chamge the voice (asynchronously) to @@ -3624,14 +3304,14 @@ START_API_FUNC VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) { - Voice *voice{GetSourceVoice(source, context.get())}; + Voice *voice{GetSourceVoice(source, context)}; if(GetSourceState(source, voice) == AL_PLAYING) { if(!cur) - cur = tail = GetVoiceChanger(context.get()); + cur = tail = GetVoiceChanger(context); else { - cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); + cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } cur->mVoice = voice; @@ -3641,7 +3321,7 @@ START_API_FUNC } if(tail) LIKELY { - SendVoiceChanges(context.get(), tail); + SendVoiceChanges(context, tail); /* Second, now that the voice changes have been sent, because it's * possible that the voice stopped after it was detected playing and * before the voice got paused, recheck that the source is still @@ -3649,60 +3329,57 @@ START_API_FUNC */ for(ALsource *source : srchandles) { - Voice *voice{GetSourceVoice(source, context.get())}; + Voice *voice{GetSourceVoice(source, context)}; if(GetSourceState(source, voice) == AL_PLAYING) source->state = AL_PAUSED; } } } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} -AL_API void AL_APIENTRY alSourceStop(ALuint source) -START_API_FUNC -{ alSourceStopv(1, &source); } -END_API_FUNC +AL_API DECL_FUNC1(void, alSourceStop, ALuint,source) +FORCE_ALIGN void AL_APIENTRY alSourceStopDirect(ALCcontext *context, ALuint source) noexcept +{ alSourceStopvDirect(context, 1, &source); } -AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Stopping %d sources", n); +AL_API DECL_FUNC2(void, alSourceStopv, ALsizei,n, const ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alSourceStopvDirect(ALCcontext *context, ALsizei n, + const ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Stopping %d sources", n}; if(n <= 0) UNLIKELY return; - al::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(static_cast(n) <= source_storage.size()) LIKELY - srchandles = {source_storage.data(), static_cast(n)}; - else + al::span sids{sources, static_cast(n)}; + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) + std::lock_guard sourcelock{context->mSourceLock}; + auto lookup_src = [context](const ALuint sid) -> ALsource* { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); - ++sources; - } + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) { - if(Voice *voice{GetSourceVoice(source, context.get())}) + if(Voice *voice{GetSourceVoice(source, context)}) { if(!cur) - cur = tail = GetVoiceChanger(context.get()); + cur = tail = GetVoiceChanger(context); else { - cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); + cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } voice->mPendingChange.store(true, std::memory_order_relaxed); @@ -3713,60 +3390,57 @@ START_API_FUNC } source->Offset = 0.0; source->OffsetType = AL_NONE; - source->VoiceIdx = INVALID_VOICE_IDX; + source->VoiceIdx = InvalidVoiceIndex; } if(tail) LIKELY - SendVoiceChanges(context.get(), tail); + SendVoiceChanges(context, tail); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourceRewind(ALuint source) -START_API_FUNC -{ alSourceRewindv(1, &source); } -END_API_FUNC +AL_API DECL_FUNC1(void, alSourceRewind, ALuint,source) +FORCE_ALIGN void AL_APIENTRY alSourceRewindDirect(ALCcontext *context, ALuint source) noexcept +{ alSourceRewindvDirect(context, 1, &source); } -AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Rewinding %d sources", n); +AL_API DECL_FUNC2(void, alSourceRewindv, ALsizei,n, const ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alSourceRewindvDirect(ALCcontext *context, ALsizei n, + const ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Rewinding %d sources", n}; if(n <= 0) UNLIKELY return; - al::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(static_cast(n) <= source_storage.size()) LIKELY - srchandles = {source_storage.data(), static_cast(n)}; - else + al::span sids{sources, static_cast(n)}; + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) + std::lock_guard sourcelock{context->mSourceLock}; + auto lookup_src = [context](const ALuint sid) -> ALsource* { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); - ++sources; - } + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) { - Voice *voice{GetSourceVoice(source, context.get())}; + Voice *voice{GetSourceVoice(source, context)}; if(source->state != AL_INITIAL) { if(!cur) - cur = tail = GetVoiceChanger(context.get()); + cur = tail = GetVoiceChanger(context); else { - cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); + cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } if(voice) @@ -3778,32 +3452,32 @@ START_API_FUNC } source->Offset = 0.0; source->OffsetType = AL_NONE; - source->VoiceIdx = INVALID_VOICE_IDX; + source->VoiceIdx = InvalidVoiceIndex; } if(tail) LIKELY - SendVoiceChanges(context.get(), tail); + SendVoiceChanges(context, tail); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourceQueueBuffers(ALuint src, ALsizei nb, const ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(nb < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Queueing %d buffers", nb); +AL_API DECL_FUNC3(void, alSourceQueueBuffers, ALuint,source, ALsizei,nb, const ALuint*,buffers) +FORCE_ALIGN void AL_APIENTRY alSourceQueueBuffersDirect(ALCcontext *context, ALuint src, + ALsizei nb, const ALuint *buffers) noexcept +try { + if(nb < 0) + throw al::context_error{AL_INVALID_VALUE, "Queueing %d buffers", nb}; if(nb <= 0) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *source{LookupSource(context.get(),src)}; - if(!source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", src); + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *source{LookupSource(context,src)}; + if(!source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src}; /* Can't queue on a Static Source */ - if(source->SourceType == AL_STATIC) UNLIKELY - return context->setError(AL_INVALID_OPERATION, "Queueing onto static source %u", src); + if(source->SourceType == AL_STATIC) + throw al::context_error{AL_INVALID_OPERATION, "Queueing onto static source %u", src}; /* Check for a valid Buffer, for its frequency and format */ ALCdevice *device{context->mALDevice.get()}; @@ -3815,90 +3489,85 @@ START_API_FUNC } std::unique_lock buflock{device->BufferLock}; + const auto bids = al::span{buffers, static_cast(nb)}; const size_t NewListStart{source->mQueue.size()}; - ALbufferQueueItem *BufferList{nullptr}; - for(ALsizei i{0};i < nb;i++) - { - bool fmt_mismatch{false}; - ALbuffer *buffer{nullptr}; - if(buffers[i] && (buffer=LookupBuffer(device, buffers[i])) == nullptr) + try { + ALbufferQueueItem *BufferList{nullptr}; + std::for_each(bids.cbegin(), bids.cend(), + [source,device,&BufferFmt,&BufferList](const ALuint bid) { - context->setError(AL_INVALID_NAME, "Queueing invalid buffer ID %u", buffers[i]); - goto buffer_error; - } - if(buffer) - { - if(buffer->mSampleRate < 1) - { - context->setError(AL_INVALID_OPERATION, "Queueing buffer %u with no format", - buffer->id); - goto buffer_error; - } - if(buffer->mCallback) - { - context->setError(AL_INVALID_OPERATION, "Queueing callback buffer %u", buffer->id); - goto buffer_error; - } - 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; - } - } + ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr}; + if(bid && !buffer) + throw al::context_error{AL_INVALID_NAME, "Queueing invalid buffer ID %u", bid}; - source->mQueue.emplace_back(); - if(!BufferList) - BufferList = &source->mQueue.back(); - else - { - auto &item = source->mQueue.back(); - BufferList->mNext.store(&item, std::memory_order_relaxed); - BufferList = &item; - } - if(!buffer) continue; - BufferList->mBlockAlign = buffer->mBlockAlign; - BufferList->mSampleLen = buffer->mSampleLen; - BufferList->mLoopEnd = buffer->mSampleLen; - BufferList->mSamples = buffer->mData.data(); - BufferList->mBuffer = buffer; - IncrementRef(buffer->ref); - - if(BufferFmt == nullptr) - BufferFmt = buffer; - else - { - fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate; - fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels; - fmt_mismatch |= BufferFmt->mType != buffer->mType; - if(BufferFmt->isBFormat()) + if(buffer) { - fmt_mismatch |= BufferFmt->mAmbiLayout != buffer->mAmbiLayout; - fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling; - } - fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder; - } - if(fmt_mismatch) UNLIKELY - { - context->setError(AL_INVALID_OPERATION, "Queueing buffer with mismatched format\n" - " Expected: %uhz, %s, %s ; Got: %uhz, %s, %s\n", BufferFmt->mSampleRate, - NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels), - buffer->mSampleRate, NameFromFormat(buffer->mType), - NameFromFormat(buffer->mChannels)); + if(buffer->mSampleRate < 1) + throw al::context_error{AL_INVALID_OPERATION, + "Queueing buffer %u with no format", buffer->id}; - buffer_error: - /* A buffer failed (invalid ID or format), so unlock and release - * each buffer we had. - */ - auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart); - for(;iter != source->mQueue.end();++iter) - { - if(ALbuffer *buf{iter->mBuffer}) - DecrementRef(buf->ref); + if(buffer->mCallback) + throw al::context_error{AL_INVALID_OPERATION, "Queueing callback buffer %u", + buffer->id}; + + if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) + throw al::context_error{AL_INVALID_OPERATION, + "Queueing non-persistently mapped buffer %u", buffer->id}; } - source->mQueue.resize(NewListStart); - return; + + source->mQueue.emplace_back(); + if(!BufferList) + BufferList = &source->mQueue.back(); + else + { + auto &item = source->mQueue.back(); + BufferList->mNext.store(&item, std::memory_order_relaxed); + BufferList = &item; + } + if(!buffer) return; + BufferList->mBlockAlign = buffer->mBlockAlign; + BufferList->mSampleLen = buffer->mSampleLen; + BufferList->mLoopEnd = buffer->mSampleLen; + BufferList->mSamples = buffer->mData; + BufferList->mBuffer = buffer; + IncrementRef(buffer->ref); + + bool fmt_mismatch{false}; + if(BufferFmt == nullptr) + BufferFmt = buffer; + else + { + fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate; + fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels; + fmt_mismatch |= BufferFmt->mType != buffer->mType; + if(BufferFmt->isBFormat()) + { + fmt_mismatch |= BufferFmt->mAmbiLayout != buffer->mAmbiLayout; + fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling; + } + fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder; + } + if(fmt_mismatch) + throw al::context_error{AL_INVALID_OPERATION, + "Queueing buffer with mismatched format\n" + " Expected: %uhz, %s, %s ; Got: %uhz, %s, %s\n", BufferFmt->mSampleRate, + NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels), + buffer->mSampleRate, NameFromFormat(buffer->mType), + NameFromFormat(buffer->mChannels)}; + }); + } + catch(...) { + /* A buffer failed (invalid ID or format), or there was some other + * unexpected error, so unlock and release each buffer we had. + */ + auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart); + for(;iter != source->mQueue.end();++iter) + { + if(ALbuffer *buf{iter->mBuffer}) + DecrementRef(buf->ref); } + source->mQueue.resize(NewListStart); + throw; } /* All buffers good. */ buflock.unlock(); @@ -3912,35 +3581,35 @@ START_API_FUNC (iter-1)->mNext.store(al::to_address(iter), std::memory_order_release); } } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} -AL_API void AL_APIENTRY alSourceUnqueueBuffers(ALuint src, ALsizei nb, ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(nb < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Unqueueing %d buffers", nb); +AL_API DECL_FUNC3(void, alSourceUnqueueBuffers, ALuint,source, ALsizei,nb, ALuint*,buffers) +FORCE_ALIGN void AL_APIENTRY alSourceUnqueueBuffersDirect(ALCcontext *context, ALuint src, + ALsizei nb, ALuint *buffers) noexcept +try { + if(nb < 0) + throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffers", nb}; if(nb <= 0) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *source{LookupSource(context.get(),src)}; - if(!source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", src); + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *source{LookupSource(context,src)}; + if(!source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src}; - if(source->SourceType != AL_STREAMING) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Unqueueing from a non-streaming source %u", - src); - if(source->Looping) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Unqueueing from looping source %u", src); + if(source->SourceType != AL_STREAMING) + throw al::context_error{AL_INVALID_VALUE, "Unqueueing from a non-streaming source %u",src}; + if(source->Looping) + throw al::context_error{AL_INVALID_VALUE, "Unqueueing from looping source %u", src}; /* Make sure enough buffers have been processed to unqueue. */ - uint processed{0u}; + const al::span bids{buffers, static_cast(nb)}; + size_t processed{0}; if(source->state != AL_INITIAL) LIKELY { VoiceBufferItem *Current{nullptr}; - if(Voice *voice{GetSourceVoice(source, context.get())}) + if(Voice *voice{GetSourceVoice(source, context)}) Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); for(auto &item : source->mQueue) { @@ -3949,51 +3618,52 @@ START_API_FUNC ++processed; } } - if(processed < static_cast(nb)) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Unqueueing %d buffer%s (only %u processed)", - nb, (nb==1)?"":"s", processed); + if(processed < bids.size()) + throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffer%s (only %zu processed)", + nb, (nb==1)?"":"s", processed}; - do { + std::generate(bids.begin(), bids.end(), [source]() noexcept -> ALuint + { auto &head = source->mQueue.front(); + ALuint bid{0}; if(ALbuffer *buffer{head.mBuffer}) { - *(buffers++) = buffer->id; + bid = buffer->id; DecrementRef(buffer->ref); } - else - *(buffers++) = 0; source->mQueue.pop_front(); - } while(--nb); + return bid; + }); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALuint*) -START_API_FUNC +AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALuint*) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; context->setError(AL_INVALID_OPERATION, "alSourceQueueBufferLayersSOFT not supported"); } -END_API_FUNC -ALsource::ALsource() +ALsource::ALsource() noexcept { Direct.Gain = 1.0f; Direct.GainHF = 1.0f; - Direct.HFReference = LOWPASSFREQREF; + Direct.HFReference = LowPassFreqRef; Direct.GainLF = 1.0f; - Direct.LFReference = HIGHPASSFREQREF; + Direct.LFReference = HighPassFreqRef; for(auto &send : Send) { send.Slot = nullptr; send.Gain = 1.0f; send.GainHF = 1.0f; - send.HFReference = LOWPASSFREQREF; + send.HFReference = LowPassFreqRef; send.GainLF = 1.0f; - send.LFReference = HIGHPASSFREQREF; + send.LFReference = HighPassFreqRef; } } @@ -4012,13 +3682,13 @@ ALsource::~ALsource() void UpdateAllSourceProps(ALCcontext *context) { - std::lock_guard _{context->mSourceLock}; + std::lock_guard srclock{context->mSourceLock}; auto voicelist = context->getVoicesSpan(); ALuint vidx{0u}; for(Voice *voice : voicelist) { ALuint sid{voice->mSourceID.load(std::memory_order_acquire)}; - ALsource *source = sid ? LookupSource(context, sid) : nullptr; + ALsource *source{sid ? LookupSource(context, sid) : nullptr}; if(source && source->VoiceIdx == vidx) { if(std::exchange(source->mPropsDirty, false)) @@ -4028,25 +3698,37 @@ void UpdateAllSourceProps(ALCcontext *context) } } +void ALsource::SetName(ALCcontext *context, ALuint id, std::string_view name) +{ + std::lock_guard srclock{context->mSourceLock}; + + auto source = LookupSource(context, id); + if(!source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", id}; + + context->mSourceNames.insert_or_assign(id, name); +} + + SourceSubList::~SourceSubList() { + if(!Sources) + return; + uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; usemask &= ~(1_u64 << idx); - al::destroy_at(Sources+idx); + std::destroy_at(al::to_address(Sources->begin() + idx)); } FreeMask = ~usemask; - al_free(Sources); + SubListAllocator{}.deallocate(Sources, 1); Sources = nullptr; } #ifdef ALSOFT_EAX -constexpr const ALsource::EaxFxSlotIds ALsource::eax4_fx_slot_ids; -constexpr const ALsource::EaxFxSlotIds ALsource::eax5_fx_slot_ids; - void ALsource::eaxInitialize(ALCcontext *context) noexcept { assert(context != nullptr); @@ -4097,7 +3779,8 @@ ALsource* ALsource::EaxLookupSource(ALCcontext& al_context, ALuint source_id) no void ALsource::eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept { - for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) { + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) + { auto& send = sends[i]; send.guidReceivingFXSlotID = *(ids[i]); send.lSend = EAXSOURCE_DEFAULTSEND; @@ -4209,7 +3892,8 @@ void ALsource::eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noex void ALsource::eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept { - for (auto i = size_t{}; i < eax_max_speakers; ++i) { + for(size_t i{0};i < eax_max_speakers;++i) + { auto& speaker_level = speaker_levels[i]; speaker_level.lSpeakerID = static_cast(EAXSPEAKER_FRONT_LEFT + i); speaker_level.lLevel = EAXSOURCE_DEFAULTSPEAKERLEVEL; @@ -4251,7 +3935,7 @@ void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept else { dst.source.ulFlags &= ~EAXSOURCEFLAGS_ROOMAUTO; - dst.sends[0].lSend = clamp(static_cast(gain_to_level_mb(src.fMix)), + dst.sends[0].lSend = std::clamp(static_cast(gain_to_level_mb(src.fMix)), EAXSOURCE_MINSEND, EAXSOURCE_MAXSEND); } } @@ -4280,7 +3964,7 @@ void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept dst.source.ulFlags = src.dwFlags; dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; - // Set everyting else to defaults. + // Set everything else to defaults. // eax5_set_sends_defaults(dst.sends); eax5_set_active_fx_slots_defaults(dst.active_fx_slots); @@ -4294,7 +3978,7 @@ void ALsource::eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept static_cast(dst.source) = src; dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; - // Set everyting else to defaults. + // Set everything else to defaults. // eax5_set_sends_defaults(dst.sends); eax5_set_active_fx_slots_defaults(dst.active_fx_slots); @@ -4312,32 +3996,35 @@ void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept // dst.sends = src.sends; - for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) dst.sends[i].guidReceivingFXSlotID = *(eax5_fx_slot_ids[i]); // Active FX slots. // - for (auto i = 0; i < EAX50_MAX_ACTIVE_FXSLOTS; ++i) { + for(size_t i{0};i < EAX50_MAX_ACTIVE_FXSLOTS;++i) + { auto& dst_id = dst.active_fx_slots.guidActiveFXSlots[i]; - if (i < EAX40_MAX_ACTIVE_FXSLOTS) { + if(i < EAX40_MAX_ACTIVE_FXSLOTS) + { const auto& src_id = src.active_fx_slots.guidActiveFXSlots[i]; - if (src_id == EAX_NULL_GUID) + if(src_id == EAX_NULL_GUID) dst_id = EAX_NULL_GUID; - else if (src_id == EAX_PrimaryFXSlotID) + else if(src_id == EAX_PrimaryFXSlotID) dst_id = EAX_PrimaryFXSlotID; - else if (src_id == EAXPROPERTYID_EAX40_FXSlot0) + else if(src_id == EAXPROPERTYID_EAX40_FXSlot0) dst_id = EAXPROPERTYID_EAX50_FXSlot0; - else if (src_id == EAXPROPERTYID_EAX40_FXSlot1) + else if(src_id == EAXPROPERTYID_EAX40_FXSlot1) dst_id = EAXPROPERTYID_EAX50_FXSlot1; - else if (src_id == EAXPROPERTYID_EAX40_FXSlot2) + else if(src_id == EAXPROPERTYID_EAX40_FXSlot2) dst_id = EAXPROPERTYID_EAX50_FXSlot2; - else if (src_id == EAXPROPERTYID_EAX40_FXSlot3) + else if(src_id == EAXPROPERTYID_EAX40_FXSlot3) dst_id = EAXPROPERTYID_EAX50_FXSlot3; else assert(false && "Unknown active FX slot ID."); - } else + } + else dst_id = EAX_NULL_GUID; } @@ -4374,19 +4061,21 @@ EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept static_cast(mEax.source.lDirectHF) + static_cast(mEax.source.lObstruction); - for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i) + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { if(!mEaxActiveFxSlots[i]) continue; - if(has_source_occlusion) { + if(has_source_occlusion) + { const auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i); const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0); const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()); const auto is_listener_environment = (is_environmental_fx && is_primary); - if(is_listener_environment) { + if(is_listener_environment) + { gain_mb += eax_calculate_dst_occlusion_mb( mEax.source.lOcclusion, mEax.source.flOcclusionDirectRatio, @@ -4398,7 +4087,8 @@ EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept const auto& send = mEax.sends[i]; - if(send.lOcclusion != 0) { + if(send.lOcclusion != 0) + { gain_mb += eax_calculate_dst_occlusion_mb( send.lOcclusion, send.flOcclusionDirectRatio, @@ -4408,11 +4098,9 @@ EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept } } - const auto al_low_pass_param = EaxAlLowPassParam{ + return EaxAlLowPassParam{ level_mb_to_gain(gain_mb), - minf(level_mb_to_gain(gain_hf_mb), 1.0f)}; - - return al_low_pass_param; + std::min(level_mb_to_gain(gain_hf_mb), 1.0f)}; } EaxAlLowPassParam ALsource::eax_create_room_filter_param( @@ -4453,11 +4141,9 @@ EaxAlLowPassParam ALsource::eax_create_room_filter_param( static_cast(mEax.source.lExclusion + send.lExclusion) : 0.0f); - const auto al_low_pass_param = EaxAlLowPassParam{ + return EaxAlLowPassParam{ level_mb_to_gain(gain_mb), - minf(level_mb_to_gain(gain_hf_mb), 1.0f)}; - - return al_low_pass_param; + std::min(level_mb_to_gain(gain_hf_mb), 1.0f)}; } void ALsource::eax_update_direct_filter() @@ -4465,16 +4151,17 @@ void ALsource::eax_update_direct_filter() const auto& direct_param = eax_create_direct_filter_param(); Direct.Gain = direct_param.gain; Direct.GainHF = direct_param.gain_hf; - Direct.HFReference = LOWPASSFREQREF; + Direct.HFReference = LowPassFreqRef; Direct.GainLF = 1.0f; - Direct.LFReference = HIGHPASSFREQREF; + Direct.LFReference = HighPassFreqRef; mPropsDirty = true; } void ALsource::eax_update_room_filters() { - for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) { - if (!mEaxActiveFxSlots[i]) + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) + { + if(!mEaxActiveFxSlots[i]) continue; auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i); @@ -4486,7 +4173,7 @@ void ALsource::eax_update_room_filters() void ALsource::eax_set_efx_outer_gain_hf() { - OuterGainHF = clamp( + OuterGainHF = std::clamp( level_mb_to_gain(static_cast(mEax.source.lOutsideVolumeHF)), AL_MIN_CONE_OUTER_GAINHF, AL_MAX_CONE_OUTER_GAINHF); @@ -4755,7 +4442,7 @@ void ALsource::eax4_set(const EaxCall& call, Eax4Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - eax4_defer_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); + eax4_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots}); break; default: @@ -4815,10 +4502,13 @@ void ALsource::eax5_set(const EaxCall& call, Eax5Props& props) case EAXSOURCE_ROLLOFFFACTOR: case EAXSOURCE_ROOMROLLOFFFACTOR: case EAXSOURCE_AIRABSORPTIONFACTOR: - case EAXSOURCE_FLAGS: eax3_set(call, props.source); break; + case EAXSOURCE_FLAGS: + eax_defer(call, props.source.ulFlags); + break; + case EAXSOURCE_SENDPARAMETERS: eax5_defer_sends(call, props.sends); break; @@ -4836,7 +4526,7 @@ void ALsource::eax5_set(const EaxCall& call, Eax5Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - eax5_defer_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); + eax5_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots}); break; case EAXSOURCE_MACROFXFACTOR: @@ -4872,13 +4562,11 @@ void ALsource::eax_set(const EaxCall& call) mEaxVersion = eax_version; } -void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count) +void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const al::span src_ids) { - assert(ids != nullptr); - assert(max_count == EAX40_MAX_ACTIVE_FXSLOTS || max_count == EAX50_MAX_ACTIVE_FXSLOTS); - const auto dst_ids = call.get_values(max_count); - const auto count = dst_ids.size(); - std::uninitialized_copy_n(ids, count, dst_ids.begin()); + assert(src_ids.size()==EAX40_MAX_ACTIVE_FXSLOTS || src_ids.size()==EAX50_MAX_ACTIVE_FXSLOTS); + const auto dst_ids = call.get_values(src_ids.size()); + std::uninitialized_copy_n(src_ids.begin(), dst_ids.size(), dst_ids.begin()); } void ALsource::eax1_get(const EaxCall& call, const Eax1Props& props) @@ -5126,7 +4814,7 @@ void ALsource::eax4_get(const EaxCall& call, const Eax4Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots, EAX40_MAX_ACTIVE_FXSLOTS); + eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); break; default: @@ -5198,7 +4886,7 @@ void ALsource::eax5_get(const EaxCall& call, const Eax5Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots, EAX50_MAX_ACTIVE_FXSLOTS); + eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); break; case EAXSOURCE_MACROFXFACTOR: @@ -5238,9 +4926,9 @@ void ALsource::eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, const auto &send = Send[sendidx]; send.Gain = filter.gain; send.GainHF = filter.gain_hf; - send.HFReference = LOWPASSFREQREF; + send.HFReference = LowPassFreqRef; send.GainLF = 1.0f; - send.LFReference = HIGHPASSFREQREF; + send.LFReference = HighPassFreqRef; if(slot != nullptr) IncrementRef(slot->ref); @@ -5280,7 +4968,7 @@ void ALsource::eax_commit_active_fx_slots() // Deactivate EFX auxiliary effect slots for inactive slots. Active slots // will be updated with the room filters. - for(auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { if(!mEaxActiveFxSlots[i]) eax_set_al_source_send(nullptr, i, EaxAlLowPassParam{1.0f, 1.0f}); diff --git a/Engine/lib/openal-soft/al/source.h b/Engine/lib/openal-soft/al/source.h index ac97c8a73..9f70bfafd 100644 --- a/Engine/lib/openal-soft/al/source.h +++ b/Engine/lib/openal-soft/al/source.h @@ -2,26 +2,26 @@ #define AL_SOURCE_H #include -#include #include -#include -#include +#include #include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" +#include "AL/alext.h" -#include "alc/alu.h" -#include "alc/context.h" -#include "alc/inprogext.h" -#include "aldeque.h" #include "almalloc.h" +#include "alnumbers.h" #include "alnumeric.h" -#include "atomic.h" +#include "alspan.h" +#include "core/context.h" #include "core/voice.h" -#include "vector.h" #ifdef ALSOFT_EAX +#include "eax/api.h" #include "eax/call.h" #include "eax/exception.h" #include "eax/fx_slot_index.h" @@ -30,23 +30,23 @@ struct ALbuffer; struct ALeffectslot; - +enum class Resampler : uint8_t; enum class SourceStereo : bool { Normal = AL_NORMAL_SOFT, Enhanced = AL_SUPER_STEREO_SOFT }; -#define DEFAULT_SENDS 2 +inline constexpr size_t DefaultSendCount{2}; -#define INVALID_VOICE_IDX static_cast(-1) +inline constexpr ALuint InvalidVoiceIndex{std::numeric_limits::max()}; -extern bool sBufferSubDataCompat; +inline bool sBufferSubDataCompat{false}; struct ALbufferQueueItem : public VoiceBufferItem { ALbuffer *mBuffer{nullptr}; - DISABLE_ALLOC() + DISABLE_ALLOC }; @@ -88,6 +88,7 @@ struct ALsource { DirectMode DirectChannels{DirectMode::Off}; SpatializeMode mSpatialize{SpatializeMode::Auto}; SourceStereo mStereoMode{SourceStereo::Normal}; + bool mPanningEnabled{false}; bool DryGainHFAuto{true}; bool WetGainAuto{true}; @@ -105,24 +106,27 @@ struct ALsource { float Radius{0.0f}; float EnhWidth{0.593f}; + float mPan{0.0f}; /** Direct filter and auxiliary send info. */ - struct { - float Gain; - float GainHF; - float HFReference; - float GainLF; - float LFReference; - } Direct; - struct SendData { - ALeffectslot *Slot; - float Gain; - float GainHF; - float HFReference; - float GainLF; - float LFReference; + struct DirectData { + float Gain{}; + float GainHF{}; + float HFReference{}; + float GainLF{}; + float LFReference{}; }; - std::array Send; + DirectData Direct; + + struct SendData { + ALeffectslot *Slot{}; + float Gain{}; + float GainHF{}; + float HFReference{}; + float GainLF{}; + float LFReference{}; + }; + std::array Send; /** * Last user-specified offset, and the offset type (bytes, samples, or @@ -138,26 +142,28 @@ struct ALsource { ALenum state{AL_INITIAL}; /** Source Buffer Queue head. */ - al::deque mQueue; + std::deque mQueue; bool mPropsDirty{true}; /* Index into the context's Voices array. Lazily updated, only checked and * reset when looking up the voice. */ - ALuint VoiceIdx{INVALID_VOICE_IDX}; + ALuint VoiceIdx{InvalidVoiceIndex}; /** Self ID */ ALuint id{0}; - ALsource(); + ALsource() noexcept; ~ALsource(); ALsource(const ALsource&) = delete; ALsource& operator=(const ALsource&) = delete; - DISABLE_ALLOC() + static void SetName(ALCcontext *context, ALuint id, std::string_view name); + + DISABLE_ALLOC #ifdef ALSOFT_EAX public: @@ -171,18 +177,18 @@ public: private: using Exception = EaxSourceException; - static constexpr auto eax_max_speakers = 9; + static constexpr auto eax_max_speakers{9u}; - using EaxFxSlotIds = const GUID* [EAX_MAX_FXSLOTS]; + using EaxFxSlotIds = std::array; - static constexpr const EaxFxSlotIds eax4_fx_slot_ids = { + static constexpr const EaxFxSlotIds eax4_fx_slot_ids{ &EAXPROPERTYID_EAX40_FXSlot0, &EAXPROPERTYID_EAX40_FXSlot1, &EAXPROPERTYID_EAX40_FXSlot2, &EAXPROPERTYID_EAX40_FXSlot3, }; - static constexpr const EaxFxSlotIds eax5_fx_slot_ids = { + static constexpr const EaxFxSlotIds eax5_fx_slot_ids{ &EAXPROPERTYID_EAX50_FXSlot0, &EAXPROPERTYID_EAX50_FXSlot1, &EAXPROPERTYID_EAX50_FXSlot2, @@ -215,11 +221,6 @@ private: Eax3Props source; EaxSends sends; EAX40ACTIVEFXSLOTS active_fx_slots; - - bool operator==(const Eax4Props& rhs) noexcept - { - return std::memcmp(this, &rhs, sizeof(Eax4Props)) == 0; - } }; struct Eax4State { @@ -232,11 +233,6 @@ private: EaxSends sends; EAX50ACTIVEFXSLOTS active_fx_slots; EaxSpeakerLevels speaker_levels; - - bool operator==(const Eax5Props& rhs) noexcept - { - return std::memcmp(this, &rhs, sizeof(Eax5Props)) == 0; - } }; struct Eax5State { @@ -546,7 +542,24 @@ private: struct Eax5SourceAllValidator { void operator()(const EAX50SOURCEPROPERTIES& props) const { - Eax3SourceAllValidator{}(static_cast(props)); + Eax2SourceDirectValidator{}(props.lDirect); + Eax2SourceDirectHfValidator{}(props.lDirectHF); + Eax2SourceRoomValidator{}(props.lRoom); + Eax2SourceRoomHfValidator{}(props.lRoomHF); + Eax2SourceObstructionValidator{}(props.lObstruction); + Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio); + Eax2SourceOcclusionValidator{}(props.lOcclusion); + Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); + Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); + Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); + Eax3SourceExclusionValidator{}(props.lExclusion); + Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); + Eax2SourceOutsideVolumeHfValidator{}(props.lOutsideVolumeHF); + Eax3SourceDopplerFactorValidator{}(props.flDopplerFactor); + Eax3SourceRolloffFactorValidator{}(props.flRolloffFactor); + Eax2SourceRoomRolloffFactorValidator{}(props.flRoomRolloffFactor); + Eax2SourceAirAbsorptionFactorValidator{}(props.flAirAbsorptionFactor); + Eax5SourceFlagsValidator{}(props.ulFlags); Eax5SourceMacroFXFactorValidator{}(props.flMacroFXFactor); } }; @@ -809,39 +822,38 @@ private: [[noreturn]] static void eax_fail_unknown_active_fx_slot_id(); [[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id(); - void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept; - void eax1_set_defaults(Eax1Props& props) noexcept; + static void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept; + static void eax1_set_defaults(Eax1Props& props) noexcept; void eax1_set_defaults() noexcept; - void eax2_set_defaults(Eax2Props& props) noexcept; + static void eax2_set_defaults(Eax2Props& props) noexcept; void eax2_set_defaults() noexcept; - void eax3_set_defaults(Eax3Props& props) noexcept; + static void eax3_set_defaults(Eax3Props& props) noexcept; void eax3_set_defaults() noexcept; - void eax4_set_sends_defaults(EaxSends& sends) noexcept; - void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept; + static void eax4_set_sends_defaults(EaxSends& sends) noexcept; + static void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept; void eax4_set_defaults() noexcept; - void eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept; - void eax5_set_sends_defaults(EaxSends& sends) noexcept; - void eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept; - void eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept; - void eax5_set_defaults(Eax5Props& props) noexcept; + static void eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept; + static void eax5_set_sends_defaults(EaxSends& sends) noexcept; + static void eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept; + static void eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept; + static void eax5_set_defaults(Eax5Props& props) noexcept; void eax5_set_defaults() noexcept; void eax_set_defaults() noexcept; - void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept; - void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept; - void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept; - void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept; + static void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept; + static void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept; + static void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept; + static void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept; static float eax_calculate_dst_occlusion_mb( long src_occlusion_mb, float path_ratio, float lf_ratio) noexcept; - EaxAlLowPassParam eax_create_direct_filter_param() const noexcept; + [[nodiscard]] auto eax_create_direct_filter_param() const noexcept -> EaxAlLowPassParam; - EaxAlLowPassParam eax_create_room_filter_param( - const ALeffectslot& fx_slot, - const EAXSOURCEALLSENDPROPERTIES& send) const noexcept; + [[nodiscard]] auto eax_create_room_filter_param(const ALeffectslot& fx_slot, + const EAXSOURCEALLSENDPROPERTIES& send) const noexcept -> EaxAlLowPassParam; void eax_update_direct_filter(); void eax_update_room_filters(); @@ -894,16 +906,16 @@ private: } } - void eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count); - void eax1_get(const EaxCall& call, const Eax1Props& props); - void eax2_get(const EaxCall& call, const Eax2Props& props); - void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props); - void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props); - void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props); - void eax3_get(const EaxCall& call, const Eax3Props& props); + static void eax_get_active_fx_slot_id(const EaxCall& call, const al::span src_ids); + static void eax1_get(const EaxCall& call, const Eax1Props& props); + static void eax2_get(const EaxCall& call, const Eax2Props& props); + static void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props); + static void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props); + static void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props); + static void eax3_get(const EaxCall& call, const Eax3Props& props); void eax4_get(const EaxCall& call, const Eax4Props& props); - void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props); - void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props); + static void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props); + static void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props); void eax5_get(const EaxCall& call, const Eax5Props& props); void eax_get(const EaxCall& call); @@ -976,21 +988,21 @@ private: } template - void eax_defer_active_fx_slot_id(const EaxCall& call, GUID (&dst_ids)[TIdCount]) + void eax_defer_active_fx_slot_id(const EaxCall& call, const al::span dst_ids) { const auto src_ids = call.get_values(TIdCount); std::for_each(src_ids.cbegin(), src_ids.cend(), TValidator{}); - std::uninitialized_copy(src_ids.cbegin(), src_ids.cend(), dst_ids); + std::uninitialized_copy(src_ids.cbegin(), src_ids.cend(), dst_ids.begin()); } template - void eax4_defer_active_fx_slot_id(const EaxCall& call, GUID (&dst_ids)[TIdCount]) + void eax4_defer_active_fx_slot_id(const EaxCall& call, const al::span dst_ids) { eax_defer_active_fx_slot_id(call, dst_ids); } template - void eax5_defer_active_fx_slot_id(const EaxCall& call, GUID (&dst_ids)[TIdCount]) + void eax5_defer_active_fx_slot_id(const EaxCall& call, const al::span dst_ids) { eax_defer_active_fx_slot_id(call, dst_ids); } @@ -1022,12 +1034,12 @@ private: void eax_set_efx_wet_gain_auto(); void eax_set_efx_wet_gain_hf_auto(); - void eax1_set(const EaxCall& call, Eax1Props& props); - void eax2_set(const EaxCall& call, Eax2Props& props); + static void eax1_set(const EaxCall& call, Eax1Props& props); + static void eax2_set(const EaxCall& call, Eax2Props& props); void eax3_set(const EaxCall& call, Eax3Props& props); void eax4_set(const EaxCall& call, Eax4Props& props); - void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props); - void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props); + static void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props); + static void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props); void eax5_set(const EaxCall& call, Eax5Props& props); void eax_set(const EaxCall& call); @@ -1041,4 +1053,19 @@ private: void UpdateAllSourceProps(ALCcontext *context); +struct SourceSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> Sources{nullptr}; + + SourceSubList() noexcept = default; + SourceSubList(const SourceSubList&) = delete; + SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources} + { rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; } + ~SourceSubList(); + + SourceSubList& operator=(const SourceSubList&) = delete; + SourceSubList& operator=(SourceSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; } +}; + #endif diff --git a/Engine/lib/openal-soft/al/state.cpp b/Engine/lib/openal-soft/al/state.cpp index 86d81b130..f0216b22f 100644 --- a/Engine/lib/openal-soft/al/state.cpp +++ b/Engine/lib/openal-soft/al/state.cpp @@ -22,26 +22,32 @@ #include "version.h" +#include #include #include +#include #include +#include #include #include +#include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" +#include "al/debug.h" +#include "al/listener.h" #include "alc/alu.h" #include "alc/context.h" #include "alc/inprogext.h" #include "alnumeric.h" -#include "aloptional.h" #include "atomic.h" #include "core/context.h" -#include "core/except.h" +#include "core/logging.h" #include "core/mixer/defs.h" #include "core/voice.h" +#include "direct_defs.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "strutils.h" @@ -56,17 +62,19 @@ namespace { -constexpr ALchar alVendor[] = "OpenAL Community"; -constexpr ALchar alVersion[] = "1.1 ALSOFT " ALSOFT_VERSION; -constexpr ALchar alRenderer[] = "OpenAL Soft"; +[[nodiscard]] constexpr auto GetVendorString() noexcept { return "OpenAL Community"; } +[[nodiscard]] constexpr auto GetVersionString() noexcept { return "1.1 ALSOFT " ALSOFT_VERSION; } +[[nodiscard]] constexpr auto GetRendererString() noexcept { return "OpenAL Soft"; } -// 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"; +/* Error Messages */ +[[nodiscard]] constexpr auto GetNoErrorString() noexcept { return "No Error"; } +[[nodiscard]] constexpr auto GetInvalidNameString() noexcept { return "Invalid Name"; } +[[nodiscard]] constexpr auto GetInvalidEnumString() noexcept { return "Invalid Enum"; } +[[nodiscard]] constexpr auto GetInvalidValueString() noexcept { return "Invalid Value"; } +[[nodiscard]] constexpr auto GetInvalidOperationString() noexcept { return "Invalid Operation"; } +[[nodiscard]] constexpr auto GetOutOfMemoryString() noexcept { return "Out of Memory"; } +[[nodiscard]] constexpr auto GetStackOverflowString() noexcept { return "Stack Overflow"; } +[[nodiscard]] constexpr auto GetStackUnderflowString() noexcept { return "Stack Underflow"; } /* Resampler strings */ template struct ResamplerName { }; @@ -74,8 +82,10 @@ template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "Nearest"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "Linear"; } }; -template<> struct ResamplerName -{ static constexpr const ALchar *Get() noexcept { return "Cubic"; } }; +template<> struct ResamplerName +{ static constexpr const ALchar *Get() noexcept { return "Cubic Spline"; } }; +template<> struct ResamplerName +{ static constexpr const ALchar *Get() noexcept { return "4-point Gaussian"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "11th order Sinc (fast)"; } }; template<> struct ResamplerName @@ -92,7 +102,8 @@ const ALchar *GetResamplerName(const Resampler rtype) { HANDLE_RESAMPLER(Resampler::Point); HANDLE_RESAMPLER(Resampler::Linear); - HANDLE_RESAMPLER(Resampler::Cubic); + HANDLE_RESAMPLER(Resampler::Spline); + HANDLE_RESAMPLER(Resampler::Gaussian); HANDLE_RESAMPLER(Resampler::FastBSinc12); HANDLE_RESAMPLER(Resampler::BSinc12); HANDLE_RESAMPLER(Resampler::FastBSinc24); @@ -103,7 +114,7 @@ const ALchar *GetResamplerName(const Resampler rtype) throw std::runtime_error{"Unexpected resampler index"}; } -al::optional DistanceModelFromALenum(ALenum model) +constexpr auto DistanceModelFromALenum(ALenum model) noexcept -> std::optional { switch(model) { @@ -115,9 +126,9 @@ al::optional DistanceModelFromALenum(ALenum model) case AL_EXPONENT_DISTANCE: return DistanceModel::Exponent; case AL_EXPONENT_DISTANCE_CLAMPED: return DistanceModel::ExponentClamped; } - return al::nullopt; + return std::nullopt; } -ALenum ALenumFromDistanceModel(DistanceModel model) +constexpr auto ALenumFromDistanceModel(DistanceModel model) -> ALenum { switch(model) { @@ -132,810 +143,463 @@ ALenum ALenumFromDistanceModel(DistanceModel model) throw std::runtime_error{"Unexpected distance model "+std::to_string(static_cast(model))}; } +enum PropertyValue : ALenum { + DopplerFactor = AL_DOPPLER_FACTOR, + DopplerVelocity = AL_DOPPLER_VELOCITY, + DistanceModel = AL_DISTANCE_MODEL, + SpeedOfSound = AL_SPEED_OF_SOUND, + DeferredUpdates = AL_DEFERRED_UPDATES_SOFT, + GainLimit = AL_GAIN_LIMIT_SOFT, + NumResamplers = AL_NUM_RESAMPLERS_SOFT, + DefaultResampler = AL_DEFAULT_RESAMPLER_SOFT, + DebugLoggedMessages = AL_DEBUG_LOGGED_MESSAGES_EXT, + DebugNextLoggedMessageLength = AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT, + MaxDebugMessageLength = AL_MAX_DEBUG_MESSAGE_LENGTH_EXT, + MaxDebugLoggedMessages = AL_MAX_DEBUG_LOGGED_MESSAGES_EXT, + MaxDebugGroupDepth = AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT, + MaxLabelLength = AL_MAX_LABEL_LENGTH_EXT, + ContextFlags = AL_CONTEXT_FLAGS_EXT, +#ifdef ALSOFT_EAX + EaxRamSize = AL_EAX_RAM_SIZE, + EaxRamFree = AL_EAX_RAM_FREE, +#endif +}; + +template +struct PropertyCastType { + template + constexpr auto operator()(U&& value) const noexcept + { return static_cast(std::forward(value)); } +}; +/* Special-case ALboolean to be an actual bool instead of a char type. */ +template<> +struct PropertyCastType { + template + constexpr ALboolean operator()(U&& value) const noexcept + { return static_cast(std::forward(value)) ? AL_TRUE : AL_FALSE; } +}; + + +template +void GetValue(ALCcontext *context, ALenum pname, T *values) +{ + auto cast_value = PropertyCastType{}; + + switch(static_cast(pname)) + { + case AL_DOPPLER_FACTOR: + *values = cast_value(context->mDopplerFactor); + return; + + case AL_DOPPLER_VELOCITY: + if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY + context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 0, + DebugSeverity::Medium, + "AL_DOPPLER_VELOCITY is deprecated in AL 1.1, use AL_SPEED_OF_SOUND; " + "AL_DOPPLER_VELOCITY -> AL_SPEED_OF_SOUND / 343.3f"); + *values = cast_value(context->mDopplerVelocity); + return; + + case AL_SPEED_OF_SOUND: + *values = cast_value(context->mSpeedOfSound); + return; + + case AL_GAIN_LIMIT_SOFT: + *values = cast_value(GainMixMax / context->mGainBoost); + return; + + case AL_DEFERRED_UPDATES_SOFT: + *values = cast_value(context->mDeferUpdates ? AL_TRUE : AL_FALSE); + return; + + case AL_DISTANCE_MODEL: + *values = cast_value(ALenumFromDistanceModel(context->mDistanceModel)); + return; + + case AL_NUM_RESAMPLERS_SOFT: + *values = cast_value(al::to_underlying(Resampler::Max) + 1); + return; + + case AL_DEFAULT_RESAMPLER_SOFT: + *values = cast_value(al::to_underlying(ResamplerDefault)); + return; + + case AL_DEBUG_LOGGED_MESSAGES_EXT: + { + std::lock_guard debuglock{context->mDebugCbLock}; + *values = cast_value(context->mDebugLog.size()); + return; + } + + case AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT: + { + std::lock_guard debuglock{context->mDebugCbLock}; + *values = cast_value(context->mDebugLog.empty() ? 0_uz + : (context->mDebugLog.front().mMessage.size()+1)); + return; + } + + case AL_MAX_DEBUG_MESSAGE_LENGTH_EXT: + *values = cast_value(MaxDebugMessageLength); + return; + + case AL_MAX_DEBUG_LOGGED_MESSAGES_EXT: + *values = cast_value(MaxDebugLoggedMessages); + return; + + case AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT: + *values = cast_value(MaxDebugGroupDepth); + return; + + case AL_MAX_LABEL_LENGTH_EXT: + *values = cast_value(MaxObjectLabelLength); + return; + + case AL_CONTEXT_FLAGS_EXT: + *values = cast_value(context->mContextFlags.to_ulong()); + return; + +#ifdef ALSOFT_EAX +#define EAX_ERROR "[alGetInteger] EAX not enabled" + + case AL_EAX_RAM_SIZE: + if(eax_g_is_enabled) + { + *values = cast_value(eax_x_ram_max_size); + return; + } + ERR(EAX_ERROR "\n"); + break; + + case AL_EAX_RAM_FREE: + if(eax_g_is_enabled) + { + auto device = context->mALDevice.get(); + std::lock_guard device_lock{device->BufferLock}; + *values = cast_value(device->eax_x_ram_free_size); + return; + } + ERR(EAX_ERROR "\n"); + break; + +#undef EAX_ERROR +#endif // ALSOFT_EAX + } + context->setError(AL_INVALID_ENUM, "Invalid context property 0x%04x", pname); +} + + +inline void UpdateProps(ALCcontext *context) +{ + if(!context->mDeferUpdates) + UpdateContextProps(context); + else + context->mPropsDirty = true; +} + } // namespace /* 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) -START_API_FUNC +AL_API auto AL_APIENTRY alsoft_get_version() noexcept -> const ALchar* { 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) \ - UpdateContextProps(context.get()); \ - else \ - context->mPropsDirty = true; \ -} while(0) -AL_API void AL_APIENTRY alEnable(ALenum capability) -START_API_FUNC +AL_API DECL_FUNC1(void, alEnable, ALenum,capability) +FORCE_ALIGN void AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capability) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - switch(capability) { case AL_SOURCE_DISTANCE_MODEL: { - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; context->mSourceDistanceModel = true; - DO_UPDATEPROPS(); + UpdateProps(context); } - break; + return; + + case AL_DEBUG_OUTPUT_EXT: + context->mDebugEnabled.store(true); + return; case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: context->setError(AL_INVALID_OPERATION, "Re-enabling AL_STOP_SOURCES_ON_DISCONNECT_SOFT not yet supported"); - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability); + return; } + context->setError(AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability); } -END_API_FUNC -AL_API void AL_APIENTRY alDisable(ALenum capability) -START_API_FUNC +AL_API DECL_FUNC1(void, alDisable, ALenum,capability) +FORCE_ALIGN void AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capability) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - switch(capability) { case AL_SOURCE_DISTANCE_MODEL: { - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; context->mSourceDistanceModel = false; - DO_UPDATEPROPS(); + UpdateProps(context); } - break; + return; + + case AL_DEBUG_OUTPUT_EXT: + context->mDebugEnabled.store(false); + return; case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: - context->mStopVoicesOnDisconnect = false; - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability); + context->mStopVoicesOnDisconnect.store(false); + return; } + 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 +AL_API DECL_FUNC1(ALboolean, alIsEnabled, ALenum,capability) +FORCE_ALIGN ALboolean AL_APIENTRY alIsEnabledDirect(ALCcontext *context, ALenum capability) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return AL_FALSE; - - std::lock_guard _{context->mPropLock}; - ALboolean value{AL_FALSE}; + std::lock_guard proplock{context->mPropLock}; switch(capability) { - case AL_SOURCE_DISTANCE_MODEL: - value = context->mSourceDistanceModel ? AL_TRUE : AL_FALSE; - break; - + case AL_SOURCE_DISTANCE_MODEL: return context->mSourceDistanceModel ? AL_TRUE : AL_FALSE; + case AL_DEBUG_OUTPUT_EXT: return context->mDebugEnabled ? AL_TRUE : AL_FALSE; case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: - value = context->mStopVoicesOnDisconnect ? AL_TRUE : AL_FALSE; - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability); + return context->mStopVoicesOnDisconnect.load() ? AL_TRUE : AL_FALSE; } - - return value; + context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability); + return AL_FALSE; } -END_API_FUNC -AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum pname) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return AL_FALSE; - - std::lock_guard _{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) - value = AL_TRUE; - break; - - case AL_GAIN_LIMIT_SOFT: - if(GainMixMax/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(ResamplerDefault) ? AL_TRUE : AL_FALSE; - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid boolean property 0x%04x", pname); - } - - return value; +#define DECL_GETFUNC(R, Name, Ext) \ +AL_API auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \ +{ \ + R value{}; \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return value; \ + Name##vDirect##Ext(GetContextRef().get(), pname, &value); \ + return value; \ +} \ +FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, ALenum pname) noexcept -> R \ +{ \ + R value{}; \ + Name##vDirect##Ext(context, pname, &value); \ + return value; \ } -END_API_FUNC -AL_API ALdouble AL_APIENTRY alGetDouble(ALenum pname) -START_API_FUNC +DECL_GETFUNC(ALboolean, alGetBoolean,) +DECL_GETFUNC(ALdouble, alGetDouble,) +DECL_GETFUNC(ALfloat, alGetFloat,) +DECL_GETFUNC(ALint, alGetInteger,) + +DECL_GETFUNC(ALint64SOFT, alGetInteger64,SOFT) +DECL_GETFUNC(ALvoid*, alGetPointer,SOFT) + +#undef DECL_GETFUNC + + +AL_API DECL_FUNC2(void, alGetBooleanv, ALenum,pname, ALboolean*,values) +FORCE_ALIGN void AL_APIENTRY alGetBooleanvDirect(ALCcontext *context, ALenum pname, ALboolean *values) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return 0.0; - - std::lock_guard _{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(ALenumFromDistanceModel(context->mDistanceModel)); - break; - - case AL_SPEED_OF_SOUND: - value = context->mSpeedOfSound; - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates) - value = static_cast(AL_TRUE); - break; - - case AL_GAIN_LIMIT_SOFT: - value = ALdouble{GainMixMax}/context->mGainBoost; - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = static_cast(Resampler::Max) + 1.0; - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = static_cast(ResamplerDefault); - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid double property 0x%04x", pname); - } - - return value; + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); + GetValue(context, pname, values); } -END_API_FUNC -AL_API ALfloat AL_APIENTRY alGetFloat(ALenum pname) -START_API_FUNC +AL_API DECL_FUNC2(void, alGetDoublev, ALenum,pname, ALdouble*,values) +FORCE_ALIGN void AL_APIENTRY alGetDoublevDirect(ALCcontext *context, ALenum pname, ALdouble *values) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return 0.0f; - - std::lock_guard _{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(ALenumFromDistanceModel(context->mDistanceModel)); - break; - - case AL_SPEED_OF_SOUND: - value = context->mSpeedOfSound; - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates) - value = static_cast(AL_TRUE); - break; - - case AL_GAIN_LIMIT_SOFT: - value = GainMixMax/context->mGainBoost; - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = static_cast(Resampler::Max) + 1.0f; - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = static_cast(ResamplerDefault); - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid float property 0x%04x", pname); - } - - return value; + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); + GetValue(context, pname, values); } -END_API_FUNC -AL_API ALint AL_APIENTRY alGetInteger(ALenum pname) -START_API_FUNC +AL_API DECL_FUNC2(void, alGetFloatv, ALenum,pname, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetFloatvDirect(ALCcontext *context, ALenum pname, ALfloat *values) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return 0; - - std::lock_guard _{context->mPropLock}; - ALint value{0}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - value = static_cast(context->mDopplerFactor); - break; - - case AL_DOPPLER_VELOCITY: - value = static_cast(context->mDopplerVelocity); - break; - - case AL_DISTANCE_MODEL: - value = ALenumFromDistanceModel(context->mDistanceModel); - break; - - case AL_SPEED_OF_SOUND: - value = static_cast(context->mSpeedOfSound); - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates) - value = AL_TRUE; - break; - - case AL_GAIN_LIMIT_SOFT: - value = static_cast(GainMixMax/context->mGainBoost); - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = static_cast(Resampler::Max) + 1; - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = static_cast(ResamplerDefault); - break; - -#ifdef ALSOFT_EAX - -#define EAX_ERROR "[alGetInteger] EAX not enabled." - - case AL_EAX_RAM_SIZE: - if (eax_g_is_enabled) - { - value = eax_x_ram_max_size; - } - else - { - context->setError(AL_INVALID_VALUE, EAX_ERROR); - } - - break; - - case AL_EAX_RAM_FREE: - if (eax_g_is_enabled) - { - auto device = context->mALDevice.get(); - std::lock_guard device_lock{device->BufferLock}; - - value = static_cast(device->eax_x_ram_free_size); - } - else - { - context->setError(AL_INVALID_VALUE, EAX_ERROR); - } - - break; - -#undef EAX_ERROR - -#endif // ALSOFT_EAX - - default: - context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname); - } - - return value; + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); + GetValue(context, pname, values); } -END_API_FUNC -AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) -START_API_FUNC +AL_API DECL_FUNC2(void, alGetIntegerv, ALenum,pname, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetIntegervDirect(ALCcontext *context, ALenum pname, ALint *values) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return 0_i64; - - std::lock_guard _{context->mPropLock}; - ALint64SOFT value{0}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - value = static_cast(context->mDopplerFactor); - break; - - case AL_DOPPLER_VELOCITY: - value = static_cast(context->mDopplerVelocity); - break; - - case AL_DISTANCE_MODEL: - value = ALenumFromDistanceModel(context->mDistanceModel); - break; - - case AL_SPEED_OF_SOUND: - value = static_cast(context->mSpeedOfSound); - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates) - value = AL_TRUE; - break; - - case AL_GAIN_LIMIT_SOFT: - value = static_cast(GainMixMax/context->mGainBoost); - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = static_cast(Resampler::Max) + 1; - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = static_cast(ResamplerDefault); - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid integer64 property 0x%04x", pname); - } - - return value; + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); + GetValue(context, pname, values); } -END_API_FUNC -AL_API ALvoid* AL_APIENTRY alGetPointerSOFT(ALenum pname) -START_API_FUNC +AL_API DECL_FUNCEXT2(void, alGetInteger64v,SOFT, ALenum,pname, ALint64SOFT*,values) +FORCE_ALIGN void AL_APIENTRY alGetInteger64vDirectSOFT(ALCcontext *context, ALenum pname, ALint64SOFT *values) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return nullptr; + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); + GetValue(context, pname, values); +} + +AL_API DECL_FUNCEXT2(void, alGetPointerv,SOFT, ALenum,pname, ALvoid**,values) +FORCE_ALIGN void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum pname, ALvoid **values) noexcept +{ + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); - std::lock_guard _{context->mPropLock}; - void *value{nullptr}; switch(pname) { case AL_EVENT_CALLBACK_FUNCTION_SOFT: - value = reinterpret_cast(context->mEventCb); - break; + *values = reinterpret_cast(context->mEventCb); + return; case AL_EVENT_CALLBACK_USER_PARAM_SOFT: - value = context->mEventParam; - break; + *values = context->mEventParam; + return; - default: - context->setError(AL_INVALID_VALUE, "Invalid pointer property 0x%04x", pname); + case AL_DEBUG_CALLBACK_FUNCTION_EXT: + *values = reinterpret_cast(context->mDebugCb); + return; + + case AL_DEBUG_CALLBACK_USER_PARAM_EXT: + *values = context->mDebugParam; + return; } - - return value; + context->setError(AL_INVALID_ENUM, "Invalid context pointer property 0x%04x", pname); } -END_API_FUNC -AL_API void AL_APIENTRY alGetBooleanv(ALenum pname, ALboolean *values) -START_API_FUNC +AL_API DECL_FUNC1(const ALchar*, alGetString, ALenum,pname) +FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALenum pname) noexcept { - 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(!context) UNLIKELY 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 void 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(!context) UNLIKELY 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 void 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(!context) UNLIKELY 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 void 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(!context) UNLIKELY 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 - -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(!context) UNLIKELY 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, ALvoid **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(!context) UNLIKELY 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(!context) UNLIKELY 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); + case AL_VENDOR: return GetVendorString(); + case AL_VERSION: return GetVersionString(); + case AL_RENDERER: return GetRendererString(); + case AL_EXTENSIONS: return context->mExtensionsString.c_str(); + case AL_NO_ERROR: return GetNoErrorString(); + case AL_INVALID_NAME: return GetInvalidNameString(); + case AL_INVALID_ENUM: return GetInvalidEnumString(); + case AL_INVALID_VALUE: return GetInvalidValueString(); + case AL_INVALID_OPERATION: return GetInvalidOperationString(); + case AL_OUT_OF_MEMORY: return GetOutOfMemoryString(); + case AL_STACK_OVERFLOW_EXT: return GetStackOverflowString(); + case AL_STACK_UNDERFLOW_EXT: return GetStackUnderflowString(); } - return value; + context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname); + return nullptr; } -END_API_FUNC -AL_API void AL_APIENTRY alDopplerFactor(ALfloat value) -START_API_FUNC +AL_API DECL_FUNC1(void, alDopplerFactor, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alDopplerFactorDirect(ALCcontext *context, ALfloat value) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - if(!(value >= 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value); else { - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; context->mDopplerFactor = value; - DO_UPDATEPROPS(); + UpdateProps(context); } } -END_API_FUNC -AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) -START_API_FUNC +AL_API DECL_FUNC1(void, alSpeedOfSound, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alSpeedOfSoundDirect(ALCcontext *context, ALfloat value) noexcept +{ + if(!(value > 0.0f && std::isfinite(value))) + context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value); + else + { + std::lock_guard proplock{context->mPropLock}; + context->mSpeedOfSound = value; + UpdateProps(context); + } +} + +AL_API DECL_FUNC1(void, alDistanceModel, ALenum,value) +FORCE_ALIGN void AL_APIENTRY alDistanceModelDirect(ALCcontext *context, ALenum value) noexcept +{ + if(auto model = DistanceModelFromALenum(value)) + { + std::lock_guard proplock{context->mPropLock}; + context->mDistanceModel = *model; + if(!context->mSourceDistanceModel) + UpdateProps(context); + } + else + context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value); +} + + +AL_API DECL_FUNCEXT(void, alDeferUpdates,SOFT) +FORCE_ALIGN void AL_APIENTRY alDeferUpdatesDirectSOFT(ALCcontext *context) noexcept +{ + std::lock_guard proplock{context->mPropLock}; + context->deferUpdates(); +} + +AL_API DECL_FUNCEXT(void, alProcessUpdates,SOFT) +FORCE_ALIGN void AL_APIENTRY alProcessUpdatesDirectSOFT(ALCcontext *context) noexcept +{ + std::lock_guard proplock{context->mPropLock}; + context->processUpdates(); +} + + +AL_API DECL_FUNCEXT2(const ALchar*, alGetStringi,SOFT, ALenum,pname, ALsizei,index) +FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringiDirectSOFT(ALCcontext *context, ALenum pname, ALsizei index) noexcept +{ + switch(pname) + { + case AL_RESAMPLER_NAME_SOFT: + if(index >= 0 && index <= static_cast(Resampler::Max)) + return GetResamplerName(static_cast(index)); + context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index); + return nullptr; + } + context->setError(AL_INVALID_VALUE, "Invalid string indexed property"); + return nullptr; +} + + +AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; + if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY + context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 0, + DebugSeverity::Medium, + "alDopplerVelocity is deprecated in AL 1.1, use alSpeedOfSound; " + "alDopplerVelocity(x) -> alSpeedOfSound(343.3f * x)"); + if(!(value >= 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Doppler velocity %f out of range", value); else { - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; context->mDopplerVelocity = value; - DO_UPDATEPROPS(); + UpdateProps(context.get()); } } -END_API_FUNC - -AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY 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 _{context->mPropLock}; - context->mSpeedOfSound = value; - DO_UPDATEPROPS(); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alDistanceModel(ALenum value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(auto model = DistanceModelFromALenum(value)) - { - std::lock_guard _{context->mPropLock}; - context->mDistanceModel = *model; - if(!context->mSourceDistanceModel) - DO_UPDATEPROPS(); - } - else - context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value); -} -END_API_FUNC - - -AL_API void AL_APIENTRY alDeferUpdatesSOFT(void) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - context->deferUpdates(); -} -END_API_FUNC - -AL_API void AL_APIENTRY alProcessUpdatesSOFT(void) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - context->processUpdates(); -} -END_API_FUNC - - -AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return nullptr; - - const ALchar *value{nullptr}; - switch(pname) - { - case AL_RESAMPLER_NAME_SOFT: - if(index < 0 || index > static_cast(Resampler::Max)) - context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index); - else - value = GetResamplerName(static_cast(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. */ + /* Get an unused property container, or allocate a new one as needed. */ ContextProps *props{context->mFreeContextProps.load(std::memory_order_acquire)}; if(!props) - props = new ContextProps{}; - else { - ContextProps *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); + context->allocContextProps(); + props = context->mFreeContextProps.load(std::memory_order_acquire); } + ContextProps *next; + do { + next = props->next.load(std::memory_order_relaxed); + } while(context->mFreeContextProps.compare_exchange_weak(props, next, + std::memory_order_acq_rel, std::memory_order_acquire) == false); /* Copy in current property values. */ - ALlistener &listener = context->mListener; + const auto &listener = context->mListener; props->Position = listener.Position; props->Velocity = listener.Velocity; props->OrientAt = listener.OrientAt; diff --git a/Engine/lib/openal-soft/alc/alc.cpp b/Engine/lib/openal-soft/alc/alc.cpp index af8ff55d4..f87fb2076 100644 --- a/Engine/lib/openal-soft/alc/alc.cpp +++ b/Engine/lib/openal-soft/alc/alc.cpp @@ -38,21 +38,24 @@ #include #include #include -#include +#include #include #include #include +#include #include #include #include #include #include #include -#include +#include #include #include -#include +#include +#include #include +#include #include "AL/al.h" #include "AL/alc.h" @@ -61,16 +64,16 @@ #include "al/auxeffectslot.h" #include "al/buffer.h" +#include "al/debug.h" #include "al/effect.h" #include "al/filter.h" -#include "al/listener.h" #include "al/source.h" +#include "alc/events.h" #include "albit.h" -#include "albyte.h" #include "alconfig.h" #include "almalloc.h" +#include "alnumbers.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "alu.h" @@ -83,25 +86,24 @@ #include "core/cpu_caps.h" #include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" -#include "core/except.h" +#include "core/filters/nfc.h" #include "core/helpers.h" #include "core/mastering.h" -#include "core/mixer/hrtfdefs.h" #include "core/fpu_ctrl.h" -#include "core/front_stablizer.h" #include "core/logging.h" #include "core/uhjfilter.h" #include "core/voice.h" #include "core/voice_change.h" #include "device.h" #include "effects/base.h" +#include "export_list.h" +#include "flexarray.h" #include "inprogext.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "strutils.h" -#include "threads.h" -#include "vector.h" #include "backends/base.h" #include "backends/null.h" @@ -156,18 +158,11 @@ #endif #ifdef ALSOFT_EAX +#include "al/eax/api.h" #include "al/eax/globals.h" -#include "al/eax/x_ram.h" -#endif // ALSOFT_EAX - - -FILE *gLogFile{stderr}; -#ifdef _DEBUG -LogLevel gLogLevel{LogLevel::Warning}; -#else -LogLevel gLogLevel{LogLevel::Error}; #endif + /************************************************ * Library initialization ************************************************/ @@ -188,7 +183,7 @@ BOOL APIENTRY DllMain(HINSTANCE module, DWORD reason, LPVOID /*reserved*/) namespace { -using namespace std::placeholders; +using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::nanoseconds; @@ -201,59 +196,59 @@ using float2 = std::array; ************************************************/ struct BackendInfo { const char *name; - BackendFactory& (*getFactory)(void); + BackendFactory& (*getFactory)(); }; -BackendInfo BackendList[] = { +std::array BackendList{ #ifdef HAVE_PIPEWIRE - { "pipewire", PipeWireBackendFactory::getFactory }, + BackendInfo{"pipewire", PipeWireBackendFactory::getFactory}, #endif #ifdef HAVE_PULSEAUDIO - { "pulse", PulseBackendFactory::getFactory }, + BackendInfo{"pulse", PulseBackendFactory::getFactory}, #endif #ifdef HAVE_WASAPI - { "wasapi", WasapiBackendFactory::getFactory }, + BackendInfo{"wasapi", WasapiBackendFactory::getFactory}, #endif #ifdef HAVE_COREAUDIO - { "core", CoreAudioBackendFactory::getFactory }, + BackendInfo{"core", CoreAudioBackendFactory::getFactory}, #endif #ifdef HAVE_OBOE - { "oboe", OboeBackendFactory::getFactory }, + BackendInfo{"oboe", OboeBackendFactory::getFactory}, #endif #ifdef HAVE_OPENSL - { "opensl", OSLBackendFactory::getFactory }, + BackendInfo{"opensl", OSLBackendFactory::getFactory}, #endif #ifdef HAVE_ALSA - { "alsa", AlsaBackendFactory::getFactory }, + BackendInfo{"alsa", AlsaBackendFactory::getFactory}, #endif #ifdef HAVE_SOLARIS - { "solaris", SolarisBackendFactory::getFactory }, + BackendInfo{"solaris", SolarisBackendFactory::getFactory}, #endif #ifdef HAVE_SNDIO - { "sndio", SndIOBackendFactory::getFactory }, + BackendInfo{"sndio", SndIOBackendFactory::getFactory}, #endif #ifdef HAVE_OSS - { "oss", OSSBackendFactory::getFactory }, + BackendInfo{"oss", OSSBackendFactory::getFactory}, #endif #ifdef HAVE_JACK - { "jack", JackBackendFactory::getFactory }, + BackendInfo{"jack", JackBackendFactory::getFactory}, #endif #ifdef HAVE_DSOUND - { "dsound", DSoundBackendFactory::getFactory }, + BackendInfo{"dsound", DSoundBackendFactory::getFactory}, #endif #ifdef HAVE_WINMM - { "winmm", WinMMBackendFactory::getFactory }, + BackendInfo{"winmm", WinMMBackendFactory::getFactory}, #endif #ifdef HAVE_PORTAUDIO - { "port", PortBackendFactory::getFactory }, + BackendInfo{"port", PortBackendFactory::getFactory}, #endif #ifdef HAVE_SDL2 - { "sdl2", SDL2BackendFactory::getFactory }, + BackendInfo{"sdl2", SDL2BackendFactory::getFactory}, #endif - { "null", NullBackendFactory::getFactory }, + BackendInfo{"null", NullBackendFactory::getFactory}, #ifdef HAVE_WAVE - { "wave", WaveBackendFactory::getFactory }, + BackendInfo{"wave", WaveBackendFactory::getFactory}, #endif }; @@ -261,688 +256,22 @@ BackendFactory *PlaybackFactory{}; BackendFactory *CaptureFactory{}; -/************************************************ - * Functions, enums, and errors - ************************************************/ -#define DECL(x) { #x, reinterpret_cast(x) } -const struct { - const char *funcName; - void *address; -} alcFunctions[] = { - DECL(alcCreateContext), - DECL(alcMakeContextCurrent), - DECL(alcProcessContext), - DECL(alcSuspendContext), - DECL(alcDestroyContext), - DECL(alcGetCurrentContext), - DECL(alcGetContextsDevice), - DECL(alcOpenDevice), - DECL(alcCloseDevice), - DECL(alcGetError), - DECL(alcIsExtensionPresent), - DECL(alcGetProcAddress), - DECL(alcGetEnumValue), - DECL(alcGetString), - DECL(alcGetIntegerv), - DECL(alcCaptureOpenDevice), - DECL(alcCaptureCloseDevice), - DECL(alcCaptureStart), - DECL(alcCaptureStop), - DECL(alcCaptureSamples), - - DECL(alcSetThreadContext), - DECL(alcGetThreadContext), - - DECL(alcLoopbackOpenDeviceSOFT), - DECL(alcIsRenderFormatSupportedSOFT), - DECL(alcRenderSamplesSOFT), - - DECL(alcDevicePauseSOFT), - DECL(alcDeviceResumeSOFT), - - DECL(alcGetStringiSOFT), - DECL(alcResetDeviceSOFT), - - DECL(alcGetInteger64vSOFT), - - DECL(alcReopenDeviceSOFT), - - DECL(alEnable), - DECL(alDisable), - DECL(alIsEnabled), - DECL(alGetString), - DECL(alGetBooleanv), - DECL(alGetIntegerv), - DECL(alGetFloatv), - DECL(alGetDoublev), - DECL(alGetBoolean), - DECL(alGetInteger), - DECL(alGetFloat), - DECL(alGetDouble), - DECL(alGetError), - DECL(alIsExtensionPresent), - DECL(alGetProcAddress), - DECL(alGetEnumValue), - DECL(alListenerf), - DECL(alListener3f), - DECL(alListenerfv), - DECL(alListeneri), - DECL(alListener3i), - DECL(alListeneriv), - DECL(alGetListenerf), - DECL(alGetListener3f), - DECL(alGetListenerfv), - DECL(alGetListeneri), - DECL(alGetListener3i), - DECL(alGetListeneriv), - DECL(alGenSources), - DECL(alDeleteSources), - DECL(alIsSource), - DECL(alSourcef), - DECL(alSource3f), - DECL(alSourcefv), - DECL(alSourcei), - DECL(alSource3i), - DECL(alSourceiv), - DECL(alGetSourcef), - DECL(alGetSource3f), - DECL(alGetSourcefv), - DECL(alGetSourcei), - DECL(alGetSource3i), - DECL(alGetSourceiv), - DECL(alSourcePlayv), - DECL(alSourceStopv), - DECL(alSourceRewindv), - DECL(alSourcePausev), - DECL(alSourcePlay), - DECL(alSourceStop), - DECL(alSourceRewind), - DECL(alSourcePause), - DECL(alSourceQueueBuffers), - DECL(alSourceUnqueueBuffers), - DECL(alGenBuffers), - DECL(alDeleteBuffers), - DECL(alIsBuffer), - DECL(alBufferData), - DECL(alBufferf), - DECL(alBuffer3f), - DECL(alBufferfv), - DECL(alBufferi), - DECL(alBuffer3i), - DECL(alBufferiv), - DECL(alGetBufferf), - DECL(alGetBuffer3f), - DECL(alGetBufferfv), - DECL(alGetBufferi), - DECL(alGetBuffer3i), - DECL(alGetBufferiv), - DECL(alDopplerFactor), - DECL(alDopplerVelocity), - DECL(alSpeedOfSound), - DECL(alDistanceModel), - - DECL(alGenFilters), - DECL(alDeleteFilters), - DECL(alIsFilter), - DECL(alFilteri), - DECL(alFilteriv), - DECL(alFilterf), - DECL(alFilterfv), - DECL(alGetFilteri), - DECL(alGetFilteriv), - DECL(alGetFilterf), - DECL(alGetFilterfv), - DECL(alGenEffects), - DECL(alDeleteEffects), - DECL(alIsEffect), - DECL(alEffecti), - DECL(alEffectiv), - DECL(alEffectf), - DECL(alEffectfv), - DECL(alGetEffecti), - DECL(alGetEffectiv), - DECL(alGetEffectf), - DECL(alGetEffectfv), - DECL(alGenAuxiliaryEffectSlots), - DECL(alDeleteAuxiliaryEffectSlots), - DECL(alIsAuxiliaryEffectSlot), - DECL(alAuxiliaryEffectSloti), - DECL(alAuxiliaryEffectSlotiv), - DECL(alAuxiliaryEffectSlotf), - DECL(alAuxiliaryEffectSlotfv), - DECL(alGetAuxiliaryEffectSloti), - DECL(alGetAuxiliaryEffectSlotiv), - DECL(alGetAuxiliaryEffectSlotf), - DECL(alGetAuxiliaryEffectSlotfv), - - DECL(alDeferUpdatesSOFT), - DECL(alProcessUpdatesSOFT), - - DECL(alSourcedSOFT), - DECL(alSource3dSOFT), - DECL(alSourcedvSOFT), - DECL(alGetSourcedSOFT), - DECL(alGetSource3dSOFT), - DECL(alGetSourcedvSOFT), - DECL(alSourcei64SOFT), - DECL(alSource3i64SOFT), - DECL(alSourcei64vSOFT), - DECL(alGetSourcei64SOFT), - DECL(alGetSource3i64SOFT), - DECL(alGetSourcei64vSOFT), - - DECL(alGetStringiSOFT), - - DECL(alBufferStorageSOFT), - DECL(alMapBufferSOFT), - DECL(alUnmapBufferSOFT), - DECL(alFlushMappedBufferSOFT), - - DECL(alEventControlSOFT), - DECL(alEventCallbackSOFT), - DECL(alGetPointerSOFT), - DECL(alGetPointervSOFT), - - DECL(alBufferCallbackSOFT), - DECL(alGetBufferPtrSOFT), - DECL(alGetBuffer3PtrSOFT), - DECL(alGetBufferPtrvSOFT), - - DECL(alAuxiliaryEffectSlotPlaySOFT), - DECL(alAuxiliaryEffectSlotPlayvSOFT), - DECL(alAuxiliaryEffectSlotStopSOFT), - DECL(alAuxiliaryEffectSlotStopvSOFT), - - DECL(alSourcePlayAtTimeSOFT), - DECL(alSourcePlayAtTimevSOFT), - - DECL(alBufferSubDataSOFT), - - DECL(alBufferDataStatic), -#ifdef ALSOFT_EAX -}, eaxFunctions[] = { - DECL(EAXGet), - DECL(EAXSet), - DECL(EAXGetBufferMode), - DECL(EAXSetBufferMode), -#endif -}; -#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_DEVICE_CLOCK_SOFT), - DECL(ALC_DEVICE_LATENCY_SOFT), - DECL(ALC_DEVICE_CLOCK_LATENCY_SOFT), - DECL(AL_SAMPLE_OFFSET_CLOCK_SOFT), - DECL(AL_SEC_OFFSET_CLOCK_SOFT), - - DECL(ALC_OUTPUT_MODE_SOFT), - DECL(ALC_ANY_SOFT), - DECL(ALC_STEREO_BASIC_SOFT), - DECL(ALC_STEREO_UHJ_SOFT), - DECL(ALC_STEREO_HRTF_SOFT), - DECL(ALC_SURROUND_5_1_SOFT), - DECL(ALC_SURROUND_6_1_SOFT), - DECL(ALC_SURROUND_7_1_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_SAMPLE_OFFSET_LATENCY_SOFT), - DECL(AL_SEC_OFFSET_LATENCY_SOFT), - - DECL(AL_STEREO_ANGLES), - - DECL(AL_UNUSED), - DECL(AL_PENDING), - DECL(AL_PROCESSED), - - DECL(AL_NO_ERROR), - DECL(AL_INVALID_NAME), - DECL(AL_INVALID_ENUM), - DECL(AL_INVALID_VALUE), - DECL(AL_INVALID_OPERATION), - DECL(AL_OUT_OF_MEMORY), - - DECL(AL_VENDOR), - DECL(AL_VERSION), - DECL(AL_RENDERER), - DECL(AL_EXTENSIONS), - - DECL(AL_DOPPLER_FACTOR), - DECL(AL_DOPPLER_VELOCITY), - DECL(AL_DISTANCE_MODEL), - DECL(AL_SPEED_OF_SOUND), - DECL(AL_SOURCE_DISTANCE_MODEL), - DECL(AL_DEFERRED_UPDATES_SOFT), - DECL(AL_GAIN_LIMIT_SOFT), - - DECL(AL_INVERSE_DISTANCE), - DECL(AL_INVERSE_DISTANCE_CLAMPED), - DECL(AL_LINEAR_DISTANCE), - DECL(AL_LINEAR_DISTANCE_CLAMPED), - DECL(AL_EXPONENT_DISTANCE), - DECL(AL_EXPONENT_DISTANCE_CLAMPED), - - DECL(AL_FILTER_TYPE), - DECL(AL_FILTER_NULL), - DECL(AL_FILTER_LOWPASS), - DECL(AL_FILTER_HIGHPASS), - DECL(AL_FILTER_BANDPASS), - - DECL(AL_LOWPASS_GAIN), - DECL(AL_LOWPASS_GAINHF), - - DECL(AL_HIGHPASS_GAIN), - DECL(AL_HIGHPASS_GAINLF), - - DECL(AL_BANDPASS_GAIN), - DECL(AL_BANDPASS_GAINHF), - DECL(AL_BANDPASS_GAINLF), - - DECL(AL_EFFECT_TYPE), - DECL(AL_EFFECT_NULL), - DECL(AL_EFFECT_REVERB), - DECL(AL_EFFECT_EAXREVERB), - DECL(AL_EFFECT_CHORUS), - DECL(AL_EFFECT_DISTORTION), - DECL(AL_EFFECT_ECHO), - DECL(AL_EFFECT_FLANGER), - DECL(AL_EFFECT_PITCH_SHIFTER), - DECL(AL_EFFECT_FREQUENCY_SHIFTER), - DECL(AL_EFFECT_VOCAL_MORPHER), - DECL(AL_EFFECT_RING_MODULATOR), - DECL(AL_EFFECT_AUTOWAH), - DECL(AL_EFFECT_COMPRESSOR), - DECL(AL_EFFECT_EQUALIZER), - DECL(AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT), - DECL(AL_EFFECT_DEDICATED_DIALOGUE), - - DECL(AL_EFFECTSLOT_EFFECT), - DECL(AL_EFFECTSLOT_GAIN), - DECL(AL_EFFECTSLOT_AUXILIARY_SEND_AUTO), - DECL(AL_EFFECTSLOT_NULL), - - DECL(AL_EAXREVERB_DENSITY), - DECL(AL_EAXREVERB_DIFFUSION), - DECL(AL_EAXREVERB_GAIN), - DECL(AL_EAXREVERB_GAINHF), - DECL(AL_EAXREVERB_GAINLF), - DECL(AL_EAXREVERB_DECAY_TIME), - DECL(AL_EAXREVERB_DECAY_HFRATIO), - DECL(AL_EAXREVERB_DECAY_LFRATIO), - DECL(AL_EAXREVERB_REFLECTIONS_GAIN), - DECL(AL_EAXREVERB_REFLECTIONS_DELAY), - DECL(AL_EAXREVERB_REFLECTIONS_PAN), - DECL(AL_EAXREVERB_LATE_REVERB_GAIN), - DECL(AL_EAXREVERB_LATE_REVERB_DELAY), - DECL(AL_EAXREVERB_LATE_REVERB_PAN), - DECL(AL_EAXREVERB_ECHO_TIME), - DECL(AL_EAXREVERB_ECHO_DEPTH), - DECL(AL_EAXREVERB_MODULATION_TIME), - DECL(AL_EAXREVERB_MODULATION_DEPTH), - DECL(AL_EAXREVERB_AIR_ABSORPTION_GAINHF), - DECL(AL_EAXREVERB_HFREFERENCE), - DECL(AL_EAXREVERB_LFREFERENCE), - DECL(AL_EAXREVERB_ROOM_ROLLOFF_FACTOR), - DECL(AL_EAXREVERB_DECAY_HFLIMIT), - - DECL(AL_REVERB_DENSITY), - DECL(AL_REVERB_DIFFUSION), - DECL(AL_REVERB_GAIN), - DECL(AL_REVERB_GAINHF), - DECL(AL_REVERB_DECAY_TIME), - DECL(AL_REVERB_DECAY_HFRATIO), - DECL(AL_REVERB_REFLECTIONS_GAIN), - DECL(AL_REVERB_REFLECTIONS_DELAY), - DECL(AL_REVERB_LATE_REVERB_GAIN), - DECL(AL_REVERB_LATE_REVERB_DELAY), - DECL(AL_REVERB_AIR_ABSORPTION_GAINHF), - DECL(AL_REVERB_ROOM_ROLLOFF_FACTOR), - DECL(AL_REVERB_DECAY_HFLIMIT), - - DECL(AL_CHORUS_WAVEFORM), - DECL(AL_CHORUS_PHASE), - DECL(AL_CHORUS_RATE), - DECL(AL_CHORUS_DEPTH), - DECL(AL_CHORUS_FEEDBACK), - DECL(AL_CHORUS_DELAY), - - DECL(AL_DISTORTION_EDGE), - DECL(AL_DISTORTION_GAIN), - DECL(AL_DISTORTION_LOWPASS_CUTOFF), - DECL(AL_DISTORTION_EQCENTER), - DECL(AL_DISTORTION_EQBANDWIDTH), - - DECL(AL_ECHO_DELAY), - DECL(AL_ECHO_LRDELAY), - DECL(AL_ECHO_DAMPING), - DECL(AL_ECHO_FEEDBACK), - DECL(AL_ECHO_SPREAD), - - DECL(AL_FLANGER_WAVEFORM), - DECL(AL_FLANGER_PHASE), - DECL(AL_FLANGER_RATE), - DECL(AL_FLANGER_DEPTH), - DECL(AL_FLANGER_FEEDBACK), - DECL(AL_FLANGER_DELAY), - - DECL(AL_FREQUENCY_SHIFTER_FREQUENCY), - DECL(AL_FREQUENCY_SHIFTER_LEFT_DIRECTION), - DECL(AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION), - - DECL(AL_RING_MODULATOR_FREQUENCY), - DECL(AL_RING_MODULATOR_HIGHPASS_CUTOFF), - DECL(AL_RING_MODULATOR_WAVEFORM), - - DECL(AL_PITCH_SHIFTER_COARSE_TUNE), - DECL(AL_PITCH_SHIFTER_FINE_TUNE), - - DECL(AL_COMPRESSOR_ONOFF), - - DECL(AL_EQUALIZER_LOW_GAIN), - DECL(AL_EQUALIZER_LOW_CUTOFF), - DECL(AL_EQUALIZER_MID1_GAIN), - DECL(AL_EQUALIZER_MID1_CENTER), - DECL(AL_EQUALIZER_MID1_WIDTH), - DECL(AL_EQUALIZER_MID2_GAIN), - DECL(AL_EQUALIZER_MID2_CENTER), - DECL(AL_EQUALIZER_MID2_WIDTH), - DECL(AL_EQUALIZER_HIGH_GAIN), - DECL(AL_EQUALIZER_HIGH_CUTOFF), - - DECL(AL_DEDICATED_GAIN), - - DECL(AL_AUTOWAH_ATTACK_TIME), - DECL(AL_AUTOWAH_RELEASE_TIME), - DECL(AL_AUTOWAH_RESONANCE), - DECL(AL_AUTOWAH_PEAK_GAIN), - - DECL(AL_VOCAL_MORPHER_PHONEMEA), - DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), - DECL(AL_VOCAL_MORPHER_PHONEMEB), - DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), - DECL(AL_VOCAL_MORPHER_WAVEFORM), - DECL(AL_VOCAL_MORPHER_RATE), - - DECL(AL_EFFECTSLOT_TARGET_SOFT), - - DECL(AL_NUM_RESAMPLERS_SOFT), - DECL(AL_DEFAULT_RESAMPLER_SOFT), - DECL(AL_SOURCE_RESAMPLER_SOFT), - DECL(AL_RESAMPLER_NAME_SOFT), - - DECL(AL_SOURCE_SPATIALIZE_SOFT), - DECL(AL_AUTO_SOFT), - - DECL(AL_MAP_READ_BIT_SOFT), - DECL(AL_MAP_WRITE_BIT_SOFT), - DECL(AL_MAP_PERSISTENT_BIT_SOFT), - DECL(AL_PRESERVE_DATA_BIT_SOFT), - - DECL(AL_EVENT_CALLBACK_FUNCTION_SOFT), - DECL(AL_EVENT_CALLBACK_USER_PARAM_SOFT), - DECL(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT), - DECL(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT), - DECL(AL_EVENT_TYPE_DISCONNECTED_SOFT), - - DECL(AL_DROP_UNMATCHED_SOFT), - DECL(AL_REMIX_UNMATCHED_SOFT), - - DECL(AL_AMBISONIC_LAYOUT_SOFT), - DECL(AL_AMBISONIC_SCALING_SOFT), - DECL(AL_FUMA_SOFT), - DECL(AL_ACN_SOFT), - DECL(AL_SN3D_SOFT), - DECL(AL_N3D_SOFT), - - DECL(AL_BUFFER_CALLBACK_FUNCTION_SOFT), - DECL(AL_BUFFER_CALLBACK_USER_PARAM_SOFT), - - DECL(AL_UNPACK_AMBISONIC_ORDER_SOFT), - - DECL(AL_EFFECT_CONVOLUTION_REVERB_SOFT), - DECL(AL_EFFECTSLOT_STATE_SOFT), - - DECL(AL_FORMAT_UHJ2CHN8_SOFT), - DECL(AL_FORMAT_UHJ2CHN16_SOFT), - DECL(AL_FORMAT_UHJ2CHN_FLOAT32_SOFT), - DECL(AL_FORMAT_UHJ3CHN8_SOFT), - DECL(AL_FORMAT_UHJ3CHN16_SOFT), - DECL(AL_FORMAT_UHJ3CHN_FLOAT32_SOFT), - DECL(AL_FORMAT_UHJ4CHN8_SOFT), - DECL(AL_FORMAT_UHJ4CHN16_SOFT), - DECL(AL_FORMAT_UHJ4CHN_FLOAT32_SOFT), - DECL(AL_STEREO_MODE_SOFT), - DECL(AL_NORMAL_SOFT), - DECL(AL_SUPER_STEREO_SOFT), - DECL(AL_SUPER_STEREO_WIDTH_SOFT), - - DECL(AL_FORMAT_UHJ2CHN_MULAW_SOFT), - DECL(AL_FORMAT_UHJ2CHN_ALAW_SOFT), - DECL(AL_FORMAT_UHJ2CHN_IMA4_SOFT), - DECL(AL_FORMAT_UHJ2CHN_MSADPCM_SOFT), - DECL(AL_FORMAT_UHJ3CHN_MULAW_SOFT), - DECL(AL_FORMAT_UHJ3CHN_ALAW_SOFT), - DECL(AL_FORMAT_UHJ4CHN_MULAW_SOFT), - DECL(AL_FORMAT_UHJ4CHN_ALAW_SOFT), - - DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT), - -#ifdef ALSOFT_EAX -}, eaxEnumerations[] = { - DECL(AL_EAX_RAM_SIZE), - DECL(AL_EAX_RAM_FREE), - DECL(AL_STORAGE_AUTOMATIC), - DECL(AL_STORAGE_HARDWARE), - DECL(AL_STORAGE_ACCESSIBLE), -#endif // ALSOFT_EAX -}; -#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"; - +[[nodiscard]] constexpr auto GetNoErrorString() noexcept { return "No Error"; } +[[nodiscard]] constexpr auto GetInvalidDeviceString() noexcept { return "Invalid Device"; } +[[nodiscard]] constexpr auto GetInvalidContextString() noexcept { return "Invalid Context"; } +[[nodiscard]] constexpr auto GetInvalidEnumString() noexcept { return "Invalid Enum"; } +[[nodiscard]] constexpr auto GetInvalidValueString() noexcept { return "Invalid Value"; } +[[nodiscard]] constexpr auto GetOutOfMemoryString() noexcept { return "Out of Memory"; } + +[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OpenAL Soft\0"; } /************************************************ * Global variables ************************************************/ /* Enumerated device names */ -constexpr ALCchar alcDefaultName[] = "OpenAL Soft\0"; - +std::vector alcAllDevicesArray; +std::vector alcCaptureDeviceArray; std::string alcAllDevicesList; std::string alcCaptureDeviceList; @@ -970,31 +299,41 @@ constexpr uint DitherRNGSeed{22222u}; /************************************************ * ALC information ************************************************/ -constexpr ALCchar alcNoDeviceExtList[] = - "ALC_ENUMERATE_ALL_EXT " - "ALC_ENUMERATION_EXT " - "ALC_EXT_CAPTURE " - "ALC_EXT_EFX " - "ALC_EXT_thread_local_context " - "ALC_SOFT_loopback " - "ALC_SOFT_loopback_bformat " - "ALC_SOFT_reopen_device"; -constexpr ALCchar alcExtensionList[] = - "ALC_ENUMERATE_ALL_EXT " - "ALC_ENUMERATION_EXT " - "ALC_EXT_CAPTURE " - "ALC_EXT_DEDICATED " - "ALC_EXT_disconnect " - "ALC_EXT_EFX " - "ALC_EXT_thread_local_context " - "ALC_SOFT_device_clock " - "ALC_SOFT_HRTF " - "ALC_SOFT_loopback " - "ALC_SOFT_loopback_bformat " - "ALC_SOFT_output_limiter " - "ALC_SOFT_output_mode " - "ALC_SOFT_pause_device " - "ALC_SOFT_reopen_device"; +[[nodiscard]] constexpr auto GetNoDeviceExtList() noexcept -> std::string_view +{ + return "ALC_ENUMERATE_ALL_EXT " + "ALC_ENUMERATION_EXT " + "ALC_EXT_CAPTURE " + "ALC_EXT_direct_context " + "ALC_EXT_EFX " + "ALC_EXT_thread_local_context " + "ALC_SOFT_loopback " + "ALC_SOFT_loopback_bformat " + "ALC_SOFT_reopen_device " + "ALC_SOFT_system_events"sv; +} +[[nodiscard]] constexpr auto GetExtensionList() noexcept -> std::string_view +{ + return "ALC_ENUMERATE_ALL_EXT " + "ALC_ENUMERATION_EXT " + "ALC_EXT_CAPTURE " + "ALC_EXT_debug " + "ALC_EXT_DEDICATED " + "ALC_EXT_direct_context " + "ALC_EXT_disconnect " + "ALC_EXT_EFX " + "ALC_EXT_thread_local_context " + "ALC_SOFT_device_clock " + "ALC_SOFT_HRTF " + "ALC_SOFT_loopback " + "ALC_SOFT_loopback_bformat " + "ALC_SOFT_output_limiter " + "ALC_SOFT_output_mode " + "ALC_SOFT_pause_device " + "ALC_SOFT_reopen_device " + "ALC_SOFT_system_events"sv; +} + constexpr int alcMajorVersion{1}; constexpr int alcMinorVersion{1}; @@ -1008,13 +347,13 @@ using DeviceRef = al::intrusive_ptr; /************************************************ * Device lists ************************************************/ -al::vector DeviceList; -al::vector ContextList; +std::vector DeviceList; +std::vector ContextList; std::recursive_mutex ListLock; -void alc_initconfig(void) +void alc_initconfig() { if(auto loglevel = al::getenv("ALSOFT_LOGLEVEL")) { @@ -1034,7 +373,7 @@ void alc_initconfig(void) if(logf) gLogFile = logf; else { - auto u8name = wstr_to_utf8(logfile->c_str()); + auto u8name = wstr_to_utf8(*logfile); ERR("Failed to open log file '%s'\n", u8name.c_str()); } } @@ -1051,7 +390,7 @@ void alc_initconfig(void) ALSOFT_GIT_BRANCH); { std::string names; - if(al::size(BackendList) < 1) + if(std::size(BackendList) < 1) names = "(none)"; else { @@ -1069,7 +408,7 @@ void alc_initconfig(void) if(auto suspendmode = al::getenv("__ALSOFT_SUSPEND_CONTEXT")) { - if(al::strcasecmp(suspendmode->c_str(), "ignore") == 0) + if(al::case_compare(*suspendmode, "ignore"sv) == 0) { SuspendDefers = false; TRACE("Selected context suspend behavior, \"ignore\"\n"); @@ -1091,39 +430,39 @@ void alc_initconfig(void) #ifdef HAVE_NEON capfilter |= CPU_CAP_NEON; #endif - if(auto cpuopt = ConfigValueStr(nullptr, nullptr, "disable-cpu-exts")) + if(auto cpuopt = ConfigValueStr({}, {}, "disable-cpu-exts"sv)) { - const char *str{cpuopt->c_str()}; - if(al::strcasecmp(str, "all") == 0) + std::string_view cpulist{*cpuopt}; + if(al::case_compare(cpulist, "all"sv) == 0) capfilter = 0; - else + else while(!cpulist.empty()) { - const char *next = str; - do { - str = next; - while(isspace(str[0])) - str++; - next = strchr(str, ','); + auto nextpos = std::min(cpulist.find(','), cpulist.size()); + auto entry = cpulist.substr(0, nextpos); - if(!str[0] || str[0] == ',') - continue; + while(nextpos < cpulist.size() && cpulist[nextpos] == ',') + ++nextpos; + cpulist.remove_prefix(nextpos); - size_t len{next ? static_cast(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++); + while(!entry.empty() && std::isspace(entry.front())) + entry.remove_prefix(1); + while(!entry.empty() && std::isspace(entry.back())) + entry.remove_suffix(1); + if(entry.empty()) + continue; + + if(al::case_compare(entry, "sse"sv) == 0) + capfilter &= ~CPU_CAP_SSE; + else if(al::case_compare(entry, "sse2"sv) == 0) + capfilter &= ~CPU_CAP_SSE2; + else if(al::case_compare(entry, "sse3"sv) == 0) + capfilter &= ~CPU_CAP_SSE3; + else if(al::case_compare(entry, "sse4.1"sv) == 0) + capfilter &= ~CPU_CAP_SSE4_1; + else if(al::case_compare(entry, "neon"sv) == 0) + capfilter &= ~CPU_CAP_NEON; + else + WARN("Invalid CPU extension \"%.*s\"\n", al::sizei(entry), entry.data()); } } if(auto cpuopt = GetCPUInfo()) @@ -1144,64 +483,56 @@ void alc_initconfig(void) CPUCapFlags = caps & capfilter; } - if(auto priopt = ConfigValueInt(nullptr, nullptr, "rt-prio")) + if(auto priopt = ConfigValueInt({}, {}, "rt-prio"sv)) RTPrioLevel = *priopt; - if(auto limopt = ConfigValueBool(nullptr, nullptr, "rt-time-limit")) + if(auto limopt = ConfigValueBool({}, {}, "rt-time-limit"sv)) AllowRTTimeLimit = *limopt; { CompatFlagBitset compatflags{}; - auto checkflag = [](const char *envname, const char *optname) -> bool + auto checkflag = [](const char *envname, const std::string_view optname) -> bool { if(auto optval = al::getenv(envname)) { - if(al::strcasecmp(optval->c_str(), "true") == 0 - || strtol(optval->c_str(), nullptr, 0) == 1) - return true; - return false; + return al::case_compare(*optval, "true"sv) == 0 + || strtol(optval->c_str(), nullptr, 0) == 1; } - return GetConfigValueBool(nullptr, "game_compat", optname, false); + return GetConfigValueBool({}, "game_compat", optname, false); }; - sBufferSubDataCompat = checkflag("__ALSOFT_ENABLE_SUB_DATA_EXT", "enable-sub-data-ext"); - compatflags.set(CompatFlags::ReverseX, checkflag("__ALSOFT_REVERSE_X", "reverse-x")); - compatflags.set(CompatFlags::ReverseY, checkflag("__ALSOFT_REVERSE_Y", "reverse-y")); - compatflags.set(CompatFlags::ReverseZ, checkflag("__ALSOFT_REVERSE_Z", "reverse-z")); + sBufferSubDataCompat = checkflag("__ALSOFT_ENABLE_SUB_DATA_EXT", "enable-sub-data-ext"sv); + compatflags.set(CompatFlags::ReverseX, checkflag("__ALSOFT_REVERSE_X", "reverse-x"sv)); + compatflags.set(CompatFlags::ReverseY, checkflag("__ALSOFT_REVERSE_Y", "reverse-y"sv)); + compatflags.set(CompatFlags::ReverseZ, checkflag("__ALSOFT_REVERSE_Z", "reverse-z"sv)); - aluInit(compatflags, ConfigValueFloat(nullptr, "game_compat", "nfc-scale").value_or(1.0f)); + aluInit(compatflags, ConfigValueFloat({}, "game_compat"sv, "nfc-scale"sv).value_or(1.0f)); } - Voice::InitMixer(ConfigValueStr(nullptr, nullptr, "resampler")); + Voice::InitMixer(ConfigValueStr({}, {}, "resampler"sv)); - auto uhjfiltopt = ConfigValueStr(nullptr, "uhj", "decode-filter"); - if(!uhjfiltopt) + if(auto uhjfiltopt = ConfigValueStr({}, "uhj"sv, "decode-filter"sv)) { - if((uhjfiltopt = ConfigValueStr(nullptr, "uhj", "filter"))) - WARN("uhj/filter is deprecated, please use uhj/decode-filter\n"); - } - if(uhjfiltopt) - { - if(al::strcasecmp(uhjfiltopt->c_str(), "fir256") == 0) + if(al::case_compare(*uhjfiltopt, "fir256"sv) == 0) UhjDecodeQuality = UhjQualityType::FIR256; - else if(al::strcasecmp(uhjfiltopt->c_str(), "fir512") == 0) + else if(al::case_compare(*uhjfiltopt, "fir512"sv) == 0) UhjDecodeQuality = UhjQualityType::FIR512; - else if(al::strcasecmp(uhjfiltopt->c_str(), "iir") == 0) + else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) UhjDecodeQuality = UhjQualityType::IIR; else WARN("Unsupported uhj/decode-filter: %s\n", uhjfiltopt->c_str()); } - if((uhjfiltopt = ConfigValueStr(nullptr, "uhj", "encode-filter"))) + if(auto uhjfiltopt = ConfigValueStr({}, "uhj"sv, "encode-filter"sv)) { - if(al::strcasecmp(uhjfiltopt->c_str(), "fir256") == 0) + if(al::case_compare(*uhjfiltopt, "fir256"sv) == 0) UhjEncodeQuality = UhjQualityType::FIR256; - else if(al::strcasecmp(uhjfiltopt->c_str(), "fir512") == 0) + else if(al::case_compare(*uhjfiltopt, "fir512"sv) == 0) UhjEncodeQuality = UhjQualityType::FIR512; - else if(al::strcasecmp(uhjfiltopt->c_str(), "iir") == 0) + else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) UhjEncodeQuality = UhjQualityType::IIR; else WARN("Unsupported uhj/encode-filter: %s\n", uhjfiltopt->c_str()); } - auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); - if(traperr && (al::strcasecmp(traperr->c_str(), "true") == 0 + if(auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); traperr + && (al::case_compare(*traperr, "true"sv) == 0 || std::strtol(traperr->c_str(), nullptr, 0) == 1)) { TrapALError = true; @@ -1211,66 +542,69 @@ void alc_initconfig(void) { traperr = al::getenv("ALSOFT_TRAP_AL_ERROR"); if(traperr) - TrapALError = al::strcasecmp(traperr->c_str(), "true") == 0 + TrapALError = al::case_compare(*traperr, "true"sv) == 0 || strtol(traperr->c_str(), nullptr, 0) == 1; else - TrapALError = !!GetConfigValueBool(nullptr, nullptr, "trap-al-error", false); + TrapALError = GetConfigValueBool({}, {}, "trap-al-error"sv, false); traperr = al::getenv("ALSOFT_TRAP_ALC_ERROR"); if(traperr) - TrapALCError = al::strcasecmp(traperr->c_str(), "true") == 0 + TrapALCError = al::case_compare(*traperr, "true"sv) == 0 || strtol(traperr->c_str(), nullptr, 0) == 1; else - TrapALCError = !!GetConfigValueBool(nullptr, nullptr, "trap-alc-error", false); + TrapALCError = GetConfigValueBool({}, {}, "trap-alc-error"sv, false); } - if(auto boostopt = ConfigValueFloat(nullptr, "reverb", "boost")) + if(auto boostopt = ConfigValueFloat({}, "reverb"sv, "boost"sv)) { - const float valf{std::isfinite(*boostopt) ? clampf(*boostopt, -24.0f, 24.0f) : 0.0f}; + const float valf{std::isfinite(*boostopt) ? std::clamp(*boostopt, -24.0f, 24.0f) : 0.0f}; ReverbBoost *= std::pow(10.0f, valf / 20.0f); } - auto BackendListEnd = std::end(BackendList); + auto BackendListEnd = BackendList.end(); auto devopt = al::getenv("ALSOFT_DRIVERS"); - if(devopt || (devopt=ConfigValueStr(nullptr, nullptr, "drivers"))) + if(!devopt) devopt = ConfigValueStr({}, {}, "drivers"sv); + if(devopt) { - auto backendlist_cur = std::begin(BackendList); + auto backendlist_cur = BackendList.begin(); bool endlist{true}; - const char *next{devopt->c_str()}; - do { - const char *devs{next}; - while(isspace(devs[0])) - devs++; - next = strchr(devs, ','); + std::string_view drvlist{*devopt}; + while(!drvlist.empty()) + { + auto nextpos = std::min(drvlist.find(','), drvlist.size()); + auto entry = drvlist.substr(0, nextpos); - const bool delitem{devs[0] == '-'}; - if(devs[0] == '-') devs++; - - if(!devs[0] || devs[0] == ',') + endlist = true; + if(nextpos < drvlist.size()) { endlist = false; - continue; + while(nextpos < drvlist.size() && drvlist[nextpos] == ',') + ++nextpos; } - endlist = true; + drvlist.remove_prefix(nextpos); + + while(!entry.empty() && std::isspace(entry.front())) + entry.remove_prefix(1); + const bool delitem{!entry.empty() && entry.front() == '-'}; + if(delitem) entry.remove_prefix(1); + + while(!entry.empty() && std::isspace(entry.back())) + entry.remove_suffix(1); + if(entry.empty()) + continue; - size_t len{next ? (static_cast(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; - } + if(entry == "mmdevapi"sv) + entry = "wasapi"sv; #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); + auto find_backend = [entry](const BackendInfo &backend) -> bool + { return entry == backend.name; }; + auto this_backend = std::find_if(BackendList.begin(), BackendListEnd, find_backend); if(this_backend == BackendListEnd) continue; @@ -1279,7 +613,7 @@ void alc_initconfig(void) 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; @@ -1309,7 +643,7 @@ void alc_initconfig(void) TRACE("Added \"%s\" for capture\n", backend.name); } }; - std::for_each(std::begin(BackendList), BackendListEnd, init_backend); + std::for_each(BackendList.begin(), BackendListEnd, init_backend); LoopbackBackendFactory::getFactory().init(); @@ -1318,36 +652,32 @@ void alc_initconfig(void) if(!CaptureFactory) WARN("No capture backend available!\n"); - if(auto exclopt = ConfigValueStr(nullptr, nullptr, "excludefx")) + if(auto exclopt = ConfigValueStr({}, {}, "excludefx"sv)) { - const char *next{exclopt->c_str()}; - do { - const char *str{next}; - next = strchr(str, ','); + std::string_view exclude{*exclopt}; + while(!exclude.empty()) + { + const auto nextpos = exclude.find(','); + const auto entry = exclude.substr(0, nextpos); + exclude.remove_prefix((nextpos < exclude.size()) ? nextpos+1 : exclude.size()); - if(!str[0] || next == str) - continue; - - size_t len{next ? static_cast(next-str) : strlen(str)}; - for(const EffectList &effectitem : gEffectList) - { - if(len == strlen(effectitem.name) && - strncmp(effectitem.name, str, len) == 0) - DisabledEffects[effectitem.type] = true; - } - } while(next++); + std::for_each(gEffectList.cbegin(), gEffectList.cend(), + [entry](const EffectList &effectitem) noexcept + { + if(entry == std::data(effectitem.name)) + DisabledEffects.set(effectitem.type); + }); + } } InitEffect(&ALCcontext::sDefaultEffect); auto defrevopt = al::getenv("ALSOFT_DEFAULT_REVERB"); - if(defrevopt || (defrevopt=ConfigValueStr(nullptr, nullptr, "default-reverb"))) - LoadReverbPreset(defrevopt->c_str(), &ALCcontext::sDefaultEffect); + if(!defrevopt) defrevopt = ConfigValueStr({}, {}, "default-reverb"sv); + if(defrevopt) LoadReverbPreset(*defrevopt, &ALCcontext::sDefaultEffect); #ifdef ALSOFT_EAX { - static constexpr char eax_block_name[] = "eax"; - - if(const auto eax_enable_opt = ConfigValueBool(nullptr, eax_block_name, "enable")) + if(const auto eax_enable_opt = ConfigValueBool({}, "eax", "enable")) { eax_g_is_enabled = *eax_enable_opt; if(!eax_g_is_enabled) @@ -1356,15 +686,15 @@ void alc_initconfig(void) else eax_g_is_enabled = true; - if((DisabledEffects[EAXREVERB_EFFECT] || DisabledEffects[CHORUS_EFFECT]) + if((DisabledEffects.test(EAXREVERB_EFFECT) || DisabledEffects.test(CHORUS_EFFECT)) && eax_g_is_enabled) { eax_g_is_enabled = false; TRACE("EAX disabled because %s disabled.\n", - (DisabledEffects[EAXREVERB_EFFECT] && DisabledEffects[CHORUS_EFFECT]) + (DisabledEffects.test(EAXREVERB_EFFECT) && DisabledEffects.test(CHORUS_EFFECT)) ? "EAXReverb and Chorus are" : - DisabledEffects[EAXREVERB_EFFECT] ? "EAXReverb is" : - DisabledEffects[CHORUS_EFFECT] ? "Chorus is" : ""); + DisabledEffects.test(EAXREVERB_EFFECT) ? "EAXReverb is" : + DisabledEffects.test(CHORUS_EFFECT) ? "Chorus is" : ""); } } #endif // ALSOFT_EAX @@ -1380,75 +710,111 @@ void ProbeAllDevicesList() { InitConfig(); - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; if(!PlaybackFactory) + { + decltype(alcAllDevicesArray){}.swap(alcAllDevicesArray); decltype(alcAllDevicesList){}.swap(alcAllDevicesList); + } else { - std::string names{PlaybackFactory->probe(BackendType::Playback)}; - if(names.empty()) names += '\0'; - names.swap(alcAllDevicesList); + alcAllDevicesArray = PlaybackFactory->enumerate(BackendType::Playback); + decltype(alcAllDevicesList){}.swap(alcAllDevicesList); + if(alcAllDevicesArray.empty()) + alcAllDevicesList += '\0'; + else for(auto &devname : alcAllDevicesArray) + alcAllDevicesList.append(devname) += '\0'; } } void ProbeCaptureDeviceList() { InitConfig(); - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; if(!CaptureFactory) + { + decltype(alcCaptureDeviceArray){}.swap(alcCaptureDeviceArray); decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); + } else { - std::string names{CaptureFactory->probe(BackendType::Capture)}; - if(names.empty()) names += '\0'; - names.swap(alcCaptureDeviceList); + alcCaptureDeviceArray = CaptureFactory->enumerate(BackendType::Capture); + decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); + if(alcCaptureDeviceArray.empty()) + alcCaptureDeviceList += '\0'; + else for(auto &devname : alcCaptureDeviceArray) + alcCaptureDeviceList.append(devname) += '\0'; } } -struct DevFmtPair { DevFmtChannels chans; DevFmtType type; }; -al::optional DecomposeDevFormat(ALenum format) +al::span SpanFromAttributeList(const ALCint *attribs) noexcept { - static const struct { + al::span attrSpan; + if(attribs) + { + const ALCint *attrEnd{attribs}; + while(*attrEnd != 0) + attrEnd += 2; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + attrSpan = {attribs, attrEnd}; + } + return attrSpan; +} + +struct DevFmtPair { DevFmtChannels chans; DevFmtType type; }; +std::optional DecomposeDevFormat(ALenum format) +{ + struct FormatType { ALenum format; DevFmtChannels channels; DevFmtType type; - } list[] = { - { AL_FORMAT_MONO8, DevFmtMono, DevFmtUByte }, - { AL_FORMAT_MONO16, DevFmtMono, DevFmtShort }, - { AL_FORMAT_MONO_FLOAT32, DevFmtMono, DevFmtFloat }, + }; + static constexpr std::array list{ + FormatType{AL_FORMAT_MONO8, DevFmtMono, DevFmtUByte}, + FormatType{AL_FORMAT_MONO16, DevFmtMono, DevFmtShort}, + FormatType{AL_FORMAT_MONO_I32, DevFmtMono, DevFmtInt}, + FormatType{AL_FORMAT_MONO_FLOAT32, DevFmtMono, DevFmtFloat}, - { AL_FORMAT_STEREO8, DevFmtStereo, DevFmtUByte }, - { AL_FORMAT_STEREO16, DevFmtStereo, DevFmtShort }, - { AL_FORMAT_STEREO_FLOAT32, DevFmtStereo, DevFmtFloat }, + FormatType{AL_FORMAT_STEREO8, DevFmtStereo, DevFmtUByte}, + FormatType{AL_FORMAT_STEREO16, DevFmtStereo, DevFmtShort}, + FormatType{AL_FORMAT_STEREO_I32, DevFmtStereo, DevFmtInt}, + FormatType{AL_FORMAT_STEREO_FLOAT32, DevFmtStereo, DevFmtFloat}, - { AL_FORMAT_QUAD8, DevFmtQuad, DevFmtUByte }, - { AL_FORMAT_QUAD16, DevFmtQuad, DevFmtShort }, - { AL_FORMAT_QUAD32, DevFmtQuad, DevFmtFloat }, + FormatType{AL_FORMAT_QUAD8, DevFmtQuad, DevFmtUByte}, + FormatType{AL_FORMAT_QUAD16, DevFmtQuad, DevFmtShort}, + FormatType{AL_FORMAT_QUAD32, DevFmtQuad, DevFmtFloat}, + FormatType{AL_FORMAT_QUAD_I32, DevFmtQuad, DevFmtInt}, + FormatType{AL_FORMAT_QUAD_FLOAT32, DevFmtQuad, DevFmtFloat}, - { AL_FORMAT_51CHN8, DevFmtX51, DevFmtUByte }, - { AL_FORMAT_51CHN16, DevFmtX51, DevFmtShort }, - { AL_FORMAT_51CHN32, DevFmtX51, DevFmtFloat }, + FormatType{AL_FORMAT_51CHN8, DevFmtX51, DevFmtUByte}, + FormatType{AL_FORMAT_51CHN16, DevFmtX51, DevFmtShort}, + FormatType{AL_FORMAT_51CHN32, DevFmtX51, DevFmtFloat}, + FormatType{AL_FORMAT_51CHN_I32, DevFmtX51, DevFmtInt}, + FormatType{AL_FORMAT_51CHN_FLOAT32, DevFmtX51, DevFmtFloat}, - { AL_FORMAT_61CHN8, DevFmtX61, DevFmtUByte }, - { AL_FORMAT_61CHN16, DevFmtX61, DevFmtShort }, - { AL_FORMAT_61CHN32, DevFmtX61, DevFmtFloat }, + FormatType{AL_FORMAT_61CHN8, DevFmtX61, DevFmtUByte}, + FormatType{AL_FORMAT_61CHN16, DevFmtX61, DevFmtShort}, + FormatType{AL_FORMAT_61CHN32, DevFmtX61, DevFmtFloat}, + FormatType{AL_FORMAT_61CHN_I32, DevFmtX61, DevFmtInt}, + FormatType{AL_FORMAT_61CHN_FLOAT32, DevFmtX61, DevFmtFloat}, - { AL_FORMAT_71CHN8, DevFmtX71, DevFmtUByte }, - { AL_FORMAT_71CHN16, DevFmtX71, DevFmtShort }, - { AL_FORMAT_71CHN32, DevFmtX71, DevFmtFloat }, + FormatType{AL_FORMAT_71CHN8, DevFmtX71, DevFmtUByte}, + FormatType{AL_FORMAT_71CHN16, DevFmtX71, DevFmtShort}, + FormatType{AL_FORMAT_71CHN32, DevFmtX71, DevFmtFloat}, + FormatType{AL_FORMAT_71CHN_I32, DevFmtX71, DevFmtInt}, + FormatType{AL_FORMAT_71CHN_FLOAT32, DevFmtX71, DevFmtFloat}, }; for(const auto &item : list) { if(item.format == format) - return al::make_optional({item.channels, item.type}); + return DevFmtPair{item.channels, item.type}; } - return al::nullopt; + return std::nullopt; } -al::optional DevFmtTypeFromEnum(ALCenum type) +std::optional DevFmtTypeFromEnum(ALCenum type) { switch(type) { @@ -1461,7 +827,7 @@ al::optional DevFmtTypeFromEnum(ALCenum type) case ALC_FLOAT_SOFT: return DevFmtFloat; } WARN("Unsupported format type: 0x%04x\n", type); - return al::nullopt; + return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtType type) { @@ -1478,7 +844,7 @@ ALCenum EnumFromDevFmt(DevFmtType type) throw std::runtime_error{"Invalid DevFmtType: "+std::to_string(int(type))}; } -al::optional DevFmtChannelsFromEnum(ALCenum channels) +std::optional DevFmtChannelsFromEnum(ALCenum channels) { switch(channels) { @@ -1491,7 +857,7 @@ al::optional DevFmtChannelsFromEnum(ALCenum channels) case ALC_BFORMAT3D_SOFT: return DevFmtAmbi3D; } WARN("Unsupported format channels: 0x%04x\n", channels); - return al::nullopt; + return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtChannels channels) { @@ -1506,12 +872,13 @@ ALCenum EnumFromDevFmt(DevFmtChannels channels) case DevFmtAmbi3D: return ALC_BFORMAT3D_SOFT; /* FIXME: Shouldn't happen. */ case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: break; } throw std::runtime_error{"Invalid DevFmtChannels: "+std::to_string(int(channels))}; } -al::optional DevAmbiLayoutFromEnum(ALCenum layout) +std::optional DevAmbiLayoutFromEnum(ALCenum layout) { switch(layout) { @@ -1519,7 +886,7 @@ al::optional DevAmbiLayoutFromEnum(ALCenum layout) case ALC_ACN_SOFT: return DevAmbiLayout::ACN; } WARN("Unsupported ambisonic layout: 0x%04x\n", layout); - return al::nullopt; + return std::nullopt; } ALCenum EnumFromDevAmbi(DevAmbiLayout layout) { @@ -1531,7 +898,7 @@ ALCenum EnumFromDevAmbi(DevAmbiLayout layout) throw std::runtime_error{"Invalid DevAmbiLayout: "+std::to_string(int(layout))}; } -al::optional DevAmbiScalingFromEnum(ALCenum scaling) +std::optional DevAmbiScalingFromEnum(ALCenum scaling) { switch(scaling) { @@ -1540,7 +907,7 @@ al::optional DevAmbiScalingFromEnum(ALCenum scaling) case ALC_N3D_SOFT: return DevAmbiScaling::N3D; } WARN("Unsupported ambisonic scaling: 0x%04x\n", scaling); - return al::nullopt; + return std::nullopt; } ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) { @@ -1554,95 +921,60 @@ ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) } -/* Downmixing channel arrays, to map the given format's missing channels to - * existing ones. Based on Wine's DSound downmix values, which are based on - * PulseAudio's. +/* Downmixing channel arrays, to map a device format's missing channels to + * existing ones. Based on what PipeWire does, though simplified. */ -constexpr std::array FrontStereoSplit{{ - {FrontLeft, 0.5f}, {FrontRight, 0.5f} -}}; -constexpr std::array FrontLeft9{{ - {FrontLeft, 1.0f/9.0f} -}}; -constexpr std::array FrontRight9{{ - {FrontRight, 1.0f/9.0f} -}}; -constexpr std::array BackMonoToFrontSplit{{ - {FrontLeft, 0.5f/9.0f}, {FrontRight, 0.5f/9.0f} -}}; -constexpr std::array LeftStereoSplit{{ - {FrontLeft, 0.5f}, {BackLeft, 0.5f} -}}; -constexpr std::array RightStereoSplit{{ - {FrontRight, 0.5f}, {BackRight, 0.5f} -}}; -constexpr std::array BackStereoSplit{{ - {BackLeft, 0.5f}, {BackRight, 0.5f} -}}; -constexpr std::array SideStereoSplit{{ - {SideLeft, 0.5f}, {SideRight, 0.5f} -}}; -constexpr std::array ToSideLeft{{ - {SideLeft, 1.0f} -}}; -constexpr std::array ToSideRight{{ - {SideRight, 1.0f} -}}; -constexpr std::array BackLeftSplit{{ - {SideLeft, 0.5f}, {BackCenter, 0.5f} -}}; -constexpr std::array BackRightSplit{{ - {SideRight, 0.5f}, {BackCenter, 0.5f} -}}; +constexpr float inv_sqrt2f{static_cast(1.0 / al::numbers::sqrt2)}; +constexpr std::array FrontStereo3dB{ + InputRemixMap::TargetMix{FrontLeft, inv_sqrt2f}, + InputRemixMap::TargetMix{FrontRight, inv_sqrt2f} +}; +constexpr std::array FrontStereo6dB{ + InputRemixMap::TargetMix{FrontLeft, 0.5f}, + InputRemixMap::TargetMix{FrontRight, 0.5f} +}; +constexpr std::array SideStereo3dB{ + InputRemixMap::TargetMix{SideLeft, inv_sqrt2f}, + InputRemixMap::TargetMix{SideRight, inv_sqrt2f} +}; +constexpr std::array BackStereo3dB{ + InputRemixMap::TargetMix{BackLeft, inv_sqrt2f}, + InputRemixMap::TargetMix{BackRight, inv_sqrt2f} +}; +constexpr std::array FrontLeft3dB{InputRemixMap::TargetMix{FrontLeft, inv_sqrt2f}}; +constexpr std::array FrontRight3dB{InputRemixMap::TargetMix{FrontRight, inv_sqrt2f}}; +constexpr std::array SideLeft0dB{InputRemixMap::TargetMix{SideLeft, 1.0f}}; +constexpr std::array SideRight0dB{InputRemixMap::TargetMix{SideRight, 1.0f}}; +constexpr std::array BackLeft0dB{InputRemixMap::TargetMix{BackLeft, 1.0f}}; +constexpr std::array BackRight0dB{InputRemixMap::TargetMix{BackRight, 1.0f}}; +constexpr std::array BackCenter3dB{InputRemixMap::TargetMix{BackCenter, inv_sqrt2f}}; -const std::array StereoDownmix{{ - { FrontCenter, FrontStereoSplit }, - { SideLeft, FrontLeft9 }, - { SideRight, FrontRight9 }, - { BackLeft, FrontLeft9 }, - { BackRight, FrontRight9 }, - { BackCenter, BackMonoToFrontSplit }, -}}; -const std::array QuadDownmix{{ - { FrontCenter, FrontStereoSplit }, - { SideLeft, LeftStereoSplit }, - { SideRight, RightStereoSplit }, - { BackCenter, BackStereoSplit }, -}}; -const std::array X51Downmix{{ - { BackLeft, ToSideLeft }, - { BackRight, ToSideRight }, - { BackCenter, SideStereoSplit }, -}}; -const std::array X61Downmix{{ - { BackLeft, BackLeftSplit }, - { BackRight, BackRightSplit }, -}}; -const std::array X71Downmix{{ - { BackCenter, BackStereoSplit }, -}}; - - -/** Stores the latest ALC device error. */ -void alcSetError(ALCdevice *device, ALCenum errorCode) -{ - WARN("Error generated on device %p, code 0x%04x\n", voidp{device}, errorCode); - if(TrapALCError) - { -#ifdef _WIN32 - /* DebugBreak() will cause an exception if there is no debugger */ - if(IsDebuggerPresent()) - DebugBreak(); -#elif defined(SIGTRAP) - raise(SIGTRAP); -#endif - } - - if(device) - device->LastError.store(errorCode); - else - LastNullDeviceError.store(errorCode); -} +constexpr std::array StereoDownmix{ + InputRemixMap{FrontCenter, FrontStereo3dB}, + InputRemixMap{SideLeft, FrontLeft3dB}, + InputRemixMap{SideRight, FrontRight3dB}, + InputRemixMap{BackLeft, FrontLeft3dB}, + InputRemixMap{BackRight, FrontRight3dB}, + InputRemixMap{BackCenter, FrontStereo6dB}, +}; +constexpr std::array QuadDownmix{ + InputRemixMap{FrontCenter, FrontStereo3dB}, + InputRemixMap{SideLeft, BackLeft0dB}, + InputRemixMap{SideRight, BackRight0dB}, + InputRemixMap{BackCenter, BackStereo3dB}, +}; +constexpr std::array X51Downmix{ + InputRemixMap{BackLeft, SideLeft0dB}, + InputRemixMap{BackRight, SideRight0dB}, + InputRemixMap{BackCenter, SideStereo3dB}, +}; +constexpr std::array X61Downmix{ + InputRemixMap{BackLeft, BackCenter3dB}, + InputRemixMap{BackRight, BackCenter3dB}, +}; +constexpr std::array X71Downmix{ + InputRemixMap{BackCenter, BackStereo3dB}, +}; std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const float threshold) @@ -1674,19 +1006,23 @@ std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const f */ inline void UpdateClockBase(ALCdevice *device) { - IncrementRef(device->MixCount); - device->ClockBase += nanoseconds{seconds{device->SamplesDone}} / device->Frequency; - device->SamplesDone = 0; - IncrementRef(device->MixCount); + const auto mixLock = device->getWriteMixLock(); + + auto samplesDone = device->mSamplesDone.load(std::memory_order_relaxed); + auto clockBase = device->mClockBase.load(std::memory_order_relaxed); + + clockBase += nanoseconds{seconds{samplesDone}} / device->Frequency; + device->mClockBase.store(clockBase, std::memory_order_relaxed); + device->mSamplesDone.store(0, std::memory_order_relaxed); } /** * Updates device parameters according to the attribute list (caller is * responsible for holding the list lock). */ -ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) +ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList) { - if((!attrList || !attrList[0]) && device->Type == DeviceType::Loopback) + if(attrList.empty() && device->Type == DeviceType::Loopback) { WARN("Missing attributes for loopback device\n"); return ALC_INVALID_VALUE; @@ -1695,15 +1031,15 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) uint numMono{device->NumMonoSources}; uint numStereo{device->NumStereoSources}; uint numSends{device->NumAuxSends}; - al::optional stereomode; - al::optional optlimit; - al::optional optsrate; - al::optional optchans; - al::optional opttype; - al::optional optlayout; - al::optional optscale; - uint period_size{DEFAULT_UPDATE_SIZE}; - uint buffer_size{DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES}; + std::optional stereomode; + std::optional optlimit; + std::optional optsrate; + std::optional optchans; + std::optional opttype; + std::optional optlayout; + std::optional optscale; + uint period_size{DefaultUpdateSize}; + uint buffer_size{DefaultUpdateSize * DefaultNumUpdates}; int hrtf_id{-1}; uint aorder{0u}; @@ -1711,71 +1047,74 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) { /* Get default settings from the user configuration */ - if(auto freqopt = device->configValue(nullptr, "frequency")) + if(auto freqopt = device->configValue({}, "frequency")) { - optsrate = clampu(*freqopt, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE); + optsrate = std::clamp(*freqopt, MinOutputRate, MaxOutputRate); - const double scale{static_cast(*optsrate) / DEFAULT_OUTPUT_RATE}; - period_size = static_cast(period_size*scale + 0.5); + const double scale{static_cast(*optsrate) / double{DefaultOutputRate}}; + period_size = static_cast(std::lround(period_size * scale)); } - if(auto persizeopt = device->configValue(nullptr, "period_size")) - period_size = clampu(*persizeopt, 64, 8192); - if(auto numperopt = device->configValue(nullptr, "periods")) - buffer_size = clampu(*numperopt, 2, 16) * period_size; + if(auto persizeopt = device->configValue({}, "period_size")) + period_size = std::clamp(*persizeopt, 64u, 8192u); + if(auto numperopt = device->configValue({}, "periods")) + buffer_size = std::clamp(*numperopt, 2u, 16u) * period_size; else - buffer_size = period_size * DEFAULT_NUM_UPDATES; + buffer_size = period_size * uint{DefaultNumUpdates}; - if(auto typeopt = device->configValue(nullptr, "sample-type")) + if(auto typeopt = device->configValue({}, "sample-type")) { - static constexpr struct TypeMap { - const char name[8]; + struct TypeMap { + std::string_view name; DevFmtType type; - } typelist[] = { - { "int8", DevFmtByte }, - { "uint8", DevFmtUByte }, - { "int16", DevFmtShort }, - { "uint16", DevFmtUShort }, - { "int32", DevFmtInt }, - { "uint32", DevFmtUInt }, - { "float32", DevFmtFloat }, + }; + constexpr std::array typelist{ + TypeMap{"int8"sv, DevFmtByte }, + TypeMap{"uint8"sv, DevFmtUByte }, + TypeMap{"int16"sv, DevFmtShort }, + TypeMap{"uint16"sv, DevFmtUShort}, + TypeMap{"int32"sv, DevFmtInt }, + TypeMap{"uint32"sv, DevFmtUInt }, + TypeMap{"float32"sv, 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)) + auto iter = std::find_if(typelist.begin(), typelist.end(), + [svfmt=std::string_view{fmt}](const TypeMap &entry) -> bool + { return al::case_compare(entry.name, svfmt) == 0; }); + if(iter == typelist.end()) ERR("Unsupported sample-type: %s\n", fmt); else opttype = iter->type; } - if(auto chanopt = device->configValue(nullptr, "channels")) + if(auto chanopt = device->configValue({}, "channels")) { - static constexpr struct ChannelMap { - const char name[16]; + struct ChannelMap { + std::string_view name; DevFmtChannels chans; uint8_t order; - } chanlist[] = { - { "mono", DevFmtMono, 0 }, - { "stereo", DevFmtStereo, 0 }, - { "quad", DevFmtQuad, 0 }, - { "surround51", DevFmtX51, 0 }, - { "surround61", DevFmtX61, 0 }, - { "surround71", DevFmtX71, 0 }, - { "surround714", DevFmtX714, 0 }, - { "surround3d71", DevFmtX3D71, 0 }, - { "surround51rear", DevFmtX51, 0 }, - { "ambi1", DevFmtAmbi3D, 1 }, - { "ambi2", DevFmtAmbi3D, 2 }, - { "ambi3", DevFmtAmbi3D, 3 }, + }; + constexpr std::array chanlist{ + ChannelMap{"mono"sv, DevFmtMono, 0}, + ChannelMap{"stereo"sv, DevFmtStereo, 0}, + ChannelMap{"quad"sv, DevFmtQuad, 0}, + ChannelMap{"surround51"sv, DevFmtX51, 0}, + ChannelMap{"surround61"sv, DevFmtX61, 0}, + ChannelMap{"surround71"sv, DevFmtX71, 0}, + ChannelMap{"surround714"sv, DevFmtX714, 0}, + ChannelMap{"surround7144"sv, DevFmtX7144, 0}, + ChannelMap{"surround3d71"sv, DevFmtX3D71, 0}, + ChannelMap{"surround51rear"sv, DevFmtX51, 0}, + ChannelMap{"ambi1"sv, DevFmtAmbi3D, 1}, + ChannelMap{"ambi2"sv, DevFmtAmbi3D, 2}, + ChannelMap{"ambi3"sv, 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)) + auto iter = std::find_if(chanlist.begin(), chanlist.end(), + [svfmt=std::string_view{fmt}](const ChannelMap &entry) -> bool + { return al::case_compare(entry.name, svfmt) == 0; }); + if(iter == chanlist.end()) ERR("Unsupported channels: %s\n", fmt); else { @@ -1783,73 +1122,70 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) aorder = iter->order; } } - if(auto ambiopt = device->configValue(nullptr, "ambi-format")) + if(auto ambiopt = device->configValue({}, "ambi-format"sv)) { - const ALCchar *fmt{ambiopt->c_str()}; - if(al::strcasecmp(fmt, "fuma") == 0) + if(al::case_compare(*ambiopt, "fuma"sv) == 0) { optlayout = DevAmbiLayout::FuMa; optscale = DevAmbiScaling::FuMa; } - else if(al::strcasecmp(fmt, "acn+fuma") == 0) + else if(al::case_compare(*ambiopt, "acn+fuma"sv) == 0) { optlayout = DevAmbiLayout::ACN; optscale = DevAmbiScaling::FuMa; } - else if(al::strcasecmp(fmt, "ambix") == 0 || al::strcasecmp(fmt, "acn+sn3d") == 0) + else if(al::case_compare(*ambiopt, "ambix"sv) == 0 + || al::case_compare(*ambiopt, "acn+sn3d"sv) == 0) { optlayout = DevAmbiLayout::ACN; optscale = DevAmbiScaling::SN3D; } - else if(al::strcasecmp(fmt, "acn+n3d") == 0) + else if(al::case_compare(*ambiopt, "acn+n3d"sv) == 0) { optlayout = DevAmbiLayout::ACN; optscale = DevAmbiScaling::N3D; } else - ERR("Unsupported ambi-format: %s\n", fmt); + ERR("Unsupported ambi-format: %s\n", ambiopt->c_str()); } - if(auto hrtfopt = device->configValue(nullptr, "hrtf")) + if(auto hrtfopt = device->configValue({}, "hrtf"sv)) { WARN("general/hrtf is deprecated, please use stereo-encoding instead\n"); - const char *hrtf{hrtfopt->c_str()}; - if(al::strcasecmp(hrtf, "true") == 0) + if(al::case_compare(*hrtfopt, "true"sv) == 0) stereomode = StereoEncoding::Hrtf; - else if(al::strcasecmp(hrtf, "false") == 0) + else if(al::case_compare(*hrtfopt, "false"sv) == 0) { if(!stereomode || *stereomode == StereoEncoding::Hrtf) stereomode = StereoEncoding::Default; } - else if(al::strcasecmp(hrtf, "auto") != 0) - ERR("Unexpected hrtf value: %s\n", hrtf); + else if(al::case_compare(*hrtfopt, "auto"sv) != 0) + ERR("Unexpected hrtf value: %s\n", hrtfopt->c_str()); } } - if(auto encopt = device->configValue(nullptr, "stereo-encoding")) + if(auto encopt = device->configValue({}, "stereo-encoding"sv)) { - const char *mode{encopt->c_str()}; - if(al::strcasecmp(mode, "basic") == 0 || al::strcasecmp(mode, "panpot") == 0) + if(al::case_compare(*encopt, "basic"sv) == 0 || al::case_compare(*encopt, "panpot"sv) == 0) stereomode = StereoEncoding::Basic; - else if(al::strcasecmp(mode, "uhj") == 0) + else if(al::case_compare(*encopt, "uhj") == 0) stereomode = StereoEncoding::Uhj; - else if(al::strcasecmp(mode, "hrtf") == 0) + else if(al::case_compare(*encopt, "hrtf") == 0) stereomode = StereoEncoding::Hrtf; else - ERR("Unexpected stereo-encoding: %s\n", mode); + ERR("Unexpected stereo-encoding: %s\n", encopt->c_str()); } // Check for app-specified attributes - if(attrList && attrList[0]) + if(!attrList.empty()) { ALenum outmode{ALC_ANY_SOFT}; - al::optional opthrtf; + std::optional opthrtf; int freqAttr{}; #define ATTRIBUTE(a) a: TRACE("%s = %d\n", #a, attrList[attrIdx + 1]); - size_t attrIdx{0}; - while(attrList[attrIdx]) + for(size_t attrIdx{0};attrIdx < attrList.size();attrIdx+=2) { switch(attrList[attrIdx]) { @@ -1894,8 +1230,8 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) case ATTRIBUTE(ALC_MAX_AUXILIARY_SENDS) numSends = static_cast(attrList[attrIdx + 1]); - if(numSends > INT_MAX) numSends = 0; - else numSends = minu(numSends, MAX_SENDS); + if(numSends > uint{std::numeric_limits::max()}) numSends = 0; + else numSends = std::min(numSends, uint{MaxSendCount}); break; case ATTRIBUTE(ALC_HRTF_SOFT) @@ -1904,7 +1240,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) else if(attrList[attrIdx + 1] == ALC_TRUE) opthrtf = true; else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) - opthrtf = al::nullopt; + opthrtf = std::nullopt; break; case ATTRIBUTE(ALC_HRTF_ID_SOFT) @@ -1917,7 +1253,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) else if(attrList[attrIdx + 1] == ALC_TRUE) optlimit = true; else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) - optlimit = al::nullopt; + optlimit = std::nullopt; break; case ATTRIBUTE(ALC_OUTPUT_MODE_SOFT) @@ -1929,8 +1265,6 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) attrList[attrIdx + 1], attrList[attrIdx + 1]); break; } - - attrIdx += 2; } #undef ATTRIBUTE @@ -1938,7 +1272,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) { if(!optchans || !opttype) return ALC_INVALID_VALUE; - if(freqAttr < MIN_OUTPUT_RATE || freqAttr > MAX_OUTPUT_RATE) + if(freqAttr < int{MinOutputRate} || freqAttr > int{MaxOutputRate}) return ALC_INVALID_VALUE; if(*optchans == DevFmtAmbi3D) { @@ -2015,12 +1349,12 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) if(freqAttr) { - uint oldrate = optsrate.value_or(DEFAULT_OUTPUT_RATE); - freqAttr = clampi(freqAttr, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE); + uint oldrate = optsrate.value_or(DefaultOutputRate); + freqAttr = std::clamp(freqAttr, MinOutputRate, MaxOutputRate); const double scale{static_cast(freqAttr) / oldrate}; - period_size = static_cast(period_size*scale + 0.5); - buffer_size = static_cast(buffer_size*scale + 0.5); + period_size = static_cast(std::lround(period_size * scale)); + buffer_size = static_cast(std::lround(buffer_size * scale)); optsrate = static_cast(freqAttr); } } @@ -2028,16 +1362,19 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) /* If a context is already running on the device, stop playback so the * device attributes can be updated. */ - if(device->Flags.test(DeviceRunning)) + if(device->mDeviceState == DeviceState::Playing) + { device->Backend->stop(); - device->Flags.reset(DeviceRunning); + device->mDeviceState = DeviceState::Unprepared; + } UpdateClockBase(device); } - if(device->Flags.test(DeviceRunning)) + if(device->mDeviceState == DeviceState::Playing) return ALC_NO_ERROR; + device->mDeviceState = DeviceState::Unprepared; device->AvgSpeakerDist = 0.0f; device->mNFCtrlFilter = NfcFilter{}; device->mUhjEncoder = nullptr; @@ -2091,14 +1428,14 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) device->mAmbiOrder = 0; device->BufferSize = buffer_size; device->UpdateSize = period_size; - device->Frequency = optsrate.value_or(DEFAULT_OUTPUT_RATE); + device->Frequency = optsrate.value_or(DefaultOutputRate); device->Flags.set(FrequencyRequest, optsrate.has_value()) .set(ChannelsRequest, optchans.has_value()) .set(SampleTypeRequest, opttype.has_value()); if(device->FmtChans == DevFmtAmbi3D) { - device->mAmbiOrder = clampu(aorder, 1, MaxAmbiOrder); + device->mAmbiOrder = std::clamp(aorder, 1u, uint{MaxAmbiOrder}); device->mAmbiLayout = optlayout.value_or(DevAmbiLayout::Default); device->mAmbiScale = optscale.value_or(DevAmbiScaling::Default); if(device->mAmbiOrder > 3 @@ -2106,11 +1443,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) || device->mAmbiScale == DevAmbiScaling::FuMa)) { ERR("FuMa is incompatible with %d%s order ambisonics (up to 3rd 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"); + device->mAmbiOrder, GetCounterSuffix(device->mAmbiOrder)); device->mAmbiOrder = 3; } } @@ -2160,15 +1493,14 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) if(device->Type != DeviceType::Loopback) { - if(auto modeopt = device->configValue(nullptr, "stereo-mode")) + if(auto modeopt = device->configValue({}, "stereo-mode")) { - const char *mode{modeopt->c_str()}; - if(al::strcasecmp(mode, "headphones") == 0) + if(al::case_compare(*modeopt, "headphones"sv) == 0) device->Flags.set(DirectEar); - else if(al::strcasecmp(mode, "speakers") == 0) + else if(al::case_compare(*modeopt, "speakers"sv) == 0) device->Flags.reset(DirectEar); - else if(al::strcasecmp(mode, "auto") != 0) - ERR("Unexpected stereo-mode: %s\n", mode); + else if(al::case_compare(*modeopt, "auto"sv) != 0) + ERR("Unexpected stereo-mode: %s\n", modeopt->c_str()); } } @@ -2177,24 +1509,24 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) /* Calculate the max number of sources, and split them between the mono and * stereo count given the requested number of stereo sources. */ - if(auto srcsopt = device->configValue(nullptr, "sources")) + if(auto srcsopt = device->configValue({}, "sources"sv)) { if(*srcsopt <= 0) numMono = 256; - else numMono = maxu(*srcsopt, 16); + else numMono = std::max(*srcsopt, 16u); } else { - numMono = minu(numMono, INT_MAX-numStereo); - numMono = maxu(numMono+numStereo, 256); + numMono = std::min(numMono, std::numeric_limits::max()-numStereo); + numMono = std::max(numMono+numStereo, 256u); } - numStereo = minu(numStereo, numMono); + numStereo = std::min(numStereo, numMono); numMono -= numStereo; device->SourcesMax = numMono + numStereo; device->NumMonoSources = numMono; device->NumStereoSources = numStereo; - if(auto sendsopt = device->configValue(nullptr, "sends")) - numSends = minu(numSends, static_cast(clampi(*sendsopt, 0, MAX_SENDS))); + if(auto sendsopt = device->configValue({}, "sends"sv)) + numSends = std::min(numSends, std::clamp(*sendsopt, 0u, uint{MaxSendCount})); device->NumAuxSends = numSends; TRACE("Max sources: %d (%d + %d), effect slots: %d, sends: %d\n", @@ -2213,17 +1545,18 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) case DevFmtX61: device->RealOut.RemixMap = X61Downmix; break; case DevFmtX71: device->RealOut.RemixMap = X71Downmix; break; case DevFmtX714: device->RealOut.RemixMap = X71Downmix; break; + case DevFmtX7144: device->RealOut.RemixMap = X71Downmix; break; case DevFmtX3D71: device->RealOut.RemixMap = X51Downmix; break; case DevFmtAmbi3D: break; } - nanoseconds::rep sample_delay{0}; + size_t sample_delay{0}; if(auto *encoder{device->mUhjEncoder.get()}) sample_delay += encoder->getDelay(); - if(device->getConfigValueBool(nullptr, "dither", true)) + if(device->getConfigValueBool({}, "dither"sv, true)) { - int depth{device->configValue(nullptr, "dither-depth").value_or(0)}; + int depth{device->configValue({}, "dither-depth"sv).value_or(0)}; if(depth <= 0) { switch(device->FmtType) @@ -2245,7 +1578,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) if(depth > 0) { - depth = clampi(depth, 2, 24); + depth = std::clamp(depth, 2, 24); device->DitherDepth = std::pow(2.0f, static_cast(depth-1)); } } @@ -2256,7 +1589,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) device->DitherDepth); if(!optlimit) - optlimit = device->configValue(nullptr, "output-limiter"); + optlimit = device->configValue({}, "output-limiter"); /* If the gain limiter is unset, use the limiter for integer-based output * (where samples must be clamped), and don't for floating-point (which can @@ -2278,7 +1611,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) break; } } - if(optlimit.value_or(false) == false) + if(!optlimit.value_or(false)) TRACE("Output limiter disabled\n"); else { @@ -2310,11 +1643,12 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) } /* Convert the sample delay from samples to nanosamples to nanoseconds. */ + sample_delay = std::min(sample_delay, std::numeric_limits::max()); device->FixedLatency += nanoseconds{seconds{sample_delay}} / device->Frequency; TRACE("Fixed device latency: %" PRId64 "ns\n", int64_t{device->FixedLatency.count()}); FPUCtl mixer_mode{}; - for(ContextBase *ctxbase : *device->mContexts.load()) + auto reset_context = [device](ContextBase *ctxbase) { auto *context = static_cast(ctxbase); @@ -2322,72 +1656,85 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) std::unique_lock slotlock{context->mEffectSlotLock}; /* Clear out unused effect slot clusters. */ - auto slot_cluster_not_in_use = [](ContextBase::EffectSlotCluster &cluster) + auto slot_cluster_not_in_use = [](ContextBase::EffectSlotCluster &clusterptr) -> bool { - for(size_t i{0};i < ContextBase::EffectSlotClusterSize;++i) - { - if(cluster[i].InUse) - return false; - } - return true; + return std::none_of(clusterptr->begin(), clusterptr->end(), + std::mem_fn(&EffectSlot::InUse)); }; - auto slotcluster_iter = std::remove_if(context->mEffectSlotClusters.begin(), + auto slotcluster_end = std::remove_if(context->mEffectSlotClusters.begin(), context->mEffectSlotClusters.end(), slot_cluster_not_in_use); - context->mEffectSlotClusters.erase(slotcluster_iter, context->mEffectSlotClusters.end()); + context->mEffectSlotClusters.erase(slotcluster_end, context->mEffectSlotClusters.end()); /* Free all wet buffers. Any in use will be reallocated with an updated * configuration in aluInitEffectPanning. */ - for(auto&& slots : context->mEffectSlotClusters) + auto clear_wetbuffers = [](ContextBase::EffectSlotCluster &clusterptr) { - for(size_t i{0};i < ContextBase::EffectSlotClusterSize;++i) + auto clear_buffer = [](EffectSlot &slot) { - slots[i].mWetBuffer.clear(); - slots[i].mWetBuffer.shrink_to_fit(); - slots[i].Wet.Buffer = {}; - } - } + slot.mWetBuffer.clear(); + slot.mWetBuffer.shrink_to_fit(); + slot.Wet.Buffer = {}; + }; + std::for_each(clusterptr->begin(), clusterptr->end(), clear_buffer); + }; + std::for_each(context->mEffectSlotClusters.begin(), context->mEffectSlotClusters.end(), + clear_wetbuffers); if(ALeffectslot *slot{context->mDefaultSlot.get()}) { - aluInitEffectPanning(slot->mSlot, context); + auto *slotbase = slot->mSlot; + aluInitEffectPanning(slotbase, context); + + if(auto *props = slotbase->Update.exchange(nullptr, std::memory_order_relaxed)) + AtomicReplaceHead(context->mFreeEffectSlotProps, props); EffectState *state{slot->Effect.State.get()}; state->mOutTarget = device->Dry.Buffer; state->deviceUpdate(device, slot->Buffer); - slot->updateProps(context); + slot->mPropsDirty = true; } if(EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_relaxed)}) - std::fill_n(curarray->end(), curarray->size(), nullptr); - for(auto &sublist : context->mEffectSlotList) + std::fill(curarray->begin()+ptrdiff_t(curarray->size()>>1), curarray->end(), nullptr); + auto reset_slots = [device,context](EffectSlotSubList &sublist) { uint64_t usemask{~sublist.FreeMask}; while(usemask) { - const int idx{al::countr_zero(usemask)}; - ALeffectslot *slot{sublist.EffectSlots + idx}; + const auto idx = static_cast(al::countr_zero(usemask)); + auto &slot = (*sublist.EffectSlots)[idx]; usemask &= ~(1_u64 << idx); - aluInitEffectPanning(slot->mSlot, context); + auto *slotbase = slot.mSlot; + aluInitEffectPanning(slotbase, context); - EffectState *state{slot->Effect.State.get()}; + if(auto *props = slotbase->Update.exchange(nullptr, std::memory_order_relaxed)) + AtomicReplaceHead(context->mFreeEffectSlotProps, props); + + EffectState *state{slot.Effect.State.get()}; state->mOutTarget = device->Dry.Buffer; - state->deviceUpdate(device, slot->Buffer); - slot->updateProps(context); + state->deviceUpdate(device, slot.Buffer); + slot.mPropsDirty = true; } - } + }; + std::for_each(context->mEffectSlotList.begin(), context->mEffectSlotList.end(), + reset_slots); + + /* Clear all effect slot props to let them get allocated again. */ + context->mEffectSlotPropClusters.clear(); + context->mFreeEffectSlotProps.store(nullptr, std::memory_order_relaxed); slotlock.unlock(); - const uint num_sends{device->NumAuxSends}; std::unique_lock srclock{context->mSourceLock}; - for(auto &sublist : context->mSourceList) + const uint num_sends{device->NumAuxSends}; + auto reset_sources = [num_sends](SourceSubList &sublist) { uint64_t usemask{~sublist.FreeMask}; while(usemask) { - const int idx{al::countr_zero(usemask)}; - ALsource *source{sublist.Sources + idx}; + const auto idx = static_cast(al::countr_zero(usemask)); + auto &source = (*sublist.Sources)[idx]; usemask &= ~(1_u64 << idx); auto clear_send = [](ALsource::SendData &send) -> void @@ -2397,30 +1744,31 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) send.Slot = nullptr; send.Gain = 1.0f; send.GainHF = 1.0f; - send.HFReference = LOWPASSFREQREF; + send.HFReference = LowPassFreqRef; send.GainLF = 1.0f; - send.LFReference = HIGHPASSFREQREF; + send.LFReference = HighPassFreqRef; }; - auto send_begin = source->Send.begin() + static_cast(num_sends); - std::for_each(send_begin, source->Send.end(), clear_send); + const auto sends = al::span{source.Send}.subspan(num_sends); + std::for_each(sends.begin(), sends.end(), clear_send); - source->mPropsDirty = true; + source.mPropsDirty = true; } - } + }; + std::for_each(context->mSourceList.begin(), context->mSourceList.end(), reset_sources); - auto voicelist = context->getVoicesSpan(); - for(Voice *voice : voicelist) + auto reset_voice = [device,num_sends,context](Voice *voice) { /* Clear extraneous property set sends. */ - std::fill(std::begin(voice->mProps.Send)+num_sends, std::end(voice->mProps.Send), - VoiceProps::SendData{}); + const auto sendparams = al::span{voice->mProps.Send}.subspan(num_sends); + std::fill(sendparams.begin(), sendparams.end(), VoiceProps::SendData{}); std::fill(voice->mSend.begin()+num_sends, voice->mSend.end(), Voice::TargetData{}); - for(auto &chandata : voice->mChans) + auto clear_wetparams = [num_sends](Voice::ChannelData &chandata) { - std::fill(chandata.mWetParams.begin()+num_sends, chandata.mWetParams.end(), - SendParams{}); - } + const auto wetparams = al::span{chandata.mWetParams}.subspan(num_sends); + std::fill(wetparams.begin(), wetparams.end(), SendParams{}); + }; + std::for_each(voice->mChans.begin(), voice->mChans.end(), clear_wetparams); if(VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_relaxed)}) AtomicReplaceHead(context->mFreeVoiceProps, props); @@ -2430,10 +1778,13 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) voice->mPlayState.compare_exchange_strong(vstate, Voice::Stopped, std::memory_order_acquire, std::memory_order_acquire); if(voice->mSourceID.load(std::memory_order_relaxed) == 0u) - continue; + return; voice->prepare(device); - } + }; + const auto voicespan = context->getVoicesSpan(); + std::for_each(voicespan.begin(), voicespan.end(), reset_voice); + /* Clear all voice props to let them get allocated again. */ context->mVoicePropClusters.clear(); context->mFreeVoiceProps.store(nullptr, std::memory_order_relaxed); @@ -2441,16 +1792,20 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) context->mPropsDirty = false; UpdateContextProps(context); + UpdateAllEffectSlotProps(context); UpdateAllSourceProps(context); - } + }; + auto ctxspan = al::span{*device->mContexts.load()}; + std::for_each(ctxspan.begin(), ctxspan.end(), reset_context); mixer_mode.leave(); + device->mDeviceState = DeviceState::Configured; if(!device->Flags.test(DevicePaused)) { try { auto backend = device->Backend.get(); backend->start(); - device->Flags.set(DeviceRunning); + device->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { ERR("%s\n", e.what()); @@ -2469,13 +1824,13 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) * Updates device parameters as above, and also first clears the disconnected * status, if set. */ -bool ResetDeviceParams(ALCdevice *device, const int *attrList) +bool ResetDeviceParams(ALCdevice *device, const al::span attrList) { /* If the device was disconnected, reset it since we're opened anew. */ if(!device->Connected.load(std::memory_order_relaxed)) UNLIKELY { /* Make sure disconnection is finished before continuing on. */ - device->waitForMix(); + std::ignore = device->waitForMix(); for(ContextBase *ctxbase : *device->mContexts.load(std::memory_order_acquire)) { @@ -2486,7 +1841,7 @@ bool ResetDeviceParams(ALCdevice *device, const int *attrList) /* Clear any pending voice changes and reallocate voices to get a * clean restart. */ - std::lock_guard __{ctx->mSourceLock}; + std::lock_guard sourcelock{ctx->mSourceLock}; auto *vchg = ctx->mCurrentVoiceChange.load(std::memory_order_acquire); while(auto *next = vchg->mNext.load(std::memory_order_acquire)) vchg = next; @@ -2514,7 +1869,7 @@ bool ResetDeviceParams(ALCdevice *device, const int *attrList) /** Checks if the device handle is valid, and returns a new reference if so. */ DeviceRef VerifyDevice(ALCdevice *device) { - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); if(iter != DeviceList.end() && *iter == device) { @@ -2530,7 +1885,7 @@ DeviceRef VerifyDevice(ALCdevice *device) */ ContextRef VerifyContext(ALCcontext *context) { - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); if(iter != ContextList.end() && *iter == context) { @@ -2542,8 +1897,13 @@ ContextRef VerifyContext(ALCcontext *context) } // namespace +FORCE_ALIGN void ALC_APIENTRY alsoft_set_log_callback(LPALSOFTLOGCALLBACK callback, void *userptr) noexcept +{ + al_set_log_callback(callback, userptr); +} + /** Returns a new reference to the currently active context for this thread. */ -ContextRef GetContextRef(void) +ContextRef GetContextRef() noexcept { ALCcontext *context{ALCcontext::getThreadContext()}; if(context) @@ -2562,89 +1922,100 @@ ContextRef GetContextRef(void) return ContextRef{context}; } +void alcSetError(ALCdevice *device, ALCenum errorCode) +{ + WARN("Error generated on device %p, code 0x%04x\n", voidp{device}, errorCode); + if(TrapALCError) + { +#ifdef _WIN32 + /* DebugBreak() will cause an exception if there is no debugger */ + if(IsDebuggerPresent()) + DebugBreak(); +#elif defined(SIGTRAP) + raise(SIGTRAP); +#endif + } + + if(device) + device->LastError.store(errorCode); + else + LastNullDeviceError.store(errorCode); +} /************************************************ * Standard ALC functions ************************************************/ -ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) -START_API_FUNC +ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(dev) return dev->LastError.exchange(ALC_NO_ERROR); return LastNullDeviceError.exchange(ALC_NO_ERROR); } -END_API_FUNC -ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) -START_API_FUNC +ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) noexcept { - if(!SuspendDefers) - return; - ContextRef ctx{VerifyContext(context)}; if(!ctx) - alcSetError(nullptr, ALC_INVALID_CONTEXT); - else { - std::lock_guard _{ctx->mPropLock}; + alcSetError(nullptr, ALC_INVALID_CONTEXT); + return; + } + + if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY + ctx->debugMessage(DebugSource::API, DebugType::Portability, 0, DebugSeverity::Medium, + "alcSuspendContext behavior is not portable -- some implementations suspend all " + "rendering, some only defer property changes, and some are completely no-op; consider " + "using alcDevicePauseSOFT to suspend all rendering, or alDeferUpdatesSOFT to only " + "defer property changes"); + + if(SuspendDefers) + { + std::lock_guard proplock{ctx->mPropLock}; ctx->deferUpdates(); } } -END_API_FUNC -ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) -START_API_FUNC +ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept { - if(!SuspendDefers) - return; - ContextRef ctx{VerifyContext(context)}; if(!ctx) - alcSetError(nullptr, ALC_INVALID_CONTEXT); - else { - std::lock_guard _{ctx->mPropLock}; + alcSetError(nullptr, ALC_INVALID_CONTEXT); + return; + } + + if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY + ctx->debugMessage(DebugSource::API, DebugType::Portability, 0, DebugSeverity::Medium, + "alcProcessContext behavior is not portable -- some implementations resume rendering, " + "some apply deferred property changes, and some are completely no-op; consider using " + "alcDeviceResumeSOFT to resume rendering, or alProcessUpdatesSOFT to apply deferred " + "property changes"); + + if(SuspendDefers) + { + std::lock_guard proplock{ctx->mPropLock}; ctx->processUpdates(); } } -END_API_FUNC -ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) -START_API_FUNC +ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) noexcept { 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_NO_ERROR: value = GetNoErrorString(); break; + case ALC_INVALID_ENUM: value = GetInvalidEnumString(); break; + case ALC_INVALID_VALUE: value = GetInvalidValueString(); break; + case ALC_INVALID_DEVICE: value = GetInvalidDeviceString(); break; + case ALC_INVALID_CONTEXT: value = GetInvalidContextString(); break; + case ALC_OUT_OF_MEMORY: value = GetOutOfMemoryString(); break; case ALC_DEVICE_SPECIFIER: - value = alcDefaultName; + value = GetDefaultName(); break; case ALC_ALL_DEVICES_SPECIFIER: @@ -2653,10 +2024,10 @@ START_API_FUNC if(dev->Type == DeviceType::Capture) alcSetError(dev.get(), ALC_INVALID_ENUM); else if(dev->Type == DeviceType::Loopback) - value = alcDefaultName; + value = GetDefaultName(); else { - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; value = dev->DeviceName.c_str(); } } @@ -2674,7 +2045,7 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_ENUM); else { - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; value = dev->DeviceName.c_str(); } } @@ -2687,7 +2058,7 @@ START_API_FUNC /* Default devices are always first in the list */ case ALC_DEFAULT_DEVICE_SPECIFIER: - value = alcDefaultName; + value = GetDefaultName(); break; case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: @@ -2695,8 +2066,13 @@ START_API_FUNC ProbeAllDevicesList(); /* Copy first entry as default. */ - alcDefaultAllDevicesSpecifier = alcAllDevicesList.c_str(); - value = alcDefaultAllDevicesSpecifier.c_str(); + if(alcAllDevicesArray.empty()) + value = GetDefaultName(); + else + { + alcDefaultAllDevicesSpecifier = alcAllDevicesArray.front(); + value = alcDefaultAllDevicesSpecifier.c_str(); + } break; case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: @@ -2704,21 +2080,26 @@ START_API_FUNC ProbeCaptureDeviceList(); /* Copy first entry as default. */ - alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceList.c_str(); - value = alcCaptureDefaultDeviceSpecifier.c_str(); + if(alcCaptureDeviceArray.empty()) + value = GetDefaultName(); + else + { + alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceArray.front(); + value = alcCaptureDefaultDeviceSpecifier.c_str(); + } break; case ALC_EXTENSIONS: if(VerifyDevice(Device)) - value = alcExtensionList; + value = GetExtensionList().data(); else - value = alcNoDeviceExtList; + value = GetNoDeviceExtList().data(); break; case ALC_HRTF_SPECIFIER_SOFT: if(DeviceRef dev{VerifyDevice(Device)}) { - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; value = (dev->mHrtf ? dev->mHrtfName.c_str() : ""); } else @@ -2732,13 +2113,10 @@ START_API_FUNC return value; } -END_API_FUNC static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values) { - size_t i; - if(values.empty()) { alcSetError(device, ALC_INVALID_VALUE); @@ -2763,7 +2141,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[0] = alcEFXMinorVersion; return 1; case ALC_MAX_AUXILIARY_SENDS: - values[0] = MAX_SENDS; + values[0] = MaxSendCount; return 1; case ALC_ATTRIBUTES_SIZE: @@ -2789,7 +2167,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 0; } - std::lock_guard _{device->StateLock}; + std::lock_guard statelock{device->StateLock}; if(device->Type == DeviceType::Capture) { static constexpr int MaxCaptureAttributes{9}; @@ -2799,11 +2177,9 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[0] = MaxCaptureAttributes; return 1; case ALC_ALL_ATTRIBUTES: - i = 0; - if(values.size() < MaxCaptureAttributes) - alcSetError(device, ALC_INVALID_VALUE); - else + if(values.size() >= MaxCaptureAttributes) { + size_t i{0}; values[i++] = ALC_MAJOR_VERSION; values[i++] = alcMajorVersion; values[i++] = ALC_MINOR_VERSION; @@ -2814,8 +2190,10 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[i++] = device->Connected.load(std::memory_order_relaxed); values[i++] = 0; assert(i == MaxCaptureAttributes); + return i; } - return i; + alcSetError(device, ALC_INVALID_VALUE); + return 0; case ALC_MAJOR_VERSION: values[0] = alcMajorVersion; @@ -2839,7 +2217,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span } /* render device */ - auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept + auto NumAttrsForDevice = [](const ALCdevice *aldev) noexcept -> uint8_t { if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) return 37; @@ -2852,11 +2230,9 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 1; case ALC_ALL_ATTRIBUTES: - i = 0; - if(values.size() < static_cast(NumAttrsForDevice(device))) - alcSetError(device, ALC_INVALID_VALUE); - else + if(values.size() >= NumAttrsForDevice(device)) { + size_t i{0}; values[i++] = ALC_MAJOR_VERSION; values[i++] = alcMajorVersion; values[i++] = ALC_MINOR_VERSION; @@ -2922,8 +2298,11 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[i++] = static_cast(device->getOutputMode1()); values[i++] = 0; + assert(i == NumAttrsForDevice(device)); + return i; } - return i; + alcSetError(device, ALC_INVALID_VALUE); + return 0; case ALC_MAJOR_VERSION: values[0] = alcMajorVersion; @@ -3034,8 +2413,8 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span case ALC_NUM_HRTF_SPECIFIERS_SOFT: device->enumerateHrtfs(); - values[0] = static_cast(minz(device->mHrtfList.size(), - std::numeric_limits::max())); + values[0] = static_cast(std::min(device->mHrtfList.size(), + size_t{std::numeric_limits::max()})); return 1; case ALC_OUTPUT_LIMITER_SOFT: @@ -3056,8 +2435,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 0; } -ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) -START_API_FUNC +ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) noexcept { DeviceRef dev{VerifyDevice(device)}; if(size <= 0 || values == nullptr) @@ -3065,10 +2443,8 @@ START_API_FUNC else GetIntegerv(dev.get(), param, {values, static_cast(size)}); } -END_API_FUNC -ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALCsizei size, ALCint64SOFT *values) -START_API_FUNC +ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALCsizei size, ALCint64SOFT *values) noexcept { DeviceRef dev{VerifyDevice(device)}; if(size <= 0 || values == nullptr) @@ -3076,94 +2452,95 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_VALUE); return; } + const auto valuespan = al::span{values, static_cast(size)}; if(!dev || dev->Type == DeviceType::Capture) { - auto ivals = al::vector(static_cast(size)); + auto ivals = std::vector(valuespan.size()); if(size_t got{GetIntegerv(dev.get(), pname, ivals)}) - std::copy_n(ivals.begin(), got, values); + std::copy_n(ivals.cbegin(), got, valuespan.begin()); return; } /* render device */ - auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept + auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept -> size_t { if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) return 41; return 35; }; - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; switch(pname) { case ALC_ATTRIBUTES_SIZE: - *values = NumAttrsForDevice(dev.get()); + valuespan[0] = static_cast(NumAttrsForDevice(dev.get())); break; case ALC_ALL_ATTRIBUTES: - if(size < NumAttrsForDevice(dev.get())) + if(valuespan.size() < NumAttrsForDevice(dev.get())) alcSetError(dev.get(), ALC_INVALID_VALUE); else { size_t i{0}; - values[i++] = ALC_FREQUENCY; - values[i++] = dev->Frequency; + valuespan[i++] = ALC_FREQUENCY; + valuespan[i++] = dev->Frequency; if(dev->Type != DeviceType::Loopback) { - values[i++] = ALC_REFRESH; - values[i++] = dev->Frequency / dev->UpdateSize; + valuespan[i++] = ALC_REFRESH; + valuespan[i++] = dev->Frequency / dev->UpdateSize; - values[i++] = ALC_SYNC; - values[i++] = ALC_FALSE; + valuespan[i++] = ALC_SYNC; + valuespan[i++] = ALC_FALSE; } else { - values[i++] = ALC_FORMAT_CHANNELS_SOFT; - values[i++] = EnumFromDevFmt(dev->FmtChans); + valuespan[i++] = ALC_FORMAT_CHANNELS_SOFT; + valuespan[i++] = EnumFromDevFmt(dev->FmtChans); - values[i++] = ALC_FORMAT_TYPE_SOFT; - values[i++] = EnumFromDevFmt(dev->FmtType); + valuespan[i++] = ALC_FORMAT_TYPE_SOFT; + valuespan[i++] = EnumFromDevFmt(dev->FmtType); if(dev->FmtChans == DevFmtAmbi3D) { - values[i++] = ALC_AMBISONIC_LAYOUT_SOFT; - values[i++] = EnumFromDevAmbi(dev->mAmbiLayout); + valuespan[i++] = ALC_AMBISONIC_LAYOUT_SOFT; + valuespan[i++] = EnumFromDevAmbi(dev->mAmbiLayout); - values[i++] = ALC_AMBISONIC_SCALING_SOFT; - values[i++] = EnumFromDevAmbi(dev->mAmbiScale); + valuespan[i++] = ALC_AMBISONIC_SCALING_SOFT; + valuespan[i++] = EnumFromDevAmbi(dev->mAmbiScale); - values[i++] = ALC_AMBISONIC_ORDER_SOFT; - values[i++] = dev->mAmbiOrder; + valuespan[i++] = ALC_AMBISONIC_ORDER_SOFT; + valuespan[i++] = dev->mAmbiOrder; } } - values[i++] = ALC_MONO_SOURCES; - values[i++] = dev->NumMonoSources; + valuespan[i++] = ALC_MONO_SOURCES; + valuespan[i++] = dev->NumMonoSources; - values[i++] = ALC_STEREO_SOURCES; - values[i++] = dev->NumStereoSources; + valuespan[i++] = ALC_STEREO_SOURCES; + valuespan[i++] = dev->NumStereoSources; - values[i++] = ALC_MAX_AUXILIARY_SENDS; - values[i++] = dev->NumAuxSends; + valuespan[i++] = ALC_MAX_AUXILIARY_SENDS; + valuespan[i++] = dev->NumAuxSends; - values[i++] = ALC_HRTF_SOFT; - values[i++] = (dev->mHrtf ? ALC_TRUE : ALC_FALSE); + valuespan[i++] = ALC_HRTF_SOFT; + valuespan[i++] = (dev->mHrtf ? ALC_TRUE : ALC_FALSE); - values[i++] = ALC_HRTF_STATUS_SOFT; - values[i++] = dev->mHrtfStatus; + valuespan[i++] = ALC_HRTF_STATUS_SOFT; + valuespan[i++] = dev->mHrtfStatus; - values[i++] = ALC_OUTPUT_LIMITER_SOFT; - values[i++] = dev->Limiter ? ALC_TRUE : ALC_FALSE; + valuespan[i++] = ALC_OUTPUT_LIMITER_SOFT; + valuespan[i++] = dev->Limiter ? ALC_TRUE : ALC_FALSE; ClockLatency clock{GetClockLatency(dev.get(), dev->Backend.get())}; - values[i++] = ALC_DEVICE_CLOCK_SOFT; - values[i++] = clock.ClockTime.count(); + valuespan[i++] = ALC_DEVICE_CLOCK_SOFT; + valuespan[i++] = clock.ClockTime.count(); - values[i++] = ALC_DEVICE_LATENCY_SOFT; - values[i++] = clock.Latency.count(); + valuespan[i++] = ALC_DEVICE_LATENCY_SOFT; + valuespan[i++] = clock.Latency.count(); - values[i++] = ALC_OUTPUT_MODE_SOFT; - values[i++] = static_cast(device->getOutputMode1()); + valuespan[i++] = ALC_OUTPUT_MODE_SOFT; + valuespan[i++] = al::to_underlying(device->getOutputMode1()); - values[i++] = 0; + valuespan[i++] = 0; } break; @@ -3173,16 +2550,17 @@ START_API_FUNC nanoseconds basecount; do { refcount = dev->waitForMix(); - basecount = dev->ClockBase; - samplecount = dev->SamplesDone; - } while(refcount != ReadRef(dev->MixCount)); + basecount = dev->mClockBase.load(std::memory_order_relaxed); + samplecount = dev->mSamplesDone.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + } while(refcount != dev->mMixCount.load(std::memory_order_relaxed)); basecount += nanoseconds{seconds{samplecount}} / dev->Frequency; - *values = basecount.count(); + valuespan[0] = basecount.count(); } break; case ALC_DEVICE_LATENCY_SOFT: - *values = GetClockLatency(dev.get(), dev->Backend.get()).Latency.count(); + valuespan[0] = GetClockLatency(dev.get(), dev->Backend.get()).Latency.count(); break; case ALC_DEVICE_CLOCK_LATENCY_SOFT: @@ -3191,51 +2569,48 @@ START_API_FUNC else { ClockLatency clock{GetClockLatency(dev.get(), dev->Backend.get())}; - values[0] = clock.ClockTime.count(); - values[1] = clock.Latency.count(); + valuespan[0] = clock.ClockTime.count(); + valuespan[1] = clock.Latency.count(); } break; default: - auto ivals = al::vector(static_cast(size)); + auto ivals = std::vector(valuespan.size()); if(size_t got{GetIntegerv(dev.get(), pname, ivals)}) - std::copy_n(ivals.begin(), got, values); + std::copy_n(ivals.cbegin(), got, valuespan.begin()); break; } } -END_API_FUNC -ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName) noexcept { 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; + alcSetError(dev.get(), ALC_INVALID_VALUE); + return ALC_FALSE; + } - if((ptr=strchr(ptr, ' ')) != nullptr) - { - do { - ++ptr; - } while(isspace(*ptr)); - } - } + const std::string_view tofind{extName}; + const auto extlist = dev ? GetExtensionList() : GetNoDeviceExtList(); + auto matchpos = extlist.find(tofind); + while(matchpos != std::string_view::npos) + { + const auto endpos = matchpos + tofind.size(); + if((matchpos == 0 || std::isspace(extlist[matchpos-1])) + && (endpos == extlist.size() || std::isspace(extlist[endpos]))) + return ALC_TRUE; + matchpos = extlist.find(tofind, matchpos+1); } return ALC_FALSE; } -END_API_FUNC -ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName) -START_API_FUNC +ALCvoid* ALC_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALCchar *funcName) noexcept +{ return alcGetProcAddress(device, funcName); } + +ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName) noexcept { if(!funcName) { @@ -3243,6 +2618,7 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_VALUE); return nullptr; } + #ifdef ALSOFT_EAX if(eax_g_is_enabled) { @@ -3260,11 +2636,9 @@ START_API_FUNC } return nullptr; } -END_API_FUNC -ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName) -START_API_FUNC +ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName) noexcept { if(!enumName) { @@ -3272,6 +2646,7 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_VALUE); return 0; } + #ifdef ALSOFT_EAX if(eax_g_is_enabled) { @@ -3290,11 +2665,9 @@ START_API_FUNC return 0; } -END_API_FUNC -ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList) -START_API_FUNC +ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList) noexcept { /* Explicitly hold the list lock while taking the StateLock in case the * device is asynchronously destroyed, to ensure this new context is @@ -3313,24 +2686,40 @@ START_API_FUNC dev->LastError.store(ALC_NO_ERROR); - ALCenum err{UpdateDeviceParams(dev.get(), attrList)}; + const auto attrSpan = SpanFromAttributeList(attrList); + ALCenum err{UpdateDeviceParams(dev.get(), attrSpan)}; if(err != ALC_NO_ERROR) { alcSetError(dev.get(), err); return nullptr; } - ContextRef context{new ALCcontext{dev}}; + ContextFlagBitset ctxflags{0}; + for(size_t i{0};i < attrSpan.size();i+=2) + { + if(attrSpan[i] == ALC_CONTEXT_FLAGS_EXT) + { + ctxflags = static_cast(attrSpan[i+1]); + break; + } + } + + auto context = ContextRef{new(std::nothrow) ALCcontext{dev, ctxflags}}; + if(!context) + { + alcSetError(dev.get(), ALC_OUT_OF_MEMORY); + return nullptr; + } context->init(); - if(auto volopt = dev->configValue(nullptr, "volume-adjust")) + if(auto volopt = dev->configValue({}, "volume-adjust")) { const float valf{*volopt}; if(!std::isfinite(valf)) ERR("volume-adjust must be finite: %f\n", valf); else { - const float db{clampf(valf, -24.0f, 24.0f)}; + const float db{std::clamp(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); @@ -3345,8 +2734,7 @@ START_API_FUNC * old array. */ auto *oldarray = device->mContexts.load(); - const size_t newcount{oldarray->size()+1}; - std::unique_ptr newarray{ContextArray::Create(newcount)}; + auto newarray = ContextArray::Create(oldarray->size() + 1); /* Copy the current/old context handles to the new array, appending the * new context. @@ -3357,24 +2745,21 @@ START_API_FUNC /* 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 != &DeviceBase::sEmptyContextArray) - { - dev->waitForMix(); - delete oldarray; - } + auto prevarray = dev->mContexts.exchange(std::move(newarray)); + std::ignore = dev->waitForMix(); } statelock.unlock(); { - std::lock_guard _{ListLock}; + listlock.lock(); auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), context.get()); ContextList.emplace(iter, context.get()); + listlock.unlock(); } if(ALeffectslot *slot{context->mDefaultSlot.get()}) { - ALenum sloterr{slot->initEffect(ALCcontext::sDefaultEffect.type, + ALenum sloterr{slot->initEffect(0, ALCcontext::sDefaultEffect.type, ALCcontext::sDefaultEffect.Props, context.get())}; if(sloterr == AL_NO_ERROR) slot->updateProps(context.get()); @@ -3385,10 +2770,8 @@ START_API_FUNC TRACE("Created context %p\n", voidp{context.get()}); return context.release(); } -END_API_FUNC -ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) -START_API_FUNC +ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept { std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); @@ -3407,33 +2790,23 @@ START_API_FUNC ALCdevice *Device{ctx->mALDevice.get()}; - std::lock_guard _{Device->StateLock}; - if(!ctx->deinit() && Device->Flags.test(DeviceRunning)) - { - Device->Backend->stop(); - Device->Flags.reset(DeviceRunning); - } + std::lock_guard statelock{Device->StateLock}; + ctx->deinit(); } -END_API_FUNC -ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) -START_API_FUNC +ALC_API auto ALC_APIENTRY alcGetCurrentContext() noexcept -> ALCcontext* { ALCcontext *Context{ALCcontext::getThreadContext()}; if(!Context) Context = ALCcontext::sGlobalContext.load(); return Context; } -END_API_FUNC /** Returns the currently active thread-local context. */ -ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) -START_API_FUNC +ALC_API auto ALC_APIENTRY alcGetThreadContext() noexcept -> ALCcontext* { return ALCcontext::getThreadContext(); } -END_API_FUNC -ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) noexcept { /* context must be valid or nullptr */ ContextRef ctx; @@ -3455,7 +2828,7 @@ START_API_FUNC * the current context as its refcount is decremented. */ } - ContextRef{ALCcontext::sGlobalContext.exchange(ctx.release())}; + ctx = ContextRef{ALCcontext::sGlobalContext.exchange(ctx.release())}; ALCcontext::sGlobalContextLock.store(false, std::memory_order_release); /* Take ownership of the thread-local context reference (if any), clearing @@ -3467,11 +2840,9 @@ START_API_FUNC return ALC_TRUE; } -END_API_FUNC /** Makes the given context the active context for the current thread. */ -ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) noexcept { /* context must be valid or nullptr */ ContextRef ctx; @@ -3490,11 +2861,9 @@ START_API_FUNC return ALC_TRUE; } -END_API_FUNC -ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *Context) -START_API_FUNC +ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *Context) noexcept { ContextRef ctx{VerifyContext(Context)}; if(!ctx) @@ -3504,11 +2873,9 @@ START_API_FUNC } return ctx->mALDevice.get(); } -END_API_FUNC -ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) -START_API_FUNC +ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcept { InitConfig(); @@ -3518,26 +2885,27 @@ START_API_FUNC return nullptr; } - if(deviceName) + std::string_view devname{deviceName ? deviceName : ""}; + if(!devname.empty()) { - TRACE("Opening playback device \"%s\"\n", deviceName); - if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0 + TRACE("Opening playback device \"%.*s\"\n", al::sizei(devname), devname.data()); + if(al::case_compare(devname, GetDefaultName()) == 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 + || al::case_compare(devname, "DirectSound3D"sv) == 0 + || al::case_compare(devname, "DirectSound"sv) == 0 + || al::case_compare(devname, "MMSYSTEM"sv) == 0 #endif /* Some old Linux apps hardcode configuration strings that were * supported by the OpenAL SI. We can't really do anything useful * with them, so just ignore. */ - || (deviceName[0] == '\'' && deviceName[1] == '(') - || al::strcasecmp(deviceName, "openal-soft") == 0) - deviceName = nullptr; + || al::starts_with(devname, "'("sv) + || al::case_compare(devname, "openal-soft"sv) == 0) + devname = {}; } else TRACE("Opening default playback device\n"); @@ -3546,17 +2914,23 @@ START_API_FUNC #ifdef ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX - DEFAULT_SENDS + uint{DefaultSendCount} }; - DeviceRef device{new ALCdevice{DeviceType::Playback}}; + DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Playback}}; + if(!device) + { + WARN("Failed to create playback device handle\n"); + alcSetError(nullptr, ALC_OUT_OF_MEMORY); + return nullptr; + } /* 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->Frequency = DefaultOutputRate; + device->UpdateSize = DefaultUpdateSize; + device->BufferSize = DefaultUpdateSize * DefaultNumUpdates; device->SourcesMax = 256; device->NumStereoSources = 1; @@ -3566,8 +2940,8 @@ START_API_FUNC try { auto backend = PlaybackFactory->createBackend(device.get(), BackendType::Playback); - std::lock_guard _{ListLock}; - backend->open(deviceName); + std::lock_guard listlock{ListLock}; + backend->open(devname); device->Backend = std::move(backend); } catch(al::backend_exception &e) { @@ -3578,7 +2952,7 @@ START_API_FUNC } { - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); DeviceList.emplace(iter, device.get()); } @@ -3586,10 +2960,8 @@ START_API_FUNC TRACE("Created device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); return device.release(); } -END_API_FUNC -ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept { std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); @@ -3611,13 +2983,13 @@ START_API_FUNC DeviceList.erase(iter); std::unique_lock statelock{dev->StateLock}; - al::vector orphanctxs; + std::vector orphanctxs; for(ContextBase *ctx : *dev->mContexts.load()) { auto ctxiter = std::lower_bound(ContextList.begin(), ContextList.end(), ctx); if(ctxiter != ContextList.end() && *ctxiter == ctx) { - orphanctxs.emplace_back(ContextRef{*ctxiter}); + orphanctxs.emplace_back(*ctxiter); ContextList.erase(ctxiter); } } @@ -3630,20 +3002,20 @@ START_API_FUNC } orphanctxs.clear(); - if(dev->Flags.test(DeviceRunning)) + if(dev->mDeviceState == DeviceState::Playing) + { dev->Backend->stop(); - dev->Flags.reset(DeviceRunning); + dev->mDeviceState = DeviceState::Configured; + } 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 +ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei samples) noexcept { InitConfig(); @@ -3659,17 +3031,24 @@ START_API_FUNC return nullptr; } - if(deviceName) + std::string_view devname{deviceName ? deviceName : ""}; + if(!devname.empty()) { - TRACE("Opening capture device \"%s\"\n", deviceName); - if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0 - || al::strcasecmp(deviceName, "openal-soft") == 0) - deviceName = nullptr; + TRACE("Opening capture device \"%.*s\"\n", al::sizei(devname), devname.data()); + if(al::case_compare(devname, GetDefaultName()) == 0 + || al::case_compare(devname, "openal-soft"sv) == 0) + devname = {}; } else TRACE("Opening default capture device\n"); - DeviceRef device{new ALCdevice{DeviceType::Capture}}; + DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Capture}}; + if(!device) + { + WARN("Failed to create capture device handle\n"); + alcSetError(nullptr, ALC_OUT_OF_MEMORY); + return nullptr; + } auto decompfmt = DecomposeDevFormat(format); if(!decompfmt) @@ -3688,14 +3067,14 @@ START_API_FUNC device->UpdateSize = static_cast(samples); device->BufferSize = static_cast(samples); - try { - TRACE("Capture format: %s, %s, %uhz, %u / %u buffer\n", - DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), - device->Frequency, device->UpdateSize, device->BufferSize); + TRACE("Capture format: %s, %s, %uhz, %u / %u buffer\n", DevFmtChannelsString(device->FmtChans), + DevFmtTypeString(device->FmtType), device->Frequency, device->UpdateSize, + device->BufferSize); + try { auto backend = CaptureFactory->createBackend(device.get(), BackendType::Capture); - std::lock_guard _{ListLock}; - backend->open(deviceName); + std::lock_guard listlock{ListLock}; + backend->open(devname); device->Backend = std::move(backend); } catch(al::backend_exception &e) { @@ -3706,18 +3085,17 @@ START_API_FUNC } { - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); DeviceList.emplace(iter, device.get()); } + device->mDeviceState = DeviceState::Configured; TRACE("Created capture device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); return device.release(); } -END_API_FUNC -ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) noexcept { std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); @@ -3736,17 +3114,17 @@ START_API_FUNC DeviceList.erase(iter); listlock.unlock(); - std::lock_guard _{dev->StateLock}; - if(dev->Flags.test(DeviceRunning)) + std::lock_guard statelock{dev->StateLock}; + if(dev->mDeviceState == DeviceState::Playing) + { dev->Backend->stop(); - dev->Flags.reset(DeviceRunning); + dev->mDeviceState = DeviceState::Configured; + } return ALC_TRUE; } -END_API_FUNC -ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) -START_API_FUNC +ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Capture) @@ -3755,15 +3133,16 @@ START_API_FUNC return; } - std::lock_guard _{dev->StateLock}; - if(!dev->Connected.load(std::memory_order_acquire)) + std::lock_guard statelock{dev->StateLock}; + if(!dev->Connected.load(std::memory_order_acquire) + || dev->mDeviceState < DeviceState::Configured) alcSetError(dev.get(), ALC_INVALID_DEVICE); - else if(!dev->Flags.test(DeviceRunning)) + else if(dev->mDeviceState != DeviceState::Playing) { try { auto backend = dev->Backend.get(); backend->start(); - dev->Flags.set(DeviceRunning); + dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { ERR("%s\n", e.what()); @@ -3772,26 +3151,24 @@ START_API_FUNC } } } -END_API_FUNC -ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) -START_API_FUNC +ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Capture) alcSetError(dev.get(), ALC_INVALID_DEVICE); else { - std::lock_guard _{dev->StateLock}; - if(dev->Flags.test(DeviceRunning)) + std::lock_guard statelock{dev->StateLock}; + if(dev->mDeviceState == DeviceState::Playing) + { dev->Backend->stop(); - dev->Flags.reset(DeviceRunning); + dev->mDeviceState = DeviceState::Configured; + } } } -END_API_FUNC -ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) -START_API_FUNC +ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Capture) @@ -3808,7 +3185,7 @@ START_API_FUNC if(samples < 1) return; - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; BackendBase *backend{dev->Backend.get()}; const auto usamples = static_cast(samples); @@ -3818,9 +3195,8 @@ START_API_FUNC return; } - backend->captureSamples(static_cast(buffer), usamples); + backend->captureSamples(static_cast(buffer), usamples); } -END_API_FUNC /************************************************ @@ -3828,13 +3204,12 @@ END_API_FUNC ************************************************/ /** Open a loopback device, for manual rendering. */ -ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) -START_API_FUNC +ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) noexcept { InitConfig(); /* Make sure the device name, if specified, is us. */ - if(deviceName && strcmp(deviceName, alcDefaultName) != 0) + if(deviceName && strcmp(deviceName, GetDefaultName()) != 0) { alcSetError(nullptr, ALC_INVALID_VALUE); return nullptr; @@ -3844,10 +3219,16 @@ START_API_FUNC #ifdef ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX - DEFAULT_SENDS + uint{DefaultSendCount} }; - DeviceRef device{new ALCdevice{DeviceType::Loopback}}; + DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Loopback}}; + if(!device) + { + WARN("Failed to create loopback device handle\n"); + alcSetError(nullptr, ALC_OUT_OF_MEMORY); + return nullptr; + } device->SourcesMax = 256; device->AuxiliaryEffectSlotMax = 64; @@ -3857,7 +3238,7 @@ START_API_FUNC device->BufferSize = 0; device->UpdateSize = 0; - device->Frequency = DEFAULT_OUTPUT_RATE; + device->Frequency = DefaultOutputRate; device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; @@ -3878,7 +3259,7 @@ START_API_FUNC } { - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); DeviceList.emplace(iter, device.get()); } @@ -3886,13 +3267,11 @@ START_API_FUNC TRACE("Created loopback device %p\n", voidp{device.get()}); return device.release(); } -END_API_FUNC /** * Determines if the loopback device supports the given format for rendering. */ -ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Loopback) @@ -3902,29 +3281,32 @@ START_API_FUNC else { if(DevFmtTypeFromEnum(type).has_value() && DevFmtChannelsFromEnum(channels).has_value() - && freq >= MIN_OUTPUT_RATE && freq <= MAX_OUTPUT_RATE) + && freq >= int{MinOutputRate} && freq <= int{MaxOutputRate}) return ALC_TRUE; } return ALC_FALSE; } -END_API_FUNC /** * Renders some samples into a buffer, using the format last set by the * attributes given to alcCreateContext. */ -FORCE_ALIGN ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) -START_API_FUNC +#if defined(__GNUC__) && defined(__i386__) +/* Needed on x86-32 even without SSE codegen, since the mixer may still use SSE + * and GCC assumes the stack is aligned (x86-64 ABI guarantees alignment). + */ +[[gnu::force_align_arg_pointer]] +#endif +ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { - if(!device || device->Type != DeviceType::Loopback) + if(!device || device->Type != DeviceType::Loopback) UNLIKELY alcSetError(device, ALC_INVALID_DEVICE); - else if(samples < 0 || (samples > 0 && buffer == nullptr)) + else if(samples < 0 || (samples > 0 && buffer == nullptr)) UNLIKELY alcSetError(device, ALC_INVALID_VALUE); else device->renderSamples(buffer, static_cast(samples), device->channelsFromFmt()); } -END_API_FUNC /************************************************ @@ -3932,26 +3314,25 @@ END_API_FUNC ************************************************/ /** Pause the DSP to stop audio processing. */ -ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) -START_API_FUNC +ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Playback) alcSetError(dev.get(), ALC_INVALID_DEVICE); else { - std::lock_guard _{dev->StateLock}; - if(dev->Flags.test(DeviceRunning)) + std::lock_guard statelock{dev->StateLock}; + if(dev->mDeviceState == DeviceState::Playing) + { dev->Backend->stop(); - dev->Flags.reset(DeviceRunning); + dev->mDeviceState = DeviceState::Configured; + } dev->Flags.set(DevicePaused); } } -END_API_FUNC /** Resume the DSP to restart audio processing. */ -ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) -START_API_FUNC +ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Playback) @@ -3960,9 +3341,21 @@ START_API_FUNC return; } - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; if(!dev->Flags.test(DevicePaused)) return; + if(dev->mDeviceState < DeviceState::Configured) + { + WARN("Cannot resume unconfigured device\n"); + alcSetError(dev.get(), ALC_INVALID_DEVICE); + return; + } + if(!dev->Connected.load()) + { + WARN("Cannot resume a disconnected device\n"); + alcSetError(dev.get(), ALC_INVALID_DEVICE); + return; + } dev->Flags.reset(DevicePaused); if(dev->mContexts.load()->empty()) return; @@ -3970,7 +3363,7 @@ START_API_FUNC try { auto backend = dev->Backend.get(); backend->start(); - dev->Flags.set(DeviceRunning); + dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { ERR("%s\n", e.what()); @@ -3979,10 +3372,9 @@ START_API_FUNC return; } TRACE("Post-resume: %s, %s, %uhz, %u / %u buffer\n", - DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), - device->Frequency, device->UpdateSize, device->BufferSize); + DevFmtChannelsString(dev->FmtChans), DevFmtTypeString(dev->FmtType), + dev->Frequency, dev->UpdateSize, dev->BufferSize); } -END_API_FUNC /************************************************ @@ -3990,8 +3382,7 @@ END_API_FUNC ************************************************/ /** Gets a string parameter at the given index. */ -ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) -START_API_FUNC +ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type == DeviceType::Capture) @@ -4011,11 +3402,9 @@ START_API_FUNC return nullptr; } -END_API_FUNC /** Resets the given device output, using the specified attribute list. */ -ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) noexcept { std::unique_lock listlock{ListLock}; DeviceRef dev{VerifyDevice(device)}; @@ -4025,19 +3414,20 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_DEVICE); return ALC_FALSE; } - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; listlock.unlock(); /* Force the backend to stop mixing first since we're resetting. Also reset * the connected state so lost devices can attempt recover. */ - if(dev->Flags.test(DeviceRunning)) + if(dev->mDeviceState == DeviceState::Playing) + { dev->Backend->stop(); - dev->Flags.reset(DeviceRunning); + dev->mDeviceState = DeviceState::Configured; + } - return ResetDeviceParams(dev.get(), attribs) ? ALC_TRUE : ALC_FALSE; + return ResetDeviceParams(dev.get(), SpanFromAttributeList(attribs)) ? ALC_TRUE : ALC_FALSE; } -END_API_FUNC /************************************************ @@ -4046,15 +3436,8 @@ END_API_FUNC /** Reopens the given device output, using the specified name and attribute list. */ FORCE_ALIGN ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, - const ALCchar *deviceName, const ALCint *attribs) -START_API_FUNC + const ALCchar *deviceName, const ALCint *attribs) noexcept { - if(deviceName) - { - if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0) - deviceName = nullptr; - } - std::unique_lock listlock{ListLock}; DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Playback) @@ -4063,20 +3446,34 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_DEVICE); return ALC_FALSE; } - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; - /* Force the backend to stop mixing first since we're reopening. */ - if(dev->Flags.test(DeviceRunning)) + std::string_view devname{deviceName ? deviceName : ""}; + if(!devname.empty()) { - auto backend = dev->Backend.get(); - backend->stop(); - dev->Flags.reset(DeviceRunning); + if(devname.length() >= size_t{std::numeric_limits::max()}) + { + ERR("Device name too long (%zu >= %d)\n", devname.length(), + std::numeric_limits::max()); + alcSetError(dev.get(), ALC_INVALID_VALUE); + return ALC_FALSE; + } + if(al::case_compare(devname, GetDefaultName()) == 0) + devname = {}; + } + + /* Force the backend device to stop first since we're opening another one. */ + const bool wasPlaying{dev->mDeviceState == DeviceState::Playing}; + if(wasPlaying) + { + dev->Backend->stop(); + dev->mDeviceState = DeviceState::Configured; } BackendPtr newbackend; try { newbackend = PlaybackFactory->createBackend(dev.get(), BackendType::Playback); - newbackend->open(deviceName); + newbackend->open(devname); } catch(al::backend_exception &e) { listlock.unlock(); @@ -4086,16 +3483,12 @@ START_API_FUNC alcSetError(dev.get(), (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); - /* If the device is connected, not paused, and has contexts, ensure it - * continues playing. - */ - if(dev->Connected.load(std::memory_order_relaxed) && !dev->Flags.test(DevicePaused) - && !dev->mContexts.load(std::memory_order_relaxed)->empty()) + if(dev->Connected.load(std::memory_order_relaxed) && wasPlaying) { try { auto backend = dev->Backend.get(); backend->start(); - dev->Flags.set(DeviceRunning); + dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception &be) { ERR("%s\n", be.what()); @@ -4106,6 +3499,7 @@ START_API_FUNC } listlock.unlock(); dev->Backend = std::move(newbackend); + dev->mDeviceState = DeviceState::Unprepared; TRACE("Reopened device %p, \"%s\"\n", voidp{dev.get()}, dev->DeviceName.c_str()); /* Always return true even if resetting fails. It shouldn't fail, but this @@ -4119,7 +3513,40 @@ START_API_FUNC * In this way, we essentially act as if the function succeeded, but * immediately disconnects following it. */ - ResetDeviceParams(dev.get(), attribs); + ResetDeviceParams(dev.get(), SpanFromAttributeList(attribs)); return ALC_TRUE; } -END_API_FUNC + +/************************************************ + * ALC event query functions + ************************************************/ + +FORCE_ALIGN ALCenum ALC_APIENTRY alcEventIsSupportedSOFT(ALCenum eventType, ALCenum deviceType) noexcept +{ + auto etype = alc::GetEventType(eventType); + if(!etype) + { + WARN("Invalid event type: 0x%04x\n", eventType); + alcSetError(nullptr, ALC_INVALID_ENUM); + return ALC_EVENT_NOT_SUPPORTED_SOFT; + } + + auto supported = alc::EventSupport::NoSupport; + switch(deviceType) + { + case ALC_PLAYBACK_DEVICE_SOFT: + if(PlaybackFactory) + supported = PlaybackFactory->queryEventSupport(*etype, BackendType::Playback); + break; + + case ALC_CAPTURE_DEVICE_SOFT: + if(CaptureFactory) + supported = CaptureFactory->queryEventSupport(*etype, BackendType::Capture); + break; + + default: + WARN("Invalid device type: 0x%04x\n", deviceType); + alcSetError(nullptr, ALC_INVALID_ENUM); + } + return al::to_underlying(supported); +} diff --git a/Engine/lib/openal-soft/alc/alconfig.cpp b/Engine/lib/openal-soft/alc/alconfig.cpp index b0544b890..8c6c7595a 100644 --- a/Engine/lib/openal-soft/alc/alconfig.cpp +++ b/Engine/lib/openal-soft/alc/alconfig.cpp @@ -22,9 +22,6 @@ #include "alconfig.h" -#include -#include -#include #ifdef _WIN32 #include #include @@ -34,25 +31,47 @@ #endif #include -#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include -#include "alfstream.h" +#include "almalloc.h" #include "alstring.h" #include "core/helpers.h" #include "core/logging.h" #include "strutils.h" -#include "vector.h" +#if defined(ALSOFT_UWP) +#include // !!This is important!! +#include +#include +#include +using namespace winrt; +#endif namespace { +using namespace std::string_view_literals; + +#if defined(_WIN32) && !defined(_GAMING_XBOX) && !defined(ALSOFT_UWP) +struct CoTaskMemDeleter { + void operator()(void *mem) const { CoTaskMemFree(mem); } +}; +#endif + struct ConfigEntry { std::string key; std::string value; }; -al::vector ConfOpts; +std::vector ConfOpts; std::string &lstrip(std::string &line) @@ -72,57 +91,48 @@ bool readline(std::istream &f, std::string &output) return std::getline(f, output) && !output.empty(); } -std::string expdup(const char *str) +std::string expdup(std::string_view str) { std::string output; - std::string envval; - while(*str != '\0') + while(!str.empty()) { - const char *addstr; - size_t addstrlen; - - if(str[0] != '$') + if(auto nextpos = str.find('$')) { - const char *next = std::strchr(str, '$'); - addstr = str; - addstrlen = next ? static_cast(next-str) : std::strlen(str); + output += str.substr(0, nextpos); + if(nextpos == std::string_view::npos) + break; - str += addstrlen; + str.remove_prefix(nextpos); } - else + + str.remove_prefix(1); + if(str.empty()) { - str++; - if(*str == '$') - { - const char *next = std::strchr(str+1, '$'); - addstr = str; - addstrlen = next ? static_cast(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(); - } + output += '$'; + break; } - if(addstrlen == 0) + if(str.front() == '$') + { + output += '$'; + str.remove_prefix(1); continue; + } - output.append(addstr, addstrlen); + const bool hasbraces{str.front() == '{'}; + if(hasbraces) str.remove_prefix(1); + + size_t envend{0}; + while(envend < str.size() && (std::isalnum(str[envend]) || str[envend] == '_')) + ++envend; + if(hasbraces && (envend == str.size() || str[envend] != '}')) + continue; + const std::string envname{str.substr(0, envend)}; + if(hasbraces) ++envend; + str.remove_prefix(envend); + + if(auto envval = al::getenv(envname.c_str())) + output += *envval; } return output; @@ -140,44 +150,43 @@ void LoadConfigFromFile(std::istream &f) if(buffer[0] == '[') { - auto line = const_cast(buffer.data()); - char *section = line+1; - char *endsection; - - endsection = std::strchr(section, ']'); - if(!endsection || section == endsection) + auto endpos = buffer.find(']', 1); + if(endpos == 1 || endpos == std::string::npos) { - ERR(" config parse error: bad line \"%s\"\n", line); + ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); continue; } - if(endsection[1] != 0) + if(buffer[endpos+1] != '\0') { - char *end = endsection+1; - while(std::isspace(*end)) - ++end; - if(*end != 0 && *end != '#') + size_t last{endpos+1}; + while(last < buffer.size() && std::isspace(buffer[last])) + ++last; + + if(last < buffer.size() && buffer[last] != '#') { - ERR(" config parse error: bad line \"%s\"\n", line); + ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); continue; } } - *endsection = 0; + + auto section = std::string_view{buffer}.substr(1, endpos-1); curSection.clear(); - if(al::strcasecmp(section, "general") != 0) + if(al::case_compare(section, "general"sv) != 0) { do { - char *nextp = std::strchr(section, '%'); - if(!nextp) + auto nextp = section.find('%'); + if(nextp == std::string_view::npos) { curSection += section; break; } - curSection.append(section, nextp); - section = nextp; + curSection += section.substr(0, nextp); + section.remove_prefix(nextp); - if(((section[1] >= '0' && section[1] <= '9') || + if(section.size() > 2 && + ((section[1] >= '0' && section[1] <= '9') || (section[1] >= 'a' && section[1] <= 'f') || (section[1] >= 'A' && section[1] <= 'F')) && ((section[2] >= '0' && section[2] <= '9') || @@ -198,19 +207,19 @@ void LoadConfigFromFile(std::istream &f) else if(section[2] >= 'A' && section[2] <= 'F') b |= (section[2]-'A'+0x0a); curSection += static_cast(b); - section += 3; + section.remove_prefix(3); } - else if(section[1] == '%') + else if(section.size() > 1 && section[1] == '%') { curSection += '%'; - section += 2; + section.remove_prefix(2); } else { curSection += '%'; - section += 1; + section.remove_prefix(1); } - } while(*section != 0); + } while(!section.empty()); } continue; @@ -228,16 +237,17 @@ void LoadConfigFromFile(std::istream &f) ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); continue; } - auto keyend = sep++; - while(keyend > 0 && std::isspace(buffer[keyend-1])) - --keyend; - if(!keyend) + auto keypart = std::string_view{buffer}.substr(0, sep++); + while(!keypart.empty() && std::isspace(keypart.back())) + keypart.remove_suffix(1); + if(keypart.empty()) { ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); continue; } - while(sep < buffer.size() && std::isspace(buffer[sep])) - sep++; + auto valpart = std::string_view{buffer}.substr(sep); + while(!valpart.empty() && std::isspace(valpart.front())) + valpart.remove_prefix(1); std::string fullKey; if(!curSection.empty()) @@ -245,20 +255,24 @@ void LoadConfigFromFile(std::istream &f) fullKey += curSection; fullKey += '/'; } - fullKey += buffer.substr(0u, keyend); + fullKey += keypart; - std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}}; - if(value.size() > 1) + if(valpart.size() > size_t{std::numeric_limits::max()}) { - if((value.front() == '"' && value.back() == '"') - || (value.front() == '\'' && value.back() == '\'')) + ERR(" config parse error: value too long in line \"%s\"\n", buffer.c_str()); + continue; + } + if(valpart.size() > 1) + { + if((valpart.front() == '"' && valpart.back() == '"') + || (valpart.front() == '\'' && valpart.back() == '\'')) { - value.pop_back(); - value.erase(value.begin()); + valpart.remove_prefix(1); + valpart.remove_suffix(1); } } - TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str()); + TRACE(" setting '%s' = '%.*s'\n", fullKey.c_str(), al::sizei(valpart), valpart.data()); /* Check if we already have this option set */ auto find_key = [&fullKey](const ConfigEntry &entry) -> bool @@ -266,61 +280,49 @@ void LoadConfigFromFile(std::istream &f) auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key); if(ent != ConfOpts.end()) { - if(!value.empty()) - ent->value = expdup(value.c_str()); + if(!valpart.empty()) + ent->value = expdup(valpart); else ConfOpts.erase(ent); } - else if(!value.empty()) - ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())}); + else if(!valpart.empty()) + ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(valpart)}); } ConfOpts.shrink_to_fit(); } -const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName) +const char *GetConfigValue(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) { - if(!keyName) + if(keyName.empty()) return nullptr; std::string key; - if(blockName && al::strcasecmp(blockName, "general") != 0) + if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0) { key = blockName; - if(devName) - { - key += '/'; - key += devName; - } key += '/'; - key += keyName; } - else + if(!devName.empty()) { - if(devName) - { - key = devName; - key += '/'; - } - key += keyName; + key += devName; + key += '/'; } + key += keyName; auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(), - [&key](const ConfigEntry &entry) -> bool - { return entry.key == key; }); + [&key](const ConfigEntry &entry) -> bool { return entry.key == key; }); if(iter != ConfOpts.cend()) { - TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str()); + TRACE("Found option %s = \"%s\"\n", key.c_str(), iter->value.c_str()); if(!iter->value.empty()) return iter->value.c_str(); return nullptr; } - if(!devName) - { - TRACE("Key %s not found\n", key.c_str()); + if(devName.empty()) return nullptr; - } - return GetConfigValue(nullptr, blockName, keyName); + return GetConfigValue({}, blockName, keyName); } } // namespace @@ -329,33 +331,48 @@ const char *GetConfigValue(const char *devName, const char *blockName, const cha #ifdef _WIN32 void ReadALConfig() { - WCHAR buffer[MAX_PATH]; - if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE) - { - std::string filepath{wstr_to_utf8(buffer)}; - filepath += "\\alsoft.ini"; + namespace fs = std::filesystem; + fs::path path; - TRACE("Loading config %s...\n", filepath.c_str()); - al::ifstream f{filepath}; - if(f.is_open()) - LoadConfigFromFile(f); +#if !defined(_GAMING_XBOX) + { +#if !defined(ALSOFT_UWP) + std::unique_ptr bufstore; + const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, + nullptr, al::out_ptr(bufstore))}; + if(SUCCEEDED(hr)) + { + const std::wstring_view buffer{bufstore.get()}; +#else + winrt::Windows::Storage::ApplicationDataContainer localSettings = winrt::Windows::Storage::ApplicationData::Current().LocalSettings(); + auto bufstore = Windows::Storage::ApplicationData::Current().RoamingFolder().Path(); + std::wstring_view buffer{bufstore}; + { +#endif + path = fs::path{buffer}; + path /= L"alsoft.ini"; + + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) + LoadConfigFromFile(f); + } } +#endif - std::string ppath{GetProcBinary().path}; - if(!ppath.empty()) + path = fs::u8path(GetProcBinary().path); + if(!path.empty()) { - ppath += "\\alsoft.ini"; - TRACE("Loading config %s...\n", ppath.c_str()); - al::ifstream f{ppath}; - if(f.is_open()) + path /= "alsoft.ini"; + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto confpath = al::getenv(L"ALSOFT_CONF")) { - TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str()); - al::ifstream f{*confpath}; - if(f.is_open()) + path = *confpath; + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } } @@ -364,13 +381,12 @@ void ReadALConfig() void ReadALConfig() { - const char *str{"/etc/openal/alsoft.conf"}; + namespace fs = std::filesystem; + fs::path path{"/etc/openal/alsoft.conf"}; - TRACE("Loading config %s...\n", str); - al::ifstream f{str}; - if(f.is_open()) + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); - f.close(); std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")}; /* Go through the list in reverse, since "the order of base directories @@ -378,48 +394,43 @@ void ReadALConfig() * important". Ergo, we need to load the settings from the later dirs * first so that the settings in the earlier dirs override them. */ - std::string fname; while(!confpaths.empty()) { - auto next = confpaths.find_last_of(':'); + auto next = confpaths.rfind(':'); if(next < confpaths.length()) { - fname = confpaths.substr(next+1); + path = fs::path{std::string_view{confpaths}.substr(next+1)}.lexically_normal(); confpaths.erase(next); } else { - fname = confpaths; + path = fs::path{confpaths}.lexically_normal(); confpaths.clear(); } - if(fname.empty() || fname.front() != '/') - WARN("Ignoring XDG config dir: %s\n", fname.c_str()); + if(!path.is_absolute()) + WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str()); else { - if(fname.back() != '/') fname += "/alsoft.conf"; - else fname += "alsoft.conf"; + path /= "alsoft.conf"; - TRACE("Loading config %s...\n", fname.c_str()); - f = al::ifstream{fname}; - if(f.is_open()) + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } - fname.clear(); } #ifdef __APPLE__ CFBundleRef mainBundle = CFBundleGetMainBundle(); if(mainBundle) { - unsigned char fileName[PATH_MAX]; - CFURLRef configURL; + CFURLRef configURL{CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), + nullptr)}; - if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) && - CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName))) + std::array fileName{}; + if(configURL && CFURLGetFileSystemRepresentation(configURL, true, fileName.data(), fileName.size())) { - f = al::ifstream{reinterpret_cast(fileName)}; - if(f.is_open()) + if(std::ifstream f{reinterpret_cast(fileName.data())}; f.is_open()) LoadConfigFromFile(f); } } @@ -427,102 +438,100 @@ void ReadALConfig() if(auto homedir = al::getenv("HOME")) { - fname = *homedir; - if(fname.back() != '/') fname += "/.alsoftrc"; - else fname += ".alsoftrc"; + path = *homedir; + path /= ".alsoftrc"; - TRACE("Loading config %s...\n", fname.c_str()); - f = al::ifstream{fname}; - if(f.is_open()) + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto configdir = al::getenv("XDG_CONFIG_HOME")) { - fname = *configdir; - if(fname.back() != '/') fname += "/alsoft.conf"; - else fname += "alsoft.conf"; + path = *configdir; + path /= "alsoft.conf"; } else { - fname.clear(); + path.clear(); if(auto homedir = al::getenv("HOME")) { - fname = *homedir; - if(fname.back() != '/') fname += "/.config/alsoft.conf"; - else fname += ".config/alsoft.conf"; + path = *homedir; + path /= ".config/alsoft.conf"; } } - if(!fname.empty()) + if(!path.empty()) { - TRACE("Loading config %s...\n", fname.c_str()); - f = al::ifstream{fname}; - if(f.is_open()) + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } - std::string ppath{GetProcBinary().path}; - if(!ppath.empty()) + path = GetProcBinary().path; + if(!path.empty()) { - if(ppath.back() != '/') ppath += "/alsoft.conf"; - else ppath += "alsoft.conf"; + path /= "alsoft.conf"; - TRACE("Loading config %s...\n", ppath.c_str()); - f = al::ifstream{ppath}; - if(f.is_open()) + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto confname = al::getenv("ALSOFT_CONF")) { TRACE("Loading config %s...\n", confname->c_str()); - f = al::ifstream{*confname}; - if(f.is_open()) + if(std::ifstream f{*confname}; f.is_open()) LoadConfigFromFile(f); } } #endif -al::optional ConfigValueStr(const char *devName, const char *blockName, const char *keyName) +std::optional ConfigValueStr(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) return val; - return al::nullopt; + return std::nullopt; } -al::optional ConfigValueInt(const char *devName, const char *blockName, const char *keyName) +std::optional ConfigValueInt(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) return static_cast(std::strtol(val, nullptr, 0)); - return al::nullopt; + return std::nullopt; } -al::optional ConfigValueUInt(const char *devName, const char *blockName, const char *keyName) +std::optional ConfigValueUInt(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) return static_cast(std::strtoul(val, nullptr, 0)); - return al::nullopt; + return std::nullopt; } -al::optional ConfigValueFloat(const char *devName, const char *blockName, const char *keyName) +std::optional ConfigValueFloat(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) return std::strtof(val, nullptr); - return al::nullopt; + return std::nullopt; } -al::optional ConfigValueBool(const char *devName, const char *blockName, const char *keyName) +std::optional ConfigValueBool(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 - || al::strcasecmp(val, "true")==0 || atoi(val) != 0; - return al::nullopt; + || al::strcasecmp(val, "true") == 0 || atoi(val) != 0; + return std::nullopt; } -bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def) +bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName, bool def) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 - || al::strcasecmp(val, "true") == 0 || atoi(val) != 0); + return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 + || al::strcasecmp(val, "true") == 0 || atoi(val) != 0; return def; } diff --git a/Engine/lib/openal-soft/alc/alconfig.h b/Engine/lib/openal-soft/alc/alconfig.h index df2830ccc..e7daac28e 100644 --- a/Engine/lib/openal-soft/alc/alconfig.h +++ b/Engine/lib/openal-soft/alc/alconfig.h @@ -1,18 +1,25 @@ #ifndef ALCONFIG_H #define ALCONFIG_H +#include #include +#include -#include "aloptional.h" void ReadALConfig(); -bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def); +bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName, bool def); -al::optional ConfigValueStr(const char *devName, const char *blockName, const char *keyName); -al::optional ConfigValueInt(const char *devName, const char *blockName, const char *keyName); -al::optional ConfigValueUInt(const char *devName, const char *blockName, const char *keyName); -al::optional ConfigValueFloat(const char *devName, const char *blockName, const char *keyName); -al::optional ConfigValueBool(const char *devName, const char *blockName, const char *keyName); +std::optional ConfigValueStr(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName); +std::optional ConfigValueInt(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName); +std::optional ConfigValueUInt(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName); +std::optional ConfigValueFloat(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName); +std::optional ConfigValueBool(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName); #endif /* ALCONFIG_H */ diff --git a/Engine/lib/openal-soft/alc/alu.cpp b/Engine/lib/openal-soft/alc/alu.cpp index e9ad68b10..7f8503dce 100644 --- a/Engine/lib/openal-soft/alc/alu.cpp +++ b/Engine/lib/openal-soft/alc/alu.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -36,7 +37,7 @@ #include #include #include -#include +#include #include #include "almalloc.h" @@ -76,7 +77,6 @@ #include "opthelpers.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" #include "vecmat.h" #include "vector.h" @@ -107,15 +107,14 @@ namespace { using uint = unsigned int; using namespace std::chrono; - -using namespace std::placeholders; +using namespace std::string_view_literals; float InitConeScale() { float ret{1.0f}; if(auto optval = al::getenv("__ALSOFT_HALF_ANGLE_CONES")) { - if(al::strcasecmp(optval->c_str(), "true") == 0 + if(al::case_compare(*optval, "true"sv) == 0 || strtol(optval->c_str(), nullptr, 0) == 1) ret *= 0.5f; } @@ -135,19 +134,14 @@ float ZScale{1.0f}; float NfcScale{1.0f}; -struct ChanMap { - Channel channel; - float angle; - float elevation; -}; - using HrtfDirectMixerFunc = void(*)(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, float *TempBuf, - HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize); + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo); HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_}; -inline HrtfDirectMixerFunc SelectHrtfMixer(void) +inline HrtfDirectMixerFunc SelectHrtfMixer() { #ifdef HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) @@ -170,7 +164,7 @@ inline void BsincPrepare(const uint increment, BsincState *state, const BSincTab if(increment > MixerFracOne) { sf = MixerFracOne/static_cast(increment) - table->scaleBase; - sf = maxf(0.0f, BSincScaleCount*sf*table->scaleRange - 1.0f); + sf = std::max(0.0f, BSincScaleCount*sf*table->scaleRange - 1.0f); si = float2uint(sf); /* The interpolation factor is fit to this diagonally-symmetric curve * to reduce the transition ripple caused by interpolating different @@ -182,7 +176,7 @@ inline void BsincPrepare(const uint increment, BsincState *state, const BSincTab state->sf = sf; state->m = table->m[si]; state->l = (state->m/2) - 1; - state->filter = table->Tab + table->filterOffset[si]; + state->filter = table->Tab.subspan(table->filterOffset[si]); } inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) @@ -205,11 +199,20 @@ inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) return Resample_; #endif return Resample_; - case Resampler::Cubic: + case Resampler::Spline: + case Resampler::Gaussian: #ifdef HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif +#ifdef HAVE_SSE4_1 + if((CPUCapFlags&CPU_CAP_SSE4_1)) + return Resample_; +#endif +#ifdef HAVE_SSE2 + if((CPUCapFlags&CPU_CAP_SSE2)) + return Resample_; +#endif #ifdef HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_; @@ -255,7 +258,7 @@ void aluInit(CompatFlagBitset flags, const float nfcscale) YScale = flags.test(CompatFlags::ReverseY) ? -1.0f : 1.0f; ZScale = flags.test(CompatFlags::ReverseZ) ? -1.0f : 1.0f; - NfcScale = clampf(nfcscale, 0.0001f, 10000.0f); + NfcScale = std::clamp(nfcscale, 0.0001f, 10000.0f); } @@ -266,16 +269,19 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState case Resampler::Point: case Resampler::Linear: break; - case Resampler::Cubic: - state->cubic.filter = gCubicSpline.Tab.data(); + case Resampler::Spline: + state->emplace(al::span{gSplineFilter.mTable}); + break; + case Resampler::Gaussian: + state->emplace(al::span{gGaussianFilter.mTable}); break; case Resampler::FastBSinc12: case Resampler::BSinc12: - BsincPrepare(increment, &state->bsinc, &gBSinc12); + BsincPrepare(increment, &state->emplace(), &gBSinc12); break; case Resampler::FastBSinc24: case Resampler::BSinc24: - BsincPrepare(increment, &state->bsinc, &gBSinc24); + BsincPrepare(increment, &state->emplace(), &gBSinc24); break; } return SelectResampler(resampler, increment); @@ -285,34 +291,33 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState void DeviceBase::ProcessHrtf(const size_t SamplesToDo) { /* HRTF is stereo output only. */ - const uint lidx{RealOut.ChannelIndex[FrontLeft]}; - const uint ridx{RealOut.ChannelIndex[FrontRight]}; + const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; + const size_t ridx{RealOut.ChannelIndex[FrontRight]}; MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData, - mHrtfState->mTemp.data(), mHrtfState->mChannels.data(), mHrtfState->mIrSize, SamplesToDo); + mHrtfState->mTemp, mHrtfState->mChannels, mHrtfState->mIrSize, SamplesToDo); } void DeviceBase::ProcessAmbiDec(const size_t SamplesToDo) { - AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo); + AmbiDecoder->process(RealOut.Buffer, Dry.Buffer, SamplesToDo); } void DeviceBase::ProcessAmbiDecStablized(const size_t SamplesToDo) { /* Decode with front image stablization. */ - const uint lidx{RealOut.ChannelIndex[FrontLeft]}; - const uint ridx{RealOut.ChannelIndex[FrontRight]}; - const uint cidx{RealOut.ChannelIndex[FrontCenter]}; + const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; + const size_t ridx{RealOut.ChannelIndex[FrontRight]}; + const size_t cidx{RealOut.ChannelIndex[FrontCenter]}; - AmbiDecoder->processStablize(RealOut.Buffer, Dry.Buffer.data(), lidx, ridx, cidx, - SamplesToDo); + AmbiDecoder->processStablize(RealOut.Buffer, Dry.Buffer, lidx, ridx, cidx, SamplesToDo); } void DeviceBase::ProcessUhj(const size_t SamplesToDo) { /* UHJ is stereo output only. */ - const uint lidx{RealOut.ChannelIndex[FrontLeft]}; - const uint ridx{RealOut.ChannelIndex[FrontRight]}; + const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; + const size_t ridx{RealOut.ChannelIndex[FrontRight]}; /* Encode to stereo-compatible 2-channel UHJ output. */ mUhjEncoder->encode(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), @@ -322,15 +327,14 @@ void DeviceBase::ProcessUhj(const size_t SamplesToDo) void DeviceBase::ProcessBs2b(const size_t SamplesToDo) { /* First, decode the ambisonic mix to the "real" output. */ - AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo); + AmbiDecoder->process(RealOut.Buffer, Dry.Buffer, SamplesToDo); /* BS2B is stereo output only. */ - const uint lidx{RealOut.ChannelIndex[FrontLeft]}; - const uint ridx{RealOut.ChannelIndex[FrontRight]}; + const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; + const size_t 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); + Bs2b->cross_feed(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), SamplesToDo); } @@ -356,48 +360,49 @@ inline uint dither_rng(uint *seed) noexcept void UpsampleBFormatTransform( const al::span,MaxAmbiChannels> output, const al::span> upsampler, - const al::span,MaxAmbiChannels> rotator, size_t coeffs_order) + const al::span,MaxAmbiChannels> rotator, + size_t ambi_order) { - const size_t num_chans{AmbiChannelsFromOrder(coeffs_order)}; + const size_t num_chans{AmbiChannelsFromOrder(ambi_order)}; for(size_t i{0};i < upsampler.size();++i) output[i].fill(0.0f); for(size_t i{0};i < upsampler.size();++i) { for(size_t k{0};k < num_chans;++k) { - float *RESTRICT out{output[i].data()}; + const float a{upsampler[i][k]}; /* Write the full number of channels. The compiler will have an * easier time optimizing if it has a fixed length. */ - for(size_t j{0};j < MaxAmbiChannels;++j) - out[j] += upsampler[i][k] * rotator[k][j]; + std::transform(rotator[k].cbegin(), rotator[k].cend(), output[i].cbegin(), + output[i].begin(), [a](float rot, float dst) noexcept { return rot*a + dst; }); } } } -inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept +constexpr auto GetAmbiScales(AmbiScaling scaletype) noexcept { switch(scaletype) { - case AmbiScaling::FuMa: return AmbiScale::FromFuMa(); - case AmbiScaling::SN3D: return AmbiScale::FromSN3D(); - case AmbiScaling::UHJ: return AmbiScale::FromUHJ(); + case AmbiScaling::FuMa: return al::span{AmbiScale::FromFuMa}; + case AmbiScaling::SN3D: return al::span{AmbiScale::FromSN3D}; + case AmbiScaling::UHJ: return al::span{AmbiScale::FromUHJ}; case AmbiScaling::N3D: break; } - return AmbiScale::FromN3D(); + return al::span{AmbiScale::FromN3D}; } -inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept +constexpr auto GetAmbiLayout(AmbiLayout layouttype) noexcept { - if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa(); - return AmbiIndex::FromACN(); + if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa}; + return al::span{AmbiIndex::FromACN}; } -inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept +constexpr auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept { - if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D(); - return AmbiIndex::FromACN2D(); + if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa2D}; + return al::span{AmbiIndex::FromACN2D}; } @@ -457,14 +462,14 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa slot->Target = props->Target; slot->EffectType = props->Type; slot->mEffectProps = props->Props; - if(props->Type == EffectSlotType::Reverb || props->Type == EffectSlotType::EAXReverb) + if(auto *reverbprops = std::get_if(&props->Props)) { - slot->RoomRolloff = props->Props.Reverb.RoomRolloffFactor; - slot->DecayTime = props->Props.Reverb.DecayTime; - slot->DecayLFRatio = props->Props.Reverb.DecayLFRatio; - slot->DecayHFRatio = props->Props.Reverb.DecayHFRatio; - slot->DecayHFLimit = props->Props.Reverb.DecayHFLimit; - slot->AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF; + slot->RoomRolloff = reverbprops->RoomRolloffFactor; + slot->DecayTime = reverbprops->DecayTime; + slot->DecayLFRatio = reverbprops->DecayLFRatio; + slot->DecayHFRatio = reverbprops->DecayHFRatio; + slot->DecayHFLimit = reverbprops->DecayHFLimit; + slot->AirAbsorptionGainHF = reverbprops->AirAbsorptionGainHF; } else { @@ -490,9 +495,8 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa auto evt_vec = ring->getWriteVector(); if(evt_vec.first.len > 0) LIKELY { - AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), - AsyncEvent::ReleaseEffectState)}; - evt->u.mEffectState = oldstate; + auto &evt = InitAsyncEvent(evt_vec.first.buf); + evt.mEffectState = oldstate; ring->writeAdvance(1); } else @@ -506,42 +510,90 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa } } - AtomicReplaceHead(context->mFreeEffectslotProps, props); + AtomicReplaceHead(context->mFreeEffectSlotProps, props); - EffectTarget output; - if(EffectSlot *target{slot->Target}) - output = EffectTarget{&target->Wet, nullptr}; - else + const auto output = [slot,context]() -> EffectTarget { + if(EffectSlot *target{slot->Target}) + return EffectTarget{&target->Wet, nullptr}; DeviceBase *device{context->mDevice}; - output = EffectTarget{&device->Dry, &device->RealOut}; - } + return EffectTarget{&device->Dry, &device->RealOut}; + }(); state->update(context, slot, &slot->mEffectProps, output); return true; } -/* Scales the given azimuth toward the side (+/- pi/2 radians) for positions in - * front. +/* Scales the azimuth of the given vector by 3 if it's in front. Effectively + * scales +/-30 degrees to +/-90 degrees, leaving > +90 and < -90 alone. */ -inline float ScaleAzimuthFront(float azimuth, float scale) +inline std::array ScaleAzimuthFront3(std::array pos) { - const float abs_azi{std::fabs(azimuth)}; - if(!(abs_azi >= al::numbers::pi_v*0.5f)) - return std::copysign(minf(abs_azi*scale, al::numbers::pi_v*0.5f), azimuth); - return azimuth; + if(pos[2] < 0.0f) + { + /* Normalize the length of the x,z components for a 2D vector of the + * azimuth angle. Negate Z since {0,0,-1} is angle 0. + */ + const float len2d{std::sqrt(pos[0]*pos[0] + pos[2]*pos[2])}; + float x{pos[0] / len2d}; + float z{-pos[2] / len2d}; + + /* Z > cos(pi/6) = -30 < azimuth < 30 degrees. */ + if(z > 0.866025403785f) + { + /* Triple the angle represented by x,z. */ + x = x*3.0f - x*x*x*4.0f; + z = z*z*z*4.0f - z*3.0f; + + /* Scale the vector back to fit in 3D. */ + pos[0] = x * len2d; + pos[2] = -z * len2d; + } + else + { + /* If azimuth >= 30 degrees, clamp to 90 degrees. */ + pos[0] = std::copysign(len2d, pos[0]); + pos[2] = 0.0f; + } + } + return pos; } -/* Wraps the given value in radians to stay between [-pi,+pi] */ -inline float WrapRadians(float r) +/* Scales the azimuth of the given vector by 1.5 (3/2) if it's in front. */ +inline std::array ScaleAzimuthFront3_2(std::array pos) { - static constexpr float Pi{al::numbers::pi_v}; - static constexpr float Pi2{Pi*2.0f}; - if(r > Pi) return std::fmod(Pi+r, Pi2) - Pi; - if(r < -Pi) return Pi - std::fmod(Pi-r, Pi2); - return r; + if(pos[2] < 0.0f) + { + const float len2d{std::sqrt(pos[0]*pos[0] + pos[2]*pos[2])}; + float x{pos[0] / len2d}; + float z{-pos[2] / len2d}; + + /* Z > cos(pi/3) = -60 < azimuth < 60 degrees. */ + if(z > 0.5f) + { + /* Halve the angle represented by x,z. */ + x = std::copysign(std::sqrt((1.0f - z) * 0.5f), x); + z = std::sqrt((1.0f + z) * 0.5f); + + /* Triple the angle represented by x,z. */ + x = x*3.0f - x*x*x*4.0f; + z = z*z*z*4.0f - z*3.0f; + + /* Scale the vector back to fit in 3D. */ + pos[0] = x * len2d; + pos[2] = -z * len2d; + } + else + { + /* If azimuth >= 60 degrees, clamp to 90 degrees. */ + pos[0] = std::copysign(len2d, pos[0]); + pos[2] = 0.0f; + } + } + return pos; } + /* Begin ambisonic rotation helpers. * * Rotating first-order B-Format just needs a straight-forward X/Y/Z rotation @@ -561,19 +613,18 @@ inline float WrapRadians(float r) * precomputed since they're constant. The second-order coefficients are * followed by the third-order coefficients, etc. */ -template -constexpr size_t CalcRotatorSize() -{ return (L*2 + 1)*(L*2 + 1) + CalcRotatorSize(); } - -template<> constexpr size_t CalcRotatorSize<0>() = delete; -template<> constexpr size_t CalcRotatorSize<1>() = delete; -template<> constexpr size_t CalcRotatorSize<2>() { return 5*5; } +constexpr size_t CalcRotatorSize(size_t l) noexcept +{ + if(l >= 2) + return (l*2 + 1)*(l*2 + 1) + CalcRotatorSize(l-1); + return 0; +} struct RotatorCoeffs { struct CoeffValues { float u, v, w; }; - std::array()> mCoeffs{}; + std::array mCoeffs{}; RotatorCoeffs() { @@ -585,17 +636,38 @@ struct RotatorCoeffs { { for(int m{-l};m <= l;++m) { - // compute u,v,w terms of Eq.8.1 (Table I) - const bool d{m == 0}; // the delta function d_m0 - const float denom{static_cast((std::abs(n) == l) ? - (2*l) * (2*l - 1) : (l*l - n*n))}; + /* compute u,v,w terms of Eq.8.1 (Table I) + * + * const bool d{m == 0}; // the delta function d_m0 + * const double denom{(std::abs(n) == l) ? + * (2*l) * (2*l - 1) : (l*l - n*n)}; + * + * const int abs_m{std::abs(m)}; + * coeffs->u = std::sqrt((l*l - m*m) / denom); + * coeffs->v = std::sqrt((l+abs_m-1) * (l+abs_m) / denom) * + * (1.0+d) * (1.0 - 2.0*d) * 0.5; + * coeffs->w = std::sqrt((l-abs_m-1) * (l-abs_m) / denom) * + * (1.0-d) * -0.5; + */ - const int abs_m{std::abs(m)}; - coeffs->u = std::sqrt(static_cast(l*l - m*m)/denom); - coeffs->v = std::sqrt(static_cast(l+abs_m-1) * - static_cast(l+abs_m) / denom) * (1.0f+d) * (1.0f - 2.0f*d) * 0.5f; - coeffs->w = std::sqrt(static_cast(l-abs_m-1) * - static_cast(l-abs_m) / denom) * (1.0f-d) * -0.5f; + const double denom{static_cast((std::abs(n) == l) ? + (2*l) * (2*l - 1) : (l*l - n*n))}; + + if(m == 0) + { + coeffs->u = static_cast(std::sqrt(l * l / denom)); + coeffs->v = static_cast(std::sqrt((l-1) * l / denom) * -1.0); + coeffs->w = 0.0f; + } + else + { + const int abs_m{std::abs(m)}; + coeffs->u = static_cast(std::sqrt((l*l - m*m) / denom)); + coeffs->v = static_cast(std::sqrt((l+abs_m-1) * (l+abs_m) / denom) * + 0.5); + coeffs->w = static_cast(std::sqrt((l-abs_m-1) * (l-abs_m) / denom) * + -0.5); + } ++coeffs; } } @@ -617,16 +689,16 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) auto P = [](const int i, const int l, const int a, const int n, const size_t last_band, const AmbiRotateMatrix &R) { - const float ri1{ R[ 1+2][static_cast(i+2)]}; - const float rim1{R[-1+2][static_cast(i+2)]}; - const float ri0{ R[ 0+2][static_cast(i+2)]}; + const float ri1{ R[ 1+2][static_cast(i+2_z)]}; + const float rim1{R[-1+2][static_cast(i+2_z)]}; + const float ri0{ R[ 0+2][static_cast(i+2_z)]}; const size_t y{last_band + static_cast(a+l-1)}; if(n == -l) - return ri1*R[last_band][y] + rim1*R[last_band + static_cast(l-1)*2][y]; + return ri1*R[last_band][y] + rim1*R[last_band + static_cast(l-1_z)*2][y]; if(n == l) - return ri1*R[last_band + static_cast(l-1)*2][y] - rim1*R[last_band][y]; - return ri0*R[last_band + static_cast(n+l-1)][y]; + return ri1*R[last_band + static_cast(l-1_z)*2][y] - rim1*R[last_band][y]; + return ri0*R[last_band + static_cast(l-1_z+n)][y]; }; auto U = [P](const int l, const int m, const int n, const size_t last_band, @@ -679,73 +751,89 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) float r{0.0f}; // computes Eq.8.1 - const float u{coeffs->u}; - if(u != 0.0f) r += u * U(l, m, n, last_band, matrix); - const float v{coeffs->v}; - if(v != 0.0f) r += v * V(l, m, n, last_band, matrix); - const float w{coeffs->w}; - if(w != 0.0f) r += w * W(l, m, n, last_band, matrix); + if(const float u{coeffs->u}; u != 0.0f) + r += u * U(l, m, n, last_band, matrix); + if(const float v{coeffs->v}; v != 0.0f) + r += v * V(l, m, n, last_band, matrix); + if(const float w{coeffs->w}; w != 0.0f) + r += w * W(l, m, n, last_band, matrix); matrix[y][x] = r; ++coeffs; } } last_band = band_idx; - band_idx += static_cast(l)*size_t{2} + 1; + band_idx += static_cast(l)*2_uz + 1; } } /* End ambisonic rotation helpers. */ -constexpr float Deg2Rad(float x) noexcept -{ return static_cast(al::numbers::pi / 180.0 * x); } +constexpr float sin30{0.5f}; +constexpr float cos30{0.866025403785f}; +constexpr float sin45{al::numbers::sqrt2_v*0.5f}; +constexpr float cos45{al::numbers::sqrt2_v*0.5f}; +constexpr float sin110{ 0.939692620786f}; +constexpr float cos110{-0.342020143326f}; + +struct ChanPosMap { + Channel channel; + std::array pos; +}; + struct GainTriplet { float Base, HF, LF; }; void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, const float zpos, const float Distance, const float Spread, const GainTriplet &DryGain, - const al::span WetGain, EffectSlot *(&SendSlots)[MAX_SENDS], - const VoiceProps *props, const ContextParams &Context, DeviceBase *Device) + const al::span WetGain, + const al::span SendSlots, const VoiceProps *props, + const ContextParams &Context, DeviceBase *Device) { - static constexpr 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 constexpr std::array MonoMap{ + ChanPosMap{FrontCenter, std::array{0.0f, 0.0f, -1.0f}} + }; + static constexpr std::array RearMap{ + ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, + ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, + }; + static constexpr std::array QuadMap{ + ChanPosMap{FrontLeft, std::array{-sin45, 0.0f, -cos45}}, + ChanPosMap{FrontRight, std::array{ sin45, 0.0f, -cos45}}, + ChanPosMap{BackLeft, std::array{-sin45, 0.0f, cos45}}, + ChanPosMap{BackRight, std::array{ sin45, 0.0f, cos45}}, + }; + static constexpr std::array X51Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{SideLeft, std::array{-sin110, 0.0f, -cos110}}, + ChanPosMap{SideRight, std::array{ sin110, 0.0f, -cos110}}, + }; + static constexpr std::array X61Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{BackCenter, std::array{ 0.0f, 0.0f, 1.0f}}, + ChanPosMap{SideLeft, std::array{-1.0f, 0.0f, 0.0f}}, + ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}}, + }; + static constexpr std::array X71Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, + ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, + ChanPosMap{SideLeft, std::array{ -1.0f, 0.0f, 0.0f}}, + ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}}, }; - ChanMap StereoMap[2]{ - { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) } + std::array StereoMap{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, }; const auto Frequency = static_cast(Device->Frequency); @@ -762,47 +850,84 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con [](SendParams ¶ms) -> void { params.Gains.Target.fill(0.0f); }); } - DirectMode DirectChannels{props->DirectChannels}; - const ChanMap *chans{nullptr}; - switch(voice->mFmtChannels) + const auto getChans = [props,&StereoMap](FmtChannels chanfmt) noexcept + -> std::pair> { - case FmtMono: - chans = MonoMap; - /* Mono buffers are never played direct. */ - DirectChannels = DirectMode::Off; - break; - - case FmtStereo: - if(DirectChannels == DirectMode::Off) + switch(chanfmt) { - /* Convert counter-clockwise to clock-wise, and wrap between - * [-pi,+pi]. - */ - StereoMap[0].angle = WrapRadians(-props->StereoPan[0]); - StereoMap[1].angle = WrapRadians(-props->StereoPan[1]); + case FmtMono: + /* Mono buffers are never played direct. */ + return {DirectMode::Off, al::span{MonoMap}}; + + case FmtStereo: + case FmtMonoDup: + if(props->DirectChannels == DirectMode::Off) + { + for(size_t i{0};i < 2;++i) + { + /* StereoPan is counter-clockwise in radians. */ + const float a{props->StereoPan[i]}; + StereoMap[i].pos[0] = -std::sin(a); + StereoMap[i].pos[2] = -std::cos(a); + } + } + return {props->DirectChannels, al::span{StereoMap}}; + + case FmtRear: return {props->DirectChannels, al::span{RearMap}}; + case FmtQuad: return {props->DirectChannels, al::span{QuadMap}}; + case FmtX51: return {props->DirectChannels, al::span{X51Map}}; + case FmtX61: return {props->DirectChannels, al::span{X61Map}}; + case FmtX71: return {props->DirectChannels, al::span{X71Map}}; + + case FmtBFormat2D: + case FmtBFormat3D: + case FmtUHJ2: + case FmtUHJ3: + case FmtUHJ4: + case FmtSuperStereo: + return {DirectMode::Off, {}}; } - chans = StereoMap; - break; - - case FmtRear: chans = RearMap; break; - case FmtQuad: chans = QuadMap; break; - case FmtX51: chans = X51Map; break; - case FmtX61: chans = X61Map; break; - case FmtX71: chans = X71Map; break; - - case FmtBFormat2D: - case FmtBFormat3D: - case FmtUHJ2: - case FmtUHJ3: - case FmtUHJ4: - case FmtSuperStereo: - DirectChannels = DirectMode::Off; - break; - } + return {props->DirectChannels, {}}; + }; + const auto [DirectChannels,chans] = getChans(voice->mFmtChannels); voice->mFlags.reset(VoiceHasHrtf).reset(VoiceHasNfc); if(auto *decoder{voice->mDecoder.get()}) - decoder->mWidthControl = minf(props->EnhWidth, 0.7f); + decoder->mWidthControl = std::min(props->EnhWidth, 0.7f); + + const float lgain{std::min(1.0f-props->Panning, 1.0f)}; + const float rgain{std::min(1.0f+props->Panning, 1.0f)}; + const float mingain{std::min(lgain, rgain)}; + auto SelectChannelGain = [lgain,rgain,mingain](const Channel chan) noexcept + { + switch(chan) + { + case FrontLeft: return lgain; + case FrontRight: return rgain; + case FrontCenter: break; + case LFE: break; + case BackLeft: return lgain; + case BackRight: return rgain; + case BackCenter: break; + case SideLeft: return lgain; + case SideRight: return rgain; + case TopCenter: break; + case TopFrontLeft: return lgain; + case TopFrontCenter: break; + case TopFrontRight: return rgain; + case TopBackLeft: return lgain; + case TopBackCenter: break; + case TopBackRight: return rgain; + case BottomFrontLeft: return lgain; + case BottomFrontRight: return rgain; + case BottomBackLeft: return lgain; + case BottomBackRight: return rgain; + case Aux0: case Aux1: case Aux2: case Aux3: case Aux4: case Aux5: case Aux6: case Aux7: + case Aux8: case Aux9: case Aux10: case Aux11: case Aux12: case Aux13: case Aux14: + case Aux15: case MaxChannels: break; + } + return mingain; + }; if(IsAmbisonic(voice->mFmtChannels)) { @@ -824,7 +949,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* Clamp the distance for really close sources, to prevent * excessive bass. */ - const float mdist{maxf(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; + const float mdist{std::max(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Only need to adjust the first channel of a B-Format source. */ @@ -845,32 +970,21 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con auto calc_coeffs = [xpos,ypos,zpos](RenderMode mode) { if(mode != RenderMode::Pairwise) - return CalcDirectionCoeffs({xpos, ypos, zpos}); - - /* Clamp Y, in case rounding errors caused it to end up outside - * of -1...+1. - */ - const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - /* Negate Z for right-handed coords with -Z in front. */ - const float az{std::atan2(xpos, -zpos)}; - - /* A scalar of 1.5 for plain stereo results in +/-60 degrees - * being moved to +/-90 degrees for direct right and left - * speaker responses. - */ - return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, 0.0f); + return CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, 0.0f); + const auto pos = ScaleAzimuthFront3_2(std::array{xpos, ypos, zpos}); + return CalcDirectionCoeffs(pos, 0.0f); }; - auto&& scales = GetAmbiScales(voice->mAmbiScaling); + const auto scales = GetAmbiScales(voice->mAmbiScaling); auto coeffs = calc_coeffs(Device->mRenderMode); if(!(coverage > 0.0f)) { - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base*scales[0], + ComputePanGains(&Device->Dry, coeffs, DryGain.Base*scales[0], voice->mChans[0].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base*scales[0], + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base*scales[0], voice->mChans[0].mWetParams[i].Gains.Target); } } @@ -922,25 +1036,25 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { if(voice->mAmbiOrder == 1) { - auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? - AmbiScale::FirstOrder2DUp : AmbiScale::FirstOrderUp; + const auto upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + al::span{AmbiScale::FirstOrder2DUp} : al::span{AmbiScale::FirstOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else if(voice->mAmbiOrder == 2) { - auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? - AmbiScale::SecondOrder2DUp : AmbiScale::SecondOrderUp; + const auto upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + al::span{AmbiScale::SecondOrder2DUp} : al::span{AmbiScale::SecondOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else if(voice->mAmbiOrder == 3) { - auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? - AmbiScale::ThirdOrder2DUp : AmbiScale::ThirdOrderUp; + const auto upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + al::span{AmbiScale::ThirdOrder2DUp} : al::span{AmbiScale::ThirdOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else if(voice->mAmbiOrder == 4) { - auto&& upsampler = AmbiScale::FourthOrder2DUp; + const auto upsampler = al::span{AmbiScale::FourthOrder2DUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else @@ -952,9 +1066,9 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* Convert the rotation matrix for input ordering and scaling, and * whether input is 2D or 3D. */ - const uint8_t *index_map{Is2DAmbisonic(voice->mFmtChannels) ? - GetAmbi2DLayout(voice->mAmbiLayout).data() : - GetAmbiLayout(voice->mAmbiLayout).data()}; + const auto index_map = Is2DAmbisonic(voice->mFmtChannels) ? + GetAmbi2DLayout(voice->mAmbiLayout).subspan(0) : + GetAmbiLayout(voice->mAmbiLayout).subspan(0); /* Scale the panned W signal inversely to coverage (full coverage * means no panned signal), and according to the channel scaling. @@ -971,16 +1085,17 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * to the coverage amount) with the directional pan. For all * other channels, use just the (scaled) B-Format signal. */ - for(size_t x{0};x < MaxAmbiChannels;++x) - coeffs[x] += mixmatrix[acn][x] * scale; + std::transform(mixmatrix[acn].cbegin(), mixmatrix[acn].cend(), coeffs.begin(), + coeffs.begin(), [scale](const float in, const float coeff) noexcept + { return in*scale + coeff; }); - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + ComputePanGains(&Device->Dry, coeffs, DryGain.Base, voice->mChans[c].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } @@ -997,13 +1112,13 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con for(size_t c{0};c < num_channels;c++) { - uint idx{Device->channelIdxByName(chans[c].channel)}; - if(idx != InvalidChannelIndex) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; + const float pangain{SelectChannelGain(chans[c].channel)}; + if(uint idx{Device->channelIdxByName(chans[c].channel)}; idx != InvalidChannelIndex) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain; else if(DirectChannels == DirectMode::RemixMismatch) { - auto match_channel = [chans,c](const InputRemixMap &map) noexcept -> bool - { return chans[c].channel == map.channel; }; + auto match_channel = [channel=chans[c].channel](const InputRemixMap &map) noexcept + { return channel == map.channel; }; auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(), Device->RealOut.RemixMap.cend(), match_channel); if(remap != Device->RealOut.RemixMap.cend()) @@ -1012,8 +1127,8 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { idx = Device->channelIdxByName(target.channel); if(idx != InvalidChannelIndex) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * - target.mix; + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain + * target.mix; } } } @@ -1028,12 +1143,13 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con if(chans[c].channel == LFE) continue; - const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f); + const float pangain{SelectChannelGain(chans[c].channel)}; + const auto coeffs = CalcDirectionCoeffs(chans[c].pos, 0.0f); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1047,21 +1163,21 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con if(Distance > std::numeric_limits::epsilon()) { - const float src_ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const float src_az{std::atan2(xpos, -zpos)}; - if(voice->mFmtChannels == FmtMono) { + const float src_ev{std::asin(std::clamp(ypos, -1.0f, 1.0f))}; + const float src_az{std::atan2(xpos, -zpos)}; + Device->mHrtf->getCoeffs(src_ev, src_az, Distance*NfcScale, Spread, voice->mChans[0].mDryParams.Hrtf.Target.Coeffs, voice->mChans[0].mDryParams.Hrtf.Target.Delay); voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base; - const auto coeffs = CalcAngleCoeffs(src_az, src_ev, Spread); + const auto coeffs = CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, Spread); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base, voice->mChans[0].mWetParams[i].Gains.Target); } } @@ -1071,34 +1187,39 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* Skip LFE */ if(chans[c].channel == LFE) continue; + const float pangain{SelectChannelGain(chans[c].channel)}; /* Warp the channel position toward the source position as the * source spread decreases. With no spread, all channels are at * the source position, at full spread (pi*2), each channel is * left unchanged. */ - const float ev{lerpf(src_ev, chans[c].elevation, inv_pi_v/2.0f * Spread)}; + const float a{1.0f - (inv_pi_v/2.0f)*Spread}; + std::array pos{ + lerpf(chans[c].pos[0], xpos, a), + lerpf(chans[c].pos[1], ypos, a), + lerpf(chans[c].pos[2], zpos, a)}; + const float len{std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2])}; + if(len < 1.0f) + { + pos[0] /= len; + pos[1] /= len; + pos[2] /= len; + } - float az{chans[c].angle - src_az}; - if(az < -pi_v) az += pi_v*2.0f; - else if(az > pi_v) az -= pi_v*2.0f; - - az *= inv_pi_v/2.0f * Spread; - - az += src_az; - if(az < -pi_v) az += pi_v*2.0f; - else if(az > pi_v) az -= pi_v*2.0f; + const float ev{std::asin(std::clamp(pos[1], -1.0f, 1.0f))}; + const float az{std::atan2(pos[0], -pos[2])}; Device->mHrtf->getCoeffs(ev, az, Distance*NfcScale, 0.0f, voice->mChans[c].mDryParams.Hrtf.Target.Coeffs, voice->mChans[c].mDryParams.Hrtf.Target.Delay); - voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base; + voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base * pangain; - const auto coeffs = CalcAngleCoeffs(az, ev, 0.0f); + const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1109,7 +1230,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * where it can be 0 or full (non-mono sources are always full * spread here). */ - const float spread{Spread * (voice->mFmtChannels == FmtMono)}; + const float spread{Spread * float(voice->mFmtChannels == FmtMono)}; /* Local sources on HRTF play with each channel panned to its * relative location around the listener, providing "virtual @@ -1120,23 +1241,26 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* Skip LFE */ if(chans[c].channel == LFE) continue; + const float pangain{SelectChannelGain(chans[c].channel)}; /* Get the HRIR coefficients and delays for this channel * position. */ - Device->mHrtf->getCoeffs(chans[c].elevation, chans[c].angle, - std::numeric_limits::infinity(), spread, + const float ev{std::asin(chans[c].pos[1])}; + const float az{std::atan2(chans[c].pos[0], -chans[c].pos[2])}; + + Device->mHrtf->getCoeffs(ev, az, std::numeric_limits::infinity(), spread, voice->mChans[c].mDryParams.Hrtf.Target.Coeffs, voice->mChans[c].mDryParams.Hrtf.Target.Delay); - voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base; + voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base * pangain; /* Normal panning for auxiliary sends. */ - const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, spread); + const auto coeffs = CalcDirectionCoeffs(chans[c].pos, spread); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1156,7 +1280,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* Clamp the distance for really close sources, to prevent * excessive bass. */ - const float mdist{maxf(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; + const float mdist{std::max(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Adjust NFC filters. */ @@ -1171,19 +1295,18 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con auto calc_coeffs = [xpos,ypos,zpos,Spread](RenderMode mode) { if(mode != RenderMode::Pairwise) - return CalcDirectionCoeffs({xpos, ypos, zpos}, Spread); - const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const float az{std::atan2(xpos, -zpos)}; - return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread); + return CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, Spread); + const auto pos = ScaleAzimuthFront3_2(std::array{xpos, ypos, zpos}); + return CalcDirectionCoeffs(pos, Spread); }; const auto coeffs = calc_coeffs(Device->mRenderMode); - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + ComputePanGains(&Device->Dry, coeffs, DryGain.Base, voice->mChans[0].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base, voice->mChans[0].mWetParams[i].Gains.Target); } } @@ -1191,11 +1314,10 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { using namespace al::numbers; - const float src_ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const float src_az{std::atan2(xpos, -zpos)}; - for(size_t c{0};c < num_channels;c++) { + const float pangain{SelectChannelGain(chans[c].channel)}; + /* Special-case LFE */ if(chans[c].channel == LFE) { @@ -1203,7 +1325,8 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { const uint idx{Device->channelIdxByName(chans[c].channel)}; if(idx != InvalidChannelIndex) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base + * pangain; } continue; } @@ -1213,29 +1336,29 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * at the source position, at full spread (pi*2), each * channel position is left unchanged. */ - const float ev{lerpf(src_ev, chans[c].elevation, - inv_pi_v/2.0f * Spread)}; - - float az{chans[c].angle - src_az}; - if(az < -pi_v) az += pi_v*2.0f; - else if(az > pi_v) az -= pi_v*2.0f; - - az *= inv_pi_v/2.0f * Spread; - - az += src_az; - if(az < -pi_v) az += pi_v*2.0f; - else if(az > pi_v) az -= pi_v*2.0f; + const float a{1.0f - (inv_pi_v/2.0f)*Spread}; + std::array pos{ + lerpf(chans[c].pos[0], xpos, a), + lerpf(chans[c].pos[1], ypos, a), + lerpf(chans[c].pos[2], zpos, a)}; + const float len{std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2])}; + if(len < 1.0f) + { + pos[0] /= len; + pos[1] /= len; + pos[2] /= len; + } if(Device->mRenderMode == RenderMode::Pairwise) - az = ScaleAzimuthFront(az, 3.0f); - const auto coeffs = CalcAngleCoeffs(az, ev, 0.0f); + pos = ScaleAzimuthFront3(pos); + const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain, voice->mChans[c].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1259,9 +1382,11 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * where it can be 0 or full (non-mono sources are always full * spread here). */ - const float spread{Spread * (voice->mFmtChannels == FmtMono)}; + const float spread{Spread * float(voice->mFmtChannels == FmtMono)}; for(size_t c{0};c < num_channels;c++) { + const float pangain{SelectChannelGain(chans[c].channel)}; + /* Special-case LFE */ if(chans[c].channel == LFE) { @@ -1269,21 +1394,20 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { const uint idx{Device->channelIdxByName(chans[c].channel)}; if(idx != InvalidChannelIndex) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain; } continue; } - const auto coeffs = CalcAngleCoeffs((Device->mRenderMode == RenderMode::Pairwise) - ? ScaleAzimuthFront(chans[c].angle, 3.0f) : chans[c].angle, - chans[c].elevation, spread); + const auto coeffs = CalcDirectionCoeffs((Device->mRenderMode==RenderMode::Pairwise) + ? ScaleAzimuthFront3(chans[c].pos) : chans[c].pos, spread); - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain, voice->mChans[c].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1332,7 +1456,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { DeviceBase *Device{context->mDevice}; - EffectSlot *SendSlots[MAX_SENDS]; + std::array SendSlots{}; voice->mDirect.Buffer = Device->Dry.Buffer; for(uint i{0};i < Device->NumAuxSends;i++) @@ -1353,19 +1477,20 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex if(Pitch > float{MaxPitch}) voice->mStep = MaxPitch<mStep = maxu(fastf2u(Pitch * MixerFracOne), 1); + voice->mStep = std::max(fastf2u(Pitch * MixerFracOne), 1u); voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState); /* Calculate gains */ - GainTriplet DryGain; - DryGain.Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * props->Direct.Gain * - context->mParams.Gain, GainMixMax); + GainTriplet DryGain{}; + DryGain.Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) * + props->Direct.Gain * context->mParams.Gain, GainMixMax); DryGain.HF = props->Direct.GainHF; DryGain.LF = props->Direct.GainLF; - GainTriplet WetGain[MAX_SENDS]; + + std::array WetGain{}; for(uint i{0};i < Device->NumAuxSends;i++) { - WetGain[i].Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * + WetGain[i].Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) * props->Send[i].Gain * context->mParams.Gain, GainMixMax); WetGain[i].HF = props->Send[i].GainHF; WetGain[i].LF = props->Send[i].GainLF; @@ -1382,20 +1507,30 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa /* Set mixing buffers and get send parameters. */ voice->mDirect.Buffer = Device->Dry.Buffer; - EffectSlot *SendSlots[MAX_SENDS]; - uint UseDryAttnForRoom{0}; + std::array SendSlots{}; + std::array RoomRolloff{}; + std::bitset UseDryAttnForRoom{0}; for(uint i{0};i < NumSends;i++) { SendSlots[i] = props->Send[i].Slot; if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) SendSlots[i] = nullptr; - else if(!SendSlots[i]->AuxSendAuto) + else if(SendSlots[i]->AuxSendAuto) { - /* 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. + /* NOTE: Contrary to the EFX docs, the effect's room rolloff factor + * applies to the selected distance model along with the source's + * room rolloff factor, not necessarily the inverse distance model. + * + * Generic Software also applies these rolloff factors regardless + * of any setting. It doesn't seem to use the effect slot's send + * auto for anything, though as far as I understand, it's supposed + * to control whether the send gets the same gain/gainhf as the + * direct path (excluding the filter). */ - UseDryAttnForRoom |= 1u<RoomRolloffFactor + SendSlots[i]->RoomRolloff; } + else + UseDryAttnForRoom.set(i); if(!SendSlots[i]) voice->mSend[i].Buffer = {}; @@ -1427,62 +1562,77 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa /* Calculate distance attenuation */ float ClampedDist{Distance}; float DryGainBase{props->Gain}; - float WetGainBase{props->Gain}; + std::array WetGainBase{}; + WetGainBase.fill(props->Gain); + float DryAttnBase{1.0f}; switch(context->mParams.SourceDistanceModel ? props->mDistanceModel : context->mParams.mDistanceModel) { - case DistanceModel::InverseClamped: - if(props->MaxDistance < props->RefDistance) break; - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - /*fall-through*/ - case DistanceModel::Inverse: - if(props->RefDistance > 0.0f) + case DistanceModel::InverseClamped: + if(props->MaxDistance < props->RefDistance) break; + ClampedDist = std::clamp(ClampedDist, props->RefDistance, props->MaxDistance); + /*fall-through*/ + case DistanceModel::Inverse: + if(props->RefDistance > 0.0f) + { + float dist{lerpf(props->RefDistance, ClampedDist, props->RolloffFactor)}; + if(dist > 0.0f) { - float dist{lerpf(props->RefDistance, ClampedDist, props->RolloffFactor)}; - if(dist > 0.0f) DryGainBase *= props->RefDistance / dist; - - dist = lerpf(props->RefDistance, ClampedDist, props->RoomRolloffFactor); - if(dist > 0.0f) WetGainBase *= props->RefDistance / dist; + DryAttnBase = props->RefDistance / dist; + DryGainBase *= DryAttnBase; } - break; - case DistanceModel::LinearClamped: - if(props->MaxDistance < props->RefDistance) break; - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - /*fall-through*/ - case DistanceModel::Linear: - if(props->MaxDistance != props->RefDistance) + for(size_t i{0};i < NumSends;++i) { - float attn{(ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance) * props->RolloffFactor}; - DryGainBase *= maxf(1.0f - attn, 0.0f); + dist = lerpf(props->RefDistance, ClampedDist, RoomRolloff[i]); + if(dist > 0.0f) WetGainBase[i] *= props->RefDistance / dist; + } + } + break; + case DistanceModel::LinearClamped: + if(props->MaxDistance < props->RefDistance) break; + ClampedDist = std::clamp(ClampedDist, props->RefDistance, props->MaxDistance); + /*fall-through*/ + case DistanceModel::Linear: + if(props->MaxDistance != props->RefDistance) + { + float attn{(ClampedDist-props->RefDistance) / + (props->MaxDistance-props->RefDistance) * props->RolloffFactor}; + DryAttnBase = std::max(1.0f - attn, 0.0f); + DryGainBase *= DryAttnBase; + + for(size_t i{0};i < NumSends;++i) + { attn = (ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance) * props->RoomRolloffFactor; - WetGainBase *= maxf(1.0f - attn, 0.0f); + (props->MaxDistance-props->RefDistance) * RoomRolloff[i]; + WetGainBase[i] *= std::max(1.0f - attn, 0.0f); } - break; + } + break; - case DistanceModel::ExponentClamped: - if(props->MaxDistance < props->RefDistance) break; - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - /*fall-through*/ - case DistanceModel::Exponent: - if(ClampedDist > 0.0f && props->RefDistance > 0.0f) - { - const float dist_ratio{ClampedDist/props->RefDistance}; - DryGainBase *= std::pow(dist_ratio, -props->RolloffFactor); - WetGainBase *= std::pow(dist_ratio, -props->RoomRolloffFactor); - } - break; + case DistanceModel::ExponentClamped: + if(props->MaxDistance < props->RefDistance) break; + ClampedDist = std::clamp(ClampedDist, props->RefDistance, props->MaxDistance); + /*fall-through*/ + case DistanceModel::Exponent: + if(ClampedDist > 0.0f && props->RefDistance > 0.0f) + { + const float dist_ratio{ClampedDist/props->RefDistance}; + DryAttnBase = std::pow(dist_ratio, -props->RolloffFactor); + DryGainBase *= DryAttnBase; + for(size_t i{0};i < NumSends;++i) + WetGainBase[i] *= std::pow(dist_ratio, -RoomRolloff[i]); + } + break; - case DistanceModel::Disable: - break; + case DistanceModel::Disable: + break; } /* Calculate directional soundcones */ - float ConeHF{1.0f}, WetConeHF{1.0f}; + float ConeHF{1.0f}, WetCone{1.0f}, WetConeHF{1.0f}; if(directional && props->InnerAngle < 360.0f) { static constexpr float Rad2Deg{static_cast(180.0 / al::numbers::pi)}; @@ -1492,40 +1642,44 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa if(Angle >= props->OuterAngle) { ConeGain = props->OuterGain; - ConeHF = lerpf(1.0f, props->OuterGainHF, props->DryGainHFAuto); + if(props->DryGainHFAuto) + ConeHF = props->OuterGainHF; } else if(Angle >= props->InnerAngle) { const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)}; ConeGain = lerpf(1.0f, props->OuterGain, scale); - ConeHF = lerpf(1.0f, props->OuterGainHF, scale * props->DryGainHFAuto); + if(props->DryGainHFAuto) + ConeHF = lerpf(1.0f, props->OuterGainHF, scale); } DryGainBase *= ConeGain; - WetGainBase *= lerpf(1.0f, ConeGain, props->WetGainAuto); - - WetConeHF = lerpf(1.0f, ConeHF, props->WetGainHFAuto); + if(props->WetGainAuto) + WetCone = ConeGain; + if(props->WetGainHFAuto) + WetConeHF = ConeHF; } /* Apply gain and frequency filters */ - DryGainBase = clampf(DryGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; - WetGainBase = clampf(WetGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; - GainTriplet DryGain{}; - DryGain.Base = minf(DryGainBase * props->Direct.Gain, GainMixMax); + DryGainBase = std::clamp(DryGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; + DryGain.Base = std::min(DryGainBase * props->Direct.Gain, GainMixMax); DryGain.HF = ConeHF * props->Direct.GainHF; DryGain.LF = props->Direct.GainLF; - GainTriplet WetGain[MAX_SENDS]{}; + + std::array WetGain{}; for(uint i{0};i < NumSends;i++) { + WetGainBase[i] = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) * + context->mParams.Gain; /* If this effect slot's Auxiliary Send Auto is off, then use the dry * path distance and cone attenuation, otherwise use the wet (room) * path distance and cone attenuation. The send filter is used instead * of the direct filter, regardless. */ - const bool use_room{!(UseDryAttnForRoom&(1u<Send[i].Gain, GainMixMax); + const bool use_room{!UseDryAttnForRoom.test(i)}; + const float gain{use_room ? WetGainBase[i] : DryGainBase}; + WetGain[i].Base = std::min(gain * props->Send[i].Gain, GainMixMax); WetGain[i].HF = (use_room ? WetConeHF : ConeHF) * props->Send[i].GainHF; WetGain[i].LF = props->Send[i].GainLF; } @@ -1547,72 +1701,34 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f)) continue; - auto calc_attenuation = [](float distance, float refdist, float rolloff) noexcept - { - const float dist{lerpf(refdist, distance, rolloff)}; - if(dist > refdist) return refdist / dist; - return 1.0f; - }; - - /* The reverb effect's room rolloff factor always applies to an - * inverse distance rolloff model. - */ - WetGain[i].Base *= calc_attenuation(Distance, props->RefDistance, - SendSlots[i]->RoomRolloff); - if(distance_meters > std::numeric_limits::epsilon()) WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, distance_meters); /* If this effect slot's Auxiliary Send Auto is off, don't apply - * the automatic initial reverb decay (should the reverb's room - * rolloff still apply?). + * the automatic initial reverb decay. + * + * NOTE: Generic Software applies the initial decay regardless of + * this setting. It doesn't seem to use it for anything, only the + * source's send filter gain auto flag affects this. */ if(!SendSlots[i]->AuxSendAuto) continue; - GainTriplet DecayDistance; - /* Calculate the distances to where this effect's decay reaches - * -60dB. - */ - DecayDistance.Base = SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec; - DecayDistance.LF = DecayDistance.Base * SendSlots[i]->DecayLFRatio; - DecayDistance.HF = DecayDistance.Base * SendSlots[i]->DecayHFRatio; - if(SendSlots[i]->DecayHFLimit) - { - const float airAbsorption{SendSlots[i]->AirAbsorptionGainHF}; - if(airAbsorption < 1.0f) - { - /* Calculate the distance to where this effect's air - * absorption reaches -60dB, and limit the effect's HF - * decay distance (so it doesn't take any longer to decay - * than the air would allow). - */ - static constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/}; - const float absorb_dist{log10_decaygain / std::log10(airAbsorption)}; - DecayDistance.HF = minf(absorb_dist, DecayDistance.HF); - } - } - - const float baseAttn = calc_attenuation(Distance, props->RefDistance, - props->RolloffFactor); + const float DecayDistance{SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec}; /* Apply a decay-time transformation to the wet path, based on the * source distance. The initial decay of the reverb effect is * calculated and applied to the wet path. + * + * FIXME: This is very likely not correct. It more likely should + * work by calculating a rolloff dynamically based on the reverb + * parameters (and source distance?) and add it to the room rolloff + * with the reverb and source rolloff parameters. */ - const float fact{distance_base / DecayDistance.Base}; + const float baseAttn{DryAttnBase}; + const float fact{distance_base / DecayDistance}; const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn}; WetGain[i].Base *= gain; - - if(gain > 0.0f) - { - const float hffact{distance_base / DecayDistance.HF}; - const float gainhf{std::pow(ReverbDecayGain, hffact)*(1.0f-baseAttn) + baseAttn}; - WetGain[i].HF *= minf(gainhf/gain, 1.0f); - const float lffact{distance_base / DecayDistance.LF}; - const float gainlf{std::pow(ReverbDecayGain, lffact)*(1.0f-baseAttn) + baseAttn}; - WetGain[i].LF *= minf(gainlf/gain, 1.0f); - } } } @@ -1659,7 +1775,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa if(Pitch > float{MaxPitch}) voice->mStep = MaxPitch<mStep = maxu(fastf2u(Pitch * MixerFracOne), 1); + voice->mStep = std::max(fastf2u(Pitch * MixerFracOne), 1u); voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState); float spread{0.0f}; @@ -1679,7 +1795,7 @@ void CalcSourceParams(Voice *voice, ContextBase *context, bool force) if(props) { - voice->mProps = *props; + voice->mProps = static_cast(*props); AtomicReplaceHead(context->mFreeVoiceProps, props); } @@ -1700,22 +1816,21 @@ void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state) auto evt_vec = ring->getWriteVector(); if(evt_vec.first.len < 1) return; - AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), - AsyncEvent::SourceStateChange)}; - evt->u.srcstate.id = id; + auto &evt = InitAsyncEvent(evt_vec.first.buf); + evt.mId = id; switch(state) { case VChangeState::Reset: - evt->u.srcstate.state = AsyncEvent::SrcState::Reset; + evt.mState = AsyncSrcState::Reset; break; case VChangeState::Stop: - evt->u.srcstate.state = AsyncEvent::SrcState::Stop; + evt.mState = AsyncSrcState::Stop; break; case VChangeState::Play: - evt->u.srcstate.state = AsyncEvent::SrcState::Play; + evt.mState = AsyncSrcState::Play; break; case VChangeState::Pause: - evt->u.srcstate.state = AsyncEvent::SrcState::Pause; + evt.mState = AsyncSrcState::Pause; break; /* Shouldn't happen. */ case VChangeState::Restart: @@ -1812,7 +1927,7 @@ void ProcessVoiceChanges(ContextBase *ctx) } oldvoice->mPendingChange.store(false, std::memory_order_release); } - if(sendevt && enabledevt.test(AsyncEvent::SourceStateChange)) + if(sendevt && enabledevt.test(al::to_underlying(AsyncEnableBits::SourceState))) SendSourceStateEvent(ctx, cur->mSourceID, cur->mState); next = cur->mNext.load(std::memory_order_acquire); @@ -1820,8 +1935,8 @@ void ProcessVoiceChanges(ContextBase *ctx) ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); } -void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots, - const al::span voices) +void ProcessParamUpdates(ContextBase *ctx, const al::span slots, + const al::span sorted_slots, const al::span voices) { ProcessVoiceChanges(ctx); @@ -1829,9 +1944,9 @@ void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots, if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) LIKELY { bool force{CalcContextParams(ctx)}; - auto sorted_slots = const_cast(slots.data() + slots.size()); + auto sorted_slot_base = al::to_address(sorted_slots.begin()); for(EffectSlot *slot : slots) - force |= CalcEffectSlotParams(slot, sorted_slots, ctx); + force |= CalcEffectSlotParams(slot, sorted_slot_base, ctx); for(Voice *voice : voices) { @@ -1847,16 +1962,19 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { ASSUME(SamplesToDo > 0); - const nanoseconds curtime{device->ClockBase + - nanoseconds{seconds{device->SamplesDone}}/device->Frequency}; + const nanoseconds curtime{device->mClockBase.load(std::memory_order_relaxed) + + nanoseconds{seconds{device->mSamplesDone.load(std::memory_order_relaxed)}}/ + device->Frequency}; for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire)) { - const EffectSlotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire); + const auto auxslotspan = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)}; + const auto auxslots = auxslotspan.first(auxslotspan.size()>>1); + const auto sorted_slots = auxslotspan.last(auxslotspan.size()>>1); const al::span voices{ctx->getVoicesSpanAcquired()}; - /* Process pending propery updates for objects on the context. */ - ProcessParamUpdates(ctx, auxslots, voices); + /* Process pending property updates for objects on the context. */ + ProcessParamUpdates(ctx, auxslots, sorted_slots, voices); /* Clear auxiliary effect slot mixing buffers. */ for(EffectSlot *slot : auxslots) @@ -1874,24 +1992,19 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) } /* Process effects. */ - if(const size_t num_slots{auxslots.size()}) + if(!auxslots.empty()) { - auto slots = auxslots.data(); - auto slots_end = slots + num_slots; - /* Sort the slots into extra storage, so that effect slots come - * before their effect slot target (or their targets' target). + * before their effect slot target (or their targets' target). Skip + * sorting if it has already been done. */ - const al::span sorted_slots{const_cast(slots_end), - num_slots}; - /* Skip sorting if it has already been done. */ if(!sorted_slots[0]) { /* First, copy the slots to the sorted list, then partition the * sorted list so that all slots without a target slot go to * the end. */ - std::copy(slots, slots_end, sorted_slots.begin()); + std::copy(auxslots.begin(), auxslots.end(), sorted_slots.begin()); auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(), [](const EffectSlot *slot) noexcept -> bool { return slot->Target != nullptr; }); @@ -1945,44 +2058,46 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) void ApplyDistanceComp(const al::span Samples, const size_t SamplesToDo, - const DistanceComp::ChanData *distcomp) + const al::span chandata) { ASSUME(SamplesToDo > 0); + auto distcomp = chandata.begin(); for(auto &chanbuffer : Samples) { const float gain{distcomp->Gain}; - const size_t base{distcomp->Length}; - float *distbuf{al::assume_aligned<16>(distcomp->Buffer)}; + auto distbuf = al::span{al::assume_aligned<16>(distcomp->Buffer.data()), + distcomp->Buffer.size()}; ++distcomp; - if(base < 1) - continue; + const size_t base{distbuf.size()}; + if(base < 1) continue; - float *inout{al::assume_aligned<16>(chanbuffer.data())}; - auto inout_end = inout + SamplesToDo; + const auto inout = al::span{al::assume_aligned<16>(chanbuffer.data()), SamplesToDo}; if(SamplesToDo >= base) LIKELY { - auto delay_end = std::rotate(inout, inout_end - base, inout_end); - std::swap_ranges(inout, delay_end, distbuf); + auto delay_end = std::rotate(inout.begin(), inout.end()-ptrdiff_t(base), inout.end()); + std::swap_ranges(inout.begin(), delay_end, distbuf.begin()); } else { - auto delay_start = std::swap_ranges(inout, inout_end, distbuf); - std::rotate(distbuf, delay_start, distbuf + base); + auto delay_start = std::swap_ranges(inout.begin(), inout.end(), distbuf.begin()); + std::rotate(distbuf.begin(), delay_start, distbuf.begin()+ptrdiff_t(base)); } - std::transform(inout, inout_end, inout, [gain](float s) { return s * gain; }); + std::transform(inout.begin(), inout.end(), inout.begin(), + [gain](float s) { return s*gain; }); } } void ApplyDither(const al::span Samples, uint *dither_seed, const float quant_scale, const size_t SamplesToDo) { + static constexpr double invRNGRange{1.0 / std::numeric_limits::max()}; ASSUME(SamplesToDo > 0); /* Dithering. Generate whitenoise (uniform distribution of random values * between -1 and +1) and add it to the sample values, after scaling up to - * the desired quantization depth amd before rounding. + * the desired quantization depth and before rounding. */ const float invscale{1.0f / quant_scale}; uint seed{*dither_seed}; @@ -1991,7 +2106,7 @@ void ApplyDither(const al::span Samples, uint *dither_seed, float val{sample * quant_scale}; uint rng0{dither_rng(&seed)}; uint rng1{dither_rng(&seed)}; - val += static_cast(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX)); + val += static_cast(rng0*invRNGRange - rng1*invRNGRange); return fast_roundf(val) * invscale; }; for(FloatBufferLine &inout : Samples) @@ -2015,12 +2130,12 @@ template<> inline int32_t SampleConv(float val) noexcept * 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)); + return fastf2i(std::clamp(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } template<> inline int16_t SampleConv(float val) noexcept -{ return static_cast(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); } +{ return static_cast(fastf2i(std::clamp(val*32768.0f, -32768.0f, 32767.0f))); } template<> inline int8_t SampleConv(float val) noexcept -{ return static_cast(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); } +{ return static_cast(fastf2i(std::clamp(val*128.0f, -128.0f, 127.0f))); } /* Define unsigned output variations. */ template<> inline uint32_t SampleConv(float val) noexcept @@ -2030,34 +2145,32 @@ template<> inline uint16_t SampleConv(float val) noexcept template<> inline uint8_t SampleConv(float val) noexcept { return static_cast(SampleConv(val) + 128); } -template +template void Write(const al::span InBuffer, void *OutBuffer, const size_t Offset, const size_t SamplesToDo, const size_t FrameStep) { ASSUME(FrameStep > 0); ASSUME(SamplesToDo > 0); - DevFmtType_t *outbase{static_cast*>(OutBuffer) + Offset*FrameStep}; + const auto output = al::span{static_cast(OutBuffer), (Offset+SamplesToDo)*FrameStep} + .subspan(Offset*FrameStep); size_t c{0}; for(const FloatBufferLine &inbuf : InBuffer) { - DevFmtType_t *out{outbase++}; - auto conv_sample = [FrameStep,&out](const float s) noexcept -> void + auto out = output.begin(); + auto conv_sample = [FrameStep,c,&out](const float s) noexcept { - *out = SampleConv>(s); - out += FrameStep; + out[c] = SampleConv(s); + out += ptrdiff_t(FrameStep); }; - std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample); + std::for_each_n(inbuf.cbegin(), SamplesToDo, conv_sample); ++c; } if(const size_t extra{FrameStep - c}) { - const auto silence = SampleConv>(0.0f); + const auto silence = SampleConv(0.0f); for(size_t i{0};i < SamplesToDo;++i) - { - std::fill_n(outbase, extra, silence); - outbase += FrameStep; - } + std::fill_n(&output[i*FrameStep + c], extra, silence); } } @@ -2065,28 +2178,28 @@ void Write(const al::span InBuffer, void *OutBuffer, cons uint DeviceBase::renderSamples(const uint numSamples) { - const uint samplesToDo{minu(numSamples, BufferLineSize)}; + const uint samplesToDo{std::min(numSamples, uint{BufferLineSize})}; /* Clear main mixing buffers. */ for(FloatBufferLine &buffer : MixBuffer) buffer.fill(0.0f); - /* Increment the mix count at the start (lsb should now be 1). */ - IncrementRef(MixCount); + { + const auto mixLock = getWriteMixLock(); - /* Process and mix each context's sources and effects. */ - ProcessContexts(this, samplesToDo); + /* Process and mix each context's sources and effects. */ + ProcessContexts(this, samplesToDo); - /* Increment the clock time. Every second's worth of samples is converted - * and added to clock base so that large sample counts don't overflow - * during conversion. This also guarantees a stable conversion. - */ - SamplesDone += samplesToDo; - ClockBase += std::chrono::seconds{SamplesDone / Frequency}; - SamplesDone %= Frequency; - - /* Increment the mix count at the end (lsb should now be 0). */ - IncrementRef(MixCount); + /* 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. + */ + auto samplesDone = mSamplesDone.load(std::memory_order_relaxed) + samplesToDo; + auto clockBase = mClockBase.load(std::memory_order_relaxed) + + std::chrono::seconds{samplesDone/Frequency}; + mSamplesDone.store(samplesDone%Frequency, std::memory_order_relaxed); + mClockBase.store(clockBase, std::memory_order_relaxed); + } /* Apply any needed post-process for finalizing the Dry mix to the RealOut * (Ambisonic decode, UHJ encode, etc). @@ -2098,7 +2211,7 @@ uint DeviceBase::renderSamples(const uint numSamples) /* Apply delays and attenuation for mismatched speaker distances. */ if(ChannelDelays) - ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels.data()); + ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels); /* Apply dithering. The compressor should have left enough headroom for the * dither noise to not saturate. @@ -2117,10 +2230,11 @@ void DeviceBase::renderSamples(const al::span outBuffers, const uint num { const uint samplesToDo{renderSamples(todo)}; - auto *srcbuf = RealOut.Buffer.data(); + auto srcbuf = RealOut.Buffer.cbegin(); for(auto *dstbuf : outBuffers) { - std::copy_n(srcbuf->data(), samplesToDo, dstbuf + total); + const auto dst = al::span{dstbuf, numSamples}.subspan(total); + std::copy_n(srcbuf->cbegin(), samplesToDo, dst.begin()); ++srcbuf; } @@ -2144,7 +2258,7 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz switch(FmtType) { #define HANDLE_WRITE(T) case T: \ - Write(RealOut.Buffer, outBuffer, total, samplesToDo, frameStep); break; + Write>(RealOut.Buffer, outBuffer, total, samplesToDo, frameStep); break; HANDLE_WRITE(DevFmtByte) HANDLE_WRITE(DevFmtUByte) HANDLE_WRITE(DevFmtShort) @@ -2162,34 +2276,43 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz void DeviceBase::handleDisconnect(const char *msg, ...) { - IncrementRef(MixCount); + const auto mixLock = getWriteMixLock(); + if(Connected.exchange(false, std::memory_order_acq_rel)) { - AsyncEvent evt{AsyncEvent::Disconnected}; + AsyncEvent evt{std::in_place_type}; + auto &disconnect = std::get(evt); - va_list args; + /* NOLINTBEGIN(*-array-to-pointer-decay) */ + va_list args, args2; va_start(args, msg); - int msglen{vsnprintf(evt.u.disconnect.msg, sizeof(evt.u.disconnect.msg), msg, args)}; + va_copy(args2, args); + if(int msglen{vsnprintf(nullptr, 0, msg, args)}; msglen > 0) + { + disconnect.msg.resize(static_cast(msglen)+1_uz); + vsnprintf(disconnect.msg.data(), disconnect.msg.size(), msg, args2); + } + else + disconnect.msg = ""; + va_end(args2); va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ - if(msglen < 0 || static_cast(msglen) >= sizeof(evt.u.disconnect.msg)) - evt.u.disconnect.msg[sizeof(evt.u.disconnect.msg)-1] = 0; + while(!disconnect.msg.empty() && disconnect.msg.back() == '\0') + disconnect.msg.pop_back(); for(ContextBase *ctx : *mContexts.load()) { - if(ctx->mEnabledEvts.load(std::memory_order_acquire).test(AsyncEvent::Disconnected)) + RingBuffer *ring{ctx->mAsyncEvents.get()}; + auto evt_data = ring->getWriteVector().first; + if(evt_data.len > 0) { - RingBuffer *ring{ctx->mAsyncEvents.get()}; - auto evt_data = ring->getWriteVector().first; - if(evt_data.len > 0) - { - al::construct_at(reinterpret_cast(evt_data.buf), evt); - ring->writeAdvance(1); - ctx->mEventSem.post(); - } + al::construct_at(reinterpret_cast(evt_data.buf), evt); + ring->writeAdvance(1); + ctx->mEventSem.post(); } - if(!ctx->mStopVoicesOnDisconnect) + if(!ctx->mStopVoicesOnDisconnect.load()) { ProcessVoiceChanges(ctx); continue; @@ -2206,5 +2329,4 @@ void DeviceBase::handleDisconnect(const char *msg, ...) std::for_each(voicelist.begin(), voicelist.end(), stop_voice); } } - IncrementRef(MixCount); } diff --git a/Engine/lib/openal-soft/alc/alu.h b/Engine/lib/openal-soft/alc/alu.h index 67fd09e57..ef7ddd4c5 100644 --- a/Engine/lib/openal-soft/alc/alu.h +++ b/Engine/lib/openal-soft/alc/alu.h @@ -2,20 +2,20 @@ #define ALU_H #include - -#include "aloptional.h" +#include +#include struct ALCcontext; struct ALCdevice; struct EffectSlot; -enum class StereoEncoding : unsigned char; +enum class StereoEncoding : std::uint8_t; constexpr float GainMixMax{1000.0f}; /* +60dB */ -enum CompatFlags : uint8_t { +enum CompatFlags : std::uint8_t { ReverseX, ReverseY, ReverseZ, @@ -31,7 +31,7 @@ void aluInit(CompatFlagBitset flags, const float nfcscale); * Set up the appropriate panning method and mixing method given the device * properties. */ -void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional stereomode); +void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional stereomode); void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); diff --git a/Engine/lib/openal-soft/alc/backends/alsa.cpp b/Engine/lib/openal-soft/alc/backends/alsa.cpp index d620a83cc..b0acf015d 100644 --- a/Engine/lib/openal-soft/alc/backends/alsa.cpp +++ b/Engine/lib/openal-soft/alc/backends/alsa.cpp @@ -31,29 +31,33 @@ #include #include #include +#include #include +#include #include #include +#include -#include "albyte.h" +#include "albit.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "aloptional.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" -#include "threads.h" -#include "vector.h" #include namespace { -constexpr char alsaDevice[] = "ALSA Default"; +using namespace std::string_view_literals; + +[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; } #ifdef HAVE_DYNLOAD @@ -248,59 +252,97 @@ struct DevMap { { } }; -al::vector PlaybackDevices; -al::vector CaptureDevices; +std::vector PlaybackDevices; +std::vector CaptureDevices; -const char *prefix_name(snd_pcm_stream_t stream) +std::string_view prefix_name(snd_pcm_stream_t stream) noexcept { - assert(stream == SND_PCM_STREAM_PLAYBACK || stream == SND_PCM_STREAM_CAPTURE); - return (stream==SND_PCM_STREAM_PLAYBACK) ? "device-prefix" : "capture-prefix"; + if(stream == SND_PCM_STREAM_PLAYBACK) + return "device-prefix"sv; + return "capture-prefix"sv; } -al::vector probe_devices(snd_pcm_stream_t stream) +struct SndCtlCardInfo { + snd_ctl_card_info_t *mInfo{}; + + SndCtlCardInfo() { snd_ctl_card_info_malloc(&mInfo); } + ~SndCtlCardInfo() { if(mInfo) snd_ctl_card_info_free(mInfo); } + SndCtlCardInfo(const SndCtlCardInfo&) = delete; + SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete; + + [[nodiscard]] + operator snd_ctl_card_info_t*() const noexcept { return mInfo; } +}; + +struct SndPcmInfo { + snd_pcm_info_t *mInfo{}; + + SndPcmInfo() { snd_pcm_info_malloc(&mInfo); } + ~SndPcmInfo() { if(mInfo) snd_pcm_info_free(mInfo); } + SndPcmInfo(const SndPcmInfo&) = delete; + SndPcmInfo& operator=(const SndPcmInfo&) = delete; + + [[nodiscard]] + operator snd_pcm_info_t*() const noexcept { return mInfo; } +}; + +struct SndCtl { + snd_ctl_t *mHandle{}; + + SndCtl() = default; + ~SndCtl() { if(mHandle) snd_ctl_close(mHandle); } + SndCtl(const SndCtl&) = delete; + SndCtl& operator=(const SndCtl&) = delete; + + [[nodiscard]] + auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); } + + [[nodiscard]] + operator snd_ctl_t*() const noexcept { return mHandle; } +}; + + +std::vector probe_devices(snd_pcm_stream_t stream) { - al::vector devlist; + std::vector devlist; - snd_ctl_card_info_t *info; - snd_ctl_card_info_malloc(&info); - snd_pcm_info_t *pcminfo; - snd_pcm_info_malloc(&pcminfo); + SndCtlCardInfo info; + SndPcmInfo pcminfo; - auto defname = ConfigValueStr(nullptr, "alsa", - (stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture"); - devlist.emplace_back(alsaDevice, defname ? defname->c_str() : "default"); + auto defname = ConfigValueStr({}, "alsa"sv, + (stream == SND_PCM_STREAM_PLAYBACK) ? "device"sv : "capture"sv); + devlist.emplace_back(GetDefaultName(), defname ? std::string_view{*defname} : "default"sv); - if(auto customdevs = ConfigValueStr(nullptr, "alsa", - (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures")) + if(auto customdevs = ConfigValueStr({}, "alsa"sv, + (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices"sv : "custom-captures"sv)) { - size_t nextpos{customdevs->find_first_not_of(';')}; - size_t curpos; - while((curpos=nextpos) < customdevs->length()) + size_t curpos{customdevs->find_first_not_of(';')}; + while(curpos < customdevs->length()) { - nextpos = customdevs->find_first_of(';', curpos+1); - - size_t seppos{customdevs->find_first_of('=', curpos)}; + size_t nextpos{customdevs->find(';', curpos+1)}; + const size_t seppos{customdevs->find('=', curpos)}; if(seppos == curpos || seppos >= nextpos) { - std::string spec{customdevs->substr(curpos, nextpos-curpos)}; + const std::string spec{customdevs->substr(curpos, nextpos-curpos)}; ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str()); } else { - devlist.emplace_back(customdevs->substr(curpos, seppos-curpos), - customdevs->substr(seppos+1, nextpos-seppos-1)); - const auto &entry = devlist.back(); + const std::string_view strview{*customdevs}; + const auto &entry = devlist.emplace_back(strview.substr(curpos, seppos-curpos), + strview.substr(seppos+1, nextpos-seppos-1)); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } if(nextpos < customdevs->length()) nextpos = customdevs->find_first_not_of(';', nextpos+1); + curpos = nextpos; } } - const std::string main_prefix{ - ConfigValueStr(nullptr, "alsa", prefix_name(stream)).value_or("plughw:")}; + const std::string main_prefix{ConfigValueStr({}, "alsa"sv, prefix_name(stream)) + .value_or("plughw:")}; int card{-1}; int err{snd_card_next(&card)}; @@ -308,16 +350,17 @@ al::vector probe_devices(snd_pcm_stream_t stream) { std::string name{"hw:" + std::to_string(card)}; - snd_ctl_t *handle; - if((err=snd_ctl_open(&handle, name.c_str(), 0)) < 0) + SndCtl handle; + err = handle.open(name.c_str(), 0); + if(err < 0) { ERR("control open (hw:%d): %s\n", card, snd_strerror(err)); continue; } - if((err=snd_ctl_card_info(handle, info)) < 0) + err = snd_ctl_card_info(handle, info); + if(err < 0) { ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err)); - snd_ctl_close(handle); continue; } @@ -326,8 +369,7 @@ al::vector probe_devices(snd_pcm_stream_t stream) name = prefix_name(stream); name += '-'; name += cardid; - const std::string card_prefix{ - ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)}; + const std::string card_prefix{ConfigValueStr({}, "alsa"sv, name).value_or(main_prefix)}; int dev{-1}; while(true) @@ -339,7 +381,8 @@ al::vector probe_devices(snd_pcm_stream_t stream) snd_pcm_info_set_device(pcminfo, static_cast(dev)); snd_pcm_info_set_subdevice(pcminfo, 0); snd_pcm_info_set_stream(pcminfo, stream); - if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0) + err = snd_ctl_pcm_info(handle, pcminfo); + if(err < 0) { if(err != -ENOENT) ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err)); @@ -352,8 +395,8 @@ al::vector probe_devices(snd_pcm_stream_t stream) name += cardid; name += '-'; name += std::to_string(dev); - const std::string device_prefix{ - ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(card_prefix)}; + const std::string device_prefix{ConfigValueStr({}, "alsa"sv, name) + .value_or(card_prefix)}; /* "CardName, PcmName (CARD=cardid,DEV=dev)" */ name = cardname; @@ -372,18 +415,13 @@ al::vector probe_devices(snd_pcm_stream_t stream) device += ",DEV="; device += std::to_string(dev); - devlist.emplace_back(std::move(name), std::move(device)); - const auto &entry = devlist.back(); + const auto &entry = devlist.emplace_back(std::move(name), std::move(device)); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } - snd_ctl_close(handle); } if(err < 0) ERR("snd_card_next failed: %s\n", snd_strerror(err)); - snd_pcm_info_free(pcminfo); - snd_ctl_card_info_free(info); - return devlist; } @@ -392,7 +430,6 @@ int verify_state(snd_pcm_t *handle) { snd_pcm_state_t state{snd_pcm_state(handle)}; - int err; switch(state) { case SND_PCM_STATE_OPEN: @@ -405,15 +442,27 @@ int verify_state(snd_pcm_t *handle) break; case SND_PCM_STATE_XRUN: - if((err=snd_pcm_recover(handle, -EPIPE, 1)) < 0) + if(int err{snd_pcm_recover(handle, -EPIPE, 1)}; err < 0) return err; break; case SND_PCM_STATE_SUSPENDED: - if((err=snd_pcm_recover(handle, -ESTRPIPE, 1)) < 0) + if(int err{snd_pcm_recover(handle, -ESTRPIPE, 1)}; err < 0) return err; break; + case SND_PCM_STATE_DISCONNECTED: return -ENODEV; + + /* ALSA headers have made this enum public, leaving us in a bind: use + * the enum despite being private and internal to the libasound, or + * ignore when an enum value isn't handled. We can't rely on it being + * declared either, since older headers don't have it and it could be + * removed in the future. We can't even really rely on its value, since + * being private/internal means it's subject to change, but this is the + * best we can do. + */ + case 1024 /*SND_PCM_STATE_PRIVATE1*/: + assert(state != 1024); } return state; @@ -427,7 +476,7 @@ struct AlsaPlayback final : public BackendBase { int mixerProc(); int mixerNoMMapProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -439,12 +488,10 @@ struct AlsaPlayback final : public BackendBase { std::mutex mMutex; uint mFrameStep{}; - al::vector mBuffer; + std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(AlsaPlayback) }; AlsaPlayback::~AlsaPlayback() @@ -458,7 +505,7 @@ AlsaPlayback::~AlsaPlayback() int AlsaPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; @@ -506,7 +553,7 @@ int AlsaPlayback::mixerProc() avail -= avail%update_size; // it is possible that contiguous areas are smaller, thus we use a loop - std::lock_guard _{mMutex}; + std::lock_guard dlock{mMutex}; while(avail > 0) { snd_pcm_uframes_t frames{avail}; @@ -520,6 +567,7 @@ int AlsaPlayback::mixerProc() break; } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ char *WritePtr{static_cast(areas->addr) + (offset * areas->step / 8)}; mDevice->renderSamples(WritePtr, static_cast(frames), mFrameStep); @@ -541,7 +589,7 @@ int AlsaPlayback::mixerProc() int AlsaPlayback::mixerNoMMapProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; @@ -585,13 +633,13 @@ int AlsaPlayback::mixerNoMMapProc() continue; } - al::byte *WritePtr{mBuffer.data()}; + auto WritePtr = mBuffer.begin(); avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast(mBuffer.size())); - std::lock_guard _{mMutex}; - mDevice->renderSamples(WritePtr, static_cast(avail), mFrameStep); + std::lock_guard dlock{mMutex}; + mDevice->renderSamples(al::to_address(WritePtr), static_cast(avail), mFrameStep); while(avail > 0) { - snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr, + snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, al::to_address(WritePtr), static_cast(avail))}; switch(ret) { @@ -626,10 +674,10 @@ int AlsaPlayback::mixerNoMMapProc() } -void AlsaPlayback::open(const char *name) +void AlsaPlayback::open(std::string_view name) { std::string driver{"default"}; - if(name) + if(!name.empty()) { if(PlaybackDevices.empty()) PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); @@ -638,13 +686,13 @@ void AlsaPlayback::open(const char *name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; driver = iter->device_name; } else { - name = alsaDevice; - if(auto driveropt = ConfigValueStr(nullptr, "alsa", "device")) + name = GetDefaultName(); + if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv)) driver = std::move(driveropt).value(); } TRACE("Opening device \"%s\"\n", driver.c_str()); @@ -692,15 +740,14 @@ bool AlsaPlayback::reset() break; } - bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", true)}; + bool allowmmap{GetConfigValueBool(mDevice->DeviceName, "alsa"sv, "mmap"sv, true)}; uint periodLen{static_cast(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)}; uint bufferLen{static_cast(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)}; uint rate{mDevice->Frequency}; - int err{}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ - if((err=(x)) < 0) \ + if(int err{x}; err < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ snd_strerror(err)}; \ } while(0) @@ -715,17 +762,18 @@ bool AlsaPlayback::reset() /* test and set format (implicitly sets sample bits) */ if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) < 0) { - static const struct { + struct FormatMap { snd_pcm_format_t format; DevFmtType fmttype; - } formatlist[] = { - { SND_PCM_FORMAT_FLOAT, DevFmtFloat }, - { SND_PCM_FORMAT_S32, DevFmtInt }, - { SND_PCM_FORMAT_U32, DevFmtUInt }, - { SND_PCM_FORMAT_S16, DevFmtShort }, - { SND_PCM_FORMAT_U16, DevFmtUShort }, - { SND_PCM_FORMAT_S8, DevFmtByte }, - { SND_PCM_FORMAT_U8, DevFmtUByte }, + }; + static constexpr std::array formatlist{ + FormatMap{SND_PCM_FORMAT_FLOAT, DevFmtFloat }, + FormatMap{SND_PCM_FORMAT_S32, DevFmtInt }, + FormatMap{SND_PCM_FORMAT_U32, DevFmtUInt }, + FormatMap{SND_PCM_FORMAT_S16, DevFmtShort }, + FormatMap{SND_PCM_FORMAT_U16, DevFmtUShort}, + FormatMap{SND_PCM_FORMAT_S8, DevFmtByte }, + FormatMap{SND_PCM_FORMAT_U8, DevFmtUByte }, }; for(const auto &fmt : formatlist) @@ -750,7 +798,7 @@ bool AlsaPlayback::reset() else mDevice->FmtChans = DevFmtStereo; } /* set rate (implicitly constrains period/buffer parameters) */ - if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", false) + if(!GetConfigValueBool(mDevice->DeviceName, "alsa", "allow-resampler", false) || !mDevice->Flags.test(FrequencyRequest)) { if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0) @@ -760,10 +808,10 @@ bool AlsaPlayback::reset() WARN("Failed to enable ALSA resampler\n"); CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr)); /* set period time (implicitly constrains period/buffer parameters) */ - if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 0) + if(int err{snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)}; err < 0) ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err)); /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */ - if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)) < 0) + if(int err{snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)}; err < 0) ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err)); /* install and prepare hardware configuration */ CHECK(snd_pcm_hw_params(mPcmHandle, hp.get())); @@ -798,11 +846,10 @@ bool AlsaPlayback::reset() void AlsaPlayback::start() { - int err{}; snd_pcm_access_t access{}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ - if((err=(x)) < 0) \ + if(int err{x}; err < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ snd_strerror(err)}; \ } while(0) @@ -849,10 +896,9 @@ void AlsaPlayback::stop() ClockLatency AlsaPlayback::getClockLatency() { - ClockLatency ret; - - std::lock_guard _{mMutex}; - ret.ClockTime = GetDeviceClockTime(mDevice); + std::lock_guard dlock{mMutex}; + ClockLatency ret{}; + ret.ClockTime = mDevice->getClockTime(); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) @@ -871,23 +917,21 @@ struct AlsaCapture final : public BackendBase { AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaCapture() override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ClockLatency getClockLatency() override; snd_pcm_t *mPcmHandle{nullptr}; - al::vector mBuffer; + std::vector mBuffer; bool mDoCapture{false}; RingBufferPtr mRing{nullptr}; snd_pcm_sframes_t mLastAvail{0}; - - DEF_NEWDEL(AlsaCapture) }; AlsaCapture::~AlsaCapture() @@ -898,10 +942,10 @@ AlsaCapture::~AlsaCapture() } -void AlsaCapture::open(const char *name) +void AlsaCapture::open(std::string_view name) { std::string driver{"default"}; - if(name) + if(!name.empty()) { if(CaptureDevices.empty()) CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); @@ -910,19 +954,18 @@ void AlsaCapture::open(const char *name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; driver = iter->device_name; } else { - name = alsaDevice; - if(auto driveropt = ConfigValueStr(nullptr, "alsa", "capture")) + name = GetDefaultName(); + if(auto driveropt = ConfigValueStr({}, "alsa"sv, "capture"sv)) driver = std::move(driveropt).value(); } TRACE("Opening device \"%s\"\n", driver.c_str()); - int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; - if(err < 0) + if(int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; err < 0) throw al::backend_exception{al::backend_error::NoDevice, "Could not open ALSA device \"%s\"", driver.c_str()}; @@ -955,13 +998,15 @@ void AlsaCapture::open(const char *name) break; } - snd_pcm_uframes_t bufferSizeInFrames{maxu(mDevice->BufferSize, 100*mDevice->Frequency/1000)}; - snd_pcm_uframes_t periodSizeInFrames{minu(mDevice->BufferSize, 25*mDevice->Frequency/1000)}; + snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->BufferSize, + 100u*mDevice->Frequency/1000u)}; + snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->BufferSize, + 25u*mDevice->Frequency/1000u)}; bool needring{false}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ - if((err=(x)) < 0) \ + if(int err{x}; err < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ snd_strerror(err)}; \ } while(0) @@ -999,13 +1044,11 @@ void AlsaCapture::open(const char *name) void AlsaCapture::start() { - int err{snd_pcm_prepare(mPcmHandle)}; - if(err < 0) + if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0) throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s", snd_strerror(err)}; - err = snd_pcm_start(mPcmHandle); - if(err < 0) + if(int err{snd_pcm_start(mPcmHandle)}; err < 0) throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s", snd_strerror(err)}; @@ -1024,25 +1067,27 @@ void AlsaCapture::stop() /* The ring buffer implicitly captures when checking availability. * Direct access needs to explicitly capture it into temp storage. */ - auto temp = al::vector( + auto temp = std::vector( static_cast(snd_pcm_frames_to_bytes(mPcmHandle, avail))); captureSamples(temp.data(), avail); mBuffer = std::move(temp); } - int err{snd_pcm_drop(mPcmHandle)}; - if(err < 0) + if(int err{snd_pcm_drop(mPcmHandle)}; err < 0) ERR("drop failed: %s\n", snd_strerror(err)); mDoCapture = false; } -void AlsaCapture::captureSamples(al::byte *buffer, uint samples) +void AlsaCapture::captureSamples(std::byte *buffer, uint samples) { if(mRing) { - mRing->read(buffer, samples); + std::ignore = mRing->read(buffer, samples); return; } + const auto outspan = al::span{buffer, + static_cast(snd_pcm_frames_to_bytes(mPcmHandle, samples))}; + auto outiter = outspan.begin(); mLastAvail -= samples; while(mDevice->Connected.load(std::memory_order_acquire) && samples > 0) { @@ -1055,20 +1100,21 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples) if(static_cast(amt) > samples) amt = samples; amt = snd_pcm_frames_to_bytes(mPcmHandle, amt); - std::copy_n(mBuffer.begin(), amt, buffer); + std::copy_n(mBuffer.begin(), amt, outiter); mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt); amt = snd_pcm_bytes_to_frames(mPcmHandle, amt); } else if(mDoCapture) - amt = snd_pcm_readi(mPcmHandle, buffer, samples); + amt = snd_pcm_readi(mPcmHandle, al::to_address(outiter), samples); if(amt < 0) { ERR("read error: %s\n", snd_strerror(static_cast(amt))); if(amt == -EAGAIN) continue; - if((amt=snd_pcm_recover(mPcmHandle, static_cast(amt), 1)) >= 0) + amt = snd_pcm_recover(mPcmHandle, static_cast(amt), 1); + if(amt >= 0) { amt = snd_pcm_start(mPcmHandle); if(amt >= 0) @@ -1088,12 +1134,12 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples) continue; } - buffer = buffer + amt; + outiter += amt; samples -= static_cast(amt); } if(samples > 0) - std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples), - al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0)); + std::fill_n(outiter, snd_pcm_frames_to_bytes(mPcmHandle, samples), + std::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0)); } uint AlsaCapture::availableSamples() @@ -1105,7 +1151,8 @@ uint AlsaCapture::availableSamples() { ERR("avail update failed: %s\n", snd_strerror(static_cast(avail))); - if((avail=snd_pcm_recover(mPcmHandle, static_cast(avail), 1)) >= 0) + avail = snd_pcm_recover(mPcmHandle, static_cast(avail), 1); + if(avail >= 0) { if(mDoCapture) avail = snd_pcm_start(mPcmHandle); @@ -1141,7 +1188,8 @@ uint AlsaCapture::availableSamples() if(amt == -EAGAIN) continue; - if((amt=snd_pcm_recover(mPcmHandle, static_cast(amt), 1)) >= 0) + amt = snd_pcm_recover(mPcmHandle, static_cast(amt), 1); + if(amt >= 0) { if(mDoCapture) amt = snd_pcm_start(mPcmHandle); @@ -1168,9 +1216,8 @@ uint AlsaCapture::availableSamples() ClockLatency AlsaCapture::getClockLatency() { - ClockLatency ret; - - ret.ClockTime = GetDeviceClockTime(mDevice); + ClockLatency ret{}; + ret.ClockTime = mDevice->getClockTime(); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) @@ -1189,13 +1236,9 @@ ClockLatency AlsaCapture::getClockLatency() bool AlsaBackendFactory::init() { - bool error{false}; - #ifdef HAVE_DYNLOAD if(!alsa_handle) { - std::string missing_funcs; - alsa_handle = LoadLib("libasound.so.2"); if(!alsa_handle) { @@ -1203,52 +1246,47 @@ bool AlsaBackendFactory::init() return false; } - error = false; + std::string missing_funcs; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(alsa_handle, #f)); \ - if(p##f == nullptr) { \ - error = true; \ - missing_funcs += "\n" #f; \ - } \ + if(p##f == nullptr) missing_funcs += "\n" #f; \ } while(0) ALSA_FUNCS(LOAD_FUNC); #undef LOAD_FUNC - if(error) + if(!missing_funcs.empty()) { WARN("Missing expected functions:%s\n", missing_funcs.c_str()); CloseLib(alsa_handle); alsa_handle = nullptr; + return false; } } #endif - return !error; + return true; } bool AlsaBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string AlsaBackendFactory::probe(BackendType type) +auto AlsaBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; - + std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - }; + { outnames.emplace_back(entry.name); }; + switch(type) { case BackendType::Playback: PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } diff --git a/Engine/lib/openal-soft/alc/backends/alsa.h b/Engine/lib/openal-soft/alc/backends/alsa.h index b256dcf5a..1327db8a4 100644 --- a/Engine/lib/openal-soft/alc/backends/alsa.h +++ b/Engine/lib/openal-soft/alc/backends/alsa.h @@ -5,15 +5,15 @@ struct AlsaBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_ALSA_H */ diff --git a/Engine/lib/openal-soft/alc/backends/base.cpp b/Engine/lib/openal-soft/alc/backends/base.cpp index e5ad84943..4f90fef1f 100644 --- a/Engine/lib/openal-soft/alc/backends/base.cpp +++ b/Engine/lib/openal-soft/alc/backends/base.cpp @@ -7,17 +7,6 @@ #include #include -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#include - -#include "albit.h" -#include "core/logging.h" -#include "aloptional.h" -#endif - -#include "atomic.h" #include "core/devformat.h" @@ -25,10 +14,12 @@ namespace al { backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code} { + /* NOLINTBEGIN(*-array-to-pointer-decay) */ std::va_list args; va_start(args, msg); setMessage(msg, args); va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ } backend_exception::~backend_exception() = default; @@ -38,7 +29,7 @@ backend_exception::~backend_exception() = default; bool BackendBase::reset() { throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; } -void BackendBase::captureSamples(al::byte*, uint) +void BackendBase::captureSamples(std::byte*, uint) { } uint BackendBase::availableSamples() @@ -46,27 +37,26 @@ uint BackendBase::availableSamples() ClockLatency BackendBase::getClockLatency() { - ClockLatency ret; + ClockLatency ret{}; uint refcount; do { refcount = mDevice->waitForMix(); - ret.ClockTime = GetDeviceClockTime(mDevice); + ret.ClockTime = mDevice->getClockTime(); std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != ReadRef(mDevice->MixCount)); + } while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed)); /* NOTE: The device will generally have about all but one periods filled at * any given time during playback. Without a more accurate measurement from * the output, this is an okay approximation. */ - ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize}, - std::chrono::seconds::zero()); + ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; return ret; } -void BackendBase::setDefaultWFXChannelOrder() +void BackendBase::setDefaultWFXChannelOrder() const { mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); @@ -126,6 +116,24 @@ void BackendBase::setDefaultWFXChannelOrder() mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; mDevice->RealOut.ChannelIndex[TopBackRight] = 11; break; + case DevFmtX7144: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[BackLeft] = 4; + mDevice->RealOut.ChannelIndex[BackRight] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; + mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; + mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; + mDevice->RealOut.ChannelIndex[TopBackRight] = 11; + mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12; + mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13; + mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14; + mDevice->RealOut.ChannelIndex[BottomBackRight] = 15; + break; case DevFmtX3D71: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; @@ -141,7 +149,7 @@ void BackendBase::setDefaultWFXChannelOrder() } } -void BackendBase::setDefaultChannelOrder() +void BackendBase::setDefaultChannelOrder() const { mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); @@ -179,6 +187,24 @@ void BackendBase::setDefaultChannelOrder() mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; mDevice->RealOut.ChannelIndex[TopBackRight] = 11; break; + case DevFmtX7144: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[BackLeft] = 2; + mDevice->RealOut.ChannelIndex[BackRight] = 3; + mDevice->RealOut.ChannelIndex[FrontCenter] = 4; + mDevice->RealOut.ChannelIndex[LFE] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; + mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; + mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; + mDevice->RealOut.ChannelIndex[TopBackRight] = 11; + mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12; + mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13; + mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14; + mDevice->RealOut.ChannelIndex[BottomBackRight] = 15; + break; case DevFmtX3D71: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; diff --git a/Engine/lib/openal-soft/alc/backends/base.h b/Engine/lib/openal-soft/alc/backends/base.h index b6b3d9227..2503c3df2 100644 --- a/Engine/lib/openal-soft/alc/backends/base.h +++ b/Engine/lib/openal-soft/alc/backends/base.h @@ -3,13 +3,16 @@ #include #include +#include #include #include #include +#include +#include -#include "albyte.h" #include "core/device.h" #include "core/except.h" +#include "alc/events.h" using uint = unsigned int; @@ -20,27 +23,33 @@ struct ClockLatency { }; struct BackendBase { - virtual void open(const char *name) = 0; + virtual void open(std::string_view name) = 0; virtual bool reset(); virtual void start() = 0; virtual void stop() = 0; - virtual void captureSamples(al::byte *buffer, uint samples); + virtual void captureSamples(std::byte *buffer, uint samples); virtual uint availableSamples(); virtual ClockLatency getClockLatency(); DeviceBase *const mDevice; + BackendBase() = delete; + BackendBase(const BackendBase&) = delete; + BackendBase(BackendBase&&) = delete; BackendBase(DeviceBase *device) noexcept : mDevice{device} { } virtual ~BackendBase() = default; + void operator=(const BackendBase&) = delete; + void operator=(BackendBase&&) = delete; + protected: /** Sets the default channel order used by most non-WaveFormatEx-based APIs. */ - void setDefaultChannelOrder(); + void setDefaultChannelOrder() const; /** Sets the default channel order used by WaveFormatEx. */ - void setDefaultWFXChannelOrder(); + void setDefaultWFXChannelOrder() const; }; using BackendPtr = std::unique_ptr; @@ -50,18 +59,6 @@ enum class BackendType { }; -/* Helper to get the current clock time from the device's ClockBase, and - * SamplesDone converted from the sample rate. - */ -inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device) -{ - using std::chrono::seconds; - using std::chrono::nanoseconds; - - auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency; - return device->ClockBase + ns; -} - /* Helper to get the device latency from the backend, including any fixed * latency from post-processing. */ @@ -74,16 +71,24 @@ inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend) struct BackendFactory { - virtual bool init() = 0; - - virtual bool querySupport(BackendType type) = 0; - - virtual std::string probe(BackendType type) = 0; - - virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0; - -protected: + BackendFactory() = default; + BackendFactory(const BackendFactory&) = delete; + BackendFactory(BackendFactory&&) = delete; virtual ~BackendFactory() = default; + + void operator=(const BackendFactory&) = delete; + void operator=(BackendFactory&&) = delete; + + virtual auto init() -> bool = 0; + + virtual auto querySupport(BackendType type) -> bool = 0; + + virtual auto queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport + { return alc::EventSupport::NoSupport; } + + virtual auto enumerate(BackendType type) -> std::vector = 0; + + virtual auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr = 0; }; namespace al { @@ -98,15 +103,15 @@ class backend_exception final : public base_exception { backend_error mErrorCode; public: -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf, 3, 4)]] +#ifdef __MINGW32__ + [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] #else [[gnu::format(printf, 3, 4)]] #endif backend_exception(backend_error code, const char *msg, ...); ~backend_exception() override; - backend_error errorCode() const noexcept { return mErrorCode; } + [[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; } }; } // namespace al diff --git a/Engine/lib/openal-soft/alc/backends/coreaudio.cpp b/Engine/lib/openal-soft/alc/backends/coreaudio.cpp index 8b0e75fdf..fd4abce04 100644 --- a/Engine/lib/openal-soft/alc/backends/coreaudio.cpp +++ b/Engine/lib/openal-soft/alc/backends/coreaudio.cpp @@ -22,18 +22,20 @@ #include "coreaudio.h" -#include +#include +#include +#include #include #include #include +#include #include #include - -#include -#include -#include +#include +#include #include "alnumeric.h" +#include "alstring.h" #include "core/converter.h" #include "core/device.h" #include "core/logging.h" @@ -42,18 +44,40 @@ #include #include - -namespace { - #if TARGET_OS_IOS || TARGET_OS_TV #define CAN_ENUMERATE 0 #else +#include #define CAN_ENUMERATE 1 #endif +namespace { + constexpr auto OutputElement = 0; constexpr auto InputElement = 1; +struct FourCCPrinter { + char mString[sizeof(UInt32) + 1]{}; + + constexpr FourCCPrinter(UInt32 code) noexcept + { + for(size_t i{0};i < sizeof(UInt32);++i) + { + const auto ch = static_cast(code & 0xff); + /* If this breaks early it'll leave the first byte null, to get + * read as a 0-length string. + */ + if(ch <= 0x1f || ch >= 0x7f) + break; + mString[sizeof(UInt32)-1-i] = ch; + code >>= 8; + } + } + constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast(code)} { } + + constexpr const char *c_str() const noexcept { return mString; } +}; + #if CAN_ENUMERATE struct DeviceEntry { AudioDeviceID mId; @@ -147,7 +171,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) &propSize); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err); + ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return 0; } @@ -158,7 +183,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) buflist); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err); + ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return 0; } @@ -182,7 +208,7 @@ void EnumerateDevices(std::vector &list, bool isCapture) auto devIds = std::vector(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown); if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data())) { - ERR("Failed to get device list: %u\n", err); + ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); return; } @@ -247,6 +273,48 @@ void EnumerateDevices(std::vector &list, bool isCapture) newdevs.swap(list); } +struct DeviceHelper { + DeviceHelper() + { + AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; + OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); + if (status != noErr) + ERR("AudioObjectAddPropertyListener fail: %d", status); + } + ~DeviceHelper() + { + AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; + OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); + if (status != noErr) + ERR("AudioObjectRemovePropertyListener fail: %d", status); + } + + static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses, + const AudioObjectPropertyAddress *inAddresses, void* /*inClientData*/) + { + for(UInt32 i = 0; i < inNumberAddresses; ++i) + { + switch(inAddresses[i].mSelector) + { + case kAudioHardwarePropertyDefaultOutputDevice: + case kAudioHardwarePropertyDefaultSystemOutputDevice: + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, + "Default playback device changed: "+std::to_string(inAddresses[i].mSelector)); + break; + case kAudioHardwarePropertyDefaultInputDevice: + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, + "Default capture device changed: "+std::to_string(inAddresses[i].mSelector)); + break; + } + } + return noErr; + } +}; + +static std::optional sDeviceHelper; + #else static constexpr char ca_device[] = "CoreAudio Default"; @@ -260,15 +328,8 @@ struct CoreAudioPlayback final : public BackendBase { OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept; - static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData) noexcept - { - return static_cast(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); - } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -277,8 +338,6 @@ struct CoreAudioPlayback final : public BackendBase { uint mFrameSize{0u}; AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD - - DEF_NEWDEL(CoreAudioPlayback) }; CoreAudioPlayback::~CoreAudioPlayback() @@ -301,11 +360,11 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi } -void CoreAudioPlayback::open(const char *name) +void CoreAudioPlayback::open(std::string_view name) { #if CAN_ENUMERATE AudioDeviceID audioDevice{kAudioDeviceUnknown}; - if(!name) + if(name.empty()) GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice), &audioDevice); else @@ -318,16 +377,16 @@ void CoreAudioPlayback::open(const char *name) auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name); if(devmatch == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; audioDevice = devmatch->mId; } #else - if(!name) + if(name.empty()) name = ca_device; - else if(strcmp(name, ca_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != ca_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; #endif /* open the default output unit */ @@ -351,7 +410,7 @@ void CoreAudioPlayback::open(const char *name) OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: %u", err}; + "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; #if CAN_ENUMERATE if(audioDevice != kAudioDeviceUnknown) @@ -362,7 +421,7 @@ void CoreAudioPlayback::open(const char *name) err = AudioUnitInitialize(audioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not initialize audio unit: %u", err}; + "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; /* WARNING: I don't know if "valid" audio unit values are guaranteed to be * non-0. If not, this logic is broken. @@ -375,7 +434,7 @@ void CoreAudioPlayback::open(const char *name) mAudioUnit = audioUnit; #if CAN_ENUMERATE - if(name) + if(!name.empty()) mDevice->DeviceName = name; else { @@ -388,6 +447,21 @@ void CoreAudioPlayback::open(const char *name) if(!devname.empty()) mDevice->DeviceName = std::move(devname); else mDevice->DeviceName = "Unknown Device Name"; } + + if(audioDevice != kAudioDeviceUnknown) + { + UInt32 type{}; + err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false, + kAudioObjectPropertyElementMaster, sizeof(type), &type); + if(err != noErr) + ERR("Failed to get audio device type: %u\n", err); + else + { + TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str()); + mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones)); + } + } + #else mDevice->DeviceName = name; #endif @@ -397,7 +471,7 @@ bool CoreAudioPlayback::reset() { OSStatus err{AudioUnitUninitialize(mAudioUnit)}; if(err != noErr) - ERR("-- AudioUnitUninitialize failed.\n"); + ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); /* retrieve default output unit's properties (output side) */ AudioStreamBasicDescription streamFormat{}; @@ -406,7 +480,8 @@ bool CoreAudioPlayback::reset() OutputElement, &streamFormat, &size); if(err != noErr || size != sizeof(streamFormat)) { - ERR("AudioUnitGetProperty failed\n"); + ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + err); return false; } @@ -473,7 +548,8 @@ bool CoreAudioPlayback::reset() OutputElement, &streamFormat, sizeof(streamFormat)); if(err != noErr) { - ERR("AudioUnitSetProperty failed\n"); + ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + err); return false; } @@ -482,14 +558,16 @@ bool CoreAudioPlayback::reset() /* setup callback */ mFrameSize = mDevice->frameSizeFromFmt(); AURenderCallbackStruct input{}; - input.inputProc = CoreAudioPlayback::MixerProcC; + input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept + { return static_cast(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); }; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) { - ERR("AudioUnitSetProperty failed\n"); + ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return false; } @@ -497,7 +575,7 @@ bool CoreAudioPlayback::reset() err = AudioUnitInitialize(mAudioUnit); if(err != noErr) { - ERR("AudioUnitInitialize failed\n"); + ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); return false; } @@ -509,14 +587,14 @@ void CoreAudioPlayback::start() const OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: %d", err}; + "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; } void CoreAudioPlayback::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); + ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); } @@ -527,18 +605,11 @@ struct CoreAudioCapture final : public BackendBase { OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept; - static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData) noexcept - { - return static_cast(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); - } - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; AudioUnit mAudioUnit{0}; @@ -548,11 +619,9 @@ struct CoreAudioCapture final : public BackendBase { SampleConverterPtr mConverter; - al::vector mCaptureData; + std::vector mCaptureData; RingBufferPtr mRing{nullptr}; - - DEF_NEWDEL(CoreAudioCapture) }; CoreAudioCapture::~CoreAudioCapture() @@ -568,7 +637,7 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, AudioBufferList*) noexcept { union { - al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))]; + std::byte buf[std::max(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))]; AudioBufferList list; } audiobuf{}; @@ -581,20 +650,20 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, inNumberFrames, &audiobuf.list)}; if(err != noErr) { - ERR("AudioUnitRender capture error: %d\n", err); + ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); return err; } - mRing->write(mCaptureData.data(), inNumberFrames); + std::ignore = mRing->write(mCaptureData.data(), inNumberFrames); return noErr; } -void CoreAudioCapture::open(const char *name) +void CoreAudioCapture::open(std::string_view name) { #if CAN_ENUMERATE AudioDeviceID audioDevice{kAudioDeviceUnknown}; - if(!name) + if(name.empty()) GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice), &audioDevice); else @@ -607,16 +676,16 @@ void CoreAudioCapture::open(const char *name) auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name); if(devmatch == CaptureList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; audioDevice = devmatch->mId; } #else - if(!name) + if(name.empty()) name = ca_device; - else if(strcmp(name, ca_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != ca_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; #endif AudioComponentDescription desc{}; @@ -640,7 +709,7 @@ void CoreAudioCapture::open(const char *name) OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: %u", err}; + "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Turn off AudioUnit output UInt32 enableIO{0}; @@ -648,7 +717,8 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable audio unit output property: %u", err}; + "Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; // Turn on AudioUnit input enableIO = 1; @@ -656,7 +726,8 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not enable audio unit input property: %u", err}; + "Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; #if CAN_ENUMERATE if(audioDevice != kAudioDeviceUnknown) @@ -666,14 +737,15 @@ void CoreAudioCapture::open(const char *name) // set capture callback AURenderCallbackStruct input{}; - input.inputProc = CoreAudioCapture::RecordProcC; + input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept + { return static_cast(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); }; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set capture callback: %u", err}; + "Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Disable buffer allocation for capture UInt32 flag{0}; @@ -681,13 +753,14 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Output, InputElement, &flag, sizeof(flag)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable buffer allocation property: %u", err}; + "Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; // Initialize the device err = AudioUnitInitialize(mAudioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not initialize audio unit: %u", err}; + "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Get the hardware format AudioStreamBasicDescription hardwareFormat{}; @@ -696,7 +769,7 @@ void CoreAudioCapture::open(const char *name) InputElement, &hardwareFormat, &propertySize); if(err != noErr || propertySize != sizeof(hardwareFormat)) throw al::backend_exception{al::backend_error::DeviceError, - "Could not get input format: %u", err}; + "Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Set up the requested format description AudioStreamBasicDescription requestedFormat{}; @@ -749,6 +822,7 @@ void CoreAudioCapture::open(const char *name) case DevFmtX61: case DevFmtX71: case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s not supported", @@ -777,14 +851,14 @@ void CoreAudioCapture::open(const char *name) InputElement, &outputFormat, sizeof(outputFormat)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set input format: %u", err}; + "Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; /* Calculate the minimum AudioUnit output format frame count for the pre- * conversion ring buffer. Ensure at least 100ms for the total buffer. */ double srateScale{outputFormat.mSampleRate / mDevice->Frequency}; - auto FrameCount64 = maxu64(static_cast(std::ceil(mDevice->BufferSize*srateScale)), - static_cast(outputFormat.mSampleRate)/10); + auto FrameCount64 = std::max(static_cast(std::ceil(mDevice->BufferSize*srateScale)), + static_cast(outputFormat.mSampleRate)/10_u64); FrameCount64 += MaxResamplerPadding; if(FrameCount64 > std::numeric_limits::max()) throw al::backend_exception{al::backend_error::DeviceError, @@ -796,11 +870,11 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize); if(err != noErr || propertySize != sizeof(outputFrameCount)) throw al::backend_exception{al::backend_error::DeviceError, - "Could not get input frame count: %u", err}; + "Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; mCaptureData.resize(outputFrameCount * mFrameSize); - outputFrameCount = static_cast(maxu64(outputFrameCount, FrameCount64)); + outputFrameCount = static_cast(std::max(uint64_t{outputFrameCount}, FrameCount64)); mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false); /* Set up sample converter if needed */ @@ -810,7 +884,7 @@ void CoreAudioCapture::open(const char *name) mDevice->Frequency, Resampler::FastBSinc24); #if CAN_ENUMERATE - if(name) + if(!name.empty()) mDevice->DeviceName = name; else { @@ -834,21 +908,21 @@ void CoreAudioCapture::start() OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: %d", err}; + "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; } void CoreAudioCapture::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); + ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); } -void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples) +void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples) { if(!mConverter) { - mRing->read(buffer, samples); + std::ignore = mRing->read(buffer, samples); return; } @@ -882,28 +956,34 @@ BackendFactory &CoreAudioBackendFactory::getFactory() return factory; } -bool CoreAudioBackendFactory::init() { return true; } +bool CoreAudioBackendFactory::init() +{ +#if CAN_ENUMERATE + sDeviceHelper.emplace(); +#endif + return true; +} bool CoreAudioBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string CoreAudioBackendFactory::probe(BackendType type) +auto CoreAudioBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; #if CAN_ENUMERATE auto append_name = [&outnames](const DeviceEntry &entry) -> void - { - /* Includes null char. */ - outnames.append(entry.mName.c_str(), entry.mName.length()+1); - }; + { outnames.emplace_back(entry.mName); }; + switch(type) { case BackendType::Playback: EnumerateDevices(PlaybackList, false); + outnames.reserve(PlaybackList.size()); std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); break; case BackendType::Capture: EnumerateDevices(CaptureList, true); + outnames.reserve(CaptureList.size()); std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name); break; } @@ -914,8 +994,7 @@ std::string CoreAudioBackendFactory::probe(BackendType type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - outnames.append(ca_device, sizeof(ca_device)); + outnames.emplace_back(ca_device); break; } #endif @@ -930,3 +1009,18 @@ BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendTyp return BackendPtr{new CoreAudioCapture{device}}; return nullptr; } + +alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DefaultDeviceChanged: + return alc::EventSupport::FullSupport; + + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/Engine/lib/openal-soft/alc/backends/coreaudio.h b/Engine/lib/openal-soft/alc/backends/coreaudio.h index 1252edde3..26c2aaf98 100644 --- a/Engine/lib/openal-soft/alc/backends/coreaudio.h +++ b/Engine/lib/openal-soft/alc/backends/coreaudio.h @@ -5,15 +5,17 @@ struct CoreAudioBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - static BackendFactory &getFactory(); + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_COREAUDIO_H */ diff --git a/Engine/lib/openal-soft/alc/backends/dsound.cpp b/Engine/lib/openal-soft/alc/backends/dsound.cpp index f549c0fe6..0f5993c6d 100644 --- a/Engine/lib/openal-soft/alc/backends/dsound.cpp +++ b/Engine/lib/openal-soft/alc/backends/dsound.cpp @@ -25,10 +25,6 @@ #define WIN32_LEAN_AND_MEAN #include -#include -#include -#include - #include #include #ifndef _WAVEFORMATEXTENSIBLE_ @@ -36,15 +32,21 @@ #include #endif +#include #include #include -#include -#include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include -#include "alnumeric.h" +#include "alspan.h" +#include "alstring.h" +#include "althrd_setname.h" #include "comptr.h" #include "core/device.h" #include "core/helpers.h" @@ -52,7 +54,6 @@ #include "dynload.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" /* MinGW-w64 needs this for some unknown reason now. */ using LPCWAVEFORMATEX = const WAVEFORMATEX*; @@ -129,10 +130,10 @@ struct DevMap { { } }; -al::vector PlaybackDevices; -al::vector CaptureDevices; +std::vector PlaybackDevices; +std::vector CaptureDevices; -bool checkName(const al::vector &list, const std::string &name) +bool checkName(const al::span list, const std::string &name) { auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; }; @@ -144,7 +145,7 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi if(!guid) return TRUE; - auto& devices = *static_cast*>(data); + auto& devices = *static_cast*>(data); const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)}; int count{1}; @@ -176,7 +177,7 @@ struct DSoundPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -189,8 +190,6 @@ struct DSoundPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(DSoundPlayback) }; DSoundPlayback::~DSoundPlayback() @@ -209,7 +208,7 @@ DSoundPlayback::~DSoundPlayback() FORCE_ALIGN int DSoundPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); DSBCAPS DSBCaps{}; DSBCaps.dwSize = sizeof(DSBCaps); @@ -299,24 +298,22 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() return 0; } -void DSoundPlayback::open(const char *name) +void DSoundPlayback::open(std::string_view name) { HRESULT hr; if(PlaybackDevices.empty()) { /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; + ComWrapper com{}; hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); if(FAILED(hr)) ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); } const GUID *guid{nullptr}; - if(!name && !PlaybackDevices.empty()) + if(name.empty() && !PlaybackDevices.empty()) { - name = PlaybackDevices[0].name.c_str(); + name = PlaybackDevices[0].name; guid = &PlaybackDevices[0].guid; } else @@ -332,7 +329,7 @@ void DSoundPlayback::open(const char *name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; } guid = &iter->guid; } @@ -347,7 +344,7 @@ void DSoundPlayback::open(const char *name) //DirectSound Init code ComPtr ds; if(SUCCEEDED(hr)) - hr = DirectSoundCreate(guid, ds.getPtr(), nullptr); + hr = DirectSoundCreate(guid, al::out_ptr(ds), nullptr); if(SUCCEEDED(hr)) hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); if(FAILED(hr)) @@ -425,49 +422,53 @@ bool DSoundPlayback::reset() case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break; case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break; case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break; + case DevFmtX7144: mDevice->FmtChans = DevFmtX714; + /* fall-through */ case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break; case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break; } -retry_open: - hr = S_OK; - OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.nChannels = static_cast(mDevice->channelsFromFmt()); - OutputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); - OutputType.Format.nBlockAlign = static_cast(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; + do { + hr = S_OK; + OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; + OutputType.Format.nChannels = static_cast(mDevice->channelsFromFmt()); + OutputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); + OutputType.Format.nBlockAlign = static_cast(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; - - mPrimaryBuffer = nullptr; - } - else - { - if(SUCCEEDED(hr) && !mPrimaryBuffer) + if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) { - DSBUFFERDESC DSBDescription{}; - DSBDescription.dwSize = sizeof(DSBDescription); - DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; - hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr); - } - if(SUCCEEDED(hr)) - hr = mPrimaryBuffer->SetFormat(&OutputType.Format); - } + OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + if(mDevice->FmtType == DevFmtFloat) + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + else + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + + mPrimaryBuffer = nullptr; + } + else + { + if(SUCCEEDED(hr) && !mPrimaryBuffer) + { + DSBUFFERDESC DSBDescription{}; + DSBDescription.dwSize = sizeof(DSBDescription); + DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; + hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr); + } + if(SUCCEEDED(hr)) + hr = mPrimaryBuffer->SetFormat(&OutputType.Format); + } + + if(FAILED(hr)) + break; - if(SUCCEEDED(hr)) - { uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; if(num_updates > MAX_UPDATES) num_updates = MAX_UPDATES; @@ -480,26 +481,21 @@ retry_open: DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; DSBDescription.lpwfxFormat = &OutputType.Format; - hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr); - if(FAILED(hr) && mDevice->FmtType == DevFmtFloat) - { - mDevice->FmtType = DevFmtShort; - goto retry_open; - } - } + hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr); + if(SUCCEEDED(hr) || mDevice->FmtType != DevFmtFloat) + break; + mDevice->FmtType = DevFmtShort; + } while(FAILED(hr)); if(SUCCEEDED(hr)) { - void *ptr; - hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr); + hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies)); if(SUCCEEDED(hr)) { - mNotifies = ComPtr{static_cast(ptr)}; - uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; assert(num_updates <= MAX_UPDATES); - std::array nots; + std::array nots{}; for(uint i{0};i < num_updates;++i) { nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign; @@ -550,10 +546,10 @@ struct DSoundCapture final : public BackendBase { DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundCapture() override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ComPtr mDSC; @@ -562,8 +558,6 @@ struct DSoundCapture final : public BackendBase { DWORD mCursor{0u}; RingBufferPtr mRing; - - DEF_NEWDEL(DSoundCapture) }; DSoundCapture::~DSoundCapture() @@ -577,24 +571,22 @@ DSoundCapture::~DSoundCapture() } -void DSoundCapture::open(const char *name) +void DSoundCapture::open(std::string_view name) { HRESULT hr; if(CaptureDevices.empty()) { /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; + ComWrapper com{}; hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); if(FAILED(hr)) ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); } const GUID *guid{nullptr}; - if(!name && !CaptureDevices.empty()) + if(name.empty() && !CaptureDevices.empty()) { - name = CaptureDevices[0].name.c_str(); + name = CaptureDevices[0].name; guid = &CaptureDevices[0].guid; } else @@ -610,7 +602,7 @@ void DSoundCapture::open(const char *name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; } guid = &iter->guid; } @@ -641,6 +633,7 @@ void DSoundCapture::open(const char *name) case DevFmtX61: InputType.dwChannelMask = X6DOT1; break; case DevFmtX71: InputType.dwChannelMask = X7DOT1; break; case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break; + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans)); @@ -657,6 +650,7 @@ void DSoundCapture::open(const char *name) InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec * InputType.Format.nBlockAlign; InputType.Format.cbSize = 0; + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; if(mDevice->FmtType == DevFmtFloat) InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; @@ -669,8 +663,7 @@ void DSoundCapture::open(const char *name) InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); } - uint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); + const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; DSCBUFFERDESC DSCBDescription{}; DSCBDescription.dwSize = sizeof(DSCBDescription); @@ -679,9 +672,9 @@ void DSoundCapture::open(const char *name) DSCBDescription.lpwfxFormat = &InputType.Format; //DirectSoundCapture Init code - hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr); + hr = DirectSoundCaptureCreate(guid, al::out_ptr(mDSC), nullptr); if(SUCCEEDED(hr)) - mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr); + mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr); if(SUCCEEDED(hr)) mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false); @@ -719,8 +712,8 @@ void DSoundCapture::stop() } } -void DSoundCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void DSoundCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint DSoundCapture::availableSamples() { @@ -743,9 +736,9 @@ uint DSoundCapture::availableSamples() } if(SUCCEEDED(hr)) { - mRing->write(ReadPtr1, ReadCnt1/FrameSize); + std::ignore = mRing->write(ReadPtr1, ReadCnt1/FrameSize); if(ReadPtr2 != nullptr && ReadCnt2 > 0) - mRing->write(ReadPtr2, ReadCnt2/FrameSize); + std::ignore = mRing->write(ReadPtr2, ReadCnt2/FrameSize); hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2); mCursor = ReadCursor; } @@ -802,40 +795,32 @@ bool DSoundBackendFactory::init() bool DSoundBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string DSoundBackendFactory::probe(BackendType type) +auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - }; + { outnames.emplace_back(entry.name); }; /* Initialize COM to prevent name truncation */ - HRESULT hr; - HRESULT hrcom{CoInitialize(nullptr)}; + ComWrapper com{}; switch(type) { case BackendType::Playback: PlaybackDevices.clear(); - hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); - if(FAILED(hr)) + if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(hr)) ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices.clear(); - hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); - if(FAILED(hr)) + if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(hr)) ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } - if(SUCCEEDED(hrcom)) - CoUninitialize(); return outnames; } diff --git a/Engine/lib/openal-soft/alc/backends/dsound.h b/Engine/lib/openal-soft/alc/backends/dsound.h index 787f227a0..33adbf297 100644 --- a/Engine/lib/openal-soft/alc/backends/dsound.h +++ b/Engine/lib/openal-soft/alc/backends/dsound.h @@ -5,15 +5,15 @@ struct DSoundBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_DSOUND_H */ diff --git a/Engine/lib/openal-soft/alc/backends/jack.cpp b/Engine/lib/openal-soft/alc/backends/jack.cpp index 791002ca9..39cfeb39c 100644 --- a/Engine/lib/openal-soft/alc/backends/jack.cpp +++ b/Engine/lib/openal-soft/alc/backends/jack.cpp @@ -22,23 +22,26 @@ #include "jack.h" +#include #include #include #include #include - -#include +#include #include #include +#include #include "alc/alconfig.h" #include "alnumeric.h" +#include "alsem.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" -#include "threads.h" #include #include @@ -46,6 +49,8 @@ namespace { +using namespace std::string_view_literals; + #ifdef HAVE_DYNLOAD #define JACK_FUNCS(MAGIC) \ MAGIC(jack_client_open); \ @@ -99,19 +104,13 @@ decltype(jack_error_callback) * pjack_error_callback; #endif -constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE; - jack_options_t ClientOptions = JackNullOption; bool jack_load() { - bool error{false}; - #ifdef HAVE_DYNLOAD if(!jack_handle) { - std::string missing_funcs; - #ifdef _WIN32 #define JACKLIB "libjack.dll" #else @@ -124,13 +123,10 @@ bool jack_load() return false; } - error = false; + std::string missing_funcs; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(jack_handle, #f)); \ - if(p##f == nullptr) { \ - error = true; \ - missing_funcs += "\n" #f; \ - } \ + if(p##f == nullptr) missing_funcs += "\n" #f; \ } while(0) JACK_FUNCS(LOAD_FUNC); #undef LOAD_FUNC @@ -139,61 +135,66 @@ bool jack_load() LOAD_SYM(jack_error_callback); #undef LOAD_SYM - if(error) + if(!missing_funcs.empty()) { WARN("Missing expected functions:%s\n", missing_funcs.c_str()); CloseLib(jack_handle); jack_handle = nullptr; + return false; } } #endif - return !error; + return true; } struct JackDeleter { void operator()(void *ptr) { jack_free(ptr); } }; -using JackPortsPtr = std::unique_ptr; +using JackPortsPtr = std::unique_ptr; /* NOLINT(*-avoid-c-arrays) */ struct DeviceEntry { std::string mName; std::string mPattern; + DeviceEntry() = default; + DeviceEntry(const DeviceEntry&) = default; + DeviceEntry(DeviceEntry&&) = default; template DeviceEntry(T&& name, U&& pattern) : mName{std::forward(name)}, mPattern{std::forward(pattern)} { } + ~DeviceEntry(); + + DeviceEntry& operator=(const DeviceEntry&) = default; + DeviceEntry& operator=(DeviceEntry&&) = default; }; +DeviceEntry::~DeviceEntry() = default; -al::vector PlaybackList; +std::vector PlaybackList; -void EnumerateDevices(jack_client_t *client, al::vector &list) +void EnumerateDevices(jack_client_t *client, std::vector &list) { std::remove_reference_t{}.swap(list); - if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)}) + if(JackPortsPtr ports{jack_get_ports(client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)}) { for(size_t i{0};ports[i];++i) { - const char *sep{std::strchr(ports[i], ':')}; - if(!sep || ports[i] == sep) continue; + const std::string_view portname{ports[i]}; + const size_t seppos{portname.find(':')}; + if(seppos == 0 || seppos >= portname.size()) + continue; - const al::span portdev{ports[i], sep}; + const auto portdev = portname.substr(0, seppos); auto check_name = [portdev](const DeviceEntry &entry) -> bool - { - const size_t len{portdev.size()}; - return entry.mName.length() == len - && entry.mName.compare(0, len, portdev.data(), len) == 0; - }; + { return entry.mName == portdev; }; if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend()) continue; - std::string name{portdev.data(), portdev.size()}; - list.emplace_back(name, name+":"); - const auto &entry = list.back(); + const auto &entry = list.emplace_back(portdev, std::string{portdev}+":"); TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); } /* There are ports but couldn't get device names from them. Add a @@ -202,11 +203,11 @@ void EnumerateDevices(jack_client_t *client, al::vector &list) if(ports[0] && list.empty()) { WARN("No device names found in available ports, adding a generic name.\n"); - list.emplace_back("JACK", ""); + list.emplace_back("JACK"sv, ""sv); } } - if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices")) + if(auto listopt = ConfigValueStr({}, "jack", "custom-devices")) { for(size_t strpos{0};strpos < listopt->size();) { @@ -214,38 +215,32 @@ void EnumerateDevices(jack_client_t *client, al::vector &list) size_t seppos{listopt->find('=', strpos)}; if(seppos >= nextpos || seppos == strpos) { - const std::string entry{listopt->substr(strpos, nextpos-strpos)}; - ERR("Invalid device entry: \"%s\"\n", entry.c_str()); + const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos); + ERR("Invalid device entry: \"%.*s\"\n", al::sizei(entry), entry.data()); if(nextpos != std::string::npos) ++nextpos; strpos = nextpos; continue; } - const al::span name{listopt->data()+strpos, seppos-strpos}; - const al::span pattern{listopt->data()+(seppos+1), - std::min(nextpos, listopt->size())-(seppos+1)}; + const auto name = std::string_view{*listopt}.substr(strpos, seppos-strpos); + const auto pattern = std::string_view{*listopt}.substr(seppos+1, + std::min(nextpos, listopt->size())-(seppos+1)); /* Check if this custom pattern already exists in the list. */ auto check_pattern = [pattern](const DeviceEntry &entry) -> bool - { - const size_t len{pattern.size()}; - return entry.mPattern.length() == len - && entry.mPattern.compare(0, len, pattern.data(), len) == 0; - }; + { return entry.mPattern == pattern; }; auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern); if(itemmatch != list.end()) { /* If so, replace the name with this custom one. */ - itemmatch->mName.assign(name.data(), name.size()); + itemmatch->mName = name; TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(), itemmatch->mPattern.c_str()); } else { /* Otherwise, add a new device entry. */ - list.emplace_back(std::string{name.data(), name.size()}, - std::string{pattern.data(), pattern.size()}); - const auto &entry = list.back(); + const auto &entry = list.emplace_back(name, pattern); TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); } @@ -295,7 +290,7 @@ struct JackPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -304,7 +299,7 @@ struct JackPlayback final : public BackendBase { std::string mPortPattern; jack_client_t *mClient{nullptr}; - std::array mPort{}; + std::array mPort{}; std::mutex mMutex; @@ -315,8 +310,6 @@ struct JackPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(JackPlayback) }; JackPlayback::~JackPlayback() @@ -336,22 +329,22 @@ JackPlayback::~JackPlayback() int JackPlayback::processRt(jack_nframes_t numframes) noexcept { - std::array out; - size_t numchans{0}; + auto outptrs = std::array{}; + auto numchans = size_t{0}; for(auto port : mPort) { if(!port || numchans == mDevice->RealOut.Buffer.size()) break; - out[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); + outptrs[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); } + const auto dst = al::span{outptrs}.first(numchans); if(mPlaying.load(std::memory_order_acquire)) LIKELY - mDevice->renderSamples({out.data(), numchans}, static_cast(numframes)); + mDevice->renderSamples(dst, static_cast(numframes)); else { - auto clear_buf = [numframes](float *outbuf) -> void - { std::fill_n(outbuf, numframes, 0.0f); }; - std::for_each(out.begin(), out.begin()+numchans, clear_buf); + std::for_each(dst.begin(), dst.end(), [numframes](float *outbuf) -> void + { std::fill_n(outbuf, numframes, 0.0f); }); } return 0; @@ -360,53 +353,46 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept int JackPlayback::process(jack_nframes_t numframes) noexcept { - std::array out; + std::array,MaxOutputChannels> out; size_t numchans{0}; for(auto port : mPort) { if(!port) break; - out[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); + out[numchans++] = {static_cast(jack_port_get_buffer(port, numframes)), numframes}; } - jack_nframes_t total{0}; + size_t total{0}; if(mPlaying.load(std::memory_order_acquire)) LIKELY { auto data = mRing->getReadVector(); - jack_nframes_t todo{minu(numframes, static_cast(data.first.len))}; - auto write_first = [&data,numchans,todo](float *outbuf) -> float* - { - const float *RESTRICT in = reinterpret_cast(data.first.buf); - auto deinterlace_input = [&in,numchans]() noexcept -> float - { - float ret{*in}; - in += numchans; - return ret; - }; - std::generate_n(outbuf, todo, deinterlace_input); - data.first.buf += sizeof(float); - return outbuf + todo; - }; - std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first); - total += todo; + const auto update_size = size_t{mDevice->UpdateSize}; - todo = minu(numframes-total, static_cast(data.second.len)); - if(todo > 0) + const auto outlen = size_t{numframes / update_size}; + const auto len1 = size_t{std::min(data.first.len/update_size, outlen)}; + const auto len2 = size_t{std::min(data.second.len/update_size, outlen-len1)}; + + auto src = al::span{reinterpret_cast(data.first.buf), update_size*len1*numchans}; + for(size_t i{0};i < len1;++i) { - auto write_second = [&data,numchans,todo](float *outbuf) -> float* + for(size_t c{0};c < numchans;++c) { - const float *RESTRICT in = reinterpret_cast(data.second.buf); - auto deinterlace_input = [&in,numchans]() noexcept -> float - { - float ret{*in}; - in += numchans; - return ret; - }; - std::generate_n(outbuf, todo, deinterlace_input); - data.second.buf += sizeof(float); - return outbuf + todo; - }; - std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second); - total += todo; + const auto iter = std::copy_n(src.begin(), update_size, out[c].begin()); + out[c] = {iter, out[c].end()}; + src = src.subspan(update_size); + } + total += update_size; + } + + src = al::span{reinterpret_cast(data.second.buf), update_size*len2*numchans}; + for(size_t i{0};i < len2;++i) + { + for(size_t c{0};c < numchans;++c) + { + const auto iter = std::copy_n(src.begin(), update_size, out[c].begin()); + out[c] = {iter, out[c].end()}; + src = src.subspan(update_size); + } + total += update_size; } mRing->readAdvance(total); @@ -415,8 +401,8 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept if(numframes > total) { - const jack_nframes_t todo{numframes - total}; - auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); }; + auto clear_buf = [](const al::span outbuf) -> void + { std::fill(outbuf.begin(), outbuf.end(), 0.0f); }; std::for_each(out.begin(), out.begin()+numchans, clear_buf); } @@ -426,45 +412,70 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept int JackPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); - const size_t frame_step{mDevice->channelsFromFmt()}; + const auto update_size = uint{mDevice->UpdateSize}; + const auto num_channels = size_t{mDevice->channelsFromFmt()}; + auto outptrs = std::vector(num_channels); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { - if(mRing->writeSpace() < mDevice->UpdateSize) + if(mRing->writeSpace() < update_size) { mSem.wait(); continue; } auto data = mRing->getWriteVector(); - size_t todo{data.first.len + data.second.len}; - todo -= todo%mDevice->UpdateSize; + const auto len1 = size_t{data.first.len / update_size}; + const auto len2 = size_t{data.second.len / update_size}; - const auto len1 = static_cast(minz(data.first.len, todo)); - const auto len2 = static_cast(minz(data.second.len, todo-len1)); - - std::lock_guard _{mMutex}; - mDevice->renderSamples(data.first.buf, len1, frame_step); + std::lock_guard dlock{mMutex}; + auto buffer = al::span{reinterpret_cast(data.first.buf), + data.first.len*num_channels}; + auto bufiter = buffer.begin(); + for(size_t i{0};i < len1;++i) + { + std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size] + { + auto ret = al::to_address(bufiter); + bufiter += ptrdiff_t(update_size); + return ret; + }); + mDevice->renderSamples(outptrs, update_size); + } if(len2 > 0) - mDevice->renderSamples(data.second.buf, len2, frame_step); - mRing->writeAdvance(todo); + { + buffer = al::span{reinterpret_cast(data.second.buf), + data.second.len*num_channels}; + bufiter = buffer.begin(); + for(size_t i{0};i < len2;++i) + { + std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size] + { + auto ret = al::to_address(bufiter); + bufiter += ptrdiff_t(update_size); + return ret; + }); + mDevice->renderSamples(outptrs, update_size); + } + } + mRing->writeAdvance((len1+len2) * update_size); } return 0; } -void JackPlayback::open(const char *name) +void JackPlayback::open(std::string_view name) { if(!mClient) { const PathNamePair &binname = GetProcBinary(); const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; - jack_status_t status; + jack_status_t status{}; mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); if(mClient == nullptr) throw al::backend_exception{al::backend_error::DeviceError, @@ -481,9 +492,9 @@ void JackPlayback::open(const char *name) if(PlaybackList.empty()) EnumerateDevices(mClient, PlaybackList); - if(!name && !PlaybackList.empty()) + if(name.empty() && !PlaybackList.empty()) { - name = PlaybackList[0].mName.c_str(); + name = PlaybackList[0].mName; mPortPattern = PlaybackList[0].mPattern; } else @@ -493,14 +504,10 @@ void JackPlayback::open(const char *name) auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name); if(iter == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name?name:""}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; mPortPattern = iter->mPattern; } - mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true); - jack_set_process_callback(mClient, - mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); - mDevice->DeviceName = name; } @@ -511,6 +518,10 @@ bool JackPlayback::reset() std::for_each(mPort.begin(), mPort.end(), unregister_port); mPort.fill(nullptr); + mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true); + jack_set_process_callback(mClient, + mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); + /* Ignore the requested buffer metrics and just keep one JACK-sized buffer * ready for when requested. */ @@ -525,9 +536,9 @@ bool JackPlayback::reset() } else { - const char *devname{mDevice->DeviceName.c_str()}; + const std::string_view devname{mDevice->DeviceName}; uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); + bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); mDevice->BufferSize = bufsize + mDevice->UpdateSize; } @@ -535,27 +546,27 @@ bool JackPlayback::reset() mDevice->FmtType = DevFmtFloat; int port_num{0}; - auto ports_end = mPort.begin() + mDevice->channelsFromFmt(); - auto bad_port = mPort.begin(); - while(bad_port != ports_end) + auto ports = al::span{mPort}.first(mDevice->channelsFromFmt()); + auto bad_port = ports.begin(); + while(bad_port != ports.end()) { std::string name{"channel_" + std::to_string(++port_num)}; - *bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType, + *bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); if(!*bad_port) break; ++bad_port; } - if(bad_port != ports_end) + if(bad_port != ports.end()) { ERR("Failed to register enough JACK ports for %s output\n", DevFmtChannelsString(mDevice->FmtChans)); - if(bad_port == mPort.begin()) return false; + if(bad_port == ports.begin()) return false; - if(bad_port == mPort.begin()+1) + if(bad_port == ports.begin()+1) mDevice->FmtChans = DevFmtMono; else { - ports_end = mPort.begin()+2; + const auto ports_end = ports.begin()+2; while(bad_port != ports_end) { jack_port_unregister(mClient, *(--bad_port)); @@ -575,10 +586,10 @@ void JackPlayback::start() if(jack_activate(mClient)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"}; - const char *devname{mDevice->DeviceName.c_str()}; + const std::string_view devname{mDevice->DeviceName}; if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true)) { - JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType, + JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)}; if(!pnames) { @@ -586,7 +597,7 @@ void JackPlayback::start() throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"}; } - for(size_t i{0};i < al::size(mPort) && mPort[i];++i) + for(size_t i{0};i < std::size(mPort) && mPort[i];++i) { if(!pnames[i]) { @@ -613,7 +624,7 @@ void JackPlayback::start() else { uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); + bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); mDevice->BufferSize = bufsize + mDevice->UpdateSize; mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true); @@ -651,10 +662,9 @@ void JackPlayback::stop() ClockLatency JackPlayback::getClockLatency() { - ClockLatency ret; - - std::lock_guard _{mMutex}; - ret.ClockTime = GetDeviceClockTime(mDevice); + std::lock_guard dlock{mMutex}; + ClockLatency ret{}; + ret.ClockTime = mDevice->getClockTime(); ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; @@ -674,7 +684,7 @@ bool JackBackendFactory::init() if(!jack_load()) return false; - if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false)) + if(!GetConfigValueBool({}, "jack", "spawn-server", false)) ClientOptions = static_cast(ClientOptions | JackNoStartServer); const PathNamePair &binname = GetProcBinary(); @@ -682,7 +692,7 @@ bool JackBackendFactory::init() void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr}; jack_set_error_function(jack_msg_handler); - jack_status_t status; + jack_status_t status{}; jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)}; jack_set_error_function(old_error_cb); if(!client) @@ -700,18 +710,15 @@ bool JackBackendFactory::init() bool JackBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } -std::string JackBackendFactory::probe(BackendType type) +auto JackBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto append_name = [&outnames](const DeviceEntry &entry) -> void - { - /* Includes null char. */ - outnames.append(entry.mName.c_str(), entry.mName.length()+1); - }; + { outnames.emplace_back(entry.mName); }; const PathNamePair &binname = GetProcBinary(); const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; - jack_status_t status; + jack_status_t status{}; switch(type) { case BackendType::Playback: @@ -722,6 +729,7 @@ std::string JackBackendFactory::probe(BackendType type) } else WARN("jack_client_open() failed, 0x%02x\n", status); + outnames.reserve(PlaybackList.size()); std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); break; case BackendType::Capture: diff --git a/Engine/lib/openal-soft/alc/backends/jack.h b/Engine/lib/openal-soft/alc/backends/jack.h index b83f24dda..1e4c9f05b 100644 --- a/Engine/lib/openal-soft/alc/backends/jack.h +++ b/Engine/lib/openal-soft/alc/backends/jack.h @@ -5,15 +5,15 @@ struct JackBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_JACK_H */ diff --git a/Engine/lib/openal-soft/alc/backends/loopback.cpp b/Engine/lib/openal-soft/alc/backends/loopback.cpp index bf4ab2467..e999ca514 100644 --- a/Engine/lib/openal-soft/alc/backends/loopback.cpp +++ b/Engine/lib/openal-soft/alc/backends/loopback.cpp @@ -30,16 +30,14 @@ namespace { struct LoopbackBackend final : public BackendBase { LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; - - DEF_NEWDEL(LoopbackBackend) }; -void LoopbackBackend::open(const char *name) +void LoopbackBackend::open(std::string_view name) { mDevice->DeviceName = name; } @@ -65,8 +63,8 @@ bool LoopbackBackendFactory::init() bool LoopbackBackendFactory::querySupport(BackendType) { return true; } -std::string LoopbackBackendFactory::probe(BackendType) -{ return std::string{}; } +auto LoopbackBackendFactory::enumerate(BackendType) -> std::vector +{ return {}; } BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType) { return BackendPtr{new LoopbackBackend{device}}; } diff --git a/Engine/lib/openal-soft/alc/backends/loopback.h b/Engine/lib/openal-soft/alc/backends/loopback.h index cb42b3c8e..876a052cb 100644 --- a/Engine/lib/openal-soft/alc/backends/loopback.h +++ b/Engine/lib/openal-soft/alc/backends/loopback.h @@ -5,15 +5,15 @@ struct LoopbackBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_LOOPBACK_H */ diff --git a/Engine/lib/openal-soft/alc/backends/null.cpp b/Engine/lib/openal-soft/alc/backends/null.cpp index 5a8fc255c..d2e036ad9 100644 --- a/Engine/lib/openal-soft/alc/backends/null.cpp +++ b/Engine/lib/openal-soft/alc/backends/null.cpp @@ -30,10 +30,11 @@ #include #include -#include "core/device.h" #include "almalloc.h" +#include "alstring.h" +#include "althrd_setname.h" +#include "core/device.h" #include "core/helpers.h" -#include "threads.h" namespace { @@ -41,8 +42,9 @@ namespace { using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; +using namespace std::string_view_literals; -constexpr char nullDevice[] = "No Output"; +[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "No Output"sv; } struct NullBackend final : public BackendBase { @@ -50,15 +52,13 @@ struct NullBackend final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(NullBackend) }; int NullBackend::mixerProc() @@ -66,7 +66,7 @@ int NullBackend::mixerProc() const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); int64_t done{0}; auto start = std::chrono::steady_clock::now(); @@ -105,13 +105,13 @@ int NullBackend::mixerProc() } -void NullBackend::open(const char *name) +void NullBackend::open(std::string_view name) { - if(!name) - name = nullDevice; - else if(strcmp(name, nullDevice) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; mDevice->DeviceName = name; } @@ -150,19 +150,17 @@ bool NullBackendFactory::init() bool NullBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } -std::string NullBackendFactory::probe(BackendType type) +auto NullBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; switch(type) { case BackendType::Playback: - /* Includes null char. */ - outnames.append(nullDevice, sizeof(nullDevice)); - break; + /* Include null char. */ + return std::vector{std::string{GetDeviceName()}}; case BackendType::Capture: break; } - return outnames; + return {}; } BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/null.h b/Engine/lib/openal-soft/alc/backends/null.h index 7048cad6f..213842af7 100644 --- a/Engine/lib/openal-soft/alc/backends/null.h +++ b/Engine/lib/openal-soft/alc/backends/null.h @@ -5,15 +5,15 @@ struct NullBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_NULL_H */ diff --git a/Engine/lib/openal-soft/alc/backends/oboe.cpp b/Engine/lib/openal-soft/alc/backends/oboe.cpp index 461f5a6ac..68acd0094 100644 --- a/Engine/lib/openal-soft/alc/backends/oboe.cpp +++ b/Engine/lib/openal-soft/alc/backends/oboe.cpp @@ -4,10 +4,11 @@ #include "oboe.h" #include +#include #include -#include #include "alnumeric.h" +#include "alstring.h" #include "core/device.h" #include "core/logging.h" #include "ringbuffer.h" @@ -17,7 +18,9 @@ namespace { -constexpr char device_name[] = "Oboe Default"; +using namespace std::string_view_literals; + +[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Oboe Default"sv; } struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { @@ -28,7 +31,9 @@ struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override; - void open(const char *name) override; + void onErrorAfterClose(oboe::AudioStream* /* audioStream */, oboe::Result /* error */) override; + + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -46,14 +51,20 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea return oboe::DataCallbackResult::Continue; } - -void OboePlayback::open(const char *name) +void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error) { - if(!name) - name = device_name; - else if(std::strcmp(name, device_name) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(error == oboe::Result::ErrorDisconnected) + mDevice->handleDisconnect("Oboe AudioStream was disconnected: %s", oboe::convertToText(error)); + TRACE("Error was %s", oboe::convertToText(error)); +} + +void OboePlayback::open(std::string_view name) +{ + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; /* Open a basic output stream, just to ensure it can work. */ oboe::ManagedStream stream; @@ -72,6 +83,7 @@ bool OboePlayback::reset() oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Output); builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); + builder.setUsage(oboe::Usage::Game); /* Don't let Oboe convert. We should be able to handle anything it gives * back. */ @@ -135,7 +147,7 @@ bool OboePlayback::reset() if(result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", oboe::convertToText(result)}; - mStream->setBufferSizeInFrames(mini(static_cast(mDevice->BufferSize), + mStream->setBufferSizeInFrames(std::min(static_cast(mDevice->BufferSize), mStream->getBufferCapacityInFrames())); TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); @@ -164,6 +176,9 @@ bool OboePlayback::reset() mDevice->FmtType = DevFmtInt; break; case oboe::AudioFormat::I24: +#endif +#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 8) + case oboe::AudioFormat::IEC61937: #endif case oboe::AudioFormat::Unspecified: case oboe::AudioFormat::Invalid: @@ -177,9 +192,9 @@ bool OboePlayback::reset() * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum * update size. */ - mDevice->UpdateSize = maxu(mDevice->Frequency / 100, + mDevice->UpdateSize = std::max(mDevice->Frequency/100u, static_cast(mStream->getFramesPerBurst())); - mDevice->BufferSize = maxu(mDevice->UpdateSize * 2, + mDevice->BufferSize = std::max(mDevice->UpdateSize*2u, static_cast(mStream->getBufferSizeInFrames())); return true; @@ -197,8 +212,7 @@ void OboePlayback::stop() { oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", - oboe::convertToText(result)}; + ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); } @@ -212,28 +226,28 @@ struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; }; oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData, int32_t numFrames) { - mRing->write(audioData, static_cast(numFrames)); + std::ignore = mRing->write(audioData, static_cast(numFrames)); return oboe::DataCallbackResult::Continue; } -void OboeCapture::open(const char *name) +void OboeCapture::open(std::string_view name) { - if(!name) - name = device_name; - else if(std::strcmp(name, device_name) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Input) @@ -259,6 +273,7 @@ void OboeCapture::open(const char *name) case DevFmtX61: case DevFmtX71: case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", @@ -297,7 +312,7 @@ void OboeCapture::open(const char *name) TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); /* Ensure a minimum ringbuffer size of 100ms. */ - mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10), + mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u), static_cast(mStream->getBytesPerFrame()), false); mDevice->DeviceName = name; @@ -315,15 +330,14 @@ void OboeCapture::stop() { const oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", - oboe::convertToText(result)}; + ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); } uint OboeCapture::availableSamples() { return static_cast(mRing->readSpace()); } -void OboeCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void OboeCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } } // namespace @@ -332,16 +346,15 @@ bool OboeBackendFactory::init() { return true; } bool OboeBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string OboeBackendFactory::probe(BackendType type) +auto OboeBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - return std::string{device_name, sizeof(device_name)}; + return std::vector{std::string{GetDeviceName()}}; } - return std::string{}; + return {}; } BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/oboe.h b/Engine/lib/openal-soft/alc/backends/oboe.h index a39c24454..d277cfe7c 100644 --- a/Engine/lib/openal-soft/alc/backends/oboe.h +++ b/Engine/lib/openal-soft/alc/backends/oboe.h @@ -5,15 +5,15 @@ struct OboeBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OBOE_H */ diff --git a/Engine/lib/openal-soft/alc/backends/opensl.cpp b/Engine/lib/openal-soft/alc/backends/opensl.cpp index f5b98fb88..26e690709 100644 --- a/Engine/lib/openal-soft/alc/backends/opensl.cpp +++ b/Engine/lib/openal-soft/alc/backends/opensl.cpp @@ -23,23 +23,26 @@ #include "opensl.h" -#include #include -#include #include +#include #include +#include +#include #include #include #include "albit.h" #include "alnumeric.h" +#include "alsem.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "opthelpers.h" #include "ringbuffer.h" -#include "threads.h" #include #include @@ -48,15 +51,17 @@ namespace { +using namespace std::string_view_literals; + /* Helper macros */ #define EXTRACT_VCALL_ARGS(...) __VA_ARGS__)) #define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS #define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS -constexpr char opensl_device[] = "OpenSL"; - +[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "OpenSL"sv; } +[[nodiscard]] constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept { switch(chans) @@ -80,6 +85,7 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT | SL_SPEAKER_TOP_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT | SL_SPEAKER_TOP_BACK_RIGHT; + case DevFmtX7144: case DevFmtAmbi3D: break; } @@ -159,12 +165,10 @@ struct OpenSLPlayback final : public BackendBase { ~OpenSLPlayback() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept - { static_cast(context)->process(bq); } int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -189,8 +193,6 @@ struct OpenSLPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(OpenSLPlayback) }; OpenSLPlayback::~OpenSLPlayback() @@ -229,7 +231,7 @@ void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept int OpenSLPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); SLPlayItf player; SLAndroidSimpleBufferQueueItf bufferQueue; @@ -312,13 +314,13 @@ int OpenSLPlayback::mixerProc() } -void OpenSLPlayback::open(const char *name) +void OpenSLPlayback::open(std::string_view name) { - if(!name) - name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; /* There's only one device, so if it's already open, there's nothing to do. */ if(mEngineObj) return; @@ -375,74 +377,6 @@ bool OpenSLPlayback::reset() mRing = nullptr; -#if 0 - if(!mDevice->Flags.get()) - { - /* 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; @@ -564,7 +498,9 @@ void OpenSLPlayback::start() PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this); + result = VCALL(bufferQueue,RegisterCallback)( + [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept + { static_cast(context)->process(bq); }, this); PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS != result) @@ -628,8 +564,8 @@ ClockLatency OpenSLPlayback::getClockLatency() { ClockLatency ret; - std::lock_guard _{mMutex}; - ret.ClockTime = GetDeviceClockTime(mDevice); + std::lock_guard dlock{mMutex}; + ret.ClockTime = mDevice->getClockTime(); ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; @@ -642,13 +578,11 @@ struct OpenSLCapture final : public BackendBase { ~OpenSLCapture() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept - { static_cast(context)->process(bq); } - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; /* engine interfaces */ @@ -662,8 +596,6 @@ struct OpenSLCapture final : public BackendBase { uint mSplOffset{0u}; uint mFrameSize{0}; - - DEF_NEWDEL(OpenSLCapture) }; OpenSLCapture::~OpenSLCapture() @@ -686,13 +618,13 @@ void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept } -void OpenSLCapture::open(const char* name) +void OpenSLCapture::open(std::string_view name) { - if(!name) - name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; PrintErr(result, "slCreateEngine"); @@ -710,10 +642,10 @@ void OpenSLCapture::open(const char* name) { mFrameSize = mDevice->frameSizeFromFmt(); /* Ensure the total length is at least 100ms */ - uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)}; + uint length{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ - uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100, - mDevice->Frequency/100*5)}; + uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u, + mDevice->Frequency/100u*5u)}; uint num_updates{(length+update_len-1) / update_len}; mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false); @@ -813,13 +745,15 @@ void OpenSLCapture::open(const char* name) } if(SL_RESULT_SUCCESS == result) { - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this); + result = VCALL(bufferQueue,RegisterCallback)( + [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept + { static_cast(context)->process(bq); }, this); PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS == result) { const uint chunk_size{mDevice->UpdateSize * mFrameSize}; - const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0}; + const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0}; auto data = mRing->getWriteVector(); std::fill_n(data.first.buf, data.first.len*chunk_size, silence); @@ -883,7 +817,7 @@ void OpenSLCapture::stop() } } -void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) +void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) { const uint update_size{mDevice->UpdateSize}; const uint chunk_size{update_size * mFrameSize}; @@ -895,7 +829,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) auto rdata = mRing->getReadVector(); for(uint i{0};i < samples;) { - const uint rem{minu(samples - i, update_size - mSplOffset)}; + const uint rem{std::min(samples - i, update_size - mSplOffset)}; std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize}, buffer + i*size_t{mFrameSize}); @@ -932,7 +866,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) return; /* For each buffer chunk that was fully read, queue another writable buffer - * chunk to keep the OpenSL queue full. This is rather convulated, as a + * chunk to keep the OpenSL queue full. This is rather convoluted, as a * result of the ring buffer holding more elements than are writable at a * given time. The end of the write vector increments when the read pointer * advances, which will "expose" a previously unwritable element. So for @@ -975,18 +909,15 @@ bool OSLBackendFactory::init() { return true; } bool OSLBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string OSLBackendFactory::probe(BackendType type) +auto OSLBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - outnames.append(opensl_device, sizeof(opensl_device)); - break; + return std::vector{std::string{GetDeviceName()}}; } - return outnames; + return {}; } BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/opensl.h b/Engine/lib/openal-soft/alc/backends/opensl.h index b81624476..9f13dd71f 100644 --- a/Engine/lib/openal-soft/alc/backends/opensl.h +++ b/Engine/lib/openal-soft/alc/backends/opensl.h @@ -5,15 +5,15 @@ struct OSLBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OSL_H */ diff --git a/Engine/lib/openal-soft/alc/backends/oss.cpp b/Engine/lib/openal-soft/alc/backends/oss.cpp index 6d4fa2615..bab99842c 100644 --- a/Engine/lib/openal-soft/alc/backends/oss.cpp +++ b/Engine/lib/openal-soft/alc/backends/oss.cpp @@ -31,27 +31,26 @@ #include #include #include -#include #include #include #include #include -#include #include +#include +#include #include #include +#include -#include "albyte.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "aloptional.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" -#include "threads.h" -#include "vector.h" #include @@ -83,117 +82,148 @@ namespace { -constexpr char DefaultName[] = "OSS Default"; -std::string DefaultPlayback{"/dev/dsp"}; -std::string DefaultCapture{"/dev/dsp"}; +using namespace std::string_literals; +using namespace std::string_view_literals; + +[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OSS Default"sv; } + +std::string DefaultPlayback{"/dev/dsp"s}; +std::string DefaultCapture{"/dev/dsp"s}; struct DevMap { std::string name; std::string device_name; + + template + DevMap(T&& name_, U&& devname_) + : name{std::forward(name_)}, device_name{std::forward(devname_)} + { } }; -al::vector PlaybackDevices; -al::vector CaptureDevices; +std::vector PlaybackDevices; +std::vector CaptureDevices; #ifdef ALC_OSS_COMPAT #define DSP_CAP_OUTPUT 0x00020000 #define DSP_CAP_INPUT 0x00010000 -void ALCossListPopulate(al::vector &devlist, int type) +void ALCossListPopulate(std::vector &devlist, int type) { - devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback}); + devlist.emplace_back(GetDefaultName(), (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback); } #else -void ALCossListAppend(al::vector &list, al::span handle, al::span path) +class FileHandle { + int mFd{-1}; + +public: + FileHandle() = default; + FileHandle(const FileHandle&) = delete; + FileHandle& operator=(const FileHandle&) = delete; + ~FileHandle() { if(mFd != -1) ::close(mFd); } + + template + [[nodiscard]] auto open(const char *fname, Args&& ...args) -> bool + { + close(); + mFd = ::open(fname, std::forward(args)...); + return mFd != -1; + } + void close() + { + if(mFd != -1) + ::close(mFd); + mFd = -1; + } + + [[nodiscard]] + auto get() const noexcept -> int { return mFd; } +}; + +void ALCossListAppend(std::vector &list, std::string_view handle, std::string_view path) { #ifdef ALC_OSS_DEVNODE_TRUC for(size_t i{0};i < path.size();++i) { - if(path[i] == '.' && handle.size() + i >= path.size()) + if(path[i] == '.' && handle.size() >= path.size() - i) { const size_t hoffset{handle.size() + i - path.size()}; if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0) - handle = handle.first(hoffset); - path = path.first(i); + handle = handle.substr(0, hoffset); + path = path.substr(0, i); } } #endif if(handle.empty()) handle = path; - std::string basename{handle.data(), handle.size()}; - std::string devname{path.data(), path.size()}; - - auto match_devname = [&devname](const DevMap &entry) -> bool - { return entry.device_name == devname; }; + auto match_devname = [path](const DevMap &entry) -> bool + { return entry.device_name == path; }; if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend()) return; - auto checkName = [&list](const std::string &name) -> bool + auto checkName = [&list](const std::string_view name) -> bool { - auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; }; + auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; }; return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); }; int count{1}; - std::string newname{basename}; + std::string newname{handle}; while(checkName(newname)) { - newname = basename; + newname = handle; newname += " #"; newname += std::to_string(++count); } - list.emplace_back(DevMap{std::move(newname), std::move(devname)}); - const DevMap &entry = list.back(); + const DevMap &entry = list.emplace_back(std::move(newname), path); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } -void ALCossListPopulate(al::vector &devlist, int type_flag) +void ALCossListPopulate(std::vector &devlist, int type_flag) { - int fd{open("/dev/mixer", O_RDONLY)}; - if(fd < 0) + oss_sysinfo si{}; + FileHandle file; + if(!file.open("/dev/mixer", O_RDONLY)) { - TRACE("Could not open /dev/mixer: %s\n", strerror(errno)); + TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno).c_str()); goto done; } - oss_sysinfo si; - if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1) + if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1) { - TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno)); + TRACE("SNDCTL_SYSINFO failed: %s\n", std::generic_category().message(errno).c_str()); goto done; } for(int i{0};i < si.numaudios;i++) { - oss_audioinfo ai; + oss_audioinfo ai{}; ai.dev = i; - if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1) + if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1) { - ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno)); + ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, + std::generic_category().message(errno).c_str()); continue; } if(!(ai.caps&type_flag) || ai.devnode[0] == '\0') continue; - al::span handle; + std::string_view handle; if(ai.handle[0] != '\0') handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))}; else handle = {ai.name, strnlen(ai.name, sizeof(ai.name))}; - al::span devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))}; + const std::string_view devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))}; ALCossListAppend(devlist, handle, devnode); } done: - if(fd >= 0) - close(fd); - fd = -1; + file.close(); const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()}; auto iter = std::find_if(devlist.cbegin(), devlist.cend(), @@ -201,7 +231,7 @@ done: { return entry.device_name == defdev; } ); if(iter == devlist.cend()) - devlist.insert(devlist.begin(), DevMap{DefaultName, defdev}); + devlist.insert(devlist.begin(), DevMap{GetDefaultName(), defdev}); else { DevMap entry{std::move(*iter)}; @@ -231,19 +261,17 @@ struct OSSPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; int mFd{-1}; - al::vector mMixData; + std::vector mMixData; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(OSSPlayback) }; OSSPlayback::~OSSPlayback() @@ -257,7 +285,7 @@ OSSPlayback::~OSSPlayback() int OSSPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const size_t frame_step{mDevice->channelsFromFmt()}; const size_t frame_size{mDevice->frameSizeFromFmt()}; @@ -269,38 +297,38 @@ int OSSPlayback::mixerProc() pollitem.fd = mFd; pollitem.events = POLLOUT; - int pret{poll(&pollitem, 1, 1000)}; - if(pret < 0) + if(int pret{poll(&pollitem, 1, 1000)}; pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; - ERR("poll failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno)); + const auto errstr = std::generic_category().message(errno); + ERR("poll failed: %s\n", errstr.c_str()); + mDevice->handleDisconnect("Failed waiting for playback buffer: %s", errstr.c_str()); break; } - else if(pret == 0) + else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */ { WARN("poll timeout\n"); continue; } - al::byte *write_ptr{mMixData.data()}; - size_t to_write{mMixData.size()}; - mDevice->renderSamples(write_ptr, static_cast(to_write/frame_size), frame_step); - while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) + al::span write_buf{mMixData}; + mDevice->renderSamples(write_buf.data(), static_cast(write_buf.size()/frame_size), + frame_step); + while(!write_buf.empty() && !mKillNow.load(std::memory_order_acquire)) { - ssize_t wrote{write(mFd, write_ptr, to_write)}; + ssize_t wrote{write(mFd, write_buf.data(), write_buf.size())}; if(wrote < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; - ERR("write failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno)); + const auto errstr = std::generic_category().message(errno); + ERR("write failed: %s\n", errstr.c_str()); + mDevice->handleDisconnect("Failed writing playback samples: %s", errstr.c_str()); break; } - to_write -= static_cast(wrote); - write_ptr += wrote; + write_buf = write_buf.subspan(static_cast(wrote)); } } @@ -308,11 +336,11 @@ int OSSPlayback::mixerProc() } -void OSSPlayback::open(const char *name) +void OSSPlayback::open(std::string_view name) { const char *devname{DefaultPlayback.c_str()}; - if(!name) - name = DefaultName; + if(name.empty()) + name = GetDefaultName(); else { if(PlaybackDevices.empty()) @@ -324,14 +352,14 @@ void OSSPlayback::open(const char *name) ); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; devname = iter->device_name.c_str(); } int fd{::open(devname, O_WRONLY)}; if(fd == -1) throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, - strerror(errno)}; + std::generic_category().message(errno).c_str()}; if(mFd != -1) ::close(mFd); @@ -367,15 +395,14 @@ bool OSSPlayback::reset() uint ossSpeed{mDevice->Frequency}; uint frameSize{numChannels * mDevice->bytesFromFmt()}; /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */ - uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)}; + uint log2FragmentSize{std::max(log2i(mDevice->UpdateSize*frameSize), 4u)}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; - const char *err; -#define CHECKERR(func) if((func) < 0) { \ - err = #func; \ - goto err; \ -} +#define CHECKERR(func) if((func) < 0) \ + throw al::backend_exception{al::backend_error::DeviceError, "%s failed: %s\n", #func, \ + std::generic_category().message(errno).c_str()}; + /* Don't fail if SETFRAGMENT fails. We can handle just about anything * that's reported back via GETOSPACE */ ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize); @@ -383,12 +410,6 @@ bool OSSPlayback::reset() CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info)); - if(0) - { - err: - ERR("%s failed: %s\n", err, strerror(errno)); - return false; - } #undef CHECKERR if(mDevice->channelsFromFmt() != numChannels) @@ -413,7 +434,7 @@ bool OSSPlayback::reset() setDefaultChannelOrder(); - mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); + mMixData.resize(size_t{mDevice->UpdateSize} * mDevice->frameSizeFromFmt()); return true; } @@ -437,7 +458,7 @@ void OSSPlayback::stop() mThread.join(); if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", strerror(errno)); + ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str()); } @@ -447,10 +468,10 @@ struct OSScapture final : public BackendBase { int recordProc(); - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; int mFd{-1}; @@ -459,8 +480,6 @@ struct OSScapture final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(OSScapture) }; OSScapture::~OSScapture() @@ -474,7 +493,7 @@ OSScapture::~OSScapture() int OSScapture::recordProc() { SetRTPriority(); - althrd_setname(RECORD_THREAD_NAME); + althrd_setname(GetRecordThreadName()); const size_t frame_size{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire)) @@ -483,16 +502,16 @@ int OSScapture::recordProc() pollitem.fd = mFd; pollitem.events = POLLIN; - int sret{poll(&pollitem, 1, 1000)}; - if(sret < 0) + if(int pret{poll(&pollitem, 1, 1000)}; pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; - ERR("poll failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno)); + const auto errstr = std::generic_category().message(errno); + ERR("poll failed: %s\n", errstr.c_str()); + mDevice->handleDisconnect("Failed to check capture samples: %s", errstr.c_str()); break; } - else if(sret == 0) + else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */ { WARN("poll timeout\n"); continue; @@ -504,8 +523,9 @@ int OSScapture::recordProc() ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)}; if(amt < 0) { - ERR("read failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno)); + const auto errstr = std::generic_category().message(errno); + ERR("read failed: %s\n", errstr.c_str()); + mDevice->handleDisconnect("Failed reading capture samples: %s", errstr.c_str()); break; } mRing->writeAdvance(static_cast(amt)/frame_size); @@ -516,11 +536,11 @@ int OSScapture::recordProc() } -void OSScapture::open(const char *name) +void OSScapture::open(std::string_view name) { const char *devname{DefaultCapture.c_str()}; - if(!name) - name = DefaultName; + if(name.empty()) + name = GetDefaultName(); else { if(CaptureDevices.empty()) @@ -532,14 +552,14 @@ void OSScapture::open(const char *name) ); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; devname = iter->device_name.c_str(); } mFd = ::open(devname, O_RDONLY); if(mFd == -1) throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, - strerror(errno)}; + std::generic_category().message(errno).c_str()}; int ossFormat{}; switch(mDevice->FmtType) @@ -566,13 +586,13 @@ void OSScapture::open(const char *name) uint frameSize{numChannels * mDevice->bytesFromFmt()}; uint ossSpeed{mDevice->Frequency}; /* according to the OSS spec, 16 bytes are the minimum */ - uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)}; + uint log2FragmentSize{std::max(log2i(mDevice->BufferSize * frameSize / periods), 4u)}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; #define CHECKERR(func) if((func) < 0) { \ throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \ - strerror(errno)}; \ + std::generic_category().message(errno).c_str()}; \ } CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); @@ -617,11 +637,11 @@ void OSScapture::stop() mThread.join(); if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", strerror(errno)); + ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str()); } -void OSScapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void OSScapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint OSScapture::availableSamples() { return static_cast(mRing->readSpace()); } @@ -637,9 +657,9 @@ BackendFactory &OSSBackendFactory::getFactory() bool OSSBackendFactory::init() { - if(auto devopt = ConfigValueStr(nullptr, "oss", "device")) + if(auto devopt = ConfigValueStr({}, "oss", "device")) DefaultPlayback = std::move(*devopt); - if(auto capopt = ConfigValueStr(nullptr, "oss", "capture")) + if(auto capopt = ConfigValueStr({}, "oss", "capture")) DefaultCapture = std::move(*capopt); return true; @@ -648,18 +668,13 @@ bool OSSBackendFactory::init() bool OSSBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string OSSBackendFactory::probe(BackendType type) +auto OSSBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; - + std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void { - struct stat buf; - if(stat(entry.device_name.c_str(), &buf) == 0) - { - /* Includes null char. */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - } + if(struct stat buf{}; stat(entry.device_name.c_str(), &buf) == 0) + outnames.emplace_back(entry.name); }; switch(type) @@ -667,12 +682,14 @@ std::string OSSBackendFactory::probe(BackendType type) case BackendType::Playback: PlaybackDevices.clear(); ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices.clear(); ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } diff --git a/Engine/lib/openal-soft/alc/backends/oss.h b/Engine/lib/openal-soft/alc/backends/oss.h index 4f2c00b96..b5faf96a5 100644 --- a/Engine/lib/openal-soft/alc/backends/oss.h +++ b/Engine/lib/openal-soft/alc/backends/oss.h @@ -5,15 +5,15 @@ struct OSSBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OSS_H */ diff --git a/Engine/lib/openal-soft/alc/backends/pipewire.cpp b/Engine/lib/openal-soft/alc/backends/pipewire.cpp index c6569a74a..23589dc6a 100644 --- a/Engine/lib/openal-soft/alc/backends/pipewire.cpp +++ b/Engine/lib/openal-soft/alc/backends/pipewire.cpp @@ -23,24 +23,30 @@ #include "pipewire.h" #include +#include #include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include +#include #include #include -#include +#include +#include #include -#include +#include #include -#include "albyte.h" #include "alc/alconfig.h" +#include "alc/backends/base.h" #include "almalloc.h" -#include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "core/devformat.h" @@ -71,10 +77,15 @@ _Pragma("GCC diagnostic ignored \"-Weverything\"") #include "spa/buffer/buffer.h" #include "spa/param/audio/format-utils.h" #include "spa/param/audio/raw.h" +#include "spa/param/format.h" #include "spa/param/param.h" #include "spa/pod/builder.h" #include "spa/utils/json.h" +/* NOLINTBEGIN : All kinds of unsafe C stuff here from PipeWire headers + * (function-like macros, C style casts in macros, etc), which we can't do + * anything about except wrap into inline functions. + */ namespace { /* Wrap some nasty macros here too... */ template @@ -107,32 +118,69 @@ template constexpr auto get_pod_body(const spa_pod *pod) noexcept { return al::span{static_cast(SPA_POD_BODY(pod)), N}; } -constexpr auto make_pod_builder(void *data, uint32_t size) noexcept -{ return SPA_POD_BUILDER_INIT(data, size); } - constexpr auto get_array_value_type(const spa_pod *pod) noexcept { return SPA_POD_ARRAY_VALUE_TYPE(pod); } +constexpr auto make_pod_builder(void *data, uint32_t size) noexcept +{ return SPA_POD_BUILDER_INIT(data, size); } + constexpr auto PwIdAny = PW_ID_ANY; } // namespace +/* NOLINTEND */ _Pragma("GCC diagnostic pop") namespace { +struct PodDynamicBuilder { +private: + std::vector mStorage; + spa_pod_builder mPod{}; + + int overflow(uint32_t size) noexcept + { + try { + mStorage.resize(size); + } + catch(...) { + ERR("Failed to resize POD storage\n"); + return -ENOMEM; + } + mPod.data = mStorage.data(); + mPod.size = size; + return 0; + } + +public: + PodDynamicBuilder(uint32_t initSize=0) : mStorage(initSize) + , mPod{make_pod_builder(mStorage.data(), initSize)} + { + static constexpr auto callbacks{[] + { + spa_pod_builder_callbacks cb{}; + cb.version = SPA_VERSION_POD_BUILDER_CALLBACKS; + cb.overflow = [](void *data, uint32_t size) noexcept + { return static_cast(data)->overflow(size); }; + return cb; + }()}; + + spa_pod_builder_set_callbacks(&mPod, &callbacks, this); + } + + spa_pod_builder *get() noexcept { return &mPod; } +}; + /* Added in 0.3.33, but we currently only require 0.3.23. */ #ifndef PW_KEY_NODE_RATE #define PW_KEY_NODE_RATE "node.rate" #endif +using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; using uint = unsigned int; -constexpr char pwireDevice[] = "PipeWire Output"; -constexpr char pwireInput[] = "PipeWire Input"; - bool check_version(const char *version) { @@ -142,10 +190,8 @@ bool check_version(const char *version) */ int major{0}, minor{0}, revision{0}; int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)}; - if(ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR) - || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO))) - return true; - return false; + return ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR) + || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO)); } #ifdef HAVE_DYNLOAD @@ -199,7 +245,7 @@ bool pwire_load() if(pwire_handle) return true; - static constexpr char pwire_library[] = "libpipewire-0.3.so.0"; + const char *pwire_library{"libpipewire-0.3.so.0"}; std::string missing_funcs; pwire_handle = LoadLib(pwire_library); @@ -294,7 +340,7 @@ using Pod_t = typename PodInfo::Type; template al::span> get_array_span(const spa_pod *pod) { - uint32_t nvals; + uint32_t nvals{}; if(void *v{spa_pod_get_array(pod, &nvals)}) { if(get_array_value_type(pod) == T) @@ -304,12 +350,12 @@ al::span> get_array_span(const spa_pod *pod) } template -al::optional> get_value(const spa_pod *value) +std::optional> get_value(const spa_pod *value) { Pod_t val{}; if(PodInfo::get_value(value, &val) == 0) return val; - return al::nullopt; + return std::nullopt; } /* Internally, PipeWire types "inherit" from each other, but this is hidden @@ -395,9 +441,11 @@ public: explicit operator bool() const noexcept { return mLoop != nullptr; } + [[nodiscard]] auto start() const { return pw_thread_loop_start(mLoop); } auto stop() const { return pw_thread_loop_stop(mLoop); } + [[nodiscard]] auto getLoop() const { return pw_thread_loop_get_loop(mLoop); } auto lock() const { return pw_thread_loop_lock(mLoop); } @@ -405,7 +453,7 @@ public: auto signal(bool wait) const { return pw_thread_loop_signal(mLoop, wait); } - auto newContext(pw_properties *props=nullptr, size_t user_data_size=0) + auto newContext(pw_properties *props=nullptr, size_t user_data_size=0) const { return PwContextPtr{pw_context_new(getLoop(), props, user_data_size)}; } static auto Create(const char *name, spa_dict *props=nullptr) @@ -433,8 +481,73 @@ using MainloopLockGuard = std::lock_guard; * devices provided by the server. */ -struct NodeProxy; -struct MetadataProxy; +/* A generic PipeWire node proxy object used to track changes to sink and + * source nodes. + */ +struct NodeProxy { + static constexpr pw_node_events CreateNodeEvents() + { + pw_node_events ret{}; + ret.version = PW_VERSION_NODE_EVENTS; + ret.info = infoCallback; + ret.param = [](void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) noexcept + { static_cast(object)->paramCallback(seq, id, index, next, param); }; + return ret; + } + + uint32_t mId{}; + + PwNodePtr mNode{}; + spa_hook mListener{}; + + NodeProxy(uint32_t id, PwNodePtr node) + : mId{id}, mNode{std::move(node)} + { + static constexpr pw_node_events nodeEvents{CreateNodeEvents()}; + ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this); + + /* Track changes to the enumerable and current formats (indicates the + * default and active format, which is what we're interested in). + */ + std::array fmtids{{SPA_PARAM_EnumFormat, SPA_PARAM_Format}}; + ppw_node_subscribe_params(mNode.get(), fmtids.data(), fmtids.size()); + } + ~NodeProxy() + { spa_hook_remove(&mListener); } + + + static void infoCallback(void *object, const pw_node_info *info) noexcept; + void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) const noexcept; +}; + +/* A metadata proxy object used to query the default sink and source. */ +struct MetadataProxy { + static constexpr pw_metadata_events CreateMetadataEvents() + { + pw_metadata_events ret{}; + ret.version = PW_VERSION_METADATA_EVENTS; + ret.property = propertyCallback; + return ret; + } + + uint32_t mId{}; + + PwMetadataPtr mMetadata{}; + spa_hook mListener{}; + + MetadataProxy(uint32_t id, PwMetadataPtr mdata) + : mId{id}, mMetadata{std::move(mdata)} + { + static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()}; + ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this); + } + ~MetadataProxy() + { spa_hook_remove(&mListener); } + + static auto propertyCallback(void *object, uint32_t id, const char *key, const char *type, + const char *value) noexcept -> int; +}; + /* The global thread watching for global events. This particular class responds * to objects being added to or removed from the registry. @@ -450,8 +563,8 @@ struct EventManager { /* A list of proxy objects watching for events about changes to objects in * the registry. */ - std::vector mNodeList; - MetadataProxy *mDefaultMetadata{nullptr}; + std::vector> mNodeList; + std::optional mDefaultMetadata; /* Initialization handling. When init() is called, mInitSeq is set to a * SequenceID that marks the end of populating the registry. As objects of @@ -463,24 +576,29 @@ struct EventManager { std::atomic mHasAudio{false}; int mInitSeq{}; + ~EventManager() { if(mLoop) mLoop.stop(); } + bool init(); - ~EventManager(); void kill(); auto lock() const { return mLoop.lock(); } auto unlock() const { return mLoop.unlock(); } + [[nodiscard]] inline + bool initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept + { return mInitDone.load(m); } + /** * Waits for initialization to finish. The event manager must *NOT* be * locked when calling this. */ void waitForInit() { - if(!mInitDone.load(std::memory_order_acquire)) UNLIKELY + if(!initIsDone(std::memory_order_acquire)) UNLIKELY { MainloopUniqueLock plock{mLoop}; - plock.wait([this](){ return mInitDone.load(std::memory_order_acquire); }); + plock.wait([this](){ return initIsDone(std::memory_order_acquire); }); } } @@ -496,7 +614,7 @@ struct EventManager { plock.wait([this,&has_audio]() { has_audio = mHasAudio.load(std::memory_order_acquire); - return has_audio || mInitDone.load(std::memory_order_acquire); + return has_audio || initIsDone(std::memory_order_acquire); }); return has_audio; } @@ -506,38 +624,34 @@ struct EventManager { /* If initialization isn't done, update the sequence ID so it won't * complete until after currently scheduled events. */ - if(!mInitDone.load(std::memory_order_relaxed)) + if(!initIsDone(std::memory_order_relaxed)) mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, mInitSeq); } void addCallback(uint32_t id, uint32_t permissions, const char *type, uint32_t version, - const spa_dict *props); - static void addCallbackC(void *object, uint32_t id, uint32_t permissions, const char *type, - uint32_t version, const spa_dict *props) - { static_cast(object)->addCallback(id, permissions, type, version, props); } + const spa_dict *props) noexcept; - void removeCallback(uint32_t id); - static void removeCallbackC(void *object, uint32_t id) - { static_cast(object)->removeCallback(id); } + void removeCallback(uint32_t id) noexcept; static constexpr pw_registry_events CreateRegistryEvents() { pw_registry_events ret{}; ret.version = PW_VERSION_REGISTRY_EVENTS; - ret.global = &EventManager::addCallbackC; - ret.global_remove = &EventManager::removeCallbackC; + ret.global = [](void *object, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props) noexcept + { static_cast(object)->addCallback(id, permissions, type, version, props); }; + ret.global_remove = [](void *object, uint32_t id) noexcept + { static_cast(object)->removeCallback(id); }; return ret; } - void coreCallback(uint32_t id, int seq); - static void coreCallbackC(void *object, uint32_t id, int seq) - { static_cast(object)->coreCallback(id, seq); } + void coreCallback(uint32_t id, int seq) noexcept; static constexpr pw_core_events CreateCoreEvents() { pw_core_events ret{}; ret.version = PW_VERSION_CORE_EVENTS; - ret.done = &EventManager::coreCallbackC; + ret.done = [](void *object, uint32_t id, int seq) noexcept + { static_cast(object)->coreCallback(id, seq); }; return ret; } }; @@ -570,12 +684,23 @@ struct DeviceNode { static std::vector sList; static DeviceNode &Add(uint32_t id); static DeviceNode *Find(uint32_t id); + static DeviceNode *FindByDevName(std::string_view devname); static void Remove(uint32_t id); - static std::vector &GetList() noexcept { return sList; } + static auto GetList() noexcept { return al::span{sList}; } - void parseSampleRate(const spa_pod *value) noexcept; - void parsePositions(const spa_pod *value) noexcept; - void parseChannelCount(const spa_pod *value) noexcept; + void parseSampleRate(const spa_pod *value, bool force_update) noexcept; + void parsePositions(const spa_pod *value, bool force_update) noexcept; + void parseChannelCount(const spa_pod *value, bool force_update) noexcept; + + void callEvent(alc::EventType type, std::string_view message) const + { + /* Source nodes aren't recognized for playback, only Sink and Duplex + * nodes are. All node types are recognized for capture. + */ + if(mType != NodeType::Source) + alc::Event(type, alc::DeviceType::Playback, message); + alc::Event(type, alc::DeviceType::Capture, message); + } }; std::vector DeviceNode::sList; std::string DefaultSinkDevice; @@ -601,8 +726,7 @@ DeviceNode &DeviceNode::Add(uint32_t id) auto match = std::find_if(sList.begin(), sList.end(), match_id); if(match != sList.end()) return *match; - sList.emplace_back(); - auto &n = sList.back(); + auto &n = sList.emplace_back(); n.mId = id; return n; } @@ -618,6 +742,17 @@ DeviceNode *DeviceNode::Find(uint32_t id) return nullptr; } +DeviceNode *DeviceNode::FindByDevName(std::string_view devname) +{ + auto match_id = [devname](DeviceNode &n) noexcept -> bool + { return n.mDevName == devname; }; + + auto match = std::find_if(sList.begin(), sList.end(), match_id); + if(match != sList.end()) return al::to_address(match); + + return nullptr; +} + void DeviceNode::Remove(uint32_t id) { auto match_id = [id](DeviceNode &n) noexcept -> bool @@ -625,6 +760,11 @@ void DeviceNode::Remove(uint32_t id) if(n.mId != id) return false; TRACE("Removing device \"%s\"\n", n.mDevName.c_str()); + if(gEventHandler.initIsDone(std::memory_order_relaxed)) + { + const std::string msg{"Device removed: "+n.mName}; + n.callEvent(alc::EventType::DeviceRemoved, msg); + } return true; }; @@ -633,25 +773,32 @@ void DeviceNode::Remove(uint32_t id) } -const spa_audio_channel MonoMap[]{ +constexpr std::array MonoMap{ SPA_AUDIO_CHANNEL_MONO -}, StereoMap[] { +}; +constexpr std::array StereoMap{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR -}, QuadMap[]{ +}; +constexpr std::array QuadMap{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR -}, X51Map[]{ +}; +constexpr std::array X51Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR -}, X51RearMap[]{ +}; +constexpr std::array X51RearMap{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR -}, X61Map[]{ +}; +constexpr std::array X61Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR -}, X71Map[]{ +}; +constexpr std::array X71Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR -}, X714Map[]{ +}; +constexpr std::array X714Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR @@ -661,20 +808,18 @@ const spa_audio_channel MonoMap[]{ * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal * to or a superset of map1). */ -template -bool MatchChannelMap(const al::span map0, const spa_audio_channel (&map1)[N]) +bool MatchChannelMap(const al::span map0, + const al::span map1) { - if(map0.size() < N) + if(map0.size() < map1.size()) return false; - for(const spa_audio_channel chid : map1) - { - if(std::find(map0.begin(), map0.end(), chid) == map0.end()) - return false; - } - return true; + + auto find_channel = [map0](const spa_audio_channel chid) -> bool + { return std::find(map0.begin(), map0.end(), chid) != map0.end(); }; + return std::all_of(map1.cbegin(), map1.cend(), find_channel); } -void DeviceNode::parseSampleRate(const spa_pod *value) noexcept +void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexcept { /* TODO: Can this be anything else? Long, Float, Double? */ uint32_t nvals{}, choiceType{}; @@ -683,7 +828,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept const uint podType{get_pod_type(value)}; if(podType != SPA_TYPE_Int) { - WARN("Unhandled sample rate POD type: %u\n", podType); + WARN(" Unhandled sample rate POD type: %u\n", podType); return; } @@ -691,15 +836,16 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept { if(nvals != 3) { - WARN("Unexpected SPA_CHOICE_Range count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_Range count: %u\n", nvals); return; } auto srates = get_pod_body(value); /* [0] is the default, [1] is the min, and [2] is the max. */ - TRACE("Device ID %" PRIu64 " sample rate: %d (range: %d -> %d)\n", mSerial, srates[0], - srates[1], srates[2]); - mSampleRate = static_cast(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); + TRACE(" sample rate: %d (range: %d -> %d)\n", srates[0], srates[1], srates[2]); + if(!mSampleRate || force_update) + mSampleRate = static_cast(std::clamp(srates[0], MinOutputRate, + MaxOutputRate)); return; } @@ -707,7 +853,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept { if(nvals == 0) { - WARN("Unexpected SPA_CHOICE_Enum count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_Enum count: %u\n", nvals); return; } auto srates = get_pod_body(value, nvals); @@ -719,15 +865,16 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept others += ", "; others += std::to_string(srates[i]); } - TRACE("Device ID %" PRIu64 " sample rate: %d (%s)\n", mSerial, srates[0], others.c_str()); + TRACE(" sample rate: %d (%s)\n", srates[0], others.c_str()); /* Pick the first rate listed that's within the allowed range (default * rate if possible). */ for(const auto &rate : srates) { - if(rate >= MIN_OUTPUT_RATE && rate <= MAX_OUTPUT_RATE) + if(rate >= int{MinOutputRate} && rate <= int{MaxOutputRate}) { - mSampleRate = static_cast(rate); + if(!mSampleRate || force_update) + mSampleRate = static_cast(rate); break; } } @@ -738,119 +885,102 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept { if(nvals != 1) { - WARN("Unexpected SPA_CHOICE_None count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_None count: %u\n", nvals); return; } auto srates = get_pod_body(value); - TRACE("Device ID %" PRIu64 " sample rate: %d\n", mSerial, srates[0]); - mSampleRate = static_cast(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); + TRACE(" sample rate: %d\n", srates[0]); + if(!mSampleRate || force_update) + mSampleRate = static_cast(std::clamp(srates[0], MinOutputRate, + MaxOutputRate)); return; } - WARN("Unhandled sample rate choice type: %u\n", choiceType); + WARN(" Unhandled sample rate choice type: %u\n", choiceType); } -void DeviceNode::parsePositions(const spa_pod *value) noexcept +void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcept { + uint32_t choiceCount{}, choiceType{}; + value = spa_pod_get_values(value, &choiceCount, &choiceType); + + if(choiceType != SPA_CHOICE_None || choiceCount != 1) + { + ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); + return; + } + const auto chanmap = get_array_span(value); if(chanmap.empty()) return; - mIs51Rear = false; - - if(MatchChannelMap(chanmap, X714Map)) - mChannels = DevFmtX714; - else if(MatchChannelMap(chanmap, X71Map)) - mChannels = DevFmtX71; - else if(MatchChannelMap(chanmap, X61Map)) - mChannels = DevFmtX61; - else if(MatchChannelMap(chanmap, X51Map)) - mChannels = DevFmtX51; - else if(MatchChannelMap(chanmap, X51RearMap)) + if(mChannels == InvalidChannelConfig || force_update) { - mChannels = DevFmtX51; - mIs51Rear = true; + mIs51Rear = false; + + if(MatchChannelMap(chanmap, X714Map)) + mChannels = DevFmtX714; + else if(MatchChannelMap(chanmap, X71Map)) + mChannels = DevFmtX71; + else if(MatchChannelMap(chanmap, X61Map)) + mChannels = DevFmtX61; + else if(MatchChannelMap(chanmap, X51Map)) + mChannels = DevFmtX51; + else if(MatchChannelMap(chanmap, X51RearMap)) + { + mChannels = DevFmtX51; + mIs51Rear = true; + } + else if(MatchChannelMap(chanmap, QuadMap)) + mChannels = DevFmtQuad; + else if(MatchChannelMap(chanmap, StereoMap)) + mChannels = DevFmtStereo; + else + mChannels = DevFmtMono; } - else if(MatchChannelMap(chanmap, QuadMap)) - mChannels = DevFmtQuad; - else if(MatchChannelMap(chanmap, StereoMap)) - mChannels = DevFmtStereo; - else - mChannels = DevFmtMono; - TRACE("Device ID %" PRIu64 " got %zu position%s for %s%s\n", mSerial, chanmap.size(), - (chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); + TRACE(" %zu position%s for %s%s\n", chanmap.size(), (chanmap.size()==1)?"":"s", + DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); } -void DeviceNode::parseChannelCount(const spa_pod *value) noexcept +void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noexcept { /* As a fallback with just a channel count, just assume mono or stereo. */ + uint32_t choiceCount{}, choiceType{}; + value = spa_pod_get_values(value, &choiceCount, &choiceType); + + if(choiceType != SPA_CHOICE_None || choiceCount != 1) + { + ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); + return; + } + const auto chancount = get_value(value); if(!chancount) return; - mIs51Rear = false; + if(mChannels == InvalidChannelConfig || force_update) + { + mIs51Rear = false; - if(*chancount >= 2) - mChannels = DevFmtStereo; - else if(*chancount >= 1) - mChannels = DevFmtMono; - TRACE("Device ID %" PRIu64 " got %d channel%s for %s\n", mSerial, *chancount, - (*chancount==1)?"":"s", DevFmtChannelsString(mChannels)); + if(*chancount >= 2) + mChannels = DevFmtStereo; + else if(*chancount >= 1) + mChannels = DevFmtMono; + } + TRACE(" %d channel%s for %s\n", *chancount, (*chancount==1)?"":"s", + DevFmtChannelsString(mChannels)); } -constexpr char MonitorPrefix[]{"Monitor of "}; -constexpr auto MonitorPrefixLen = al::size(MonitorPrefix) - 1; -constexpr char AudioSinkClass[]{"Audio/Sink"}; -constexpr char AudioSourceClass[]{"Audio/Source"}; -constexpr char AudioSourceVirtualClass[]{"Audio/Source/Virtual"}; -constexpr char AudioDuplexClass[]{"Audio/Duplex"}; -constexpr char StreamClass[]{"Stream/"}; - -/* A generic PipeWire node proxy object used to track changes to sink and - * source nodes. - */ -struct NodeProxy { - static constexpr pw_node_events CreateNodeEvents() - { - pw_node_events ret{}; - ret.version = PW_VERSION_NODE_EVENTS; - ret.info = &NodeProxy::infoCallbackC; - ret.param = &NodeProxy::paramCallbackC; - return ret; - } - - uint32_t mId{}; - - PwNodePtr mNode{}; - spa_hook mListener{}; - - NodeProxy(uint32_t id, PwNodePtr node) - : mId{id}, mNode{std::move(node)} - { - static constexpr pw_node_events nodeEvents{CreateNodeEvents()}; - ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this); - - /* Track changes to the enumerable formats (indicates the default - * format, which is what we're interested in). - */ - uint32_t fmtids[]{SPA_PARAM_EnumFormat}; - ppw_node_subscribe_params(mNode.get(), al::data(fmtids), al::size(fmtids)); - } - ~NodeProxy() - { spa_hook_remove(&mListener); } +[[nodiscard]] constexpr auto GetMonitorPrefix() noexcept { return "Monitor of "sv; } +[[nodiscard]] constexpr auto GetMonitorSuffix() noexcept { return ".monitor"sv; } +[[nodiscard]] constexpr auto GetAudioSinkClassName() noexcept { return "Audio/Sink"sv; } +[[nodiscard]] constexpr auto GetAudioSourceClassName() noexcept { return "Audio/Source"sv; } +[[nodiscard]] constexpr auto GetAudioDuplexClassName() noexcept { return "Audio/Duplex"sv; } +[[nodiscard]] constexpr auto GetAudioSourceVirtualClassName() noexcept +{ return "Audio/Source/Virtual"sv; } - void infoCallback(const pw_node_info *info); - static void infoCallbackC(void *object, const pw_node_info *info) - { static_cast(object)->infoCallback(info); } - - void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param); - static void paramCallbackC(void *object, int seq, uint32_t id, uint32_t index, uint32_t next, - const spa_pod *param) - { static_cast(object)->paramCallback(seq, id, index, next, param); } -}; - -void NodeProxy::infoCallback(const pw_node_info *info) +void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept { /* We only care about property changes here (media class, name/desc). * Format changes will automatically invoke the param callback. @@ -863,14 +993,15 @@ void NodeProxy::infoCallback(const pw_node_info *info) /* Can this actually change? */ const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)}; if(!media_class) UNLIKELY return; + const std::string_view className{media_class}; NodeType ntype{}; - if(al::strcasecmp(media_class, AudioSinkClass) == 0) + if(al::case_compare(className, GetAudioSinkClassName()) == 0) ntype = NodeType::Sink; - else if(al::strcasecmp(media_class, AudioSourceClass) == 0 - || al::strcasecmp(media_class, AudioSourceVirtualClass) == 0) + else if(al::case_compare(className, GetAudioSourceClassName()) == 0 + || al::case_compare(className, GetAudioSourceVirtualClassName()) == 0) ntype = NodeType::Source; - else if(al::strcasecmp(media_class, AudioDuplexClass) == 0) + else if(al::case_compare(className, GetAudioDuplexClassName()) == 0) ntype = NodeType::Duplex; else { @@ -888,6 +1019,7 @@ void NodeProxy::infoCallback(const pw_node_info *info) #ifdef PW_KEY_OBJECT_SERIAL if(const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)}) { + errno = 0; char *serial_end{}; serial_id = std::strtoull(serial_str, &serial_end, 0); if(*serial_end != '\0' || errno == ERANGE) @@ -898,81 +1030,92 @@ void NodeProxy::infoCallback(const pw_node_info *info) } #endif + std::string name; + if(nodeName && *nodeName) name = nodeName; + else name = "PipeWire node #"+std::to_string(info->id); + const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)}; TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)", form_factor?" (":"", form_factor?form_factor:"", form_factor?")":""); - TRACE(" \"%s\" = ID %" PRIu64 "\n", nodeName ? nodeName : "(nil)", serial_id); + TRACE(" \"%s\" = ID %" PRIu64 "\n", name.c_str(), serial_id); DeviceNode &node = DeviceNode::Add(info->id); node.mSerial = serial_id; - if(nodeName && *nodeName) node.mName = nodeName; - else node.mName = "PipeWire node #"+std::to_string(info->id); + /* This method is called both to notify about a new sink/source node, + * and update properties for the node. It's unclear what properties can + * change for an existing node without being removed first, so err on + * the side of caution: send a DeviceRemoved event if it had a name + * that's being changed, and send a DeviceAdded event when the name + * differs or it didn't have one. + * + * The DeviceRemoved event needs to be called before the potentially + * new NodeType is set, so the removal event is called for the previous + * device type, while the DeviceAdded event needs to be called after. + * + * This is overkill if the node type, name, and devname can't change. + */ + bool notifyAdd{false}; + if(node.mName != name) + { + if(gEventHandler.initIsDone(std::memory_order_relaxed)) + { + if(!node.mName.empty()) + { + const std::string msg{"Device removed: "+node.mName}; + node.callEvent(alc::EventType::DeviceRemoved, msg); + } + notifyAdd = true; + } + node.mName = std::move(name); + } node.mDevName = devName ? devName : ""; node.mType = ntype; - node.mIsHeadphones = form_factor && (al::strcasecmp(form_factor, "headphones") == 0 - || al::strcasecmp(form_factor, "headset") == 0); + node.mIsHeadphones = form_factor && (al::case_compare(form_factor, "headphones"sv) == 0 + || al::case_compare(form_factor, "headset"sv) == 0); + if(notifyAdd) + { + const std::string msg{"Device added: "+node.mName}; + node.callEvent(alc::EventType::DeviceAdded, msg); + } } } -void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param) +void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param) const noexcept { - if(id == SPA_PARAM_EnumFormat) + if(id == SPA_PARAM_EnumFormat || id == SPA_PARAM_Format) { DeviceNode *node{DeviceNode::Find(mId)}; if(!node) UNLIKELY return; + TRACE("Device ID %" PRIu64 " %s format%s:\n", node->mSerial, + (id == SPA_PARAM_EnumFormat) ? "available" : "current", + (id == SPA_PARAM_EnumFormat) ? "s" : ""); + + const bool force_update{id == SPA_PARAM_Format}; if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)}) - node->parseSampleRate(&prop->value); + node->parseSampleRate(&prop->value, force_update); if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position)}) - node->parsePositions(&prop->value); - else if((prop=spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels)) != nullptr) - node->parseChannelCount(&prop->value); + node->parsePositions(&prop->value, force_update); + else + { + prop = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels); + if(prop) node->parseChannelCount(&prop->value, force_update); + } } } -/* A metadata proxy object used to query the default sink and source. */ -struct MetadataProxy { - static constexpr pw_metadata_events CreateMetadataEvents() - { - pw_metadata_events ret{}; - ret.version = PW_VERSION_METADATA_EVENTS; - ret.property = &MetadataProxy::propertyCallbackC; - return ret; - } - - uint32_t mId{}; - - PwMetadataPtr mMetadata{}; - spa_hook mListener{}; - - MetadataProxy(uint32_t id, PwMetadataPtr mdata) - : mId{id}, mMetadata{std::move(mdata)} - { - static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()}; - ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this); - } - ~MetadataProxy() - { spa_hook_remove(&mListener); } - - - int propertyCallback(uint32_t id, const char *key, const char *type, const char *value); - static int propertyCallbackC(void *object, uint32_t id, const char *key, const char *type, - const char *value) - { return static_cast(object)->propertyCallback(id, key, type, value); } -}; - -int MetadataProxy::propertyCallback(uint32_t id, const char *key, const char *type, - const char *value) +auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const char *type, + const char *value) noexcept -> int { if(id != PW_ID_CORE) return 0; bool isCapture{}; - if(std::strcmp(key, "default.audio.sink") == 0) + if("default.audio.sink"sv == key) isCapture = false; - else if(std::strcmp(key, "default.audio.source") == 0) + else if("default.audio.source"sv == key) isCapture = true; else return 0; @@ -984,50 +1127,70 @@ int MetadataProxy::propertyCallback(uint32_t id, const char *key, const char *ty else DefaultSourceDevice.clear(); return 0; } - if(std::strcmp(type, "Spa:String:JSON") != 0) + if("Spa:String:JSON"sv != type) { ERR("Unexpected %s property type: %s\n", key, type); return 0; } - spa_json it[2]{}; - spa_json_init(&it[0], value, strlen(value)); - if(spa_json_enter_object(&it[0], &it[1]) <= 0) + std::array it{}; + spa_json_init(it.data(), value, strlen(value)); + if(spa_json_enter_object(&std::get<0>(it), &std::get<1>(it)) <= 0) return 0; auto get_json_string = [](spa_json *iter) { - al::optional str; + std::optional str; const char *val{}; int len{spa_json_next(iter, &val)}; if(len <= 0) return str; - str.emplace().resize(static_cast(len), '\0'); - if(spa_json_parse_string(val, len, &str->front()) <= 0) + str.emplace(static_cast(len), '\0'); + if(spa_json_parse_string(val, len, str->data()) <= 0) str.reset(); else while(!str->empty() && str->back() == '\0') str->pop_back(); return str; }; - while(auto propKey = get_json_string(&it[1])) + while(auto propKey = get_json_string(&std::get<1>(it))) { - if(*propKey == "name") + if("name"sv == *propKey) { - auto propValue = get_json_string(&it[1]); + auto propValue = get_json_string(&std::get<1>(it)); if(!propValue) break; TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback", propValue->c_str()); - if(!isCapture) + if(!isCapture && DefaultSinkDevice != *propValue) + { + if(gEventHandler.mInitDone.load(std::memory_order_relaxed)) + { + auto entry = DeviceNode::FindByDevName(*propValue); + const std::string message{"Default playback device changed: "+ + (entry ? entry->mName : std::string{})}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, + message); + } DefaultSinkDevice = std::move(*propValue); - else + } + else if(isCapture && DefaultSourceDevice != *propValue) + { + if(gEventHandler.mInitDone.load(std::memory_order_relaxed)) + { + auto entry = DeviceNode::FindByDevName(*propValue); + const std::string message{"Default capture device changed: "+ + (entry ? entry->mName : std::string{})}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, + message); + } DefaultSourceDevice = std::move(*propValue); + } } else { const char *v{}; - if(spa_json_next(&it[1], &v) <= 0) + if(spa_json_next(&std::get<1>(it), &v) <= 0) break; } } @@ -1044,7 +1207,7 @@ bool EventManager::init() return false; } - mContext = mLoop.newContext(pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)); + mContext = mLoop.newContext(); if(!mContext) { ERR("Failed to create PipeWire event context (errno: %d)\n", errno); @@ -1085,26 +1248,12 @@ bool EventManager::init() return true; } -EventManager::~EventManager() -{ - if(mLoop) mLoop.stop(); - - for(NodeProxy *node : mNodeList) - al::destroy_at(node); - if(mDefaultMetadata) - al::destroy_at(mDefaultMetadata); -} - void EventManager::kill() { if(mLoop) mLoop.stop(); - for(NodeProxy *node : mNodeList) - al::destroy_at(node); + mDefaultMetadata.reset(); mNodeList.clear(); - if(mDefaultMetadata) - al::destroy_at(mDefaultMetadata); - mDefaultMetadata = nullptr; mRegistry = nullptr; mCore = nullptr; @@ -1113,30 +1262,30 @@ void EventManager::kill() } void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version, - const spa_dict *props) + const spa_dict *props) noexcept { /* We're only interested in interface nodes. */ if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0) { const char *media_class{spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)}; if(!media_class) return; + const std::string_view className{media_class}; /* Specifically, audio sinks and sources (and duplexes). */ - const bool isGood{al::strcasecmp(media_class, AudioSinkClass) == 0 - || al::strcasecmp(media_class, AudioSourceClass) == 0 - || al::strcasecmp(media_class, AudioSourceVirtualClass) == 0 - || al::strcasecmp(media_class, AudioDuplexClass) == 0}; + const bool isGood{al::case_compare(className, GetAudioSinkClassName()) == 0 + || al::case_compare(className, GetAudioSourceClassName()) == 0 + || al::case_compare(className, GetAudioSourceVirtualClassName()) == 0 + || al::case_compare(className, GetAudioDuplexClassName()) == 0}; if(!isGood) { - if(std::strstr(media_class, "/Video") == nullptr - && std::strncmp(media_class, StreamClass, sizeof(StreamClass)-1) != 0) + if(!al::contains(className, "/Video"sv) && !al::starts_with(className, "Stream/"sv)) TRACE("Ignoring node class %s\n", media_class); return; } /* Create the proxy object. */ auto node = PwNodePtr{static_cast(pw_registry_bind(mRegistry.get(), id, type, - version, sizeof(NodeProxy)))}; + version, 0))}; if(!node) { ERR("Failed to create node proxy object (errno: %d)\n", errno); @@ -1146,8 +1295,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t /* Initialize the NodeProxy to hold the node object, add it to the * active node list, and update the sync point. */ - auto *proxy = static_cast(pw_proxy_get_user_data(as(node.get()))); - mNodeList.emplace_back(al::construct_at(proxy, id, std::move(node))); + mNodeList.emplace_back(std::make_unique(id, std::move(node))); syncInit(); /* Signal any waiters that we have found a source or sink for audio @@ -1161,7 +1309,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t const char *data_class{spa_dict_lookup(props, PW_KEY_METADATA_NAME)}; if(!data_class) return; - if(std::strcmp(data_class, "default") != 0) + if("default"sv != data_class) { TRACE("Ignoring metadata \"%s\"\n", data_class); return; @@ -1174,42 +1322,32 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t } auto mdata = PwMetadataPtr{static_cast(pw_registry_bind(mRegistry.get(), id, - type, version, sizeof(MetadataProxy)))}; + type, version, 0))}; if(!mdata) { ERR("Failed to create metadata proxy object (errno: %d)\n", errno); return; } - auto *proxy = static_cast( - pw_proxy_get_user_data(as(mdata.get()))); - mDefaultMetadata = al::construct_at(proxy, id, std::move(mdata)); + mDefaultMetadata.emplace(id, std::move(mdata)); syncInit(); } } -void EventManager::removeCallback(uint32_t id) +void EventManager::removeCallback(uint32_t id) noexcept { DeviceNode::Remove(id); - auto clear_node = [id](NodeProxy *node) noexcept - { - if(node->mId != id) - return false; - al::destroy_at(node); - return true; - }; + auto clear_node = [id](std::unique_ptr &node) noexcept + { return node->mId == id; }; auto node_end = std::remove_if(mNodeList.begin(), mNodeList.end(), clear_node); mNodeList.erase(node_end, mNodeList.end()); if(mDefaultMetadata && mDefaultMetadata->mId == id) - { - al::destroy_at(mDefaultMetadata); - mDefaultMetadata = nullptr; - } + mDefaultMetadata.reset(); } -void EventManager::coreCallback(uint32_t id, int seq) +void EventManager::coreCallback(uint32_t id, int seq) noexcept { if(id == PW_ID_CORE && seq == mInitSeq) { @@ -1260,6 +1398,7 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u case DevFmtX71: map = X71Map; break; case DevFmtX714: map = X714Map; break; case DevFmtX3D71: map = X71Map; break; + case DevFmtX7144: case DevFmtAmbi3D: info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED; info.channels = device->channelsFromFmt(); @@ -1268,27 +1407,18 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u if(!map.empty()) { info.channels = static_cast(map.size()); - std::copy(map.begin(), map.end(), info.position); + std::copy(map.begin(), map.end(), std::begin(info.position)); } return info; } class PipeWirePlayback final : public BackendBase { - void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error); - static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state, - const char *error) - { static_cast(data)->stateChangedCallback(old, state, error); } + void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept; + void ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept; + void outputCallback() noexcept; - void ioChangedCallback(uint32_t id, void *area, uint32_t size); - static void ioChangedCallbackC(void *data, uint32_t id, void *area, uint32_t size) - { static_cast(data)->ioChangedCallback(id, area, size); } - - void outputCallback(); - static void outputCallbackC(void *data) - { static_cast(data)->outputCallback(); } - - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -1302,52 +1432,54 @@ class PipeWirePlayback final : public BackendBase { PwStreamPtr mStream; spa_hook mStreamListener{}; spa_io_rate_match *mRateMatch{}; - std::unique_ptr mChannelPtrs; - uint mNumChannels{}; + std::vector mChannelPtrs; static constexpr pw_stream_events CreateEvents() { pw_stream_events ret{}; ret.version = PW_VERSION_STREAM_EVENTS; - ret.state_changed = &PipeWirePlayback::stateChangedCallbackC; - ret.io_changed = &PipeWirePlayback::ioChangedCallbackC; - ret.process = &PipeWirePlayback::outputCallbackC; + ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept + { static_cast(data)->stateChangedCallback(old, state, error); }; + ret.io_changed = [](void *data, uint32_t id, void *area, uint32_t size) noexcept + { static_cast(data)->ioChangedCallback(id, area, size); }; + ret.process = [](void *data) noexcept + { static_cast(data)->outputCallback(); }; return ret; } public: PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { } - ~PipeWirePlayback() + ~PipeWirePlayback() final { /* Stop the mainloop so the stream can be properly destroyed. */ if(mLoop) mLoop.stop(); } - - DEF_NEWDEL(PipeWirePlayback) }; -void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) +void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept { mLoop.signal(false); } -void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) +void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept { switch(id) { case SPA_IO_RateMatch: if(size >= sizeof(spa_io_rate_match)) mRateMatch = static_cast(area); + else + mRateMatch = nullptr; break; } } -void PipeWirePlayback::outputCallback() +void PipeWirePlayback::outputCallback() noexcept { pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; if(!pw_buf) UNLIKELY return; const al::span datas{pw_buf->buffer->datas, - minu(mNumChannels, pw_buf->buffer->n_datas)}; + std::min(mChannelPtrs.size(), size_t{pw_buf->buffer->n_datas})}; #if PW_CHECK_VERSION(0,3,49) /* In 0.3.49, pw_buffer::requested specifies the number of samples needed * by the resampler/graph for this audio update. @@ -1367,37 +1499,35 @@ void PipeWirePlayback::outputCallback() * buffer length in any one channel is smaller than we wanted (shouldn't * be, but just in case). */ - float **chanptr_end{mChannelPtrs.get()}; + auto chanptr_end = mChannelPtrs.begin(); for(const auto &data : datas) { - length = minu(length, data.maxsize/sizeof(float)); + length = std::min(length, data.maxsize/uint{sizeof(float)}); *chanptr_end = static_cast(data.data); ++chanptr_end; - } - mDevice->renderSamples({mChannelPtrs.get(), chanptr_end}, length); - - for(const auto &data : datas) - { data.chunk->offset = 0; data.chunk->stride = sizeof(float); data.chunk->size = length * sizeof(float); } + + mDevice->renderSamples(mChannelPtrs, length); + pw_buf->size = length; pw_stream_queue_buffer(mStream.get(), pw_buf); } -void PipeWirePlayback::open(const char *name) +void PipeWirePlayback::open(std::string_view name) { static std::atomic OpenCount{0}; uint64_t targetid{PwIdAny}; std::string devname{}; gEventHandler.waitForInit(); - if(!name) + if(name.empty()) { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match = devlist.cend(); @@ -1422,15 +1552,15 @@ void PipeWirePlayback::open(const char *name) } else { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_name = [name](const DeviceNode &n) -> bool - { return n.mType != NodeType::Source && n.mName == name; }; + { return n.mType != NodeType::Source && (n.mName == name || n.mDevName == name); }; auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; targetid = match->mSerial; devname = match->mName; @@ -1472,19 +1602,19 @@ void PipeWirePlayback::open(const char *name) if(!devname.empty()) mDevice->DeviceName = std::move(devname); else - mDevice->DeviceName = pwireDevice; + mDevice->DeviceName = "PipeWire Output"sv; } bool PipeWirePlayback::reset() { if(mStream) { - MainloopLockGuard _{mLoop}; + MainloopLockGuard looplock{mLoop}; mStream = nullptr; } mStreamListener = {}; mRateMatch = nullptr; - mTimeBase = GetDeviceClockTime(mDevice); + mTimeBase = mDevice->getClockTime(); /* If connecting to a specific device, update various device parameters to * match its format. @@ -1493,7 +1623,7 @@ bool PipeWirePlayback::reset() mDevice->Flags.reset(DirectEar); if(mTargetId != PwIdAny) { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool @@ -1505,11 +1635,12 @@ bool PipeWirePlayback::reset() { /* Scale the update size if the sample rate changes. */ const double scale{static_cast(match->mSampleRate) / mDevice->Frequency}; - const double numbufs{static_cast(mDevice->BufferSize)/mDevice->UpdateSize}; + const double updatesize{std::round(mDevice->UpdateSize * scale)}; + const double buffersize{std::round(mDevice->BufferSize * scale)}; + mDevice->Frequency = match->mSampleRate; - mDevice->UpdateSize = static_cast(clampd(mDevice->UpdateSize*scale + 0.5, - 64.0, 8192.0)); - mDevice->BufferSize = static_cast(numbufs*mDevice->UpdateSize + 0.5); + mDevice->UpdateSize = static_cast(std::clamp(updatesize, 64.0, 8192.0)); + mDevice->BufferSize = static_cast(std::max(buffersize, 128.0)); } if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig) mDevice->FmtChans = match->mChannels; @@ -1523,14 +1654,10 @@ bool PipeWirePlayback::reset() */ spa_audio_info_raw info{make_spa_info(mDevice, is51rear, ForceF32Planar)}; - /* TODO: How to tell what an appropriate size is? Examples just use this - * magic value. - */ - constexpr uint32_t pod_buffer_size{1024}; - auto pod_buffer = std::make_unique(pod_buffer_size); - spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)}; + static constexpr uint32_t pod_buffer_size{1024}; + PodDynamicBuilder b(pod_buffer_size); - const spa_pod *params{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)}; + const spa_pod *params{spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)}; if(!params) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set PipeWire audio format parameters"}; @@ -1571,7 +1698,7 @@ bool PipeWirePlayback::reset() pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS}; - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pipewire", "rt-mix", true)) + if(GetConfigValueBool(mDevice->DeviceName, "pipewire", "rt-mix", false)) flags |= PW_STREAM_FLAG_RT_PROCESS; if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, ¶ms, 1)}) throw al::backend_exception{al::backend_error::DeviceError, @@ -1596,8 +1723,7 @@ bool PipeWirePlayback::reset() */ plock.unlock(); - mNumChannels = mDevice->channelsFromFmt(); - mChannelPtrs = std::make_unique(mNumChannels); + mChannelPtrs.resize(mDevice->channelsFromFmt()); setDefaultWFXChannelOrder(); @@ -1655,7 +1781,7 @@ void PipeWirePlayback::start() mDevice->UpdateSize = updatesize; mDevice->BufferSize = static_cast(ptime.buffered + delay + - totalbuffers*updatesize); + uint64_t{totalbuffers}*updatesize); break; } #else @@ -1674,7 +1800,10 @@ void PipeWirePlayback::start() } #endif if(!--wait_count) + { + ERR("Timeout getting PipeWire stream buffering info\n"); break; + } plock.unlock(); std::this_thread::sleep_for(milliseconds{20}); @@ -1686,8 +1815,7 @@ void PipeWirePlayback::stop() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), false)}) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to stop PipeWire stream (res: %d)", res}; + ERR("Failed to stop PipeWire stream (res: %d)\n", res); /* Wait for the stream to stop playing. */ plock.wait([stream=mStream.get()]() @@ -1706,7 +1834,7 @@ ClockLatency PipeWirePlayback::getClockLatency() pw_time ptime{}; if(mStream) { - MainloopLockGuard _{mLoop}; + MainloopLockGuard looplock{mLoop}; if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))}) ERR("Failed to get PipeWire stream time (res: %d)\n", res); } @@ -1719,10 +1847,10 @@ ClockLatency PipeWirePlayback::getClockLatency() uint refcount; do { refcount = mDevice->waitForMix(); - mixtime = GetDeviceClockTime(mDevice); + mixtime = mDevice->getClockTime(); clock_gettime(CLOCK_MONOTONIC, &tspec); std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != ReadRef(mDevice->MixCount)); + } while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed)); /* Convert the monotonic clock, stream ticks, and stream delay to * nanoseconds. @@ -1769,7 +1897,7 @@ ClockLatency PipeWirePlayback::getClockLatency() delay -= monoclock - nanoseconds{ptime.now}; /* Return the mixer time and delay. Clamp the delay to no less than 0, - * incase timer drift got that severe. + * in case timer drift got that severe. */ ClockLatency ret{}; ret.ClockTime = mixtime; @@ -1780,19 +1908,13 @@ ClockLatency PipeWirePlayback::getClockLatency() class PipeWireCapture final : public BackendBase { - void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error); - static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state, - const char *error) - { static_cast(data)->stateChangedCallback(old, state, error); } + void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept; + void inputCallback() noexcept; - void inputCallback(); - static void inputCallbackC(void *data) - { static_cast(data)->inputCallback(); } - - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; uint64_t mTargetId{PwIdAny}; @@ -1808,47 +1930,48 @@ class PipeWireCapture final : public BackendBase { { pw_stream_events ret{}; ret.version = PW_VERSION_STREAM_EVENTS; - ret.state_changed = &PipeWireCapture::stateChangedCallbackC; - ret.process = &PipeWireCapture::inputCallbackC; + ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept + { static_cast(data)->stateChangedCallback(old, state, error); }; + ret.process = [](void *data) noexcept + { static_cast(data)->inputCallback(); }; return ret; } public: PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { } - ~PipeWireCapture() { if(mLoop) mLoop.stop(); } - - DEF_NEWDEL(PipeWireCapture) + ~PipeWireCapture() final { if(mLoop) mLoop.stop(); } }; -void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) +void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept { mLoop.signal(false); } -void PipeWireCapture::inputCallback() +void PipeWireCapture::inputCallback() noexcept { pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; if(!pw_buf) UNLIKELY return; spa_data *bufdata{pw_buf->buffer->datas}; - const uint offset{minu(bufdata->chunk->offset, bufdata->maxsize)}; - const uint size{minu(bufdata->chunk->size, bufdata->maxsize - offset)}; + const uint offset{bufdata->chunk->offset % bufdata->maxsize}; + const auto input = al::span{static_cast(bufdata->data), bufdata->maxsize} + .subspan(offset, std::min(bufdata->chunk->size, bufdata->maxsize - offset)); - mRing->write(static_cast(bufdata->data) + offset, size / mRing->getElemSize()); + std::ignore = mRing->write(input.data(), input.size() / mRing->getElemSize()); pw_stream_queue_buffer(mStream.get(), pw_buf); } -void PipeWireCapture::open(const char *name) +void PipeWireCapture::open(std::string_view name) { static std::atomic OpenCount{0}; uint64_t targetid{PwIdAny}; std::string devname{}; gEventHandler.waitForInit(); - if(!name) + if(name.empty()) { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match = devlist.cend(); @@ -1874,29 +1997,39 @@ void PipeWireCapture::open(const char *name) targetid = match->mSerial; if(match->mType != NodeType::Sink) devname = match->mName; - else devname = MonitorPrefix+match->mName; + else devname = std::string{GetMonitorPrefix()}+match->mName; } else { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); + const std::string_view prefix{GetMonitorPrefix()}; + const std::string_view suffix{GetMonitorSuffix()}; auto match_name = [name](const DeviceNode &n) -> bool { return n.mType != NodeType::Sink && n.mName == name; }; auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); - if(match == devlist.cend() && std::strncmp(name, MonitorPrefix, MonitorPrefixLen) == 0) + if(match == devlist.cend() && al::starts_with(name, prefix)) { - const char *sinkname{name + MonitorPrefixLen}; + const std::string_view sinkname{name.substr(prefix.length())}; auto match_sinkname = [sinkname](const DeviceNode &n) -> bool { return n.mType == NodeType::Sink && n.mName == sinkname; }; match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname); } + else if(match == devlist.cend() && al::ends_with(name, suffix)) + { + const std::string_view sinkname{name.substr(0, name.size()-suffix.size())}; + auto match_sinkname = [sinkname](const DeviceNode &n) -> bool + { return n.mType == NodeType::Sink && n.mDevName == sinkname; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname); + } if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; targetid = match->mSerial; - devname = name; + if(match->mType != NodeType::Sink) devname = match->mName; + else devname = std::string{GetMonitorPrefix()}+match->mName; } if(!mLoop) @@ -1935,13 +2068,13 @@ void PipeWireCapture::open(const char *name) if(!devname.empty()) mDevice->DeviceName = std::move(devname); else - mDevice->DeviceName = pwireInput; + mDevice->DeviceName = "PipeWire Input"sv; bool is51rear{false}; if(mTargetId != PwIdAny) { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool @@ -1952,11 +2085,11 @@ void PipeWireCapture::open(const char *name) } spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)}; - constexpr uint32_t pod_buffer_size{1024}; - auto pod_buffer = std::make_unique(pod_buffer_size); - spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)}; + static constexpr uint32_t pod_buffer_size{1024}; + PodDynamicBuilder b(pod_buffer_size); - const spa_pod *params[]{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)}; + std::array params{static_cast(spa_format_audio_raw_build(b.get(), + SPA_PARAM_EnumFormat, &info))}; if(!params[0]) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set PipeWire audio format parameters"}; @@ -1998,7 +2131,7 @@ void PipeWireCapture::open(const char *name) constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS}; - if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params, 1)}) + if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params.data(), 1)}) throw al::backend_exception{al::backend_error::DeviceError, "Error connecting PipeWire stream (res: %d)", res}; @@ -2017,7 +2150,7 @@ void PipeWireCapture::open(const char *name) setDefaultWFXChannelOrder(); /* Ensure at least a 100ms capture buffer. */ - mRing = RingBuffer::Create(maxu(mDevice->Frequency/10, mDevice->BufferSize), + mRing = RingBuffer::Create(std::max(mDevice->Frequency/10u, mDevice->BufferSize), mDevice->frameSizeFromFmt(), false); } @@ -2044,8 +2177,7 @@ void PipeWireCapture::stop() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), false)}) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to stop PipeWire stream (res: %d)", res}; + ERR("Failed to stop PipeWire stream (res: %d)\n", res); plock.wait([stream=mStream.get()]() { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; }); @@ -2054,8 +2186,8 @@ void PipeWireCapture::stop() uint PipeWireCapture::availableSamples() { return static_cast(mRing->readSpace()); } -void PipeWireCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void PipeWireCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } } // namespace @@ -2074,11 +2206,11 @@ bool PipeWireBackendFactory::init() } TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version()); - pw_init(0, nullptr); + pw_init(nullptr, nullptr); if(!gEventHandler.init()) return false; - if(!GetConfigValueBool(nullptr, "pipewire", "assume-audio", false) + if(!GetConfigValueBool({}, "pipewire", "assume-audio", false) && !gEventHandler.waitForAudio()) { gEventHandler.kill(); @@ -2094,12 +2226,12 @@ bool PipeWireBackendFactory::init() bool PipeWireBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string PipeWireBackendFactory::probe(BackendType type) +auto PipeWireBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; gEventHandler.waitForInit(); - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_defsink = [](const DeviceNode &n) -> bool @@ -2117,31 +2249,31 @@ std::string PipeWireBackendFactory::probe(BackendType type) case BackendType::Playback: defmatch = std::find_if(defmatch, devlist.cend(), match_defsink); if(defmatch != devlist.cend()) - { - /* Includes null char. */ - outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1); - } + outnames.emplace_back(defmatch->mName); for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) { if(iter != defmatch && iter->mType != NodeType::Source) - outnames.append(iter->mName.c_str(), iter->mName.length()+1); + outnames.emplace_back(iter->mName); } break; case BackendType::Capture: + outnames.reserve(devlist.size()); defmatch = std::find_if(defmatch, devlist.cend(), match_defsource); if(defmatch != devlist.cend()) { if(defmatch->mType == NodeType::Sink) - outnames.append(MonitorPrefix); - outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1); + outnames.emplace_back(std::string{GetMonitorPrefix()}+defmatch->mName); + else + outnames.emplace_back(defmatch->mName); } for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) { if(iter != defmatch) { if(iter->mType == NodeType::Sink) - outnames.append(MonitorPrefix); - outnames.append(iter->mName.c_str(), iter->mName.length()+1); + outnames.emplace_back(std::string{GetMonitorPrefix()}+iter->mName); + else + outnames.emplace_back(iter->mName); } } break; @@ -2150,6 +2282,7 @@ std::string PipeWireBackendFactory::probe(BackendType type) return outnames; } + BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) @@ -2164,3 +2297,18 @@ BackendFactory &PipeWireBackendFactory::getFactory() static PipeWireBackendFactory factory{}; return factory; } + +alc::EventSupport PipeWireBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DefaultDeviceChanged: + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: + return alc::EventSupport::FullSupport; + + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/Engine/lib/openal-soft/alc/backends/pipewire.h b/Engine/lib/openal-soft/alc/backends/pipewire.h index 5f930239c..567d18edd 100644 --- a/Engine/lib/openal-soft/alc/backends/pipewire.h +++ b/Engine/lib/openal-soft/alc/backends/pipewire.h @@ -2,22 +2,26 @@ #define BACKENDS_PIPEWIRE_H #include +#include +#include "alc/events.h" #include "base.h" struct DeviceBase; struct PipeWireBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - static BackendFactory &getFactory(); + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PIPEWIRE_H */ diff --git a/Engine/lib/openal-soft/alc/backends/portaudio.cpp b/Engine/lib/openal-soft/alc/backends/portaudio.cpp index 9c94587da..eb1c289f3 100644 --- a/Engine/lib/openal-soft/alc/backends/portaudio.cpp +++ b/Engine/lib/openal-soft/alc/backends/portaudio.cpp @@ -26,21 +26,20 @@ #include #include +#include "albit.h" #include "alc/alconfig.h" #include "alnumeric.h" +#include "alstring.h" #include "core/device.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" -#include +#include /* NOLINT(*-duplicate-include) Not the same header. */ namespace { -constexpr char pa_device[] = "PortAudio Default"; - - #ifdef HAVE_DYNLOAD void *pa_handle; #define MAKE_FUNC(x) decltype(x) * p##x @@ -51,6 +50,8 @@ MAKE_FUNC(Pa_StartStream); MAKE_FUNC(Pa_StopStream); MAKE_FUNC(Pa_OpenStream); MAKE_FUNC(Pa_CloseStream); +MAKE_FUNC(Pa_GetDeviceCount); +MAKE_FUNC(Pa_GetDeviceInfo); MAKE_FUNC(Pa_GetDefaultOutputDevice); MAKE_FUNC(Pa_GetDefaultInputDevice); MAKE_FUNC(Pa_GetStreamInfo); @@ -64,12 +65,49 @@ MAKE_FUNC(Pa_GetStreamInfo); #define Pa_StopStream pPa_StopStream #define Pa_OpenStream pPa_OpenStream #define Pa_CloseStream pPa_CloseStream +#define Pa_GetDeviceCount pPa_GetDeviceCount +#define Pa_GetDeviceInfo pPa_GetDeviceInfo #define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice #define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice #define Pa_GetStreamInfo pPa_GetStreamInfo #endif #endif +struct DeviceEntry { + std::string mName; + bool mIsPlayback{}; + bool mIsCapture{}; +}; +std::vector DeviceNames; + +void EnumerateDevices() +{ + const auto devcount = Pa_GetDeviceCount(); + if(devcount < 0) + { + ERR("Error getting device count: %s\n", Pa_GetErrorText(devcount)); + return; + } + + std::vector(static_cast(devcount)).swap(DeviceNames); + PaDeviceIndex idx{0}; + for(auto &entry : DeviceNames) + { + if(auto info = Pa_GetDeviceInfo(idx); info && info->name) + { +#ifdef _WIN32 + entry.mName = "OpenAL Soft on "+std::string{info->name}; +#else + entry.mName = info->name; +#endif + entry.mIsPlayback = (info->maxOutputChannels > 0); + entry.mIsCapture = (info->maxInputChannels > 0); + TRACE("Device %d \"%s\": %d playback, %d capture channels\n", idx, entry.mName.c_str(), + info->maxOutputChannels, info->maxInputChannels); + } + ++idx; + } +} struct PortPlayback final : public BackendBase { PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { } @@ -77,24 +115,17 @@ struct PortPlayback final : public BackendBase { int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept; - static int writeCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData) noexcept - { - return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); - } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; PaStream *mStream{nullptr}; PaStreamParameters mParams{}; + DevFmtChannels mChannelConfig{}; + uint mAmbiOrder{}; uint mUpdateSize{0u}; - - DEF_NEWDEL(PortPlayback) }; PortPlayback::~PortPlayback() @@ -115,22 +146,38 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f } -void PortPlayback::open(const char *name) +void PortPlayback::open(std::string_view name) { - if(!name) - name = pa_device; - else if(strcmp(name, pa_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(DeviceNames.empty()) + EnumerateDevices(); + + int deviceid{-1}; + if(name.empty()) + { + if(auto devidopt = ConfigValueInt({}, "port", "device")) + deviceid = *devidopt; + if(deviceid < 0 || static_cast(deviceid) >= DeviceNames.size()) + deviceid = Pa_GetDefaultOutputDevice(); + name = DeviceNames.at(static_cast(deviceid)).mName; + } + else + { + auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), + [name](const DeviceEntry &entry) { return entry.mIsPlayback && name == entry.mName; }); + if(iter == DeviceNames.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + deviceid = static_cast(std::distance(DeviceNames.cbegin(), iter)); + } PaStreamParameters params{}; - auto devidopt = ConfigValueInt(nullptr, "port", "device"); - if(devidopt && *devidopt >= 0) params.device = *devidopt; - else params.device = Pa_GetDefaultOutputDevice(); + params.device = deviceid; params.suggestedLatency = mDevice->BufferSize / static_cast(mDevice->Frequency); params.hostApiSpecificStreamInfo = nullptr; - params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); + mChannelConfig = mDevice->FmtChans; + mAmbiOrder = mDevice->mAmbiOrder; + params.channelCount = static_cast(mDevice->channelsFromFmt()); switch(mDevice->FmtType) { @@ -155,19 +202,21 @@ void PortPlayback::open(const char *name) break; } -retry_open: - PaStream *stream{}; - PaError err{Pa_OpenStream(&stream, nullptr, ¶ms, mDevice->Frequency, mDevice->UpdateSize, - paNoFlag, &PortPlayback::writeCallbackC, this)}; - if(err != paNoError) + static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, + const PaStreamCallbackFlags statusFlags, void *userData) noexcept { - if(params.sampleFormat == paFloat32) - { - params.sampleFormat = paInt16; - goto retry_open; - } - throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", - Pa_GetErrorText(err)}; + return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, + framesPerBuffer, timeInfo, statusFlags); + }; + PaStream *stream{}; + while(PaError err{Pa_OpenStream(&stream, nullptr, ¶ms, mDevice->Frequency, + mDevice->UpdateSize, paNoFlag, writeCallback, this)}) + { + if(params.sampleFormat != paFloat32) + throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", + Pa_GetErrorText(err)}; + params.sampleFormat = paInt16; } Pa_CloseStream(mStream); @@ -182,7 +231,18 @@ bool PortPlayback::reset() { const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)}; mDevice->Frequency = static_cast(streamInfo->sampleRate); + mDevice->FmtChans = mChannelConfig; + mDevice->mAmbiOrder = mAmbiOrder; mDevice->UpdateSize = mUpdateSize; + mDevice->BufferSize = mUpdateSize * 2; + if(streamInfo->outputLatency > 0.0f) + { + const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate}; + TRACE("Reported stream latency: %f sec (%f samples)\n", streamInfo->outputLatency, + sampleLatency); + mDevice->BufferSize = static_cast(std::clamp(sampleLatency, + double(mDevice->BufferSize), double{std::numeric_limits::max()})); + } if(mParams.sampleFormat == paInt8) mDevice->FmtType = DevFmtByte; @@ -200,15 +260,6 @@ bool PortPlayback::reset() return false; } - if(mParams.channelCount >= 2) - mDevice->FmtChans = DevFmtStereo; - else if(mParams.channelCount == 1) - mDevice->FmtChans = DevFmtMono; - else - { - ERR("Unexpected channel count: %u\n", mParams.channelCount); - return false; - } setDefaultChannelOrder(); return true; @@ -216,16 +267,14 @@ bool PortPlayback::reset() void PortPlayback::start() { - const PaError err{Pa_StartStream(mStream)}; - if(err == paNoError) + if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s", Pa_GetErrorText(err)}; } void PortPlayback::stop() { - PaError err{Pa_StopStream(mStream)}; - if(err != paNoError) + if(PaError err{Pa_StopStream(mStream)}; err != paNoError) ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); } @@ -235,27 +284,18 @@ struct PortCapture final : public BackendBase { ~PortCapture() override; int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept; - static int readCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData) noexcept - { - return static_cast(userData)->readCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); - } + const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) const noexcept; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; PaStream *mStream{nullptr}; - PaStreamParameters mParams; + PaStreamParameters mParams{}; RingBufferPtr mRing{nullptr}; - - DEF_NEWDEL(PortCapture) }; PortCapture::~PortCapture() @@ -268,30 +308,43 @@ PortCapture::~PortCapture() int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept + const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) const noexcept { - mRing->write(inputBuffer, framesPerBuffer); + std::ignore = mRing->write(inputBuffer, framesPerBuffer); return 0; } -void PortCapture::open(const char *name) +void PortCapture::open(std::string_view name) { - if(!name) - name = pa_device; - else if(strcmp(name, pa_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(DeviceNames.empty()) + EnumerateDevices(); - uint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); - uint frame_size{mDevice->frameSizeFromFmt()}; + int deviceid{}; + if(name.empty()) + { + if(auto devidopt = ConfigValueInt({}, "port", "capture")) + deviceid = *devidopt; + if(deviceid < 0 || static_cast(deviceid) >= DeviceNames.size()) + deviceid = Pa_GetDefaultInputDevice(); + name = DeviceNames.at(static_cast(deviceid)).mName; + } + else + { + auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), + [name](const DeviceEntry &entry) { return entry.mIsCapture && name == entry.mName; }); + if(iter == DeviceNames.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + deviceid = static_cast(std::distance(DeviceNames.cbegin(), iter)); + } + + const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; + const uint frame_size{mDevice->frameSizeFromFmt()}; mRing = RingBuffer::Create(samples, frame_size, false); - auto devidopt = ConfigValueInt(nullptr, "port", "capture"); - if(devidopt && *devidopt >= 0) mParams.device = *devidopt; - else mParams.device = Pa_GetDefaultOutputDevice(); + mParams.device = deviceid; mParams.suggestedLatency = 0.0f; mParams.hostApiSpecificStreamInfo = nullptr; @@ -319,8 +372,15 @@ void PortCapture::open(const char *name) } mParams.channelCount = static_cast(mDevice->channelsFromFmt()); + static constexpr auto readCallback = [](const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, + const PaStreamCallbackFlags statusFlags, void *userData) noexcept + { + return static_cast(userData)->readCallback(inputBuffer, outputBuffer, + framesPerBuffer, timeInfo, statusFlags); + }; PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency, - paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)}; + paFramesPerBufferUnspecified, paNoFlag, readCallback, this)}; if(err != paNoError) throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", Pa_GetErrorText(err)}; @@ -331,16 +391,14 @@ void PortCapture::open(const char *name) void PortCapture::start() { - const PaError err{Pa_StartStream(mStream)}; - if(err != paNoError) + if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start recording: %s", Pa_GetErrorText(err)}; } void PortCapture::stop() { - PaError err{Pa_StopStream(mStream)}; - if(err != paNoError) + if(PaError err{Pa_StopStream(mStream)}; err != paNoError) ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); } @@ -348,16 +406,14 @@ void PortCapture::stop() uint PortCapture::availableSamples() { return static_cast(mRing->readSpace()); } -void PortCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void PortCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } } // namespace bool PortBackendFactory::init() { - PaError err; - #ifdef HAVE_DYNLOAD if(!pa_handle) { @@ -391,12 +447,15 @@ bool PortBackendFactory::init() LOAD_FUNC(Pa_StopStream); LOAD_FUNC(Pa_OpenStream); LOAD_FUNC(Pa_CloseStream); + LOAD_FUNC(Pa_GetDeviceCount); + LOAD_FUNC(Pa_GetDeviceInfo); LOAD_FUNC(Pa_GetDefaultOutputDevice); LOAD_FUNC(Pa_GetDefaultInputDevice); LOAD_FUNC(Pa_GetStreamInfo); #undef LOAD_FUNC - if((err=Pa_Initialize()) != paNoError) + const PaError err{Pa_Initialize()}; + if(err != paNoError) { ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); CloseLib(pa_handle); @@ -405,7 +464,8 @@ bool PortBackendFactory::init() } } #else - if((err=Pa_Initialize()) != paNoError) + const PaError err{Pa_Initialize()}; + if(err != paNoError) { ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); return false; @@ -417,18 +477,52 @@ bool PortBackendFactory::init() bool PortBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string PortBackendFactory::probe(BackendType type) +auto PortBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector devices; + + EnumerateDevices(); + int defaultid{-1}; switch(type) { case BackendType::Playback: + defaultid = Pa_GetDefaultOutputDevice(); + if(auto devidopt = ConfigValueInt({}, "port", "device"); devidopt && *devidopt >= 0 + && static_cast(*devidopt) < DeviceNames.size()) + defaultid = *devidopt; + + for(size_t i{0};i < DeviceNames.size();++i) + { + if(DeviceNames[i].mIsPlayback) + { + if(defaultid >= 0 && static_cast(defaultid) == i) + devices.emplace(devices.cbegin(), DeviceNames[i].mName); + else + devices.emplace_back(DeviceNames[i].mName); + } + } + break; + case BackendType::Capture: - /* Includes null char. */ - outnames.append(pa_device, sizeof(pa_device)); + defaultid = Pa_GetDefaultInputDevice(); + if(auto devidopt = ConfigValueInt({}, "port", "capture"); devidopt && *devidopt >= 0 + && static_cast(*devidopt) < DeviceNames.size()) + defaultid = *devidopt; + + for(size_t i{0};i < DeviceNames.size();++i) + { + if(DeviceNames[i].mIsCapture) + { + if(defaultid >= 0 && static_cast(defaultid) == i) + devices.emplace(devices.cbegin(), DeviceNames[i].mName); + else + devices.emplace_back(DeviceNames[i].mName); + } + } break; } - return outnames; + + return devices; } BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/portaudio.h b/Engine/lib/openal-soft/alc/backends/portaudio.h index c35ccff2d..f5abe8e4c 100644 --- a/Engine/lib/openal-soft/alc/backends/portaudio.h +++ b/Engine/lib/openal-soft/alc/backends/portaudio.h @@ -5,15 +5,15 @@ struct PortBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PORTAUDIO_H */ diff --git a/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp b/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp index 4b0e316f4..91fa4b6c1 100644 --- a/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp +++ b/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp @@ -28,28 +28,30 @@ #include #include #include +#include +#include +#include #include +#include #include #include -#include -#include +#include #include +#include #include #include +#include -#include "albyte.h" #include "alc/alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" +#include "alstring.h" +#include "base.h" #include "core/devformat.h" #include "core/device.h" #include "core/logging.h" #include "dynload.h" #include "opthelpers.h" #include "strutils.h" -#include "vector.h" #include @@ -65,6 +67,8 @@ using uint = unsigned int; MAGIC(pa_context_get_state); \ MAGIC(pa_context_disconnect); \ MAGIC(pa_context_set_state_callback); \ + MAGIC(pa_context_set_subscribe_callback); \ + MAGIC(pa_context_subscribe); \ MAGIC(pa_context_errno); \ MAGIC(pa_context_connect); \ MAGIC(pa_context_get_server_info); \ @@ -136,6 +140,8 @@ PULSE_FUNCS(MAKE_FUNC) #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_set_subscribe_callback ppa_context_set_subscribe_callback +#define pa_context_subscribe ppa_context_subscribe #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 @@ -270,6 +276,9 @@ constexpr pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_fla return lhs; } +constexpr pa_subscription_mask_t operator|(pa_subscription_mask_t lhs, pa_subscription_mask_t rhs) +{ return pa_subscription_mask_t(lhs | al::to_underlying(rhs)); } + struct DevMap { std::string name; @@ -282,8 +291,8 @@ bool checkName(const al::span list, const std::string &name) return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } -al::vector PlaybackDevices; -al::vector CaptureDevices; +std::vector PlaybackDevices; +std::vector CaptureDevices; /* Global flags and properties */ @@ -291,13 +300,14 @@ pa_context_flags_t pulse_ctx_flags; class PulseMainloop { pa_threaded_mainloop *mLoop{}; + pa_context *mContext{}; public: PulseMainloop() = default; PulseMainloop(const PulseMainloop&) = delete; PulseMainloop(PulseMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; } explicit PulseMainloop(pa_threaded_mainloop *loop) noexcept : mLoop{loop} { } - ~PulseMainloop() { if(mLoop) pa_threaded_mainloop_free(mLoop); } + ~PulseMainloop(); PulseMainloop& operator=(const PulseMainloop&) = delete; PulseMainloop& operator=(PulseMainloop&& rhs) noexcept @@ -312,10 +322,12 @@ public: explicit operator bool() const noexcept { return mLoop != nullptr; } + [[nodiscard]] auto start() const { return pa_threaded_mainloop_start(mLoop); } auto stop() const { return pa_threaded_mainloop_stop(mLoop); } - auto getApi() const { return pa_threaded_mainloop_get_api(mLoop); } + [[nodiscard]] auto getApi() const { return pa_threaded_mainloop_get_api(mLoop); } + [[nodiscard]] auto getContext() const noexcept { return mContext; } auto lock() const { return pa_threaded_mainloop_lock(mLoop); } auto unlock() const { return pa_threaded_mainloop_unlock(mLoop); } @@ -325,14 +337,14 @@ public: static auto Create() { return PulseMainloop{pa_threaded_mainloop_new()}; } - void streamSuccessCallback(pa_stream*, int) noexcept { signal(); } + void streamSuccessCallback(pa_stream*, int) const noexcept { signal(); } static void streamSuccessCallbackC(pa_stream *stream, int success, void *pdata) noexcept { static_cast(pdata)->streamSuccessCallback(stream, success); } - void close(pa_context *context, pa_stream *stream=nullptr); + void close(pa_stream *stream=nullptr); - void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) noexcept + void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) const noexcept { if(eol) { @@ -363,7 +375,7 @@ public: TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); } - void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) noexcept + void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) const noexcept { if(eol) { @@ -410,7 +422,7 @@ struct MainloopUniqueLock : public std::unique_lock { auto wait(Predicate done_waiting) const -> void { while(!done_waiting()) wait(); } - void waitForOperation(pa_operation *op) + void waitForOperation(pa_operation *op) const { if(op) { @@ -420,6 +432,41 @@ struct MainloopUniqueLock : public std::unique_lock { } + void setEventHandler() + { + pa_operation *op{pa_context_subscribe(mutex()->mContext, + PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, + [](pa_context*, int, void *pdata) noexcept + { static_cast(pdata)->signal(); }, + mutex())}; + waitForOperation(op); + + /* Watch for device added/removed events. + * + * TODO: Also track the "default" device, in as much as PulseAudio has + * the concept of a default device (whatever device is opened when not + * specifying a specific sink or source name). There doesn't seem to be + * an event for this. + */ + auto handler = [](pa_context*, pa_subscription_event_type_t t, uint32_t, void*) noexcept + { + const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); + if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK + || eventFacility == PA_SUBSCRIPTION_EVENT_SOURCE) + { + const auto deviceType = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) + ? alc::DeviceType::Playback : alc::DeviceType::Capture; + const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); + if(eventType == PA_SUBSCRIPTION_EVENT_NEW) + alc::Event(alc::EventType::DeviceAdded, deviceType, "Device added"); + else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE) + alc::Event(alc::EventType::DeviceRemoved, deviceType, "Device removed"); + } + }; + pa_context_set_subscribe_callback(mutex()->mContext, handler, nullptr); + } + + void contextStateCallback(pa_context *context) noexcept { pa_context_state_t state{pa_context_get_state(context)}; @@ -434,59 +481,71 @@ struct MainloopUniqueLock : public std::unique_lock { mutex()->signal(); } - pa_context *connectContext(); - pa_stream *connectStream(const char *device_name, pa_context *context, pa_stream_flags_t flags, + void connectContext(); + pa_stream *connectStream(const char *device_name, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type); }; using MainloopLockGuard = std::lock_guard; - -pa_context *MainloopUniqueLock::connectContext() +PulseMainloop::~PulseMainloop() { - pa_context *context{pa_context_new(mutex()->getApi(), nullptr)}; - if(!context) throw al::backend_exception{al::backend_error::OutOfMemory, + if(mContext) + { + MainloopUniqueLock looplock{*this}; + pa_context_disconnect(mContext); + pa_context_unref(mContext); + } + if(mLoop) + pa_threaded_mainloop_free(mLoop); +} + + +void MainloopUniqueLock::connectContext() +{ + if(mutex()->mContext) + return; + + mutex()->mContext = pa_context_new(mutex()->getApi(), nullptr); + if(!mutex()->mContext) throw al::backend_exception{al::backend_error::OutOfMemory, "pa_context_new() failed"}; - pa_context_set_state_callback(context, [](pa_context *ctx, void *pdata) noexcept + pa_context_set_state_callback(mutex()->mContext, [](pa_context *ctx, void *pdata) noexcept { return static_cast(pdata)->contextStateCallback(ctx); }, this); - int err; - if((err=pa_context_connect(context, nullptr, pulse_ctx_flags, nullptr)) >= 0) + int err{pa_context_connect(mutex()->mContext, nullptr, pulse_ctx_flags, nullptr)}; + if(err >= 0) { - pa_context_state_t state; - while((state=pa_context_get_state(context)) != PA_CONTEXT_READY) + wait([&err,this]() { + pa_context_state_t state{pa_context_get_state(mutex()->mContext)}; if(!PA_CONTEXT_IS_GOOD(state)) { - err = pa_context_errno(context); - if(err > 0) err = -err; - break; + err = pa_context_errno(mutex()->mContext); + if(err > 0) err = -err; + return true; } - - wait(); - } + return state == PA_CONTEXT_READY; + }); } - pa_context_set_state_callback(context, nullptr, nullptr); + pa_context_set_state_callback(mutex()->mContext, nullptr, nullptr); if(err < 0) { - pa_context_unref(context); + pa_context_unref(mutex()->mContext); + mutex()->mContext = nullptr; throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect (%s)", pa_strerror(err)}; } - - return context; } -pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_context *context, - pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, - BackendType type) +pa_stream *MainloopUniqueLock::connectStream(const char *device_name, 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)}; + pa_stream *stream{pa_stream_new(mutex()->mContext, stream_id, spec, chanmap)}; if(!stream) throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)", - pa_strerror(pa_context_errno(context))}; + pa_strerror(pa_context_errno(mutex()->mContext))}; pa_stream_set_state_callback(stream, [](pa_stream *strm, void *pdata) noexcept { return static_cast(pdata)->streamStateCallback(strm); }, this); @@ -501,93 +560,79 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_context stream_id, pa_strerror(err)}; } - pa_stream_state_t state; - while((state=pa_stream_get_state(stream)) != PA_STREAM_READY) + wait([&err,stream,stream_id,this]() { + pa_stream_state_t state{pa_stream_get_state(stream)}; if(!PA_STREAM_IS_GOOD(state)) { - err = pa_context_errno(context); + err = pa_context_errno(mutex()->mContext); pa_stream_unref(stream); throw al::backend_exception{al::backend_error::DeviceError, "%s did not get ready (%s)", stream_id, pa_strerror(err)}; } + return state == PA_STREAM_READY; + }); - wait(); - } pa_stream_set_state_callback(stream, nullptr, nullptr); return stream; } -void PulseMainloop::close(pa_context *context, pa_stream *stream) +void PulseMainloop::close(pa_stream *stream) { - MainloopUniqueLock _{*this}; - 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); - } + if(!stream) + return; - pa_context_disconnect(context); - pa_context_unref(context); + MainloopUniqueLock looplock{*this}; + 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); } void PulseMainloop::probePlaybackDevices() { - pa_context *context{}; - PlaybackDevices.clear(); try { MainloopUniqueLock plock{*this}; + plock.connectContext(); + auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; - context = plock.connectContext(); - pa_operation *op{pa_context_get_sink_info_by_name(context, nullptr, sink_callback, this)}; + pa_operation *op{pa_context_get_sink_info_by_name(mContext, nullptr, sink_callback, this)}; plock.waitForOperation(op); - op = pa_context_get_sink_info_list(context, sink_callback, this); + op = pa_context_get_sink_info_list(mContext, sink_callback, this); plock.waitForOperation(op); - - pa_context_disconnect(context); - pa_context_unref(context); - context = nullptr; } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) close(context); } } void PulseMainloop::probeCaptureDevices() { - pa_context *context{}; - CaptureDevices.clear(); try { MainloopUniqueLock plock{*this}; + plock.connectContext(); + auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->deviceSourceCallback(ctx, info, eol); }; - context = plock.connectContext(); - pa_operation *op{pa_context_get_source_info_by_name(context, nullptr, src_callback, this)}; + pa_operation *op{pa_context_get_source_info_by_name(mContext, nullptr, src_callback, + this)}; plock.waitForOperation(op); - op = pa_context_get_source_info_list(context, src_callback, this); + op = pa_context_get_source_info_list(mContext, src_callback, this); plock.waitForOperation(op); - - pa_context_disconnect(context); - pa_context_unref(context); - context = nullptr; } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) close(context); } } @@ -607,7 +652,7 @@ struct PulsePlayback final : public BackendBase { void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept; void streamMovedCallback(pa_stream *stream) noexcept; - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -615,29 +660,19 @@ struct PulsePlayback final : public BackendBase { PulseMainloop mMainloop; - al::optional mDeviceName{al::nullopt}; + std::optional mDeviceName{std::nullopt}; bool mIs51Rear{false}; - pa_buffer_attr mAttr; - pa_sample_spec mSpec; + pa_buffer_attr mAttr{}; + pa_sample_spec mSpec{}; pa_stream *mStream{nullptr}; - pa_context *mContext{nullptr}; uint mFrameSize{0u}; - - DEF_NEWDEL(PulsePlayback) }; PulsePlayback::~PulsePlayback() -{ - if(!mContext) - return; - - mMainloop.close(mContext, mStream); - mContext = nullptr; - mStream = nullptr; -} +{ if(mStream) mMainloop.close(mStream); } void PulsePlayback::bufferAttrCallback(pa_stream *stream) noexcept @@ -674,7 +709,7 @@ void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexce free_func = pa_xfree; } else - buflen = minz(buflen, nbytes); + buflen = std::min(buflen, nbytes); nbytes -= buflen; mDevice->renderSamples(buf, static_cast(buflen/mFrameSize), mSpec.channels); @@ -722,9 +757,9 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int else { mIs51Rear = false; - char chanmap_str[PA_CHANNEL_MAP_SNPRINT_MAX]{}; - pa_channel_map_snprint(chanmap_str, sizeof(chanmap_str), &info->channel_map); - WARN("Failed to find format for channel map:\n %s\n", chanmap_str); + std::array chanmap_str{}; + pa_channel_map_snprint(chanmap_str.data(), chanmap_str.size(), &info->channel_map); + WARN("Failed to find format for channel map:\n %s\n", chanmap_str.data()); } if(info->active_port) @@ -750,33 +785,37 @@ void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept } -void PulsePlayback::open(const char *name) +void PulsePlayback::open(std::string_view name) { mMainloop = PulseMainloop::Create(); - mMainloop.start(); + if(mMainloop.start() != 0) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start device mainloop"}; const char *pulse_name{nullptr}; - const char *dev_name{nullptr}; - if(name) + std::string_view display_name; + if(!name.empty()) { if(PlaybackDevices.empty()) mMainloop.probePlaybackDevices(); - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool { return entry.name == name; }); + auto match_name = [name](const DevMap &entry) -> bool + { return entry.name == name || entry.device_name == name; }; + auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_name); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + pulse_name = iter->device_name.c_str(); - dev_name = iter->name.c_str(); + display_name = iter->name; } MainloopUniqueLock plock{mMainloop}; - mContext = plock.connectContext(); + plock.connectContext(); 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", true)) + if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; pa_sample_spec spec{}; @@ -790,25 +829,26 @@ void PulsePlayback::open(const char *name) if(defname) pulse_name = defname->c_str(); } TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = plock.connectStream(pulse_name, mContext, flags, nullptr, &spec, nullptr, + mStream = plock.connectStream(pulse_name, flags, nullptr, &spec, nullptr, BackendType::Playback); - pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamMovedCallback(stream); }, this); + constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamMovedCallback(stream); }; + pa_stream_set_moved_callback(mStream, move_callback, this); mFrameSize = static_cast(pa_frame_size(pa_stream_get_sample_spec(mStream))); if(pulse_name) mDeviceName.emplace(pulse_name); else mDeviceName.reset(); - if(!dev_name) + if(display_name.empty()) { auto name_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->sinkNameCallback(context, info, eol); }; - pa_operation *op{pa_context_get_sink_info_by_name(mContext, + pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this)}; plock.waitForOperation(op); } else - mDevice->DeviceName = dev_name; + mDevice->DeviceName = display_name; } bool PulsePlayback::reset() @@ -827,16 +867,17 @@ bool PulsePlayback::reset() mStream = nullptr; } - auto info_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept + auto info_cb = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->sinkInfoCallback(context, info, eol); }; - pa_operation *op{pa_context_get_sink_info_by_name(mContext, deviceName, info_callback, this)}; + pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), deviceName, info_cb, + this)}; plock.waitForOperation(op); 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", true)) + if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "adjust-latency", false)) + if(GetConfigValueBool(mDevice->DeviceName, "pulse", "adjust-latency", false)) { /* ADJUST_LATENCY can't be specified with EARLY_REQUESTS, for some * reason. So if the user wants to adjust the overall device latency, @@ -845,7 +886,7 @@ bool PulsePlayback::reset() flags &= ~PA_STREAM_EARLY_REQUESTS; flags |= PA_STREAM_ADJUST_LATENCY; } - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", false) + if(GetConfigValueBool(mDevice->DeviceName, "pulse", "fix-rate", false) || !mDevice->Flags.test(FrequencyRequest)) flags |= PA_STREAM_FIX_RATE; @@ -874,6 +915,9 @@ bool PulsePlayback::reset() case DevFmtX3D71: chanmap = X71ChanMap; break; + case DevFmtX7144: + mDevice->FmtChans = DevFmtX714; + /*fall-through*/ case DevFmtX714: chanmap = X714ChanMap; break; @@ -916,13 +960,16 @@ bool PulsePlayback::reset() mAttr.minreq = mDevice->UpdateSize * frame_size; mAttr.fragsize = ~0u; - mStream = plock.connectStream(deviceName, mContext, flags, &mAttr, &mSpec, &chanmap, + mStream = plock.connectStream(deviceName, flags, &mAttr, &mSpec, &chanmap, BackendType::Playback); - pa_stream_set_state_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamStateCallback(stream); }, this); - pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamMovedCallback(stream); }, this); + constexpr auto state_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamStateCallback(stream); }; + pa_stream_set_state_callback(mStream, state_callback, this); + + constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamMovedCallback(stream); }; + pa_stream_set_moved_callback(mStream, move_callback, this); mSpec = *(pa_stream_get_sample_spec(mStream)); mFrameSize = static_cast(pa_frame_size(&mSpec)); @@ -933,15 +980,15 @@ bool PulsePlayback::reset() * accordingly. */ const auto scale = static_cast(mSpec.rate) / mDevice->Frequency; - const auto perlen = static_cast(clampd(scale*mDevice->UpdateSize + 0.5, 64.0, - 8192.0)); - const auto buflen = static_cast(clampd(scale*mDevice->BufferSize + 0.5, perlen*2, - std::numeric_limits::max()/mFrameSize)); + const auto perlen = std::clamp(std::round(scale*mDevice->UpdateSize), 64.0, 8192.0); + const auto bufmax = uint{std::numeric_limits::max()} / mFrameSize; + const auto buflen = std::clamp(std::round(scale*mDevice->BufferSize), perlen*2.0, + static_cast(bufmax)); mAttr.maxlength = ~0u; - mAttr.tlength = buflen * mFrameSize; + mAttr.tlength = static_cast(buflen) * mFrameSize; mAttr.prebuf = 0u; - mAttr.minreq = perlen * mFrameSize; + mAttr.minreq = static_cast(perlen) * mFrameSize; op = pa_stream_set_buffer_attr(mStream, &mAttr, &PulseMainloop::streamSuccessCallbackC, &mMainloop); @@ -950,7 +997,7 @@ bool PulsePlayback::reset() mDevice->Frequency = mSpec.rate; } - auto attr_callback = [](pa_stream *stream, void *pdata) noexcept + constexpr auto attr_callback = [](pa_stream *stream, void *pdata) noexcept { return static_cast(pdata)->bufferAttrCallback(stream); }; pa_stream_set_buffer_attr_callback(mStream, attr_callback, this); bufferAttrCallback(mStream); @@ -975,8 +1022,9 @@ void PulsePlayback::start() pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE); } - pa_stream_set_write_callback(mStream, [](pa_stream *stream, size_t nbytes, void *pdata)noexcept - { return static_cast(pdata)->streamWriteCallback(stream, nbytes); }, this); + constexpr auto stream_write = [](pa_stream *stream, size_t nbytes, void *pdata) noexcept + { return static_cast(pdata)->streamWriteCallback(stream, nbytes); }; + pa_stream_set_write_callback(mStream, stream_write, this); pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; @@ -996,13 +1044,13 @@ void PulsePlayback::stop() ClockLatency PulsePlayback::getClockLatency() { - ClockLatency ret; - pa_usec_t latency; - int neg, err; + ClockLatency ret{}; + pa_usec_t latency{}; + int neg{}, err{}; { MainloopUniqueLock plock{mMainloop}; - ret.ClockTime = GetDeviceClockTime(mDevice); + ret.ClockTime = mDevice->getClockTime(); err = pa_stream_get_latency(mStream, &latency, &neg); } @@ -1033,42 +1081,32 @@ struct PulseCapture final : public BackendBase { void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol) noexcept; void streamMovedCallback(pa_stream *stream) noexcept; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ClockLatency getClockLatency() override; PulseMainloop mMainloop; - al::optional mDeviceName{al::nullopt}; + std::optional mDeviceName{std::nullopt}; - al::span mCapBuffer; + al::span mCapBuffer; size_t mHoleLength{0}; size_t mPacketLength{0}; uint mLastReadable{0u}; - al::byte mSilentVal{}; + std::byte mSilentVal{}; 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; -} +{ if(mStream) mMainloop.close(mStream); } void PulseCapture::streamStateCallback(pa_stream *stream) noexcept @@ -1098,56 +1136,47 @@ void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept } -void PulseCapture::open(const char *name) +void PulseCapture::open(std::string_view name) { if(!mMainloop) { mMainloop = PulseMainloop::Create(); - mMainloop.start(); + if(mMainloop.start() != 0) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start device mainloop"}; } const char *pulse_name{nullptr}; - if(name) + if(!name.empty()) { if(CaptureDevices.empty()) mMainloop.probeCaptureDevices(); - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool { return entry.name == name; }); + auto match_name = [name](const DevMap &entry) -> bool + { return entry.name == name || entry.device_name == name; }; + auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_name); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + pulse_name = iter->device_name.c_str(); mDevice->DeviceName = iter->name; } MainloopUniqueLock plock{mMainloop}; - mContext = plock.connectContext(); + plock.connectContext(); 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 DevFmtX61: - chanmap = X61ChanMap; - break; - case DevFmtX71: - chanmap = X71ChanMap; - break; - case DevFmtX714: - chanmap = X714ChanMap; - break; + case DevFmtMono: chanmap = MonoChanMap; break; + case DevFmtStereo: chanmap = StereoChanMap; break; + case DevFmtQuad: chanmap = QuadChanMap; break; + case DevFmtX51: chanmap = X51ChanMap; break; + case DevFmtX61: chanmap = X61ChanMap; break; + case DevFmtX71: chanmap = X71ChanMap; break; + case DevFmtX714: chanmap = X714ChanMap; break; + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", @@ -1158,7 +1187,7 @@ void PulseCapture::open(const char *name) switch(mDevice->FmtType) { case DevFmtUByte: - mSilentVal = al::byte(0x80); + mSilentVal = std::byte(0x80); mSpec.format = PA_SAMPLE_U8; break; case DevFmtShort: @@ -1182,33 +1211,37 @@ void PulseCapture::open(const char *name) throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"}; const auto frame_size = static_cast(pa_frame_size(&mSpec)); - const uint samples{maxu(mDevice->BufferSize, 100 * mDevice->Frequency / 1000)}; + const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency*100u/1000u)}; mAttr.minreq = ~0u; mAttr.prebuf = ~0u; mAttr.maxlength = samples * frame_size; mAttr.tlength = ~0u; - mAttr.fragsize = minu(samples, 50*mDevice->Frequency/1000) * frame_size; + mAttr.fragsize = std::min(samples, mDevice->Frequency*50u/1000u) * frame_size; pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true)) + if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = plock.connectStream(pulse_name, mContext, flags, &mAttr, &mSpec, &chanmap, + mStream = plock.connectStream(pulse_name, flags, &mAttr, &mSpec, &chanmap, BackendType::Capture); - pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamMovedCallback(stream); }, this); - pa_stream_set_state_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamStateCallback(stream); }, this); + constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamMovedCallback(stream); }; + pa_stream_set_moved_callback(mStream, move_callback, this); + + constexpr auto state_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamStateCallback(stream); }; + pa_stream_set_state_callback(mStream, state_callback, this); if(pulse_name) mDeviceName.emplace(pulse_name); else mDeviceName.reset(); if(mDevice->DeviceName.empty()) { - auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept + constexpr auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, + void *pdata) noexcept { return static_cast(pdata)->sourceNameCallback(context, info, eol); }; - pa_operation *op{pa_context_get_source_info_by_name(mContext, + pa_operation *op{pa_context_get_source_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this)}; plock.waitForOperation(op); } @@ -1230,9 +1263,9 @@ void PulseCapture::stop() plock.waitForOperation(op); } -void PulseCapture::captureSamples(al::byte *buffer, uint samples) +void PulseCapture::captureSamples(std::byte *buffer, uint samples) { - al::span dstbuf{buffer, samples * pa_frame_size(&mSpec)}; + al::span dstbuf{buffer, samples * pa_frame_size(&mSpec)}; /* Capture is done in fragment-sized chunks, so we loop until we get all * that's available. @@ -1242,7 +1275,7 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) { if(mHoleLength > 0) UNLIKELY { - const size_t rem{minz(dstbuf.size(), mHoleLength)}; + const size_t rem{std::min(dstbuf.size(), mHoleLength)}; std::fill_n(dstbuf.begin(), rem, mSilentVal); dstbuf = dstbuf.subspan(rem); mHoleLength -= rem; @@ -1251,7 +1284,7 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) } if(!mCapBuffer.empty()) { - const size_t rem{minz(dstbuf.size(), mCapBuffer.size())}; + const size_t rem{std::min(dstbuf.size(), mCapBuffer.size())}; std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin()); dstbuf = dstbuf.subspan(rem); mCapBuffer = mCapBuffer.subspan(rem); @@ -1276,12 +1309,12 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) break; } - const void *capbuf; - size_t caplen; + const void *capbuf{}; + size_t caplen{}; if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) UNLIKELY { mDevice->handleDisconnect("Failed retrieving capture samples: %s", - pa_strerror(pa_context_errno(mContext))); + pa_strerror(pa_context_errno(mMainloop.getContext()))); break; } plock.unlock(); @@ -1290,7 +1323,7 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) if(!capbuf) UNLIKELY mHoleLength = caplen; else - mCapBuffer = {static_cast(capbuf), caplen}; + mCapBuffer = {static_cast(capbuf), caplen}; mPacketLength = caplen; } if(!dstbuf.empty()) @@ -1299,7 +1332,7 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) uint PulseCapture::availableSamples() { - size_t readable{maxz(mCapBuffer.size(), mHoleLength)}; + size_t readable{std::max(mCapBuffer.size(), mHoleLength)}; if(mDevice->Connected.load(std::memory_order_acquire)) { @@ -1332,13 +1365,13 @@ uint PulseCapture::availableSamples() ClockLatency PulseCapture::getClockLatency() { - ClockLatency ret; - pa_usec_t latency; - int neg, err; + ClockLatency ret{}; + pa_usec_t latency{}; + int neg{}, err{}; { MainloopUniqueLock plock{mMainloop}; - ret.ClockTime = GetDeviceClockTime(mDevice); + ret.ClockTime = mDevice->getClockTime(); err = pa_stream_get_latency(mStream, &latency, &neg); } @@ -1363,9 +1396,6 @@ 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__) @@ -1380,17 +1410,15 @@ bool PulseBackendFactory::init() return false; } + std::string missing_funcs; #define LOAD_FUNC(x) do { \ p##x = reinterpret_cast(GetSymbol(pulse_handle, #x)); \ - if(!(p##x)) { \ - ret = false; \ - missing_funcs += "\n" #x; \ - } \ + if(!(p##x)) missing_funcs += "\n" #x; \ } while(0) PULSE_FUNCS(LOAD_FUNC) #undef LOAD_FUNC - if(!ret) + if(!missing_funcs.empty()) { WARN("Missing expected functions:%s\n", missing_funcs.c_str()); CloseLib(pulse_handle); @@ -1401,20 +1429,23 @@ bool PulseBackendFactory::init() #endif /* HAVE_DYNLOAD */ pulse_ctx_flags = PA_CONTEXT_NOFLAGS; - if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", false)) + if(!GetConfigValueBool({}, "pulse", "spawn-server", false)) pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN; try { if(!gGlobalMainloop) { gGlobalMainloop = PulseMainloop::Create(); - gGlobalMainloop.start(); + if(gGlobalMainloop.start() != 0) + { + gGlobalMainloop = nullptr; + return false; + } } MainloopUniqueLock plock{gGlobalMainloop}; - pa_context *context{plock.connectContext()}; - pa_context_disconnect(context); - pa_context_unref(context); + plock.connectContext(); + plock.setEventHandler(); return true; } catch(...) { @@ -1425,27 +1456,24 @@ bool PulseBackendFactory::init() bool PulseBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string PulseBackendFactory::probe(BackendType type) +auto PulseBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - }; + { outnames.push_back(entry.name); }; switch(type) { case BackendType::Playback: gGlobalMainloop.probePlaybackDevices(); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: gGlobalMainloop.probeCaptureDevices(); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } @@ -1467,3 +1495,18 @@ BackendFactory &PulseBackendFactory::getFactory() static PulseBackendFactory factory{}; return factory; } + +alc::EventSupport PulseBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: + return alc::EventSupport::FullSupport; + + case alc::EventType::DefaultDeviceChanged: + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/Engine/lib/openal-soft/alc/backends/pulseaudio.h b/Engine/lib/openal-soft/alc/backends/pulseaudio.h index 6690fe8a9..c183d4794 100644 --- a/Engine/lib/openal-soft/alc/backends/pulseaudio.h +++ b/Engine/lib/openal-soft/alc/backends/pulseaudio.h @@ -1,19 +1,27 @@ #ifndef BACKENDS_PULSEAUDIO_H #define BACKENDS_PULSEAUDIO_H +#include +#include + +#include "alc/events.h" #include "base.h" +struct DeviceBase; + class PulseBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - static BackendFactory &getFactory(); + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PULSEAUDIO_H */ diff --git a/Engine/lib/openal-soft/alc/backends/sdl2.cpp b/Engine/lib/openal-soft/alc/backends/sdl2.cpp index a4a5a9ac1..ec3be6b0e 100644 --- a/Engine/lib/openal-soft/alc/backends/sdl2.cpp +++ b/Engine/lib/openal-soft/alc/backends/sdl2.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "almalloc.h" #include "alnumeric.h" @@ -46,17 +47,17 @@ namespace { #define DEVNAME_PREFIX "" #endif -constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device"; +constexpr auto getDevicePrefix() noexcept -> std::string_view { return DEVNAME_PREFIX; } +constexpr auto getDefaultDeviceName() noexcept -> std::string_view +{ return DEVNAME_PREFIX "Default Device"; } struct Sdl2Backend final : public BackendBase { Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { } ~Sdl2Backend() override; void audioCallback(Uint8 *stream, int len) noexcept; - static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept - { static_cast(ptr)->audioCallback(stream, len); } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -68,8 +69,6 @@ struct Sdl2Backend final : public BackendBase { DevFmtChannels mFmtChans{}; DevFmtType mFmtType{}; uint mUpdateSize{0u}; - - DEF_NEWDEL(Sdl2Backend) }; Sdl2Backend::~Sdl2Backend() @@ -86,7 +85,7 @@ void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt()); } -void Sdl2Backend::open(const char *name) +void Sdl2Backend::open(std::string_view name) { SDL_AudioSpec want{}, have{}; @@ -102,24 +101,39 @@ void Sdl2Backend::open(const char *name) case DevFmtFloat: want.format = AUDIO_F32; break; } want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; - want.samples = static_cast(minu(mDevice->UpdateSize, 8192)); - want.callback = &Sdl2Backend::audioCallbackC; + want.samples = static_cast(std::min(mDevice->UpdateSize, 8192u)); + want.callback = [](void *ptr, Uint8 *stream, int len) noexcept + { return static_cast(ptr)->audioCallback(stream, len); }; want.userdata = this; /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't * necessarily the first in the list. */ + const auto defaultDeviceName = getDefaultDeviceName(); SDL_AudioDeviceID devid; - if(!name || strcmp(name, defaultDeviceName) == 0) + if(name.empty() || name == defaultDeviceName) + { + name = defaultDeviceName; devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); + } else { - const size_t prefix_len = strlen(DEVNAME_PREFIX); - if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0) - devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have, + const auto namePrefix = getDevicePrefix(); + if(name.size() >= namePrefix.size() && name.substr(0, namePrefix.size()) == namePrefix) + { + /* Copy the string_view to a string to ensure it's null terminated + * for this call. + */ + const std::string devname{name.substr(namePrefix.size())}; + devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); + } else - devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); + { + const std::string devname{name}; + devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); + } } if(!devid) throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()}; @@ -161,7 +175,7 @@ void Sdl2Backend::open(const char *name) mFmtType = devtype; mUpdateSize = have.samples; - mDevice->DeviceName = name ? name : defaultDeviceName; + mDevice->DeviceName = name; } bool Sdl2Backend::reset() @@ -195,23 +209,27 @@ bool SDL2BackendFactory::init() bool SDL2BackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -std::string SDL2BackendFactory::probe(BackendType type) +auto SDL2BackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; if(type != BackendType::Playback) return outnames; int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)}; + if(num_devices <= 0) + return outnames; - /* Includes null char. */ - outnames.append(defaultDeviceName, sizeof(defaultDeviceName)); + outnames.reserve(static_cast(num_devices)); + outnames.emplace_back(getDefaultDeviceName()); 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); + std::string outname{getDevicePrefix()}; + if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE)) + outname += name; + else + outname += "Unknown Device Name #"+std::to_string(i); + outnames.emplace_back(std::move(outname)); } return outnames; } diff --git a/Engine/lib/openal-soft/alc/backends/sdl2.h b/Engine/lib/openal-soft/alc/backends/sdl2.h index 3bd8df863..8c5829121 100644 --- a/Engine/lib/openal-soft/alc/backends/sdl2.h +++ b/Engine/lib/openal-soft/alc/backends/sdl2.h @@ -5,15 +5,15 @@ struct SDL2BackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SDL2_H */ diff --git a/Engine/lib/openal-soft/alc/backends/sndio.cpp b/Engine/lib/openal-soft/alc/backends/sndio.cpp index 077e77f20..8d0693af7 100644 --- a/Engine/lib/openal-soft/alc/backends/sndio.cpp +++ b/Engine/lib/openal-soft/alc/backends/sndio.cpp @@ -22,31 +22,35 @@ #include "sndio.h" +#include +#include +#include +#include #include -#include #include -#include -#include -#include +#include #include +#include #include "alnumeric.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" -#include "threads.h" -#include "vector.h" -#include +#include /* NOLINT(*-duplicate-include) Not the same header. */ namespace { -static const char sndio_device[] = "SndIO Default"; +using namespace std::string_view_literals; + +[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "SndIO Default"sv; } struct SioPar : public sio_par { - SioPar() { sio_initpar(this); } + SioPar() : sio_par{} { sio_initpar(this); } void clear() { sio_initpar(this); } }; @@ -57,7 +61,7 @@ struct SndioPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -65,12 +69,10 @@ struct SndioPlayback final : public BackendBase { sio_hdl *mSndHandle{nullptr}; uint mFrameStep{}; - al::vector mBuffer; + std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(SndioPlayback) }; SndioPlayback::~SndioPlayback() @@ -86,12 +88,12 @@ int SndioPlayback::mixerProc() const size_t frameSize{frameStep * mDevice->bytesFromFmt()}; SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { - al::span buffer{mBuffer}; + al::span buffer{mBuffer}; mDevice->renderSamples(buffer.data(), static_cast(buffer.size() / frameSize), frameStep); @@ -112,13 +114,13 @@ int SndioPlayback::mixerProc() } -void SndioPlayback::open(const char *name) +void SndioPlayback::open(std::string_view name) { - if(!name) - name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDefaultName(); + else if(name != GetDefaultName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)}; if(!sndHandle) @@ -136,72 +138,75 @@ bool SndioPlayback::reset() SioPar par; auto tryfmt = mDevice->FmtType; -retry_params: - switch(tryfmt) + while(true) { - case DevFmtByte: - par.bits = 8; - par.sig = 1; - break; - case DevFmtUByte: - par.bits = 8; - par.sig = 0; - break; - case DevFmtShort: - par.bits = 16; - par.sig = 1; - break; - case DevFmtUShort: - par.bits = 16; - par.sig = 0; - break; - case DevFmtFloat: - case DevFmtInt: - par.bits = 32; - par.sig = 1; - break; - case DevFmtUInt: - par.bits = 32; - par.sig = 0; - break; - } - par.bps = SIO_BPS(par.bits); - par.le = SIO_LE_NATIVE; - par.msb = 1; + switch(tryfmt) + { + case DevFmtByte: + par.bits = 8; + par.sig = 1; + break; + case DevFmtUByte: + par.bits = 8; + par.sig = 0; + break; + case DevFmtShort: + par.bits = 16; + par.sig = 1; + break; + case DevFmtUShort: + par.bits = 16; + par.sig = 0; + break; + case DevFmtFloat: + case DevFmtInt: + par.bits = 32; + par.sig = 1; + break; + case DevFmtUInt: + par.bits = 32; + par.sig = 0; + break; + } + par.bps = SIO_BPS(par.bits); + par.le = SIO_LE_NATIVE; + par.msb = 1; - par.rate = mDevice->Frequency; - par.pchan = mDevice->channelsFromFmt(); + par.rate = mDevice->Frequency; + par.pchan = mDevice->channelsFromFmt(); - par.round = mDevice->UpdateSize; - par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; - if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; + par.round = mDevice->UpdateSize; + par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; + if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; - try { - if(!sio_setpar(mSndHandle, &par)) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to set device parameters"}; + try { + if(!sio_setpar(mSndHandle, &par)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set device parameters"}; - par.clear(); - if(!sio_getpar(mSndHandle, &par)) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to get device parameters"}; + par.clear(); + if(!sio_getpar(mSndHandle, &par)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to get device parameters"}; - if(par.bps > 1 && par.le != SIO_LE_NATIVE) - throw al::backend_exception{al::backend_error::DeviceError, - "%s-endian samples not supported", par.le ? "Little" : "Big"}; - if(par.bits < par.bps*8 && !par.msb) - throw al::backend_exception{al::backend_error::DeviceError, - "MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8}; - if(par.pchan < 1) - throw al::backend_exception{al::backend_error::DeviceError, - "No playback channels on device"}; - } - catch(al::backend_exception &e) { - if(tryfmt == DevFmtShort) - throw; - par.clear(); - tryfmt = DevFmtShort; - goto retry_params; + if(par.bps > 1 && par.le != SIO_LE_NATIVE) + throw al::backend_exception{al::backend_error::DeviceError, + "%s-endian samples not supported", par.le ? "Little" : "Big"}; + if(par.bits < par.bps*8 && !par.msb) + throw al::backend_exception{al::backend_error::DeviceError, + "MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8}; + if(par.pchan < 1) + throw al::backend_exception{al::backend_error::DeviceError, + "No playback channels on device"}; + + break; + } + catch(al::backend_exception &e) { + if(tryfmt == DevFmtShort) + throw; + par.clear(); + tryfmt = DevFmtShort; + } } if(par.bps == 1) @@ -229,11 +234,11 @@ retry_params: mDevice->UpdateSize = par.round; mDevice->BufferSize = par.bufsz + par.round; - mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps); + mBuffer.resize(size_t{mDevice->UpdateSize} * par.pchan*par.bps); if(par.sig == 1) - std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); + std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); else if(par.bits == 8) - std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80)); + std::fill_n(mBuffer.data(), mBuffer.size(), std::byte(0x80)); else if(par.bits == 16) std::fill_n(reinterpret_cast(mBuffer.data()), mBuffer.size()/2, 0x8000); else if(par.bits == 32) @@ -280,10 +285,10 @@ struct SndioCapture final : public BackendBase { int recordProc(); - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; sio_hdl *mSndHandle{nullptr}; @@ -292,8 +297,6 @@ struct SndioCapture final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(SndioCapture) }; SndioCapture::~SndioCapture() @@ -306,7 +309,7 @@ SndioCapture::~SndioCapture() int SndioCapture::recordProc() { SetRTPriority(); - althrd_setname(RECORD_THREAD_NAME); + althrd_setname(GetRecordThreadName()); const uint frameSize{mDevice->frameSizeFromFmt()}; @@ -317,29 +320,30 @@ int SndioCapture::recordProc() return 1; } - auto fds = std::make_unique(static_cast(nfds_pre)); + auto fds = std::vector(static_cast(nfds_pre)); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { /* Wait until there's some samples to read. */ - const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)}; + const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)}; if(nfds <= 0) { mDevice->handleDisconnect("Failed to get polling fds: %d", nfds); break; } - int pollres{::poll(fds.get(), static_cast(nfds), 2000)}; + int pollres{::poll(fds.data(), fds.size(), 2000)}; if(pollres < 0) { if(errno == EINTR) continue; - mDevice->handleDisconnect("Poll error: %s", strerror(errno)); + mDevice->handleDisconnect("Poll error: %s", + std::generic_category().message(errno).c_str()); break; } if(pollres == 0) continue; - const int revents{sio_revents(mSndHandle, fds.get())}; + const int revents{sio_revents(mSndHandle, fds.data())}; if((revents&POLLHUP)) { mDevice->handleDisconnect("Got POLLHUP from poll events"); @@ -349,7 +353,7 @@ int SndioCapture::recordProc() continue; auto data = mRing->getWriteVector(); - al::span buffer{data.first.buf, data.first.len*frameSize}; + al::span buffer{data.first.buf, data.first.len*frameSize}; while(!buffer.empty()) { size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())}; @@ -373,8 +377,8 @@ int SndioCapture::recordProc() if(buffer.empty()) { /* Got samples to read, but no place to store it. Drop it. */ - static char junk[4096]; - sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize)); + static std::array junk; + sio_read(mSndHandle, junk.data(), junk.size() - (junk.size()%frameSize)); } } @@ -382,13 +386,13 @@ int SndioCapture::recordProc() } -void SndioCapture::open(const char *name) +void SndioCapture::open(std::string_view name) { - if(!name) - name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDefaultName(); + else if(name != GetDefaultName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; mSndHandle = sio_open(nullptr, SIO_REC, true); if(mSndHandle == nullptr) @@ -431,12 +435,12 @@ void SndioCapture::open(const char *name) par.rchan = mDevice->channelsFromFmt(); par.rate = mDevice->Frequency; - par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10); - par.round = minu(par.appbufsz/2, mDevice->Frequency/40); + par.appbufsz = std::max(mDevice->BufferSize, mDevice->Frequency/10u); + par.round = std::min(par.appbufsz/2u, mDevice->Frequency/40u); if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to set device praameters"}; + "Failed to set device parameters"}; if(par.bps > 1 && par.le != SIO_LE_NATIVE) throw al::backend_exception{al::backend_error::DeviceError, @@ -461,7 +465,7 @@ void SndioCapture::open(const char *name) DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate}; - mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false); + mRing = RingBuffer::Create(mDevice->BufferSize, size_t{par.bps}*par.rchan, false); mDevice->BufferSize = static_cast(mRing->writeSpace()); mDevice->UpdateSize = par.round; @@ -496,8 +500,8 @@ void SndioCapture::stop() ERR("Error stopping device\n"); } -void SndioCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void SndioCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint SndioCapture::availableSamples() { return static_cast(mRing->readSpace()); } @@ -516,18 +520,15 @@ bool SndIOBackendFactory::init() bool SndIOBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string SndIOBackendFactory::probe(BackendType type) +auto SndIOBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - outnames.append(sndio_device, sizeof(sndio_device)); - break; + return std::vector{std::string{GetDefaultName()}}; } - return outnames; + return {}; } BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/sndio.h b/Engine/lib/openal-soft/alc/backends/sndio.h index d94331911..a4496c92a 100644 --- a/Engine/lib/openal-soft/alc/backends/sndio.h +++ b/Engine/lib/openal-soft/alc/backends/sndio.h @@ -5,15 +5,15 @@ struct SndIOBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SNDIO_H */ diff --git a/Engine/lib/openal-soft/alc/backends/solaris.cpp b/Engine/lib/openal-soft/alc/backends/solaris.cpp index 791609ce7..047736c6a 100644 --- a/Engine/lib/openal-soft/alc/backends/solaris.cpp +++ b/Engine/lib/openal-soft/alc/backends/solaris.cpp @@ -35,24 +35,26 @@ #include #include #include +#include #include #include -#include "albyte.h" #include "alc/alconfig.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" -#include "threads.h" -#include "vector.h" #include namespace { -constexpr char solaris_device[] = "Solaris Default"; +using namespace std::string_view_literals; + +[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "Solaris Default"sv; } std::string solaris_driver{"/dev/audio"}; @@ -63,7 +65,7 @@ struct SolarisBackend final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -71,12 +73,10 @@ struct SolarisBackend final : public BackendBase { int mFd{-1}; uint mFrameStep{}; - al::vector mBuffer; + std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(SolarisBackend) }; SolarisBackend::~SolarisBackend() @@ -89,10 +89,10 @@ SolarisBackend::~SolarisBackend() int SolarisBackend::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const size_t frame_step{mDevice->channelsFromFmt()}; - const uint frame_size{mDevice->frameSizeFromFmt()}; + const size_t frame_size{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) @@ -116,12 +116,12 @@ int SolarisBackend::mixerProc() continue; } - al::byte *write_ptr{mBuffer.data()}; - size_t to_write{mBuffer.size()}; - mDevice->renderSamples(write_ptr, static_cast(to_write/frame_size), frame_step); - while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) + al::span buffer{mBuffer}; + mDevice->renderSamples(buffer.data(), static_cast(buffer.size()/frame_size), + frame_step); + while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire)) { - ssize_t wrote{write(mFd, write_ptr, to_write)}; + ssize_t wrote{write(mFd, buffer.data(), buffer.size())}; if(wrote < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) @@ -131,8 +131,7 @@ int SolarisBackend::mixerProc() break; } - to_write -= static_cast(wrote); - write_ptr += wrote; + buffer = buffer.subspan(static_cast(wrote)); } } @@ -140,13 +139,13 @@ int SolarisBackend::mixerProc() } -void SolarisBackend::open(const char *name) +void SolarisBackend::open(std::string_view name) { - if(!name) - name = solaris_device; - else if(strcmp(name, solaris_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDefaultName(); + else if(name != GetDefaultName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; int fd{::open(solaris_driver.c_str(), O_WRONLY)}; if(fd == -1) @@ -231,7 +230,7 @@ bool SolarisBackend::reset() setDefaultChannelOrder(); mBuffer.resize(mDevice->UpdateSize * size_t{frame_size}); - std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); + std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); return true; } @@ -268,7 +267,7 @@ BackendFactory &SolarisBackendFactory::getFactory() bool SolarisBackendFactory::init() { - if(auto devopt = ConfigValueStr(nullptr, "solaris", "device")) + if(auto devopt = ConfigValueStr({}, "solaris", "device")) solaris_driver = std::move(*devopt); return true; } @@ -276,23 +275,19 @@ bool SolarisBackendFactory::init() bool SolarisBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -std::string SolarisBackendFactory::probe(BackendType type) +auto SolarisBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; switch(type) { case BackendType::Playback: - { - struct stat buf; - if(stat(solaris_driver.c_str(), &buf) == 0) - outnames.append(solaris_device, sizeof(solaris_device)); - } - break; + if(struct stat buf{}; stat(solaris_driver.c_str(), &buf) == 0) + return std::vector{std::string{GetDefaultName()}}; + break; case BackendType::Capture: break; } - return outnames; + return {}; } BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/solaris.h b/Engine/lib/openal-soft/alc/backends/solaris.h index 5da6ad3a9..8415c362b 100644 --- a/Engine/lib/openal-soft/alc/backends/solaris.h +++ b/Engine/lib/openal-soft/alc/backends/solaris.h @@ -5,15 +5,15 @@ struct SolarisBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SOLARIS_H */ diff --git a/Engine/lib/openal-soft/alc/backends/wasapi.cpp b/Engine/lib/openal-soft/alc/backends/wasapi.cpp index e834eef42..d640e1d41 100644 --- a/Engine/lib/openal-soft/alc/backends/wasapi.cpp +++ b/Engine/lib/openal-soft/alc/backends/wasapi.cpp @@ -25,13 +25,15 @@ #define WIN32_LEAN_AND_MEAN #include -#include -#include +#include +#include #include #include #include +#include #include +#include #include #include #include @@ -53,12 +55,16 @@ #include #include #include +#include #include #include #include "albit.h" #include "alc/alconfig.h" #include "alnumeric.h" +#include "alspan.h" +#include "alstring.h" +#include "althrd_setname.h" #include "comptr.h" #include "core/converter.h" #include "core/device.h" @@ -66,8 +72,22 @@ #include "core/logging.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" +#if defined(ALSOFT_UWP) + +#include // !!This is important!! +#include +#include +#include +#include +#include + +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Media::Devices; +using namespace Windows::Devices::Enumeration; +using namespace Windows::Media::Devices; +#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 @@ -79,22 +99,22 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); #endif - +#if !defined(ALSOFT_UWP) 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 namespace { +using namespace std::string_view_literals; using std::chrono::nanoseconds; using std::chrono::milliseconds; using std::chrono::seconds; -using ReferenceTime = std::chrono::duration>; +[[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return "OpenAL Soft on "sv; } -inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept -{ return ReferenceTime{static_cast(n)}; } +using ReferenceTime = std::chrono::duration>; #define MONO SPEAKER_FRONT_CENTER @@ -124,47 +144,127 @@ constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)}; constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)}; constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)}; -constexpr char DevNameHead[] = "OpenAL Soft on "; -constexpr size_t DevNameHeadLen{al::size(DevNameHead) - 1}; + +#ifndef _MSC_VER +constexpr AudioObjectType operator|(AudioObjectType lhs, AudioObjectType rhs) noexcept +{ return static_cast(lhs | al::to_underlying(rhs)); } +#endif + +constexpr AudioObjectType ChannelMask_Mono{AudioObjectType_FrontCenter}; +constexpr AudioObjectType ChannelMask_Stereo{AudioObjectType_FrontLeft + | AudioObjectType_FrontRight}; +constexpr AudioObjectType ChannelMask_Quad{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_BackLeft | AudioObjectType_BackRight}; +constexpr AudioObjectType ChannelMask_X51{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight}; +constexpr AudioObjectType ChannelMask_X51Rear{AudioObjectType_FrontLeft + | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency + | AudioObjectType_BackLeft | AudioObjectType_BackRight}; +constexpr AudioObjectType ChannelMask_X61{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackCenter}; +constexpr AudioObjectType ChannelMask_X71{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight}; +constexpr AudioObjectType ChannelMask_X714{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight + | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft + | AudioObjectType_TopBackRight}; +constexpr AudioObjectType ChannelMask_X7144{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight + | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft + | AudioObjectType_TopBackRight | AudioObjectType_BottomFrontLeft + | AudioObjectType_BottomFrontRight | AudioObjectType_BottomBackLeft + | AudioObjectType_BottomBackRight}; + + +template +struct overloaded : Ts... { using Ts::operator()...; }; + +template +overloaded(Ts...) -> overloaded; + + +template +constexpr auto as_unsigned(T value) noexcept +{ + using UT = std::make_unsigned_t; + return static_cast(value); +} /* Scales the given reftime value, rounding the result. */ -inline uint RefTime2Samples(const ReferenceTime &val, uint srate) +template +constexpr uint RefTime2Samples(const ReferenceTime &val, T srate) noexcept { const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1}; - return static_cast(mini64(retval, std::numeric_limits::max())); + return static_cast(std::min(retval, std::numeric_limits::max())); } class GuidPrinter { - char mMsg[64]; + std::array mMsg{}; public: GuidPrinter(const GUID &guid) { - std::snprintf(mMsg, al::size(mMsg), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", + std::snprintf(mMsg.data(), mMsg.size(), "{%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; } + [[nodiscard]] auto c_str() const -> const char* { return mMsg.data(); } }; struct PropVariant { - PROPVARIANT mProp; + PROPVARIANT mProp{}; public: PropVariant() { PropVariantInit(&mProp); } + PropVariant(const PropVariant &rhs) : PropVariant{} { PropVariantCopy(&mProp, &rhs.mProp); } ~PropVariant() { clear(); } + auto operator=(const PropVariant &rhs) -> PropVariant& + { + if(this != &rhs) + PropVariantCopy(&mProp, &rhs.mProp); + return *this; + } + void clear() { PropVariantClear(&mProp); } PROPVARIANT* get() noexcept { return &mProp; } - PROPVARIANT& operator*() noexcept { return mProp; } - const PROPVARIANT& operator*() const noexcept { return mProp; } + /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */ + [[nodiscard]] + auto type() const noexcept -> VARTYPE { return mProp.vt; } - PROPVARIANT* operator->() noexcept { return &mProp; } - const PROPVARIANT* operator->() const noexcept { return &mProp; } + template [[nodiscard]] + auto value() const -> T + { + if constexpr(std::is_same_v) + { + alassert(mProp.vt == VT_UI4); + return mProp.uiVal; + } + else if constexpr(std::is_same_v || std::is_same_v) + { + alassert(mProp.vt == VT_LPWSTR); + return mProp.pwszVal; + } + } + + void setBlob(const al::span data) + { + if constexpr(sizeof(size_t) > sizeof(ULONG)) + alassert(data.size() <= std::numeric_limits::max()); + mProp.vt = VT_BLOB; + mProp.blob.cbSize = static_cast(data.size()); + mProp.blob.pBlobData = data.data(); + } + /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */ }; struct DevMap { @@ -178,71 +278,123 @@ struct DevMap { , endpoint_guid{std::forward(guid_)} , devid{std::forward(devid_)} { } + /* To prevent GCC from complaining it doesn't want to inline this. */ + ~DevMap(); }; +DevMap::~DevMap() = default; -bool checkName(const al::vector &list, const std::string &name) +bool checkName(const al::span list, const std::string_view name) { - auto match_name = [&name](const DevMap &entry) -> bool - { return entry.name == name; }; + auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; }; return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } -al::vector PlaybackDevices; -al::vector CaptureDevices; + +struct DeviceList { + auto lock() noexcept(noexcept(mMutex.lock())) { return mMutex.lock(); } + auto unlock() noexcept(noexcept(mMutex.unlock())) { return mMutex.unlock(); } + +private: + std::mutex mMutex; + std::vector mPlayback; + std::vector mCapture; + std::wstring mPlaybackDefaultId; + std::wstring mCaptureDefaultId; + + friend struct DeviceListLock; +}; +struct DeviceListLock : public std::unique_lock { + using std::unique_lock::unique_lock; + + [[nodiscard]] auto& getPlaybackList() const noexcept { return mutex()->mPlayback; } + [[nodiscard]] auto& getCaptureList() const noexcept { return mutex()->mCapture; } + + void setPlaybackDefaultId(std::wstring_view devid) const { mutex()->mPlaybackDefaultId = devid; } + [[nodiscard]] auto getPlaybackDefaultId() const noexcept -> std::wstring_view { return mutex()->mPlaybackDefaultId; } + void setCaptureDefaultId(std::wstring_view devid) const { mutex()->mCaptureDefaultId = devid; } + [[nodiscard]] auto getCaptureDefaultId() const noexcept -> std::wstring_view { return mutex()->mCaptureDefaultId; } +}; + +DeviceList gDeviceList; + + +#if defined(ALSOFT_UWP) +enum EDataFlow { + eRender = 0, + eCapture = (eRender + 1), + eAll = (eCapture + 1), + EDataFlow_enum_count = (eAll + 1) +}; +#endif + +#if defined(ALSOFT_UWP) +using DeviceHandle = Windows::Devices::Enumeration::DeviceInformation; +using EventRegistrationToken = winrt::event_token; +#else +using DeviceHandle = ComPtr; +#endif using NameGUIDPair = std::pair; -NameGUIDPair get_device_name_and_guid(IMMDevice *device) +NameGUIDPair GetDeviceNameAndGuid(const DeviceHandle &device) { - static constexpr char UnknownName[]{"Unknown Device Name"}; - static constexpr char UnknownGuid[]{"Unknown Device GUID"}; + constexpr auto UnknownName = "Unknown Device Name"sv; + constexpr auto UnknownGuid = "Unknown Device GUID"sv; + +#if !defined(ALSOFT_UWP) std::string name, guid; ComPtr ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr()); + HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return std::make_pair(UnknownName, UnknownGuid); + return {std::string{UnknownName}, std::string{UnknownGuid}}; } PropVariant pvprop; - hr = ps->GetValue(reinterpret_cast(DEVPKEY_Device_FriendlyName), pvprop.get()); + hr = ps->GetValue(al::bit_cast(DEVPKEY_Device_FriendlyName), pvprop.get()); if(FAILED(hr)) - { WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr); - name += UnknownName; - } - else if(pvprop->vt == VT_LPWSTR) - name += wstr_to_utf8(pvprop->pwszVal); + else if(pvprop.type() == VT_LPWSTR) + name = wstr_to_utf8(pvprop.value()); else - { - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - name += UnknownName; - } + WARN("Unexpected Device_FriendlyName PROPVARIANT type: 0x%04x\n", pvprop.type()); pvprop.clear(); - hr = ps->GetValue(reinterpret_cast(PKEY_AudioEndpoint_GUID), pvprop.get()); + hr = ps->GetValue(al::bit_cast(PKEY_AudioEndpoint_GUID), pvprop.get()); if(FAILED(hr)) - { WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr); - guid = UnknownGuid; - } - else if(pvprop->vt == VT_LPWSTR) - guid = wstr_to_utf8(pvprop->pwszVal); + else if(pvprop.type() == VT_LPWSTR) + guid = wstr_to_utf8(pvprop.value()); else + WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: 0x%04x\n", pvprop.type()); +#else + std::string name{wstr_to_utf8(device.Name())}; + std::string guid; + // device->Id is DeviceInterfacePath: \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{a21c17a0-fc1d-405e-ab5a-b513422b57d1}#{e6327cad-dcec-4949-ae8a-991e976a79d2} + auto devIfPath = device.Id(); + if(auto devIdStart = wcsstr(devIfPath.data(), L"}.")) { - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - guid = UnknownGuid; + devIdStart += 2; // L"}." + if(auto devIdStartEnd = wcschr(devIdStart, L'#')) + { + std::wstring wDevId{devIdStart, static_cast(devIdStartEnd - devIdStart)}; + guid = wstr_to_utf8(wDevId.c_str()); + std::transform(guid.begin(), guid.end(), guid.begin(), + [](char ch) { return static_cast(std::toupper(ch)); }); + } } - - return std::make_pair(std::move(name), std::move(guid)); +#endif + if(name.empty()) name = UnknownName; + if(guid.empty()) guid = UnknownGuid; + return {std::move(name), std::move(guid)}; } - -EndpointFormFactor get_device_formfactor(IMMDevice *device) +#if !defined(ALSOFT_UWP) +EndpointFormFactor GetDeviceFormfactor(IMMDevice *device) { ComPtr ps; - HRESULT hr{device->OpenPropertyStore(STGM_READ, ps.getPtr())}; + HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); @@ -254,96 +406,430 @@ EndpointFormFactor get_device_formfactor(IMMDevice *device) hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get()); if(FAILED(hr)) WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr); - else if(pvform->vt == VT_UI4) - formfactor = static_cast(pvform->ulVal); - else if(pvform->vt != VT_EMPTY) - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); + else if(pvform.type() == VT_UI4) + formfactor = static_cast(pvform.value()); + else if(pvform.type() != VT_EMPTY) + WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform.type()); return formfactor; } +#endif -void add_device(IMMDevice *device, const WCHAR *devid, al::vector &list) +#if defined(ALSOFT_UWP) +struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler +#else +struct DeviceHelper final : private IMMNotificationClient +#endif { - for(auto &entry : list) +#if defined(ALSOFT_UWP) + DeviceHelper() { - if(entry.devid == devid) - return; + /* TODO: UWP also needs to watch for device added/removed events and + * dynamically add/remove devices from the lists. + */ + mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + + mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioRenderDeviceChangedEventArgs& args) { + if (args.Role() == AudioDeviceRole::Default) + { + const std::string msg{ "Default playback device changed: " + + wstr_to_utf8(args.Id())}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, + msg); + } + }); + + mCaptureDeviceChangedToken = MediaDevice::DefaultAudioCaptureDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioCaptureDeviceChangedEventArgs& args) { + if (args.Role() == AudioDeviceRole::Default) + { + const std::string msg{ "Default capture device changed: " + + wstr_to_utf8(args.Id()) }; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, + msg); + } + }); + } +#else + DeviceHelper() = default; +#endif + ~DeviceHelper() + { +#if defined(ALSOFT_UWP) + MediaDevice::DefaultAudioRenderDeviceChanged(mRenderDeviceChangedToken); + MediaDevice::DefaultAudioCaptureDeviceChanged(mCaptureDeviceChangedToken); + + if(mActiveClientEvent != nullptr) + CloseHandle(mActiveClientEvent); + mActiveClientEvent = nullptr; +#else + if(mEnumerator) + mEnumerator->UnregisterEndpointNotificationCallback(this); + mEnumerator = nullptr; +#endif } - auto name_guid = get_device_name_and_guid(device); + /** -------------------------- IUnknown ----------------------------- */ + std::atomic mRefCount{1}; + STDMETHODIMP_(ULONG) AddRef() noexcept override { return mRefCount.fetch_add(1u) + 1u; } + STDMETHODIMP_(ULONG) Release() noexcept override { return mRefCount.fetch_sub(1u) - 1u; } - int count{1}; - std::string newname{name_guid.first}; - while(checkName(list, newname)) + STDMETHODIMP QueryInterface(const IID& IId, void **UnknownPtrPtr) noexcept override { - newname = name_guid.first; - newname += " #"; - newname += std::to_string(++count); - } - list.emplace_back(std::move(newname), std::move(name_guid.second), devid); - const DevMap &newentry = list.back(); + // Three rules of QueryInterface: + // https://docs.microsoft.com/en-us/windows/win32/com/rules-for-implementing-queryinterface + // 1. Objects must have identity. + // 2. The set of interfaces on an object instance must be static. + // 3. It must be possible to query successfully for any interface on an object from any other interface. - TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(), - newentry.endpoint_guid.c_str(), newentry.devid.c_str()); -} + // If ppvObject(the address) is nullptr, then this method returns E_POINTER. + if(!UnknownPtrPtr) + return E_POINTER; -WCHAR *get_device_id(IMMDevice *device) -{ - WCHAR *devid; - - const HRESULT hr{device->GetId(&devid)}; - if(FAILED(hr)) - { - ERR("Failed to get device id: %lx\n", hr); - return nullptr; - } - - return devid; -} - -void probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector &list) -{ - al::vector{}.swap(list); - - ComPtr coll; - HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, coll.getPtr())}; - if(FAILED(hr)) - { - ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); - return; - } - - UINT count{0}; - hr = coll->GetCount(&count); - if(SUCCEEDED(hr) && count > 0) - list.reserve(count); - - ComPtr device; - hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, device.getPtr()); - if(SUCCEEDED(hr)) - { - if(WCHAR *devid{get_device_id(device.get())}) + // https://docs.microsoft.com/en-us/windows/win32/com/implementing-reference-counting + // Whenever a client calls a method(or API function), such as QueryInterface, that returns a new interface + // pointer, the method being called is responsible for incrementing the reference count through the returned + // pointer. For example, when a client first creates an object, it receives an interface pointer to an object + // that, from the client's point of view, has a reference count of one. If the client then calls AddRef on the + // interface pointer, the reference count becomes two. The client must call Release twice on the interface + // pointer to drop all of its references to the object. +#if defined(ALSOFT_UWP) + if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler)) { - add_device(device.get(), devid, list); - CoTaskMemFree(devid); + *UnknownPtrPtr = static_cast(this); + AddRef(); + return S_OK; } - device = nullptr; - } - - for(UINT i{0};i < count;++i) - { - hr = coll->Item(i, device.getPtr()); - if(FAILED(hr)) continue; - - if(WCHAR *devid{get_device_id(device.get())}) +#else + if(IId == __uuidof(IMMNotificationClient)) { - add_device(device.get(), devid, list); - CoTaskMemFree(devid); + *UnknownPtrPtr = static_cast(this); + AddRef(); + return S_OK; + } +#endif + else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown)) + { + *UnknownPtrPtr = static_cast(this); + AddRef(); + return S_OK; } - device = nullptr; - } -} + // This method returns S_OK if the interface is supported, and E_NOINTERFACE otherwise. + *UnknownPtrPtr = nullptr; + return E_NOINTERFACE; + } + +#if defined(ALSOFT_UWP) + /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */ + HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override + { + SetEvent(mActiveClientEvent); + + // Need to return S_OK + return S_OK; + } +#else + /** ----------------------- IMMNotificationClient ------------ */ + STDMETHODIMP OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/) noexcept override { return S_OK; } + + STDMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId) noexcept override + { + ComPtr device; + HRESULT hr{mEnumerator->GetDevice(pwstrDeviceId, al::out_ptr(device))}; + if(FAILED(hr)) + { + ERR("Failed to get device: 0x%08lx\n", hr); + return S_OK; + } + + ComPtr endpoint; + hr = device->QueryInterface(__uuidof(IMMEndpoint), al::out_ptr(endpoint)); + if(FAILED(hr)) + { + ERR("Failed to get device endpoint: 0x%08lx\n", hr); + return S_OK; + } + + EDataFlow flowdir{}; + hr = endpoint->GetDataFlow(&flowdir); + if(FAILED(hr)) + { + ERR("Failed to get endpoint data flow: 0x%08lx\n", hr); + return S_OK; + } + + auto devlock = DeviceListLock{gDeviceList}; + auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); + + if(AddDevice(device, pwstrDeviceId, list)) + { + const auto devtype = (flowdir==eRender) ? alc::DeviceType::Playback + : alc::DeviceType::Capture; + const std::string msg{"Device added: "+list.back().name}; + alc::Event(alc::EventType::DeviceAdded, devtype, msg); + } + + return S_OK; + } + + STDMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId) noexcept override + { + auto devlock = DeviceListLock{gDeviceList}; + for(auto flowdir : std::array{eRender, eCapture}) + { + auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); + auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture; + + /* Find the ID in the list to remove. */ + auto iter = std::find_if(list.begin(), list.end(), + [pwstrDeviceId](const DevMap &entry) noexcept + { return pwstrDeviceId == entry.devid; }); + if(iter == list.end()) continue; + + TRACE("Removing device \"%s\", \"%s\", \"%ls\"\n", iter->name.c_str(), + iter->endpoint_guid.c_str(), iter->devid.c_str()); + + std::string msg{"Device removed: "+std::move(iter->name)}; + list.erase(iter); + + alc::Event(alc::EventType::DeviceRemoved, devtype, msg); + } + return S_OK; + } + + STDMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) noexcept override { return S_OK; } + + STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) noexcept override + { + if(role != eMultimedia) + return S_OK; + + const std::wstring_view devid{pwstrDefaultDeviceId ? pwstrDefaultDeviceId + : std::wstring_view{}}; + if(flow == eRender) + { + DeviceListLock{gDeviceList}.setPlaybackDefaultId(devid); + const std::string msg{"Default playback device changed: " + wstr_to_utf8(devid)}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg); + } + else if(flow == eCapture) + { + DeviceListLock{gDeviceList}.setCaptureDefaultId(devid); + const std::string msg{"Default capture device changed: " + wstr_to_utf8(devid)}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg); + } + return S_OK; + } +#endif + + /** -------------------------- DeviceHelper ----------------------------- */ + HRESULT init() + { +#if !defined(ALSOFT_UWP) + HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))}; + if(SUCCEEDED(hr)) + mEnumerator->RegisterEndpointNotificationCallback(this); + else + WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + return hr; +#else + return S_OK; +#endif + } + + HRESULT openDevice(std::wstring_view devid, EDataFlow flow, DeviceHandle& device) + { +#if !defined(ALSOFT_UWP) + HRESULT hr{E_FAIL}; + if(mEnumerator) + { + if(devid.empty()) + hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device)); + else + hr = mEnumerator->GetDevice(devid.data(), al::out_ptr(device)); + } + return hr; +#else + const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; + auto devIfPath = + devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole)) + : winrt::hstring(devid.data()); + if (devIfPath.empty()) + return E_POINTER; + + auto&& deviceInfo = DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, DeviceInformationKind::DeviceInterface).get(); + if (!deviceInfo) + return E_NOINTERFACE; + device = deviceInfo; + return S_OK; +#endif + } + +#if !defined(ALSOFT_UWP) + static HRESULT activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv) + { return device->Activate(iid, CLSCTX_INPROC_SERVER, nullptr, ppv); } +#else + HRESULT activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv) + { + ComPtr asyncOp; + HRESULT hr{ActivateAudioInterfaceAsync(device.Id().data(), iid, nullptr, this, + al::out_ptr(asyncOp))}; + if(FAILED(hr)) + return hr; + + /* I don't like waiting for INFINITE time, but the activate operation + * can take an indefinite amount of time since it can require user + * input. + */ + DWORD res{WaitForSingleObjectEx(mActiveClientEvent, INFINITE, FALSE)}; + if(res != WAIT_OBJECT_0) + { + ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + return E_FAIL; + } + + HRESULT hrActivateRes{E_FAIL}; + ComPtr punkAudioIface; + hr = asyncOp->GetActivateResult(&hrActivateRes, al::out_ptr(punkAudioIface)); + if(SUCCEEDED(hr)) hr = hrActivateRes; + if(FAILED(hr)) return hr; + + return punkAudioIface->QueryInterface(iid, ppv); + } +#endif + + std::wstring probeDevices(EDataFlow flowdir, std::vector &list) + { + std::wstring defaultId; + std::vector{}.swap(list); + +#if !defined(ALSOFT_UWP) + ComPtr coll; + HRESULT hr{mEnumerator->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, + al::out_ptr(coll))}; + if(FAILED(hr)) + { + ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); + return defaultId; + } + + UINT count{0}; + hr = coll->GetCount(&count); + if(SUCCEEDED(hr) && count > 0) + list.reserve(count); + + ComPtr device; + hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device)); + if(SUCCEEDED(hr)) + { + if(WCHAR *devid{GetDeviceId(device.get())}) + { + defaultId = devid; + CoTaskMemFree(devid); + } + device = nullptr; + } + + for(UINT i{0};i < count;++i) + { + hr = coll->Item(i, al::out_ptr(device)); + if(FAILED(hr)) + continue; + + if(WCHAR *devid{GetDeviceId(device.get())}) + { + std::ignore = AddDevice(device, devid, list); + CoTaskMemFree(devid); + } + device = nullptr; + } +#else + const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; + auto DefaultAudioId = flowdir == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) + : MediaDevice::GetDefaultAudioCaptureId(deviceRole); + if(!DefaultAudioId.empty()) + { + auto deviceInfo = DeviceInformation::CreateFromIdAsync(DefaultAudioId, nullptr, + DeviceInformationKind::DeviceInterface).get(); + if(deviceInfo) + defaultId = deviceInfo.Id().data(); + } + + // Get the string identifier of the audio renderer + auto AudioSelector = flowdir == eRender ? MediaDevice::GetAudioRenderSelector() : MediaDevice::GetAudioCaptureSelector(); + + // Setup the asynchronous callback + auto&& DeviceInfoCollection = DeviceInformation::FindAllAsync(AudioSelector, /*PropertyList*/nullptr, DeviceInformationKind::DeviceInterface).get(); + if(DeviceInfoCollection) + { + try { + auto deviceCount = DeviceInfoCollection.Size(); + for(unsigned int i{0};i < deviceCount;++i) + { + auto deviceInfo = DeviceInfoCollection.GetAt(i); + if(deviceInfo) + std::ignore = AddDevice(deviceInfo, deviceInfo.Id().data(), list); + } + } + catch (const winrt::hresult_error& /*ex*/) { + } + } +#endif + + return defaultId; + } + +private: + static bool AddDevice(const DeviceHandle &device, const WCHAR *devid, std::vector &list) + { + for(auto &entry : list) + { + if(entry.devid == devid) + return false; + } + + auto name_guid = GetDeviceNameAndGuid(device); + int count{1}; + std::string newname{name_guid.first}; + while(checkName(list, newname)) + { + newname = name_guid.first; + newname += " #"; + newname += std::to_string(++count); + } + list.emplace_back(std::move(newname), std::move(name_guid.second), devid); + const DevMap &newentry = list.back(); + + TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(), + newentry.endpoint_guid.c_str(), newentry.devid.c_str()); + return true; + } + +#if !defined(ALSOFT_UWP) + static WCHAR *GetDeviceId(IMMDevice *device) + { + WCHAR *devid; + + const HRESULT hr{device->GetId(&devid)}; + if(FAILED(hr)) + { + ERR("Failed to get device id: %lx\n", hr); + return nullptr; + } + + return devid; + } + ComPtr mEnumerator{nullptr}; + +#else + + HANDLE mActiveClientEvent{nullptr}; + + EventRegistrationToken mRenderDeviceChangedToken; + EventRegistrationToken mCaptureDeviceChangedToken; +#endif +}; bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) { @@ -357,6 +843,7 @@ bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) { out->Format = *in; out->Format.cbSize = 0; + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; if(out->Format.nChannels == 1) out->dwChannelMask = MONO; @@ -370,6 +857,7 @@ bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) { out->Format = *in; out->Format.cbSize = 0; + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; if(out->Format.nChannels == 1) out->dwChannelMask = MONO; @@ -394,6 +882,7 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format) { const WAVEFORMATEXTENSIBLE *fmtex{ CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)}; + /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */ TRACE("%s:\n" " FormatTag = 0x%04x\n" " Channels = %d\n" @@ -409,6 +898,7 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format) fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample, fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask, GuidPrinter{fmtex->SubFormat}.c_str()); + /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */ } else TRACE("%s:\n" @@ -430,56 +920,62 @@ enum class MsgType { StartDevice, StopDevice, CloseDevice, - EnumeratePlayback, - EnumerateCapture, - Count, - QuitThread = Count + QuitThread }; -constexpr char MessageStr[static_cast(MsgType::Count)][20]{ - "Open Device", - "Reset Device", - "Start Device", - "Stop Device", - "Close Device", - "Enumerate Playback", - "Enumerate Capture" -}; +constexpr const char *GetMessageTypeName(MsgType type) noexcept +{ + switch(type) + { + case MsgType::OpenDevice: return "Open Device"; + case MsgType::ResetDevice: return "Reset Device"; + case MsgType::StartDevice: return "Start Device"; + case MsgType::StopDevice: return "Stop Device"; + case MsgType::CloseDevice: return "Close Device"; + case MsgType::QuitThread: break; + } + return ""; +} /* Proxy interface used by the message handler. */ struct WasapiProxy { + WasapiProxy() = default; + WasapiProxy(const WasapiProxy&) = delete; + WasapiProxy(WasapiProxy&&) = delete; virtual ~WasapiProxy() = default; - virtual HRESULT openProxy(const char *name) = 0; + void operator=(const WasapiProxy&) = delete; + void operator=(WasapiProxy&&) = delete; + + virtual HRESULT openProxy(std::string_view name) = 0; virtual void closeProxy() = 0; virtual HRESULT resetProxy() = 0; virtual HRESULT startProxy() = 0; - virtual void stopProxy() = 0; + virtual void stopProxy() = 0; struct Msg { MsgType mType; WasapiProxy *mProxy; - const char *mParam; + std::string_view mParam; std::promise mPromise; explicit operator bool() const noexcept { return mType != MsgType::QuitThread; } }; - static std::thread sThread; - static std::deque mMsgQueue; - static std::mutex mMsgQueueLock; - static std::condition_variable mMsgQueueCond; - static std::mutex sThreadLock; - static size_t sInitCount; + static inline std::deque mMsgQueue; + static inline std::mutex mMsgQueueLock; + static inline std::condition_variable mMsgQueueCond; - std::future pushMessage(MsgType type, const char *param=nullptr) + static inline std::optional sDeviceHelper; + + std::future pushMessage(MsgType type, std::string_view param={}) { std::promise promise; std::future future{promise.get_future()}; { - std::lock_guard _{mMsgQueueLock}; + std::lock_guard msglock{mMsgQueueLock}; mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); } mMsgQueueCond.notify_one(); @@ -491,8 +987,8 @@ struct WasapiProxy { std::promise promise; std::future future{promise.get_future()}; { - std::lock_guard _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, nullptr, nullptr, std::move(promise)}); + std::lock_guard msglock{mMsgQueueLock}; + mMsgQueue.emplace_back(Msg{type, nullptr, {}, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; @@ -508,65 +1004,45 @@ struct WasapiProxy { } static int messageHandler(std::promise *promise); - - static HRESULT InitThread() - { - std::lock_guard _{sThreadLock}; - HRESULT res{S_OK}; - if(!sThread.joinable()) - { - std::promise promise; - auto future = promise.get_future(); - - sThread = std::thread{&WasapiProxy::messageHandler, &promise}; - res = future.get(); - if(FAILED(res)) - { - sThread.join(); - return res; - } - } - ++sInitCount; - return res; - } - - static void DeinitThread() - { - std::lock_guard _{sThreadLock}; - if(!--sInitCount && sThread.joinable()) - { - pushMessageStatic(MsgType::QuitThread); - sThread.join(); - } - } }; -std::thread WasapiProxy::sThread; -std::deque WasapiProxy::mMsgQueue; -std::mutex WasapiProxy::mMsgQueueLock; -std::condition_variable WasapiProxy::mMsgQueueCond; -std::mutex WasapiProxy::sThreadLock; -size_t WasapiProxy::sInitCount{0}; int WasapiProxy::messageHandler(std::promise *promise) { TRACE("Starting message thread\n"); - HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(hr)) + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) { - WARN("Failed to initialize COM: 0x%08lx\n", hr); - promise->set_value(hr); + WARN("Failed to initialize COM: 0x%08lx\n", com.status()); + promise->set_value(com.status()); return 0; } - promise->set_value(S_OK); + + struct HelperResetter { + ~HelperResetter() { sDeviceHelper.reset(); } + }; + HelperResetter scoped_watcher; + + HRESULT hr{sDeviceHelper.emplace().init()}; + promise->set_value(hr); promise = nullptr; + if(FAILED(hr)) + return 0; + + { + auto devlock = DeviceListLock{gDeviceList}; + auto defaultId = sDeviceHelper->probeDevices(eRender, devlock.getPlaybackList()); + if(!defaultId.empty()) devlock.setPlaybackDefaultId(defaultId); + defaultId = sDeviceHelper->probeDevices(eCapture, devlock.getCaptureList()); + if(!defaultId.empty()) devlock.setCaptureDefaultId(defaultId); + } TRACE("Starting message loop\n"); while(Msg msg{popMessage()}) { - TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n", - MessageStr[static_cast(msg.mType)], static_cast(msg.mType), - static_cast(msg.mProxy), static_cast(msg.mParam)); + TRACE("Got message \"%s\" (0x%04x, this=%p, param=\"%.*s\")\n", + GetMessageTypeName(msg.mType), static_cast(msg.mType), + static_cast(msg.mProxy), al::sizei(msg.mParam), msg.mParam.data()); switch(msg.mType) { @@ -595,27 +1071,6 @@ int WasapiProxy::messageHandler(std::promise *promise) msg.mPromise.set_value(S_OK); continue; - case MsgType::EnumeratePlayback: - case MsgType::EnumerateCapture: - { - void *ptr{}; - hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr); - if(FAILED(hr)) - msg.mPromise.set_value(hr); - else - { - ComPtr devenum{static_cast(ptr)}; - - if(msg.mType == MsgType::EnumeratePlayback) - probe_devices(devenum.get(), eRender, PlaybackDevices); - else if(msg.mType == MsgType::EnumerateCapture) - probe_devices(devenum.get(), eCapture, CaptureDevices); - msg.mPromise.set_value(S_OK); - } - continue; - } - case MsgType::QuitThread: break; } @@ -623,20 +1078,19 @@ int WasapiProxy::messageHandler(std::promise *promise) msg.mPromise.set_value(E_FAIL); } TRACE("Message loop finished\n"); - CoUninitialize(); return 0; } - struct WasapiPlayback final : public BackendBase, WasapiProxy { WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiPlayback() override; int mixerProc(); + int mixerSpatialProc(); - void open(const char *name) override; - HRESULT openProxy(const char *name) override; + void open(std::string_view name) override; + HRESULT openProxy(std::string_view name) override; void closeProxy() override; bool reset() override; @@ -648,14 +1102,28 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ClockLatency getClockLatency() override; + void prepareFormat(WAVEFORMATEXTENSIBLE &OutputType); + void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType); + + auto initSpatial() -> bool; + HRESULT mOpenStatus{E_FAIL}; - ComPtr mMMDev{nullptr}; - ComPtr mClient{nullptr}; - ComPtr mRender{nullptr}; + DeviceHandle mMMDev{nullptr}; + + struct PlainDevice { + ComPtr mClient{nullptr}; + ComPtr mRender{nullptr}; + }; + struct SpatialDevice { + ComPtr mClient{nullptr}; + ComPtr mRender{nullptr}; + AudioObjectType mStaticMask{}; + }; + std::variant mAudio; HANDLE mNotifyEvent{nullptr}; UINT32 mOrigBufferSize{}, mOrigUpdateSize{}; - std::unique_ptr mResampleBuffer{}; + std::vector mResampleBuffer{}; uint mBufferFilled{0}; SampleConverterPtr mResampler; @@ -666,17 +1134,12 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WasapiPlayback) }; WasapiPlayback::~WasapiPlayback() { if(SUCCEEDED(mOpenStatus)) - { pushMessage(MsgType::CloseDevice).wait(); - DeinitThread(); - } mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) @@ -687,24 +1150,29 @@ WasapiPlayback::~WasapiPlayback() FORCE_ALIGN int WasapiPlayback::mixerProc() { - HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(hr)) + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("COM init failed: 0x%08lx", hr); + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); + mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); return 1; } + auto &audio = std::get(mAudio); + SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u}; const uint update_size{mOrigUpdateSize}; const UINT32 buffer_len{mOrigBufferSize}; + const void *resbufferptr{}; + + mBufferFilled = 0; while(!mKillNow.load(std::memory_order_relaxed)) { UINT32 written; - hr = mClient->GetCurrentPadding(&written); + HRESULT hr{audio.mClient->GetCurrentPadding(&written)}; if(FAILED(hr)) { ERR("Failed to get padding: 0x%08lx\n", hr); @@ -723,43 +1191,38 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() } BYTE *buffer; - hr = mRender->GetBuffer(len, &buffer); + hr = audio.mRender->GetBuffer(len, &buffer); if(SUCCEEDED(hr)) { if(mResampler) { - std::lock_guard _{mMutex}; + std::lock_guard dlock{mMutex}; + auto dst = al::span{buffer, size_t{len}*frame_size}; for(UINT32 done{0};done < len;) { if(mBufferFilled == 0) { - mDevice->renderSamples(mResampleBuffer.get(), mDevice->UpdateSize, + mDevice->renderSamples(mResampleBuffer.data(), mDevice->UpdateSize, mFormat.Format.nChannels); + resbufferptr = mResampleBuffer.data(); mBufferFilled = mDevice->UpdateSize; } - const void *src{mResampleBuffer.get()}; - uint srclen{mBufferFilled}; - uint got{mResampler->convert(&src, &srclen, buffer, len-done)}; - buffer += got*frame_size; + uint got{mResampler->convert(&resbufferptr, &mBufferFilled, dst.data(), + len-done)}; + dst = dst.subspan(size_t{got}*frame_size); done += got; mPadding.store(written + done, std::memory_order_relaxed); - if(srclen) - { - const char *bsrc{static_cast(src)}; - std::copy(bsrc, bsrc + srclen*frame_size, mResampleBuffer.get()); - } - mBufferFilled = srclen; } } else { - std::lock_guard _{mMutex}; + std::lock_guard dlock{mMutex}; mDevice->renderSamples(buffer, len, mFormat.Format.nChannels); mPadding.store(written + len, std::memory_order_relaxed); } - hr = mRender->ReleaseBuffer(len, 0); + hr = audio.mRender->ReleaseBuffer(len, 0); } if(FAILED(hr)) { @@ -770,12 +1233,129 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() } mPadding.store(0u, std::memory_order_release); - CoUninitialize(); + return 0; +} + +FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() +{ + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) + { + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); + mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); + return 1; + } + + auto &audio = std::get(mAudio); + + SetRTPriority(); + althrd_setname(GetMixerThreadName()); + + std::vector> channels; + std::vector buffers; + std::vector resbuffers; + std::vector tmpbuffers; + + /* TODO: Set mPadding appropriately. There doesn't seem to be a way to + * update it dynamically based on the stream, so a fixed size may be the + * best we can do. + */ + mPadding.store(mOrigBufferSize-mOrigUpdateSize, std::memory_order_release); + + mBufferFilled = 0; + while(!mKillNow.load(std::memory_order_relaxed)) + { + if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 1000, FALSE)}; res != WAIT_OBJECT_0) + { + ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + + HRESULT hr{audio.mRender->Reset()}; + if(FAILED(hr)) + { + ERR("ISpatialAudioObjectRenderStream::Reset failed: 0x%08lx\n", hr); + mDevice->handleDisconnect("Device lost: 0x%08lx", hr); + break; + } + } + + UINT32 dynamicCount{}, framesToDo{}; + HRESULT hr{audio.mRender->BeginUpdatingAudioObjects(&dynamicCount, &framesToDo)}; + if(SUCCEEDED(hr)) + { + if(channels.empty()) UNLIKELY + { + auto flags = as_unsigned(audio.mStaticMask); + channels.reserve(as_unsigned(al::popcount(flags))); + while(flags) + { + auto id = decltype(flags){1} << al::countr_zero(flags); + flags &= ~id; + + channels.emplace_back(); + audio.mRender->ActivateSpatialAudioObject(static_cast(id), + al::out_ptr(channels.back())); + } + buffers.resize(channels.size()); + if(mResampler) + { + tmpbuffers.resize(buffers.size()); + resbuffers.resize(buffers.size()); + auto bufptr = mResampleBuffer.begin(); + for(size_t i{0};i < tmpbuffers.size();++i) + { + resbuffers[i] = reinterpret_cast(al::to_address(bufptr)); + bufptr += ptrdiff_t(mDevice->UpdateSize*sizeof(float)); + } + } + } + + /* We have to call to get each channel's buffer individually every + * update, unfortunately. + */ + std::transform(channels.cbegin(), channels.cend(), buffers.begin(), + [](const ComPtr &obj) -> float* + { + BYTE *buffer{}; + UINT32 size{}; + obj->GetBuffer(&buffer, &size); + return reinterpret_cast(buffer); + }); + + if(!mResampler) + mDevice->renderSamples(buffers, framesToDo); + else + { + std::lock_guard dlock{mMutex}; + for(UINT32 pos{0};pos < framesToDo;) + { + if(mBufferFilled == 0) + { + mDevice->renderSamples(resbuffers, mDevice->UpdateSize); + std::copy(resbuffers.cbegin(), resbuffers.cend(), tmpbuffers.begin()); + mBufferFilled = mDevice->UpdateSize; + } + + const uint got{mResampler->convertPlanar(tmpbuffers.data(), &mBufferFilled, + reinterpret_cast(buffers.data()), framesToDo-pos)}; + for(auto &buf : buffers) + buf += got; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + pos += got; + } + } + + hr = audio.mRender->EndUpdatingAudioObjects(); + } + + if(FAILED(hr)) + ERR("Failed to update playback objects: 0x%08lx\n", hr); + } + mPadding.store(0u, std::memory_order_release); + return 0; } -void WasapiPlayback::open(const char *name) +void WasapiPlayback::open(std::string_view name) { if(SUCCEEDED(mOpenStatus)) throw al::backend_exception{al::backend_error::DeviceError, @@ -789,132 +1369,65 @@ void WasapiPlayback::open(const char *name) "Failed to create notify events"}; } - HRESULT hr{InitThread()}; - if(FAILED(hr)) - { - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to init COM thread: 0x%08lx", hr}; - } - - if(name) - { - if(PlaybackDevices.empty()) - pushMessage(MsgType::EnumeratePlayback); - if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) - { - name += DevNameHeadLen; - if(*name == '\0') - name = nullptr; - } - } + if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix)) + name = name.substr(prefix.size()); mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); if(FAILED(mOpenStatus)) - { - DeinitThread(); throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", mOpenStatus}; - } } -HRESULT WasapiPlayback::openProxy(const char *name) +HRESULT WasapiPlayback::openProxy(std::string_view name) { - const wchar_t *devid{nullptr}; - if(name) + std::string devname; + std::wstring devid; + if(!name.empty()) { - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + auto devlock = DeviceListLock{gDeviceList}; + auto list = al::span{devlock.getPlaybackList()}; + auto iter = std::find_if(list.cbegin(), list.cend(), [name](const DevMap &entry) -> bool { return entry.name == name || entry.endpoint_guid == name; }); - if(iter == PlaybackDevices.cend()) + if(iter == list.cend()) { const std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + iter = std::find_if(list.cbegin(), list.cend(), [&wname](const DevMap &entry) -> bool { return entry.devid == wname; }); } - if(iter == PlaybackDevices.cend()) + if(iter == list.cend()) { - WARN("Failed to find device name matching \"%s\"\n", name); + WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data()); return E_FAIL; } - name = iter->name.c_str(); - devid = iter->devid.c_str(); + devname = iter->name; + devid = iter->devid; } - void *ptr; - ComPtr mmdev; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) - { - ComPtr enumerator{static_cast(ptr)}; - if(!devid) - hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mmdev.getPtr()); - else - hr = enumerator->GetDevice(devid, mmdev.getPtr()); - } + HRESULT hr{sDeviceHelper->openDevice(devid, eRender, mMMDev)}; if(FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str()); return hr; } + if(!devname.empty()) + mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname); + else + mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first; - mClient = nullptr; - mMMDev = std::move(mmdev); - if(name) mDevice->DeviceName = std::string{DevNameHead} + name; - else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; - - return hr; + return S_OK; } void WasapiPlayback::closeProxy() { - mClient = nullptr; + mAudio.emplace(); mMMDev = nullptr; } -bool WasapiPlayback::reset() +void WasapiPlayback::prepareFormat(WAVEFORMATEXTENSIBLE &OutputType) { - HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; - if(FAILED(hr)) - throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr}; - return true; -} - -HRESULT WasapiPlayback::resetProxy() -{ - mClient = nullptr; - - void *ptr; - HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)}; - if(FAILED(hr)) - { - ERR("Failed to reactivate audio client: 0x%08lx\n", hr); - return hr; - } - mClient = ComPtr{static_cast(ptr)}; - - WAVEFORMATEX *wfx; - hr = mClient->GetMixFormat(&wfx); - if(FAILED(hr)) - { - ERR("Failed to get mix format: 0x%08lx\n", hr); - return hr; - } - TraceFormat("Device mix format", wfx); - - WAVEFORMATEXTENSIBLE OutputType; - if(!MakeExtensible(&OutputType, wfx)) - { - CoTaskMemFree(wfx); - return E_FAIL; - } - CoTaskMemFree(wfx); - wfx = nullptr; - - const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; - const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; bool isRear51{false}; if(!mDevice->Flags.test(FrequencyRequest)) @@ -928,7 +1441,7 @@ HRESULT WasapiPlayback::resetProxy() const uint32_t chancount{OutputType.Format.nChannels}; const DWORD chanmask{OutputType.dwChannelMask}; if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) - mDevice->FmtChans = DevFmtX71; + mDevice->FmtChans = DevFmtX714; else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) mDevice->FmtChans = DevFmtX71; else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) @@ -987,6 +1500,9 @@ HRESULT WasapiPlayback::resetProxy() OutputType.Format.nChannels = 8; OutputType.dwChannelMask = X7DOT1; break; + case DevFmtX7144: + mDevice->FmtChans = DevFmtX714; + /*fall-through*/ case DevFmtX714: OutputType.Format.nChannels = 12; OutputType.dwChannelMask = X7DOT1DOT4; @@ -999,7 +1515,6 @@ HRESULT WasapiPlayback::resetProxy() /* fall-through */ case DevFmtUByte: OutputType.Format.wBitsPerSample = 8; - OutputType.Samples.wValidBitsPerSample = 8; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case DevFmtUShort: @@ -1007,7 +1522,6 @@ HRESULT WasapiPlayback::resetProxy() /* fall-through */ case DevFmtShort: OutputType.Format.wBitsPerSample = 16; - OutputType.Samples.wValidBitsPerSample = 16; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case DevFmtUInt: @@ -1015,28 +1529,417 @@ HRESULT WasapiPlayback::resetProxy() /* 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; } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; OutputType.Format.nSamplesPerSec = mDevice->Frequency; OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * OutputType.Format.wBitsPerSample / 8); OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * OutputType.Format.nBlockAlign; +} + +void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) +{ + if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true)) + mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec); + else + mDevice->Frequency = std::min(mDevice->Frequency, uint(OutputType.Format.nSamplesPerSec)); + + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + /* Don't update the channel format if the requested format fits what's + * supported. + */ + bool chansok{false}; + if(mDevice->Flags.test(ChannelsRequest)) + { + /* When requesting a channel configuration, make sure it fits the + * mask's lsb (to ensure no gaps in the output channels). If there's no + * mask, assume the request fits with enough channels. + */ + switch(mDevice->FmtChans) + { + case DevFmtMono: + chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)); + break; + case DevFmtStereo: + chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)); + break; + case DevFmtQuad: + chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask)); + break; + case DevFmtX51: + chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask)); + break; + case DevFmtX61: + chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask)); + break; + case DevFmtX71: + case DevFmtX3D71: + chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask)); + break; + case DevFmtX714: + chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask)); + case DevFmtX7144: + case DevFmtAmbi3D: + break; + } + } + if(!chansok) + { + if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) + mDevice->FmtChans = DevFmtX714; + else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) + mDevice->FmtChans = DevFmtX71; + else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) + mDevice->FmtChans = DevFmtX61; + else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR)) + mDevice->FmtChans = DevFmtX51; + else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) + mDevice->FmtChans = DevFmtStereo; + else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) + mDevice->FmtChans = DevFmtMono; + else + { + ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, + OutputType.dwChannelMask); + mDevice->FmtChans = DevFmtStereo; + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + } + } + + if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) + { + if(OutputType.Format.wBitsPerSample == 8) + mDevice->FmtType = DevFmtUByte; + else if(OutputType.Format.wBitsPerSample == 16) + mDevice->FmtType = DevFmtShort; + else if(OutputType.Format.wBitsPerSample == 32) + mDevice->FmtType = DevFmtInt; + else + { + mDevice->FmtType = DevFmtShort; + OutputType.Format.wBitsPerSample = 16; + } + } + else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) + { + mDevice->FmtType = DevFmtFloat; + OutputType.Format.wBitsPerSample = 32; + } + else + { + ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str()); + mDevice->FmtType = DevFmtShort; + if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) + OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; + OutputType.Format.wBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; +} + + +auto WasapiPlayback::initSpatial() -> bool +{ + auto &audio = mAudio.emplace(); + HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(ISpatialAudioClient), + al::out_ptr(audio.mClient))}; + if(FAILED(hr)) + { + ERR("Failed to activate spatial audio client: 0x%08lx\n", hr); + return false; + } + + ComPtr fmtenum; + hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum)); + if(FAILED(hr)) + { + ERR("Failed to get format enumerator: 0x%08lx\n", hr); + return false; + } + + UINT32 fmtcount{}; + hr = fmtenum->GetCount(&fmtcount); + if(FAILED(hr) || fmtcount == 0) + { + ERR("Failed to get format count: 0x%08lx\n", hr); + return false; + } + + WAVEFORMATEX *preferredFormat{}; + hr = fmtenum->GetFormat(0, &preferredFormat); + if(FAILED(hr)) + { + ERR("Failed to get preferred format: 0x%08lx\n", hr); + return false; + } + TraceFormat("Preferred mix format", preferredFormat); + + UINT32 maxFrames{}; + hr = audio.mClient->GetMaxFrameCount(preferredFormat, &maxFrames); + if(FAILED(hr)) + ERR("Failed to get max frames: 0x%08lx\n", hr); + else + TRACE("Max sample frames: %u\n", maxFrames); + for(UINT32 i{1};i < fmtcount;++i) + { + WAVEFORMATEX *otherFormat{}; + hr = fmtenum->GetFormat(i, &otherFormat); + if(FAILED(hr)) + ERR("Failed to format %u: 0x%08lx\n", i+1, hr); + else + { + TraceFormat("Other mix format", otherFormat); + UINT32 otherMaxFrames{}; + hr = audio.mClient->GetMaxFrameCount(otherFormat, &otherMaxFrames); + if(FAILED(hr)) + ERR("Failed to get max frames: 0x%08lx\n", hr); + else + TRACE("Max sample frames: %u\n", otherMaxFrames); + } + } + + WAVEFORMATEXTENSIBLE OutputType; + if(!MakeExtensible(&OutputType, preferredFormat)) + return false; + + /* Force 32-bit float. This is currently required for planar output. */ + if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE + && OutputType.Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT) + { + OutputType.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + OutputType.Format.cbSize = 0; + } + if(OutputType.Format.wBitsPerSample != 32) + { + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nAvgBytesPerSec * 32u + / OutputType.Format.wBitsPerSample; + OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nBlockAlign * 32 + / OutputType.Format.wBitsPerSample); + OutputType.Format.wBitsPerSample = 32; + } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + + /* Match the output rate if not requesting anything specific. */ + if(!mDevice->Flags.test(FrequencyRequest)) + mDevice->Frequency = OutputType.Format.nSamplesPerSec; + + bool isRear51{false}; + if(!mDevice->Flags.test(ChannelsRequest)) + { + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) + mDevice->FmtChans = DevFmtX714; + else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) + mDevice->FmtChans = DevFmtX71; + else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) + mDevice->FmtChans = DevFmtX61; + else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) + mDevice->FmtChans = DevFmtX51; + else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) + { + mDevice->FmtChans = DevFmtX51; + isRear51 = true; + } + else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) + mDevice->FmtChans = DevFmtStereo; + /* HACK: Don't autoselect mono. Wine returns this and makes the audio + * terrible. + */ + else if(!(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))) + ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask); + } + else + { + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR); + } + + auto getTypeMask = [isRear51](DevFmtChannels chans) noexcept + { + switch(chans) + { + case DevFmtMono: return ChannelMask_Mono; + case DevFmtStereo: return ChannelMask_Stereo; + case DevFmtQuad: return ChannelMask_Quad; + case DevFmtX51: return isRear51 ? ChannelMask_X51Rear : ChannelMask_X51; + case DevFmtX61: return ChannelMask_X61; + case DevFmtX3D71: + case DevFmtX71: return ChannelMask_X71; + case DevFmtX714: return ChannelMask_X714; + case DevFmtX7144: return ChannelMask_X7144; + case DevFmtAmbi3D: + break; + } + return ChannelMask_Stereo; + }; + + SpatialAudioObjectRenderStreamActivationParams streamParams{}; + streamParams.ObjectFormat = &OutputType.Format; + streamParams.StaticObjectTypeMask = getTypeMask(mDevice->FmtChans); + streamParams.Category = AudioCategory_Media; + streamParams.EventHandle = mNotifyEvent; + + PropVariant paramProp{}; + paramProp.setBlob({reinterpret_cast(&streamParams), sizeof(streamParams)}); + + hr = audio.mClient->ActivateSpatialAudioStream(paramProp.get(), + __uuidof(ISpatialAudioObjectRenderStream), al::out_ptr(audio.mRender)); + if(FAILED(hr)) + { + ERR("Failed to activate spatial audio stream: 0x%08lx\n", hr); + return false; + } + + audio.mStaticMask = streamParams.StaticObjectTypeMask; + mFormat = OutputType; + + mDevice->FmtType = DevFmtFloat; + mDevice->Flags.reset(DirectEar).set(Virtualization); + if(streamParams.StaticObjectTypeMask == ChannelMask_Stereo) + mDevice->FmtChans = DevFmtStereo; + if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true)) + mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec); + else + mDevice->Frequency = std::min(mDevice->Frequency, + uint(OutputType.Format.nSamplesPerSec)); + + setDefaultWFXChannelOrder(); + + /* FIXME: Get the real update and buffer size. Presumably the actual device + * is configured once ActivateSpatialAudioStream succeeds, and an + * IAudioClient from the same IMMDevice accesses the same device + * configuration. This isn't obviously correct, but for now assume + * IAudioClient::GetDevicePeriod returns the current device period time + * that ISpatialAudioObjectRenderStream will try to wake up at. + * + * Unfortunately this won't get the buffer size of the + * ISpatialAudioObjectRenderStream, so we only assume there's two periods. + */ + mOrigUpdateSize = mDevice->UpdateSize; + mOrigBufferSize = mOrigUpdateSize*2; + ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; + + ComPtr tmpClient; + hr = sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), + al::out_ptr(tmpClient)); + if(FAILED(hr)) + ERR("Failed to activate audio client: 0x%08lx\n", hr); + else + { + hr = tmpClient->GetDevicePeriod(&reinterpret_cast(per_time), nullptr); + if(FAILED(hr)) + ERR("Failed to get device period: 0x%08lx\n", hr); + else + { + mOrigUpdateSize = RefTime2Samples(per_time, mFormat.Format.nSamplesPerSec); + mOrigBufferSize = mOrigUpdateSize*2; + } + } + tmpClient = nullptr; + + mDevice->UpdateSize = RefTime2Samples(per_time, mDevice->Frequency); + mDevice->BufferSize = mDevice->UpdateSize*2; + + mResampler = nullptr; + mResampleBuffer.clear(); + mResampleBuffer.shrink_to_fit(); + mBufferFilled = 0; + if(mDevice->Frequency != mFormat.Format.nSamplesPerSec) + { + const auto flags = as_unsigned(streamParams.StaticObjectTypeMask); + const auto channelCount = as_unsigned(al::popcount(flags)); + mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, channelCount, + mDevice->Frequency, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); + mResampleBuffer.resize(size_t{mDevice->UpdateSize} * channelCount * + mFormat.Format.wBitsPerSample / 8); + + TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n", + DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), + mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency, + mDevice->UpdateSize); + } + + return true; +} + +bool WasapiPlayback::reset() +{ + HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; + if(FAILED(hr)) + throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr}; + return true; +} + +HRESULT WasapiPlayback::resetProxy() +{ + if(GetConfigValueBool(mDevice->DeviceName, "wasapi", "spatial-api", false)) + { + if(initSpatial()) + return S_OK; + } + + mDevice->Flags.reset(Virtualization); + + auto &audio = mAudio.emplace(); + HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), + al::out_ptr(audio.mClient))}; + if(FAILED(hr)) + { + ERR("Failed to reactivate audio client: 0x%08lx\n", hr); + return hr; + } + + WAVEFORMATEX *wfx; + hr = audio.mClient->GetMixFormat(&wfx); + if(FAILED(hr)) + { + ERR("Failed to get mix format: 0x%08lx\n", hr); + return hr; + } + TraceFormat("Device mix format", wfx); + + WAVEFORMATEXTENSIBLE OutputType; + if(!MakeExtensible(&OutputType, wfx)) + { + CoTaskMemFree(wfx); + return E_FAIL; + } + CoTaskMemFree(wfx); + wfx = nullptr; + + const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; + const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; + + prepareFormat(OutputType); TraceFormat("Requesting playback format", &OutputType.Format); - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); + hr = audio.mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); if(FAILED(hr)) { WARN("Failed to check format support: 0x%08lx\n", hr); - hr = mClient->GetMixFormat(&wfx); + hr = audio.mClient->GetMixFormat(&wfx); } if(FAILED(hr)) { @@ -1055,116 +1958,19 @@ HRESULT WasapiPlayback::resetProxy() CoTaskMemFree(wfx); wfx = nullptr; - if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "wasapi", "allow-resampler", true)) - mDevice->Frequency = OutputType.Format.nSamplesPerSec; - else - mDevice->Frequency = minu(mDevice->Frequency, OutputType.Format.nSamplesPerSec); - - const uint32_t chancount{OutputType.Format.nChannels}; - const DWORD chanmask{OutputType.dwChannelMask}; - /* Don't update the channel format if the requested format fits what's - * supported. - */ - bool chansok{false}; - if(mDevice->Flags.test(ChannelsRequest)) - { - /* When requesting a channel configuration, make sure it fits the - * mask's lsb (to ensure no gaps in the output channels). If - * there's no mask, assume the request fits with enough channels. - */ - switch(mDevice->FmtChans) - { - case DevFmtMono: - chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)); - break; - case DevFmtStereo: - chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)); - break; - case DevFmtQuad: - chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask)); - break; - case DevFmtX51: - chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 - || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask)); - break; - case DevFmtX61: - chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask)); - break; - case DevFmtX71: - case DevFmtX3D71: - chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask)); - break; - case DevFmtX714: - chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask)); - case DevFmtAmbi3D: - break; - } - } - if(!chansok) - { - if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) - mDevice->FmtChans = DevFmtX714; - else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) - mDevice->FmtChans = DevFmtX71; - else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 - || (chanmask&X51RearMask) == X5DOT1REAR)) - mDevice->FmtChans = DevFmtX51; - else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) - mDevice->FmtChans = DevFmtStereo; - else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) - mDevice->FmtChans = DevFmtMono; - else - { - ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, - OutputType.dwChannelMask); - mDevice->FmtChans = DevFmtStereo; - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; - } - } - - if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) - { - if(OutputType.Format.wBitsPerSample == 8) - mDevice->FmtType = DevFmtUByte; - else if(OutputType.Format.wBitsPerSample == 16) - mDevice->FmtType = DevFmtShort; - else if(OutputType.Format.wBitsPerSample == 32) - mDevice->FmtType = DevFmtInt; - else - { - mDevice->FmtType = DevFmtShort; - OutputType.Format.wBitsPerSample = 16; - } - } - else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) - { - mDevice->FmtType = DevFmtFloat; - OutputType.Format.wBitsPerSample = 32; - } - else - { - ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str()); - mDevice->FmtType = DevFmtShort; - if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) - OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.wBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - } - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + finalizeFormat(OutputType); } mFormat = OutputType; - const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())}; +#if !defined(ALSOFT_UWP) + const EndpointFormFactor formfactor{GetDeviceFormfactor(mMMDev.get())}; mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset)); - +#else + mDevice->Flags.set(DirectEar, false); +#endif setDefaultWFXChannelOrder(); - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time.count(), 0, &OutputType.Format, nullptr); if(FAILED(hr)) { @@ -1174,37 +1980,46 @@ HRESULT WasapiPlayback::resetProxy() UINT32 buffer_len{}; ReferenceTime min_per{}; - hr = mClient->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); + hr = audio.mClient->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); if(SUCCEEDED(hr)) - hr = mClient->GetBufferSize(&buffer_len); + hr = audio.mClient->GetBufferSize(&buffer_len); if(FAILED(hr)) { ERR("Failed to get audio buffer info: 0x%08lx\n", hr); return hr; } + hr = audio.mClient->SetEventHandle(mNotifyEvent); + if(FAILED(hr)) + { + ERR("Failed to set event handle: 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_per *= std::max((per_time + min_per/2) / min_per, 1_i64); mOrigBufferSize = buffer_len; - mOrigUpdateSize = minu(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), buffer_len/2); + mOrigUpdateSize = std::min(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), + buffer_len/2u); mDevice->BufferSize = static_cast(uint64_t{buffer_len} * mDevice->Frequency / mFormat.Format.nSamplesPerSec); - mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency), - mDevice->BufferSize/2); + mDevice->UpdateSize = std::min(RefTime2Samples(min_per, mDevice->Frequency), + mDevice->BufferSize/2u); mResampler = nullptr; - mResampleBuffer = nullptr; + mResampleBuffer.clear(); + mResampleBuffer.shrink_to_fit(); mBufferFilled = 0; if(mDevice->Frequency != mFormat.Format.nSamplesPerSec) { mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); - mResampleBuffer = std::make_unique(size_t{mDevice->UpdateSize} * - mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8); + mResampleBuffer.resize(size_t{mDevice->UpdateSize} * mFormat.Format.nChannels * + mFormat.Format.wBitsPerSample / 8); TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), @@ -1212,13 +2027,6 @@ HRESULT WasapiPlayback::resetProxy() mDevice->UpdateSize); } - hr = mClient->SetEventHandle(mNotifyEvent); - if(FAILED(hr)) - { - ERR("Failed to set event handle: 0x%08lx\n", hr); - return hr; - } - return hr; } @@ -1235,33 +2043,61 @@ HRESULT WasapiPlayback::startProxy() { ResetEvent(mNotifyEvent); - HRESULT hr{mClient->Start()}; - if(FAILED(hr)) + auto mstate_fallback = [](std::monostate) -> HRESULT + { return E_FAIL; }; + auto start_plain = [&](PlainDevice &audio) -> HRESULT { - ERR("Failed to start audio client: 0x%08lx\n", hr); - return hr; - } + HRESULT hr{audio.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)) + hr = audio.mClient->GetService(__uuidof(IAudioRenderClient), al::out_ptr(audio.mRender)); + if(SUCCEEDED(hr)) + { + try { + mKillNow.store(false, std::memory_order_release); + mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this}; + } + catch(...) { + audio.mRender = nullptr; + ERR("Failed to start thread\n"); + hr = E_FAIL; + } + } + if(FAILED(hr)) + audio.mClient->Stop(); + return hr; + }; + auto start_spatial = [&](SpatialDevice &audio) -> HRESULT { - mRender = ComPtr{static_cast(ptr)}; + HRESULT hr{audio.mRender->Start()}; + if(FAILED(hr)) + { + ERR("Failed to start spatial audio stream: 0x%08lx\n", hr); + return hr; + } + try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this}; + mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerSpatialProc), this}; } catch(...) { - mRender = nullptr; ERR("Failed to start thread\n"); hr = E_FAIL; } - } - if(FAILED(hr)) - mClient->Stop(); + if(FAILED(hr)) + { + audio.mRender->Stop(); + audio.mRender->Reset(); + } + return hr; + }; - return hr; + return std::visit(overloaded{mstate_fallback, start_plain, start_spatial}, mAudio); } @@ -1270,23 +2106,33 @@ void WasapiPlayback::stop() void WasapiPlayback::stopProxy() { - if(!mRender || !mThread.joinable()) + if(!mThread.joinable()) return; mKillNow.store(true, std::memory_order_release); mThread.join(); - mRender = nullptr; - mClient->Stop(); + auto mstate_fallback = [](std::monostate) -> void + { }; + auto stop_plain = [](PlainDevice &audio) -> void + { + audio.mRender = nullptr; + audio.mClient->Stop(); + }; + auto stop_spatial = [](SpatialDevice &audio) -> void + { + audio.mRender->Stop(); + audio.mRender->Reset(); + }; + std::visit(overloaded{mstate_fallback, stop_plain, stop_spatial}, mAudio); } ClockLatency WasapiPlayback::getClockLatency() { - ClockLatency ret; - - std::lock_guard _{mMutex}; - ret.ClockTime = GetDeviceClockTime(mDevice); + std::lock_guard dlock{mMutex}; + ClockLatency ret{}; + ret.ClockTime = mDevice->getClockTime(); ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)}; ret.Latency /= mFormat.Format.nSamplesPerSec; if(mResampler) @@ -1306,8 +2152,8 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { int recordProc(); - void open(const char *name) override; - HRESULT openProxy(const char *name) override; + void open(std::string_view name) override; + HRESULT openProxy(std::string_view name) override; void closeProxy() override; HRESULT resetProxy() override; @@ -1316,11 +2162,11 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { void stop() override; void stopProxy() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; HRESULT mOpenStatus{E_FAIL}; - ComPtr mMMDev{nullptr}; + DeviceHandle mMMDev{nullptr}; ComPtr mClient{nullptr}; ComPtr mCapture{nullptr}; HANDLE mNotifyEvent{nullptr}; @@ -1331,17 +2177,12 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WasapiCapture) }; WasapiCapture::~WasapiCapture() { if(SUCCEEDED(mOpenStatus)) - { pushMessage(MsgType::CloseDevice).wait(); - DeinitThread(); - } mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) @@ -1352,21 +2193,21 @@ WasapiCapture::~WasapiCapture() FORCE_ALIGN int WasapiCapture::recordProc() { - HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(hr)) + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("COM init failed: 0x%08lx", hr); + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); + mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); return 1; } - althrd_setname(RECORD_THREAD_NAME); + althrd_setname(GetRecordThreadName()); - al::vector samples; + std::vector samples; while(!mKillNow.load(std::memory_order_relaxed)) { UINT32 avail; - hr = mCapture->GetNextPacketSize(&avail); + HRESULT hr{mCapture->GetNextPacketSize(&avail)}; if(FAILED(hr)) ERR("Failed to get next packet size: 0x%08lx\n", hr); else if(avail > 0) @@ -1382,7 +2223,7 @@ FORCE_ALIGN int WasapiCapture::recordProc() { if(mChannelConv.is_active()) { - samples.resize(numsamples*2); + samples.resize(numsamples*2_uz); mChannelConv.convert(rdata, samples.data(), numsamples); rdata = reinterpret_cast(samples.data()); } @@ -1392,11 +2233,12 @@ FORCE_ALIGN int WasapiCapture::recordProc() size_t dstframes; if(mSampleConv) { + static constexpr auto lenlimit = size_t{std::numeric_limits::max()}; const void *srcdata{rdata}; uint srcframes{numsamples}; dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf, - static_cast(minz(data.first.len, INT_MAX))); + static_cast(std::min(data.first.len, lenlimit))); if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0) { /* If some source samples remain, all of the first dest @@ -1404,18 +2246,22 @@ FORCE_ALIGN int WasapiCapture::recordProc() * dest block, do another run for the second block. */ dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf, - static_cast(minz(data.second.len, INT_MAX))); + static_cast(std::min(data.second.len, lenlimit))); } } else { const uint framesize{mDevice->frameSizeFromFmt()}; - size_t len1{minz(data.first.len, numsamples)}; - size_t len2{minz(data.second.len, numsamples-len1)}; + auto dst = al::span{rdata, size_t{numsamples}*framesize}; + size_t len1{std::min(data.first.len, size_t{numsamples})}; + size_t len2{std::min(data.second.len, numsamples-len1)}; - memcpy(data.first.buf, rdata, len1*framesize); + memcpy(data.first.buf, dst.data(), len1*framesize); if(len2 > 0) - memcpy(data.second.buf, rdata+len1*framesize, len2*framesize); + { + dst = dst.subspan(len1*framesize); + memcpy(data.second.buf, dst.data(), len2*framesize); + } dstframes = len1 + len2; } @@ -1437,12 +2283,11 @@ FORCE_ALIGN int WasapiCapture::recordProc() ERR("WaitForSingleObjectEx error: 0x%lx\n", res); } - CoUninitialize(); return 0; } -void WasapiCapture::open(const char *name) +void WasapiCapture::open(std::string_view name) { if(SUCCEEDED(mOpenStatus)) throw al::backend_exception{al::backend_error::DeviceError, @@ -1456,34 +2301,15 @@ void WasapiCapture::open(const char *name) "Failed to create notify events"}; } - HRESULT hr{InitThread()}; - if(FAILED(hr)) - { - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to init COM thread: 0x%08lx", hr}; - } - - if(name) - { - if(CaptureDevices.empty()) - pushMessage(MsgType::EnumerateCapture); - if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) - { - name += DevNameHeadLen; - if(*name == '\0') - name = nullptr; - } - } + if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix)) + name = name.substr(prefix.size()); mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); if(FAILED(mOpenStatus)) - { - DeinitThread(); throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", mOpenStatus}; - } - hr = pushMessage(MsgType::ResetDevice).get(); + HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; if(FAILED(hr)) { if(hr == E_OUTOFMEMORY) @@ -1492,52 +2318,46 @@ void WasapiCapture::open(const char *name) } } -HRESULT WasapiCapture::openProxy(const char *name) +HRESULT WasapiCapture::openProxy(std::string_view name) { - const wchar_t *devid{nullptr}; - if(name) + std::string devname; + std::wstring devid; + if(!name.empty()) { - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + auto devlock = DeviceListLock{gDeviceList}; + auto devlist = al::span{devlock.getCaptureList()}; + auto iter = std::find_if(devlist.cbegin(), devlist.cend(), [name](const DevMap &entry) -> bool { return entry.name == name || entry.endpoint_guid == name; }); - if(iter == CaptureDevices.cend()) + if(iter == devlist.cend()) { const std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + iter = std::find_if(devlist.cbegin(), devlist.cend(), [&wname](const DevMap &entry) -> bool { return entry.devid == wname; }); } - if(iter == CaptureDevices.cend()) + if(iter == devlist.cend()) { - WARN("Failed to find device name matching \"%s\"\n", name); + WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data()); return E_FAIL; } - name = iter->name.c_str(); - devid = iter->devid.c_str(); + devname = iter->name; + devid = iter->devid; } - void *ptr; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) - { - ComPtr enumerator{static_cast(ptr)}; - if(!devid) - hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr()); - else - hr = enumerator->GetDevice(devid, mMMDev.getPtr()); - } + HRESULT hr{sDeviceHelper->openDevice(devid, eCapture, mMMDev)}; if(FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str()); return hr; } - mClient = nullptr; - if(name) mDevice->DeviceName = std::string{DevNameHead} + name; - else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; + if(!devname.empty()) + mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname); + else + mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first; - return hr; + return S_OK; } void WasapiCapture::closeProxy() @@ -1550,14 +2370,13 @@ HRESULT WasapiCapture::resetProxy() { mClient = nullptr; - void *ptr; - HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)}; + HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), + al::out_ptr(mClient))}; if(FAILED(hr)) { ERR("Failed to reactivate audio client: 0x%08lx\n", hr); return hr; } - mClient = ComPtr{static_cast(ptr)}; WAVEFORMATEX *wfx; hr = mClient->GetMixFormat(&wfx); @@ -1617,6 +2436,7 @@ HRESULT WasapiCapture::resetProxy() InputType.dwChannelMask = X7DOT1DOT4; break; + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: return E_FAIL; @@ -1644,6 +2464,7 @@ HRESULT WasapiCapture::resetProxy() InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; break; } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; InputType.Format.nSamplesPerSec = mDevice->Frequency; @@ -1706,6 +2527,8 @@ HRESULT WasapiCapture::resetProxy() return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1)); case DevFmtX714: return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4)); + case DevFmtX7144: + return (chancount == 16 && chanmask == 0); case DevFmtAmbi3D: return (chanmask == 0 && chancount == device->channelsFromFmt()); } @@ -1849,11 +2672,9 @@ HRESULT WasapiCapture::startProxy() return hr; } - void *ptr; - hr = mClient->GetService(IID_IAudioCaptureClient, &ptr); + hr = mClient->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(mCapture)); if(SUCCEEDED(hr)) { - mCapture = ComPtr{static_cast(ptr)}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this}; @@ -1892,8 +2713,8 @@ void WasapiCapture::stopProxy() } -void WasapiCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void WasapiCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint WasapiCapture::availableSamples() { return static_cast(mRing->readSpace()); } @@ -1904,34 +2725,13 @@ uint WasapiCapture::availableSamples() bool WasapiBackendFactory::init() { static HRESULT InitResult{E_FAIL}; - if(FAILED(InitResult)) try { - auto res = std::async(std::launch::async, []() -> HRESULT - { - HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(hr)) - { - WARN("Failed to initialize COM: 0x%08lx\n", hr); - return hr; - } + std::promise promise; + auto future = promise.get_future(); - void *ptr{}; - hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr); - if(FAILED(hr)) - { - WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); - CoUninitialize(); - return hr; - } - static_cast(ptr)->Release(); - CoUninitialize(); - - return S_OK; - }); - - InitResult = res.get(); + std::thread{&WasapiProxy::messageHandler, &promise}.detach(); + InitResult = future.get(); } catch(...) { } @@ -1942,36 +2742,42 @@ bool WasapiBackendFactory::init() bool WasapiBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string WasapiBackendFactory::probe(BackendType type) +auto WasapiBackendFactory::enumerate(BackendType type) -> std::vector { - struct ProxyControl { - HRESULT mResult{}; - ProxyControl() { mResult = WasapiProxy::InitThread(); } - ~ProxyControl() { if(SUCCEEDED(mResult)) WasapiProxy::DeinitThread(); } - }; - ProxyControl proxy; - - std::string outnames; - if(FAILED(proxy.mResult)) - return outnames; + std::vector outnames; + auto devlock = DeviceListLock{gDeviceList}; switch(type) { case BackendType::Playback: - WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).wait(); - for(const DevMap &entry : PlaybackDevices) { - /* +1 to also append the null char (to ensure a null-separated list - * and double-null terminated list). - */ - outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1); + auto defaultId = devlock.getPlaybackDefaultId(); + for(const DevMap &entry : devlock.getPlaybackList()) + { + if(entry.devid != defaultId) + { + outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name); + continue; + } + /* Default device goes first. */ + outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name); + } } break; case BackendType::Capture: - WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).wait(); - for(const DevMap &entry : CaptureDevices) - outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1); + { + auto defaultId = devlock.getCaptureDefaultId(); + for(const DevMap &entry : devlock.getCaptureList()) + { + if(entry.devid != defaultId) + { + outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name); + continue; + } + outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name); + } + } break; } @@ -1992,3 +2798,22 @@ BackendFactory &WasapiBackendFactory::getFactory() static WasapiBackendFactory factory{}; return factory; } + +alc::EventSupport WasapiBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DefaultDeviceChanged: + return alc::EventSupport::FullSupport; + + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: +#if !defined(ALSOFT_UWP) + return alc::EventSupport::FullSupport; +#endif + + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/Engine/lib/openal-soft/alc/backends/wasapi.h b/Engine/lib/openal-soft/alc/backends/wasapi.h index bb2671ee8..f3cb85413 100644 --- a/Engine/lib/openal-soft/alc/backends/wasapi.h +++ b/Engine/lib/openal-soft/alc/backends/wasapi.h @@ -5,15 +5,17 @@ struct WasapiBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - static BackendFactory &getFactory(); + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WASAPI_H */ diff --git a/Engine/lib/openal-soft/alc/backends/wave.cpp b/Engine/lib/openal-soft/alc/backends/wave.cpp index 1b40640c7..5e61ab52b 100644 --- a/Engine/lib/openal-soft/alc/backends/wave.cpp +++ b/Engine/lib/openal-soft/alc/backends/wave.cpp @@ -31,24 +31,26 @@ #include #include #include +#include #include +#include #include "albit.h" -#include "albyte.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "opthelpers.h" #include "strutils.h" -#include "threads.h" -#include "vector.h" namespace { +using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; @@ -56,38 +58,43 @@ using std::chrono::nanoseconds; using ubyte = unsigned char; using ushort = unsigned short; -constexpr char waveDevice[] = "Wave File Writer"; +struct FileDeleter { + void operator()(gsl::owner f) { fclose(f); } +}; +using FilePtr = std::unique_ptr; -constexpr ubyte SUBTYPE_PCM[]{ +[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Wave File Writer"sv; } + +constexpr std::array SUBTYPE_PCM{{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 -}; -constexpr ubyte SUBTYPE_FLOAT[]{ +}}; +constexpr std::array SUBTYPE_FLOAT{{ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 -}; +}}; -constexpr ubyte SUBTYPE_BFORMAT_PCM[]{ +constexpr std::array SUBTYPE_BFORMAT_PCM{{ 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 -}; +}}; -constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{ +constexpr std::array SUBTYPE_BFORMAT_FLOAT{{ 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 -}; +}}; void fwrite16le(ushort val, FILE *f) { - ubyte data[2]{ static_cast(val&0xff), static_cast((val>>8)&0xff) }; - fwrite(data, 1, 2, f); + std::array data{static_cast(val&0xff), static_cast((val>>8)&0xff)}; + fwrite(data.data(), 1, data.size(), f); } void fwrite32le(uint val, FILE *f) { - ubyte data[4]{ static_cast(val&0xff), static_cast((val>>8)&0xff), - static_cast((val>>16)&0xff), static_cast((val>>24)&0xff) }; - fwrite(data, 1, 4, f); + std::array data{static_cast(val&0xff), static_cast((val>>8)&0xff), + static_cast((val>>16)&0xff), static_cast((val>>24)&0xff)}; + fwrite(data.data(), 1, data.size(), f); } @@ -97,34 +104,27 @@ struct WaveBackend final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; - FILE *mFile{nullptr}; + FilePtr mFile{nullptr}; long mDataStart{-1}; - al::vector mBuffer; + std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WaveBackend) }; -WaveBackend::~WaveBackend() -{ - if(mFile) - fclose(mFile); - mFile = nullptr; -} +WaveBackend::~WaveBackend() = default; int WaveBackend::mixerProc() { const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const size_t frameStep{mDevice->channelsFromFmt()}; const size_t frameSize{mDevice->frameSizeFromFmt()}; @@ -155,13 +155,13 @@ int WaveBackend::mixerProc() if(bytesize == 2) { - const size_t len{mBuffer.size() & ~size_t{1}}; + const size_t len{mBuffer.size() & ~1_uz}; for(size_t i{0};i < len;i+=2) std::swap(mBuffer[i], mBuffer[i+1]); } else if(bytesize == 4) { - const size_t len{mBuffer.size() & ~size_t{3}}; + const size_t len{mBuffer.size() & ~3_uz}; for(size_t i{0};i < len;i+=4) { std::swap(mBuffer[i ], mBuffer[i+3]); @@ -170,8 +170,8 @@ int WaveBackend::mixerProc() } } - const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)}; - if(fs < mDevice->UpdateSize || ferror(mFile)) + const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile.get())}; + if(fs < mDevice->UpdateSize || ferror(mFile.get())) { ERR("Error writing to file\n"); mDevice->handleDisconnect("Failed to write playback samples"); @@ -195,32 +195,32 @@ int WaveBackend::mixerProc() return 0; } -void WaveBackend::open(const char *name) +void WaveBackend::open(std::string_view name) { - auto fname = ConfigValueStr(nullptr, "wave", "file"); + auto fname = ConfigValueStr({}, "wave", "file"); if(!fname) throw al::backend_exception{al::backend_error::NoDevice, "No wave output filename"}; - if(!name) - name = waveDevice; - else if(strcmp(name, waveDevice) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; /* There's only one "device", so if it's already open, we're done. */ if(mFile) return; #ifdef _WIN32 { - std::wstring wname{utf8_to_wstr(fname->c_str())}; - mFile = _wfopen(wname.c_str(), L"wb"); + std::wstring wname{utf8_to_wstr(fname.value())}; + mFile = FilePtr{_wfopen(wname.c_str(), L"wb")}; } #else - mFile = fopen(fname->c_str(), "wb"); + mFile = FilePtr{fopen(fname->c_str(), "wb")}; #endif if(!mFile) throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s", - fname->c_str(), strerror(errno)}; + fname->c_str(), std::generic_category().message(errno).c_str()}; mDevice->DeviceName = name; } @@ -229,12 +229,11 @@ bool WaveBackend::reset() { uint channels{0}, bytes{0}, chanmask{0}; bool isbformat{false}; - size_t val; - fseek(mFile, 0, SEEK_SET); - clearerr(mFile); + fseek(mFile.get(), 0, SEEK_SET); + clearerr(mFile.get()); - if(GetConfigValueBool(nullptr, "wave", "bformat", false)) + if(GetConfigValueBool({}, "wave", "bformat", false)) { mDevice->FmtChans = DevFmtAmbi3D; mDevice->mAmbiOrder = 1; @@ -265,6 +264,9 @@ bool WaveBackend::reset() case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; + case DevFmtX7144: + mDevice->FmtChans = DevFmtX714; + [[fallthrough]]; case DevFmtX714: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000 | 0x8000 | 0x20000; @@ -273,7 +275,7 @@ bool WaveBackend::reset() case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; case DevFmtAmbi3D: /* .amb output requires FuMa */ - mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3); + mDevice->mAmbiOrder = std::min(mDevice->mAmbiOrder, 3u); mDevice->mAmbiLayout = DevAmbiLayout::FuMa; mDevice->mAmbiScale = DevAmbiScaling::FuMa; isbformat = true; @@ -283,49 +285,48 @@ bool WaveBackend::reset() bytes = mDevice->bytesFromFmt(); channels = mDevice->channelsFromFmt(); - rewind(mFile); + rewind(mFile.get()); - fputs("RIFF", mFile); - fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close + fputs("RIFF", mFile.get()); + fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at close - fputs("WAVE", mFile); + fputs("WAVE", mFile.get()); - fputs("fmt ", mFile); - fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE + fputs("fmt ", mFile.get()); + fwrite32le(40, mFile.get()); // 'fmt ' header len; 40 bytes for EXTENSIBLE // 16-bit val, format type id (extensible: 0xFFFE) - fwrite16le(0xFFFE, mFile); + fwrite16le(0xFFFE, mFile.get()); // 16-bit val, channel count - fwrite16le(static_cast(channels), mFile); + fwrite16le(static_cast(channels), mFile.get()); // 32-bit val, frequency - fwrite32le(mDevice->Frequency, mFile); + fwrite32le(mDevice->Frequency, mFile.get()); // 32-bit val, bytes per second - fwrite32le(mDevice->Frequency * channels * bytes, mFile); + fwrite32le(mDevice->Frequency * channels * bytes, mFile.get()); // 16-bit val, frame size - fwrite16le(static_cast(channels * bytes), mFile); + fwrite16le(static_cast(channels * bytes), mFile.get()); // 16-bit val, bits per sample - fwrite16le(static_cast(bytes * 8), mFile); + fwrite16le(static_cast(bytes * 8), mFile.get()); // 16-bit val, extra byte count - fwrite16le(22, mFile); + fwrite16le(22, mFile.get()); // 16-bit val, valid bits per sample - fwrite16le(static_cast(bytes * 8), mFile); + fwrite16le(static_cast(bytes * 8), mFile.get()); // 32-bit val, channel mask - fwrite32le(chanmask, mFile); + fwrite32le(chanmask, mFile.get()); // 16 byte GUID, sub-type format - val = fwrite((mDevice->FmtType == DevFmtFloat) ? - (isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) : - (isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile); - (void)val; + std::ignore = fwrite((mDevice->FmtType == DevFmtFloat) ? + (isbformat ? SUBTYPE_BFORMAT_FLOAT.data() : SUBTYPE_FLOAT.data()) : + (isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get()); - fputs("data", mFile); - fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close + fputs("data", mFile.get()); + fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at close - if(ferror(mFile)) + if(ferror(mFile.get())) { - ERR("Error writing header: %s\n", strerror(errno)); + ERR("Error writing header: %s\n", std::generic_category().message(errno).c_str()); return false; } - mDataStart = ftell(mFile); + mDataStart = ftell(mFile.get()); setDefaultWFXChannelOrder(); @@ -337,7 +338,7 @@ bool WaveBackend::reset() void WaveBackend::start() { - if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0) + if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0) WARN("Failed to seek on output file\n"); try { mKillNow.store(false, std::memory_order_release); @@ -357,14 +358,14 @@ void WaveBackend::stop() if(mDataStart > 0) { - long size{ftell(mFile)}; + long size{ftell(mFile.get())}; if(size > 0) { long dataLen{size - mDataStart}; - if(fseek(mFile, 4, SEEK_SET) == 0) - fwrite32le(static_cast(size-8), mFile); // 'WAVE' header len - if(fseek(mFile, mDataStart-4, SEEK_SET) == 0) - fwrite32le(static_cast(dataLen), mFile); // 'data' header len + if(fseek(mFile.get(), 4, SEEK_SET) == 0) + fwrite32le(static_cast(size-8), mFile.get()); // 'WAVE' header len + if(fseek(mFile.get(), mDataStart-4, SEEK_SET) == 0) + fwrite32le(static_cast(dataLen), mFile.get()); // 'data' header len } } } @@ -378,19 +379,16 @@ bool WaveBackendFactory::init() bool WaveBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -std::string WaveBackendFactory::probe(BackendType type) +auto WaveBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; switch(type) { case BackendType::Playback: - /* Includes null char. */ - outnames.append(waveDevice, sizeof(waveDevice)); - break; + return std::vector{std::string{GetDeviceName()}}; case BackendType::Capture: break; } - return outnames; + return {}; } BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/wave.h b/Engine/lib/openal-soft/alc/backends/wave.h index e768d3361..85f4c76ff 100644 --- a/Engine/lib/openal-soft/alc/backends/wave.h +++ b/Engine/lib/openal-soft/alc/backends/wave.h @@ -5,15 +5,15 @@ struct WaveBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WAVE_H */ diff --git a/Engine/lib/openal-soft/alc/backends/winmm.cpp b/Engine/lib/openal-soft/alc/backends/winmm.cpp index 38e1193f9..ea4fee1e8 100644 --- a/Engine/lib/openal-soft/alc/backends/winmm.cpp +++ b/Engine/lib/openal-soft/alc/backends/winmm.cpp @@ -22,8 +22,8 @@ #include "winmm.h" -#include -#include +#include +#include #include #include @@ -38,13 +38,15 @@ #include #include -#include "alnumeric.h" +#include "alsem.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" +#include "vector.h" #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 @@ -55,13 +57,13 @@ namespace { #define DEVNAME_HEAD "OpenAL Soft on " -al::vector PlaybackDevices; -al::vector CaptureDevices; +std::vector PlaybackDevices; +std::vector CaptureDevices; -bool checkName(const al::vector &list, const std::string &name) +bool checkName(const std::vector &list, const std::string &name) { return std::find(list.cbegin(), list.cend(), name) != list.cend(); } -void ProbePlaybackDevices(void) +void ProbePlaybackDevices() { PlaybackDevices.clear(); @@ -74,7 +76,7 @@ void ProbePlaybackDevices(void) WAVEOUTCAPSW WaveCaps{}; if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)}; + const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))}; int count{1}; std::string newname{basename}; @@ -92,7 +94,7 @@ void ProbePlaybackDevices(void) } } -void ProbeCaptureDevices(void) +void ProbeCaptureDevices() { CaptureDevices.clear(); @@ -105,7 +107,7 @@ void ProbeCaptureDevices(void) WAVEINCAPSW WaveCaps{}; if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)}; + const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))}; int count{1}; std::string newname{basename}; @@ -134,7 +136,7 @@ struct WinMMPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -143,6 +145,7 @@ struct WinMMPlayback final : public BackendBase { al::semaphore mSem; uint mIdx{0u}; std::array mWaveBuffer{}; + al::vector mBuffer; HWAVEOUT mOutHdl{nullptr}; @@ -150,8 +153,6 @@ struct WinMMPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WinMMPlayback) }; WinMMPlayback::~WinMMPlayback() @@ -159,14 +160,11 @@ WinMMPlayback::~WinMMPlayback() if(mOutHdl) waveOutClose(mOutHdl); mOutHdl = nullptr; - - al_free(mWaveBuffer[0].lpData); - std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); } /* WinMMPlayback::waveOutProc * - * Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is + * Posts a message to 'WinMMPlayback::mixerProc' every time a WaveOut Buffer is * completed and returns to the application (for more data) */ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept @@ -179,7 +177,7 @@ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PT FORCE_ALIGN int WinMMPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) @@ -207,59 +205,55 @@ FORCE_ALIGN int WinMMPlayback::mixerProc() } -void WinMMPlayback::open(const char *name) +void WinMMPlayback::open(std::string_view name) { if(PlaybackDevices.empty()) ProbePlaybackDevices(); // Find the Device ID matching the deviceName if valid - auto iter = name ? + auto iter = !name.empty() ? std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : PlaybackDevices.cbegin(); if(iter == PlaybackDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; auto DeviceID = static_cast(std::distance(PlaybackDevices.cbegin(), iter)); DevFmtType fmttype{mDevice->FmtType}; -retry_open: WAVEFORMATEX format{}; - if(fmttype == DevFmtFloat) - { - format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - format.wBitsPerSample = 32; - } - else - { - format.wFormatTag = WAVE_FORMAT_PCM; - if(fmttype == DevFmtUByte || fmttype == DevFmtByte) - format.wBitsPerSample = 8; - else - format.wBitsPerSample = 16; - } - format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - format.nBlockAlign = static_cast(format.wBitsPerSample * format.nChannels / 8); - format.nSamplesPerSec = mDevice->Frequency; - format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; - format.cbSize = 0; - - HWAVEOUT outHandle{}; - MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format, - reinterpret_cast(&WinMMPlayback::waveOutProcC), - reinterpret_cast(this), CALLBACK_FUNCTION)}; - if(res != MMSYSERR_NOERROR) - { + do { + format = WAVEFORMATEX{}; if(fmttype == DevFmtFloat) { - fmttype = DevFmtShort; - goto retry_open; + format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + format.wBitsPerSample = 32; } - throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res}; - } + else + { + format.wFormatTag = WAVE_FORMAT_PCM; + if(fmttype == DevFmtUByte || fmttype == DevFmtByte) + format.wBitsPerSample = 8; + else + format.wBitsPerSample = 16; + } + format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); + format.nBlockAlign = static_cast(format.wBitsPerSample * format.nChannels / 8); + format.nSamplesPerSec = mDevice->Frequency; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + + MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &format, + reinterpret_cast(&WinMMPlayback::waveOutProcC), + reinterpret_cast(this), CALLBACK_FUNCTION)}; + if(res == MMSYSERR_NOERROR) break; + + if(fmttype != DevFmtFloat) + throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", + res}; + + fmttype = DevFmtShort; + } while(true); - if(mOutHdl) - waveOutClose(mOutHdl); - mOutHdl = outHandle; mFormat = format; mDevice->DeviceName = PlaybackDevices[DeviceID]; @@ -312,11 +306,11 @@ bool WinMMPlayback::reset() } setDefaultWFXChannelOrder(); - uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; + const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; - al_free(mWaveBuffer[0].lpData); + decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer); mWaveBuffer[0] = WAVEHDR{}; - mWaveBuffer[0].lpData = static_cast(al_calloc(16, BufferSize * mWaveBuffer.size())); + mWaveBuffer[0].lpData = mBuffer.data(); mWaveBuffer[0].dwBufferLength = BufferSize; for(size_t i{1};i < mWaveBuffer.size();i++) { @@ -369,16 +363,17 @@ struct WinMMCapture final : public BackendBase { int captureProc(); - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; std::atomic mReadable{0u}; al::semaphore mSem; uint mIdx{0}; std::array mWaveBuffer{}; + al::vector mBuffer; HWAVEIN mInHdl{nullptr}; @@ -388,8 +383,6 @@ struct WinMMCapture final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WinMMCapture) }; WinMMCapture::~WinMMCapture() @@ -398,14 +391,11 @@ WinMMCapture::~WinMMCapture() if(mInHdl) waveInClose(mInHdl); mInHdl = nullptr; - - al_free(mWaveBuffer[0].lpData); - std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); } /* WinMMCapture::waveInProc * - * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is + * Posts a message to 'WinMMCapture::captureProc' every time a WaveIn Buffer is * completed and returns to the application (with more data). */ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept @@ -417,7 +407,7 @@ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) int WinMMCapture::captureProc() { - althrd_setname(RECORD_THREAD_NAME); + althrd_setname(GetRecordThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) @@ -434,7 +424,8 @@ int WinMMCapture::captureProc() WAVEHDR &waveHdr = mWaveBuffer[widx]; widx = (widx+1) % mWaveBuffer.size(); - mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign); + std::ignore = mRing->write(waveHdr.lpData, + waveHdr.dwBytesRecorded / mFormat.nBlockAlign); mReadable.fetch_sub(1, std::memory_order_acq_rel); waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); @@ -445,18 +436,18 @@ int WinMMCapture::captureProc() } -void WinMMCapture::open(const char *name) +void WinMMCapture::open(std::string_view name) { if(CaptureDevices.empty()) ProbeCaptureDevices(); // Find the Device ID matching the deviceName if valid - auto iter = name ? + auto iter = !name.empty() ? std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : CaptureDevices.cbegin(); if(iter == CaptureDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; auto DeviceID = static_cast(std::distance(CaptureDevices.cbegin(), iter)); switch(mDevice->FmtChans) @@ -470,6 +461,7 @@ void WinMMCapture::open(const char *name) case DevFmtX61: case DevFmtX71: case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", @@ -513,14 +505,14 @@ void WinMMCapture::open(const char *name) // Allocate circular memory buffer for the captured audio // Make sure circular buffer is at least 100ms in size - uint CapturedDataSize{mDevice->BufferSize}; - CapturedDataSize = static_cast(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size())); + const auto CapturedDataSize = std::max(mDevice->BufferSize, + BufferSize*mWaveBuffer.size()); mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false); - al_free(mWaveBuffer[0].lpData); + decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer); mWaveBuffer[0] = WAVEHDR{}; - mWaveBuffer[0].lpData = static_cast(al_calloc(16, BufferSize * mWaveBuffer.size())); + mWaveBuffer[0].lpData = mBuffer.data(); mWaveBuffer[0].dwBufferLength = BufferSize; for(size_t i{1};i < mWaveBuffer.size();++i) { @@ -571,8 +563,8 @@ void WinMMCapture::stop() mIdx = 0; } -void WinMMCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void WinMMCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint WinMMCapture::availableSamples() { return static_cast(mRing->readSpace()); } @@ -586,26 +578,23 @@ bool WinMMBackendFactory::init() bool WinMMBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string WinMMBackendFactory::probe(BackendType type) +auto WinMMBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto add_device = [&outnames](const std::string &dname) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - if(!dname.empty()) - outnames.append(dname.c_str(), dname.length()+1); - }; + { if(!dname.empty()) outnames.emplace_back(dname); }; + switch(type) { case BackendType::Playback: ProbePlaybackDevices(); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: ProbeCaptureDevices(); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } diff --git a/Engine/lib/openal-soft/alc/backends/winmm.h b/Engine/lib/openal-soft/alc/backends/winmm.h index 45a706aa3..51ce432ca 100644 --- a/Engine/lib/openal-soft/alc/backends/winmm.h +++ b/Engine/lib/openal-soft/alc/backends/winmm.h @@ -5,15 +5,15 @@ struct WinMMBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WINMM_H */ diff --git a/Engine/lib/openal-soft/alc/context.cpp b/Engine/lib/openal-soft/alc/context.cpp index e02c549b3..2b7a9ea9d 100644 --- a/Engine/lib/openal-soft/alc/context.cpp +++ b/Engine/lib/openal-soft/alc/context.cpp @@ -4,21 +4,28 @@ #include "context.h" #include +#include +#include +#include #include #include #include -#include #include +#include +#include #include "AL/efx.h" #include "al/auxeffectslot.h" +#include "al/debug.h" #include "al/source.h" #include "al/effect.h" #include "al/event.h" #include "al/listener.h" #include "albit.h" #include "alc/alu.h" +#include "alc/backends/base.h" +#include "alspan.h" #include "core/async_event.h" #include "core/device.h" #include "core/effectslot.h" @@ -26,63 +33,68 @@ #include "core/voice.h" #include "core/voice_change.h" #include "device.h" +#include "flexarray.h" #include "ringbuffer.h" #include "vecmat.h" #ifdef ALSOFT_EAX -#include -#include "alstring.h" #include "al/eax/globals.h" #endif // ALSOFT_EAX namespace { -using namespace std::placeholders; - +using namespace std::string_view_literals; using voidp = void*; /* Default context extensions */ -constexpr ALchar alExtList[] = - "AL_EXT_ALAW " - "AL_EXT_BFORMAT " - "AL_EXT_DOUBLE " - "AL_EXT_EXPONENT_DISTANCE " - "AL_EXT_FLOAT32 " - "AL_EXT_IMA4 " - "AL_EXT_LINEAR_DISTANCE " - "AL_EXT_MCFORMATS " - "AL_EXT_MULAW " - "AL_EXT_MULAW_BFORMAT " - "AL_EXT_MULAW_MCFORMATS " - "AL_EXT_OFFSET " - "AL_EXT_source_distance_model " - "AL_EXT_SOURCE_RADIUS " - "AL_EXT_STATIC_BUFFER " - "AL_EXT_STEREO_ANGLES " - "AL_LOKI_quadriphonic " - "AL_SOFT_bformat_ex " - "AL_SOFTX_bformat_hoa " - "AL_SOFT_block_alignment " - "AL_SOFT_buffer_length_query " - "AL_SOFT_callback_buffer " - "AL_SOFTX_convolution_reverb " - "AL_SOFT_deferred_updates " - "AL_SOFT_direct_channels " - "AL_SOFT_direct_channels_remix " - "AL_SOFT_effect_target " - "AL_SOFT_events " - "AL_SOFT_gain_clamp_ex " - "AL_SOFTX_hold_on_disconnect " - "AL_SOFT_loop_points " - "AL_SOFTX_map_buffer " - "AL_SOFT_MSADPCM " - "AL_SOFT_source_latency " - "AL_SOFT_source_length " - "AL_SOFT_source_resampler " - "AL_SOFT_source_spatialize " - "AL_SOFT_source_start_delay " - "AL_SOFT_UHJ " - "AL_SOFT_UHJ_ex"; +std::vector getContextExtensions() noexcept +{ + return std::vector{ + "AL_EXT_ALAW"sv, + "AL_EXT_BFORMAT"sv, + "AL_EXT_debug"sv, + "AL_EXT_direct_context"sv, + "AL_EXT_DOUBLE"sv, + "AL_EXT_EXPONENT_DISTANCE"sv, + "AL_EXT_FLOAT32"sv, + "AL_EXT_IMA4"sv, + "AL_EXT_LINEAR_DISTANCE"sv, + "AL_EXT_MCFORMATS"sv, + "AL_EXT_MULAW"sv, + "AL_EXT_MULAW_BFORMAT"sv, + "AL_EXT_MULAW_MCFORMATS"sv, + "AL_EXT_OFFSET"sv, + "AL_EXT_source_distance_model"sv, + "AL_EXT_SOURCE_RADIUS"sv, + "AL_EXT_STATIC_BUFFER"sv, + "AL_EXT_STEREO_ANGLES"sv, + "AL_LOKI_quadriphonic"sv, + "AL_SOFT_bformat_ex"sv, + "AL_SOFTX_bformat_hoa"sv, + "AL_SOFT_block_alignment"sv, + "AL_SOFT_buffer_length_query"sv, + "AL_SOFT_callback_buffer"sv, + "AL_SOFTX_convolution_effect"sv, + "AL_SOFT_deferred_updates"sv, + "AL_SOFT_direct_channels"sv, + "AL_SOFT_direct_channels_remix"sv, + "AL_SOFT_effect_target"sv, + "AL_SOFT_events"sv, + "AL_SOFT_gain_clamp_ex"sv, + "AL_SOFTX_hold_on_disconnect"sv, + "AL_SOFT_loop_points"sv, + "AL_SOFTX_map_buffer"sv, + "AL_SOFT_MSADPCM"sv, + "AL_SOFT_source_latency"sv, + "AL_SOFT_source_length"sv, + "AL_SOFTX_source_panning"sv, + "AL_SOFT_source_resampler"sv, + "AL_SOFT_source_spatialize"sv, + "AL_SOFT_source_start_delay"sv, + "AL_SOFT_UHJ"sv, + "AL_SOFT_UHJ_ex"sv, + }; +} } // namespace @@ -90,10 +102,9 @@ constexpr ALchar alExtList[] = std::atomic ALCcontext::sGlobalContextLock{false}; std::atomic ALCcontext::sGlobalContext{nullptr}; -thread_local ALCcontext *ALCcontext::sLocalContext{nullptr}; ALCcontext::ThreadCtx::~ThreadCtx() { - if(ALCcontext *ctx{ALCcontext::sLocalContext}) + if(ALCcontext *ctx{std::exchange(ALCcontext::sLocalContext, nullptr)}) { const bool result{ctx->releaseIfNoDelete()}; ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx}, @@ -105,23 +116,18 @@ thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext; ALeffect ALCcontext::sDefaultEffect; -#ifdef __MINGW32__ -ALCcontext *ALCcontext::getThreadContext() noexcept -{ return sLocalContext; } -void ALCcontext::setThreadContext(ALCcontext *context) noexcept -{ sThreadContext.set(context); } -#endif - -ALCcontext::ALCcontext(al::intrusive_ptr device) - : ContextBase{device.get()}, mALDevice{std::move(device)} +ALCcontext::ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags) + : ContextBase{device.get()}, mALDevice{std::move(device)}, mContextFlags{flags} { + mDebugGroups.emplace_back(DebugSource::Other, 0, std::string{}); + mDebugEnabled.store(mContextFlags.test(ContextFlags::DebugBit), std::memory_order_relaxed); } ALCcontext::~ALCcontext() { TRACE("Freeing context %p\n", voidp{this}); - size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), size_t{0u}, + size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), 0_uz, [](size_t cur, const SourceSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; if(count > 0) @@ -134,7 +140,7 @@ ALCcontext::~ALCcontext() #endif // ALSOFT_EAX mDefaultSlot = nullptr; - count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u}, + count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), 0_uz, [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) @@ -151,16 +157,17 @@ void ALCcontext::init() aluInitEffectPanning(mDefaultSlot->mSlot, this); } - EffectSlotArray *auxslots; + std::unique_ptr auxslots; if(!mDefaultSlot) auxslots = EffectSlot::CreatePtrArray(0); else { - auxslots = EffectSlot::CreatePtrArray(1); + auxslots = EffectSlot::CreatePtrArray(2); (*auxslots)[0] = mDefaultSlot->mSlot; + (*auxslots)[1] = mDefaultSlot->mSlot; mDefaultSlot->mState = SlotState::Playing; } - mActiveAuxSlots.store(auxslots, std::memory_order_relaxed); + mActiveAuxSlots.store(std::move(auxslots), std::memory_order_relaxed); allocVoiceChanges(); { @@ -170,26 +177,41 @@ void ALCcontext::init() mCurrentVoiceChange.store(cur, std::memory_order_relaxed); } - mExtensionList = alExtList; + mExtensions = getContextExtensions(); if(sBufferSubDataCompat) { - std::string extlist{mExtensionList}; - - const auto pos = extlist.find("AL_EXT_SOURCE_RADIUS "); - if(pos != std::string::npos) - extlist.replace(pos, 20, "AL_SOFT_buffer_sub_data"); - else - extlist += " AL_SOFT_buffer_sub_data"; - - mExtensionListOverride = std::move(extlist); - mExtensionList = mExtensionListOverride.c_str(); + auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS"sv); + if(iter != mExtensions.end()) mExtensions.erase(iter); + /* TODO: Would be nice to sort this alphabetically. Needs case- + * insensitive searching. + */ + mExtensions.emplace_back("AL_SOFT_buffer_sub_data"sv); } #ifdef ALSOFT_EAX eax_initialize_extensions(); #endif // ALSOFT_EAX + if(!mExtensions.empty()) + { + const size_t len{std::accumulate(mExtensions.cbegin()+1, mExtensions.cend(), + mExtensions.front().length(), + [](size_t current, std::string_view ext) noexcept + { return current + ext.length() + 1; })}; + + std::string extensions; + extensions.reserve(len); + extensions += mExtensions.front(); + for(std::string_view ext : al::span{mExtensions}.subspan<1>()) + { + extensions += ' '; + extensions += ext; + } + + mExtensionsString = std::move(extensions); + } + mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f}; mParams.Matrix = alu::Matrix::Identity(); mParams.Velocity = alu::Vector{}; @@ -202,7 +224,7 @@ void ALCcontext::init() mParams.mDistanceModel = mDistanceModel; - mAsyncEvents = RingBuffer::Create(511, sizeof(AsyncEvent), false); + mAsyncEvents = RingBuffer::Create(1024, sizeof(AsyncEvent), false); StartEventThrd(this); @@ -210,7 +232,7 @@ void ALCcontext::init() mActiveVoiceCount.store(64, std::memory_order_relaxed); } -bool ALCcontext::deinit() +void ALCcontext::deinit() { if(sLocalContext == this) { @@ -230,18 +252,14 @@ bool ALCcontext::deinit() dec_ref(); } - bool ret{}; + bool stopPlayback{}; /* First make sure this context exists in the device's list. */ auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire); if(auto toremove = static_cast(std::count(oldarray->begin(), oldarray->end(), this))) { using ContextArray = al::FlexArray; - auto alloc_ctx_array = [](const size_t count) -> ContextArray* - { - if(count == 0) return &DeviceBase::sEmptyContextArray; - return ContextArray::Create(count).release(); - }; - auto *newarray = alloc_ctx_array(oldarray->size() - toremove); + const size_t newsize{oldarray->size() - toremove}; + auto newarray = ContextArray::Create(newsize); /* Copy the current/old context handles to the new array, excluding the * given context. @@ -252,21 +270,21 @@ bool ALCcontext::deinit() /* Store the new context array in the device. Wait for any current mix * to finish before deleting the old array. */ - mDevice->mContexts.store(newarray); - if(oldarray != &DeviceBase::sEmptyContextArray) - { - mDevice->waitForMix(); - delete oldarray; - } + auto prevarray = mDevice->mContexts.exchange(std::move(newarray)); + std::ignore = mDevice->waitForMix(); - ret = !newarray->empty(); + stopPlayback = (newsize == 0); } else - ret = !oldarray->empty(); + stopPlayback = oldarray->empty(); StopEventThrd(this); - return ret; + if(stopPlayback && mALDevice->mDeviceState == DeviceState::Playing) + { + mALDevice->Backend->stop(); + mALDevice->mDeviceState = DeviceState::Configured; + } } void ALCcontext::applyAllUpdates() @@ -295,6 +313,7 @@ void ALCcontext::applyAllUpdates() mHoldUpdates.store(false, std::memory_order_release); } + #ifdef ALSOFT_EAX namespace { @@ -306,10 +325,10 @@ void ForEachSource(ALCcontext *context, F func) uint64_t usemask{~sublist.FreeMask}; while(usemask) { - const int idx{al::countr_zero(usemask)}; + const auto idx = static_cast(al::countr_zero(usemask)); usemask &= ~(1_u64 << idx); - func(sublist.Sources[idx]); + func((*sublist.Sources)[idx]); } } } @@ -447,43 +466,15 @@ void ALCcontext::eax_initialize_extensions() if(!eax_g_is_enabled) return; - const auto string_max_capacity = - std::strlen(mExtensionList) + 1 + - std::strlen(eax1_ext_name) + 1 + - std::strlen(eax2_ext_name) + 1 + - std::strlen(eax3_ext_name) + 1 + - std::strlen(eax4_ext_name) + 1 + - std::strlen(eax5_ext_name) + 1 + - std::strlen(eax_x_ram_ext_name) + 1; - - std::string extlist; - extlist.reserve(string_max_capacity); - + mExtensions.emplace(mExtensions.begin(), "EAX-RAM"sv); if(eaxIsCapable()) { - extlist += eax1_ext_name; - extlist += ' '; - - extlist += eax2_ext_name; - extlist += ' '; - - extlist += eax3_ext_name; - extlist += ' '; - - extlist += eax4_ext_name; - extlist += ' '; - - extlist += eax5_ext_name; - extlist += ' '; + mExtensions.emplace(mExtensions.begin(), "EAX5.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX4.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX3.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX2.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX"sv); } - - extlist += eax_x_ram_ext_name; - extlist += ' '; - - extlist += mExtensionList; - - mExtensionListOverride = std::move(extlist); - mExtensionList = mExtensionListOverride.c_str(); } void ALCcontext::eax_initialize() @@ -555,10 +546,11 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const case DevFmtX51: return SPEAKERS_5; case DevFmtX61: return SPEAKERS_6; case DevFmtX71: return SPEAKERS_7; - /* 7.1.4 is compatible with 7.1. This could instead be HEADPHONES to + /* 7.1.4(.4) is compatible with 7.1. This could instead be HEADPHONES to * suggest with-height surround sound (like HRTF). */ case DevFmtX714: return SPEAKERS_7; + case DevFmtX7144: return SPEAKERS_7; /* 3D7.1 is only compatible with 5.1. This could instead be HEADPHONES to * suggest full-sphere surround sound (like HRTF). */ @@ -582,7 +574,7 @@ void ALCcontext::eax_update_speaker_configuration() void ALCcontext::eax_set_last_error_defaults() noexcept { - mEaxLastError = EAX_OK; + mEaxLastError = EAXCONTEXT_DEFAULTLASTERROR; } void ALCcontext::eax_session_set_defaults() noexcept @@ -671,6 +663,7 @@ void ALCcontext::eax_get_misc(const EaxCall& call) break; case EAXCONTEXT_LASTERROR: call.set_value(mEaxLastError); + mEaxLastError = EAX_OK; break; case EAXCONTEXT_SPEAKERCONFIG: call.set_value(mEaxSpeakerConfig); @@ -1018,88 +1011,49 @@ void ALCcontext::eaxCommit() eax_update_sources(); } -namespace { -class EaxSetException : public EaxException { -public: - explicit EaxSetException(const char* message) - : EaxException{"EAX_SET", message} - {} -}; - -[[noreturn]] void eax_fail_set(const char* message) -{ - throw EaxSetException{message}; -} - -class EaxGetException : public EaxException { -public: - explicit EaxGetException(const char* message) - : EaxException{"EAX_GET", message} - {} -}; - -[[noreturn]] void eax_fail_get(const char* message) -{ - throw EaxGetException{message}; -} - -} // namespace - - -FORCE_ALIGN ALenum AL_APIENTRY EAXSet( - const GUID* property_set_id, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_value, - ALuint property_value_size) noexcept -try +FORCE_ALIGN auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id, + ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum { auto context = GetContextRef(); - - if(!context) - eax_fail_set("No current context."); - - std::lock_guard prop_lock{context->mPropLock}; - - return context->eax_eax_set( - property_set_id, - property_id, - property_source_id, - property_value, - property_value_size); + if(!context) UNLIKELY return AL_INVALID_OPERATION; + return EAXSetDirect(context.get(), property_set_id, property_id, source_id, value, value_size); } -catch (...) + +FORCE_ALIGN auto AL_APIENTRY EAXSetDirect(ALCcontext *context, const GUID *property_set_id, + ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum +try { - eax_log_exception(__func__); + std::lock_guard prop_lock{context->mPropLock}; + return context->eax_eax_set(property_set_id, property_id, source_id, value, value_size); +} +catch(...) +{ + context->eaxSetLastError(); + eax_log_exception(std::data(__func__)); return AL_INVALID_OPERATION; } -FORCE_ALIGN ALenum AL_APIENTRY EAXGet( - const GUID* property_set_id, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_value, - ALuint property_value_size) noexcept -try + +FORCE_ALIGN auto AL_APIENTRY EAXGet(const GUID *property_set_id, ALuint property_id, + ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum { auto context = GetContextRef(); - - if(!context) - eax_fail_get("No current context."); - - std::lock_guard prop_lock{context->mPropLock}; - - return context->eax_eax_get( - property_set_id, - property_id, - property_source_id, - property_value, - property_value_size); + if(!context) UNLIKELY return AL_INVALID_OPERATION; + return EAXGetDirect(context.get(), property_set_id, property_id, source_id, value, value_size); } -catch (...) + +FORCE_ALIGN auto AL_APIENTRY EAXGetDirect(ALCcontext *context, const GUID *property_set_id, + ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum +try { - eax_log_exception(__func__); + std::lock_guard prop_lock{context->mPropLock}; + return context->eax_eax_get(property_set_id, property_id, source_id, value, value_size); +} +catch(...) +{ + context->eaxSetLastError(); + eax_log_exception(std::data(__func__)); return AL_INVALID_OPERATION; } #endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/alc/context.h b/Engine/lib/openal-soft/alc/context.h index e8efdbf1d..e1437fb31 100644 --- a/Engine/lib/openal-soft/alc/context.h +++ b/Engine/lib/openal-soft/alc/context.h @@ -1,11 +1,17 @@ #ifndef ALC_CONTEXT_H #define ALC_CONTEXT_H +#include #include +#include +#include #include #include -#include +#include +#include +#include #include +#include #include "AL/al.h" #include "AL/alc.h" @@ -14,10 +20,11 @@ #include "al/listener.h" #include "almalloc.h" #include "alnumeric.h" +#include "althreads.h" #include "atomic.h" #include "core/context.h" +#include "inprogext.h" #include "intrusive_ptr.h" -#include "vector.h" #ifdef ALSOFT_EAX #include "al/eax/call.h" @@ -30,40 +37,40 @@ struct ALeffect; struct ALeffectslot; struct ALsource; +struct DebugGroup; +struct EffectSlotSubList; +struct SourceSubList; + +enum class DebugSource : std::uint8_t; +enum class DebugType : std::uint8_t; +enum class DebugSeverity : std::uint8_t; using uint = unsigned int; -struct SourceSubList { - uint64_t FreeMask{~0_u64}; - ALsource *Sources{nullptr}; /* 64 */ +enum ContextFlags { + DebugBit = 0, /* ALC_CONTEXT_DEBUG_BIT_EXT */ +}; +using ContextFlagBitset = std::bitset; - 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 DebugLogEntry { + const DebugSource mSource; + const DebugType mType; + const DebugSeverity mSeverity; + const uint mId; + + std::string mMessage; + + template + DebugLogEntry(DebugSource source, DebugType type, uint id, DebugSeverity severity, T&& message) + : mSource{source}, mType{type}, mSeverity{severity}, mId{id} + , mMessage{std::forward(message)} + { } + DebugLogEntry(const DebugLogEntry&) = default; + DebugLogEntry(DebugLogEntry&&) = default; }; -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, ContextBase { const al::intrusive_ptr mALDevice; @@ -74,7 +81,10 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { std::mutex mPropLock; - std::atomic mLastError{AL_NO_ERROR}; + al::tss mLastThreadError{AL_NO_ERROR}; + + const ContextFlagBitset mContextFlags; + std::atomic mDebugEnabled{false}; DistanceModel mDistanceModel{DistanceModel::Default}; bool mSourceDistanceModel{false}; @@ -88,25 +98,32 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { ALEVENTPROCSOFT mEventCb{}; void *mEventParam{nullptr}; + std::mutex mDebugCbLock; + ALDEBUGPROCEXT mDebugCb{}; + void *mDebugParam{nullptr}; + std::vector mDebugGroups; + std::deque mDebugLog; + ALlistener mListener{}; - al::vector mSourceList; + std::vector mSourceList; ALuint mNumSources{0}; std::mutex mSourceLock; - al::vector mEffectSlotList; + std::vector mEffectSlotList; ALuint mNumEffectSlots{0u}; std::mutex mEffectSlotLock; /* Default effect slot */ std::unique_ptr mDefaultSlot; - const char *mExtensionList{nullptr}; + std::vector mExtensions; + std::string mExtensionsString{}; - std::string mExtensionListOverride{}; + std::unordered_map mSourceNames; + std::unordered_map mEffectSlotNames; - - ALCcontext(al::intrusive_ptr device); + ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags); ALCcontext(const ALCcontext&) = delete; ALCcontext& operator=(const ALCcontext&) = delete; ~ALCcontext(); @@ -114,10 +131,10 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { void init(); /** * Removes the context from its device and removes it from being current on - * the running thread or globally. Returns true if other contexts still - * exist on the device. + * the running thread or globally. Stops device playback if this was the + * last context on its device. */ - bool deinit(); + void deinit(); /** * Defers/suspends updates for the given context's listener and sources. @@ -142,20 +159,32 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { */ void applyAllUpdates(); -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf, 3, 4)]] +#ifdef __MINGW32__ + [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] #else [[gnu::format(printf, 3, 4)]] #endif void setError(ALenum errorCode, const char *msg, ...); + void sendDebugMessage(std::unique_lock &debuglock, DebugSource source, + DebugType type, ALuint id, DebugSeverity severity, std::string_view message); + + void debugMessage(DebugSource source, DebugType type, ALuint id, DebugSeverity severity, + std::string_view message) + { + if(!mDebugEnabled.load(std::memory_order_relaxed)) LIKELY + return; + std::unique_lock debuglock{mDebugCbLock}; + sendDebugMessage(debuglock, source, type, id, severity, message); + } + /* Process-wide current context */ static std::atomic sGlobalContextLock; static std::atomic sGlobalContext; private: /* Thread-local current context. */ - static thread_local ALCcontext *sLocalContext; + static inline thread_local ALCcontext *sLocalContext{}; /* Thread-local context handling. This handles attempting to release the * context which may have been left current when the thread is destroyed. @@ -163,30 +192,25 @@ private: class ThreadCtx { public: ~ThreadCtx(); + /* NOLINTBEGIN(readability-convert-member-functions-to-static) + * This should be non-static to invoke construction of the thread-local + * sThreadContext, so that it's destructor gets run at thread exit to + * clear sLocalContext (which isn't a member variable to make read + * access efficient). + */ void set(ALCcontext *ctx) const noexcept { sLocalContext = ctx; } + /* NOLINTEND(readability-convert-member-functions-to-static) */ }; static thread_local ThreadCtx sThreadContext; public: - /* HACK: MinGW generates bad code when accessing an extern thread_local - * object. Add a wrapper function for it that only accesses it where it's - * defined. - */ -#ifdef __MINGW32__ - static ALCcontext *getThreadContext() noexcept; - static void setThreadContext(ALCcontext *context) noexcept; -#else static ALCcontext *getThreadContext() noexcept { return sLocalContext; } static void setThreadContext(ALCcontext *context) noexcept { sThreadContext.set(context); } -#endif /* Default effect that applies to sources that don't have an effect on send 0. */ static ALeffect sDefaultEffect; - DEF_NEWDEL(ALCcontext) - #ifdef ALSOFT_EAX -public: bool hasEax() const noexcept { return mEaxIsInitialized; } bool eaxIsCapable() const noexcept; @@ -430,7 +454,7 @@ private: typename TMemberResult, typename TProps, typename TState> - void eax_defer(const EaxCall& call, TState& state, TMemberResult TProps::*member) noexcept + void eax_defer(const EaxCall& call, TState& state, TMemberResult TProps::*member) { const auto& src = call.get_value(); TValidator{}(src); @@ -513,28 +537,20 @@ private: using ContextRef = al::intrusive_ptr; -ContextRef GetContextRef(void); +ContextRef GetContextRef() noexcept; void UpdateContextProps(ALCcontext *context); -extern bool TrapALError; +inline bool TrapALError{false}; #ifdef ALSOFT_EAX -ALenum AL_APIENTRY EAXSet( - const GUID* property_set_id, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_value, - ALuint property_value_size) noexcept; +auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id, + ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum; -ALenum AL_APIENTRY EAXGet( - const GUID* property_set_id, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_value, - ALuint property_value_size) noexcept; +auto AL_APIENTRY EAXGet(const GUID *property_set_id, ALuint property_id, + ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum; #endif // ALSOFT_EAX #endif /* ALC_CONTEXT_H */ diff --git a/Engine/lib/openal-soft/alc/device.cpp b/Engine/lib/openal-soft/alc/device.cpp index 66b13c5e4..86fddcabd 100644 --- a/Engine/lib/openal-soft/alc/device.cpp +++ b/Engine/lib/openal-soft/alc/device.cpp @@ -3,19 +3,22 @@ #include "device.h" +#include +#include #include -#include +#include "al/buffer.h" +#include "al/effect.h" +#include "al/filter.h" #include "albit.h" -#include "alconfig.h" +#include "alnumeric.h" +#include "atomic.h" #include "backends/base.h" -#include "core/bformatdec.h" -#include "core/bs2b.h" -#include "core/front_stablizer.h" +#include "core/devformat.h" #include "core/hrtf.h" #include "core/logging.h" #include "core/mastering.h" -#include "core/uhjfilter.h" +#include "flexarray.h" namespace { @@ -34,19 +37,19 @@ ALCdevice::~ALCdevice() Backend = nullptr; - size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), size_t{0u}, + size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), 0_uz, [](size_t cur, const BufferSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; if(count > 0) WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s"); - count = std::accumulate(EffectList.cbegin(), EffectList.cend(), size_t{0u}, + count = std::accumulate(EffectList.cbegin(), EffectList.cend(), 0_uz, [](size_t cur, const EffectSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s"); - count = std::accumulate(FilterList.cbegin(), FilterList.cend(), size_t{0u}, + count = std::accumulate(FilterList.cbegin(), FilterList.cend(), 0_uz, [](size_t cur, const FilterSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) @@ -55,8 +58,8 @@ ALCdevice::~ALCdevice() void ALCdevice::enumerateHrtfs() { - mHrtfList = EnumerateHrtf(configValue(nullptr, "hrtf-paths")); - if(auto defhrtfopt = configValue(nullptr, "default-hrtf")) + mHrtfList = EnumerateHrtf(configValue({}, "hrtf-paths")); + if(auto defhrtfopt = configValue({}, "default-hrtf")) { auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt); if(iter == mHrtfList.end()) @@ -85,6 +88,7 @@ auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 case DevFmtX61: return OutputMode1::X61; case DevFmtX71: return OutputMode1::X71; case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: break; diff --git a/Engine/lib/openal-soft/alc/device.h b/Engine/lib/openal-soft/alc/device.h index ef50f53e7..47a5513f8 100644 --- a/Engine/lib/openal-soft/alc/device.h +++ b/Engine/lib/openal-soft/alc/device.h @@ -4,79 +4,32 @@ #include #include #include -#include +#include #include -#include +#include +#include +#include +#include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" #include "core/device.h" -#include "inprogext.h" #include "intrusive_ptr.h" -#include "vector.h" #ifdef ALSOFT_EAX #include "al/eax/x_ram.h" #endif // ALSOFT_EAX -struct ALbuffer; -struct ALeffect; -struct ALfilter; struct BackendBase; +struct BufferSubList; +struct EffectSubList; +struct FilterSubList; using uint = unsigned int; -struct BufferSubList { - uint64_t FreeMask{~0_u64}; - ALbuffer *Buffers{nullptr}; /* 64 */ - - BufferSubList() noexcept = default; - BufferSubList(const BufferSubList&) = delete; - BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} - { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } - ~BufferSubList(); - - BufferSubList& operator=(const BufferSubList&) = delete; - BufferSubList& operator=(BufferSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } -}; - -struct EffectSubList { - uint64_t FreeMask{~0_u64}; - ALeffect *Effects{nullptr}; /* 64 */ - - EffectSubList() noexcept = default; - EffectSubList(const EffectSubList&) = delete; - EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} - { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } - ~EffectSubList(); - - EffectSubList& operator=(const EffectSubList&) = delete; - EffectSubList& operator=(EffectSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } -}; - -struct FilterSubList { - uint64_t FreeMask{~0_u64}; - ALfilter *Filters{nullptr}; /* 64 */ - - FilterSubList() noexcept = default; - FilterSubList(const FilterSubList&) = delete; - FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} - { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } - ~FilterSubList(); - - FilterSubList& operator=(const FilterSubList&) = delete; - FilterSubList& operator=(FilterSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } -}; - - struct ALCdevice : public al::intrusive_ref, DeviceBase { /* This lock protects the device state (format, update size, etc) from * being from being changed in multiple threads, or being accessed while @@ -94,7 +47,7 @@ struct ALCdevice : public al::intrusive_ref, DeviceBase { uint AuxiliaryEffectSlotMax{}; std::string mHrtfName; - al::vector mHrtfList; + std::vector mHrtfList; ALCenum mHrtfStatus{ALC_FALSE}; enum class OutputMode1 : ALCenum { @@ -117,49 +70,54 @@ struct ALCdevice : public al::intrusive_ref, DeviceBase { // Map of Buffers for this device std::mutex BufferLock; - al::vector BufferList; + std::vector BufferList; // Map of Effects for this device std::mutex EffectLock; - al::vector EffectList; + std::vector EffectList; // Map of Filters for this device std::mutex FilterLock; - al::vector FilterList; + std::vector FilterList; #ifdef ALSOFT_EAX ALuint eax_x_ram_free_size{eax_x_ram_max_size}; #endif // ALSOFT_EAX + std::unordered_map mBufferNames; + std::unordered_map mEffectNames; + std::unordered_map mFilterNames; + ALCdevice(DeviceType type); ~ALCdevice(); void enumerateHrtfs(); - bool getConfigValueBool(const char *block, const char *key, bool def) - { return GetConfigValueBool(DeviceName.c_str(), block, key, def); } + bool getConfigValueBool(const std::string_view block, const std::string_view key, bool def) + { return GetConfigValueBool(DeviceName, block, key, def); } template - inline al::optional configValue(const char *block, const char *key) = delete; - - DEF_NEWDEL(ALCdevice) + inline std::optional configValue(const std::string_view block, const std::string_view key) = delete; }; template<> -inline al::optional ALCdevice::configValue(const char *block, const char *key) -{ return ConfigValueStr(DeviceName.c_str(), block, key); } +inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) +{ return ConfigValueStr(DeviceName, block, key); } template<> -inline al::optional ALCdevice::configValue(const char *block, const char *key) -{ return ConfigValueInt(DeviceName.c_str(), block, key); } +inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) +{ return ConfigValueInt(DeviceName, block, key); } template<> -inline al::optional ALCdevice::configValue(const char *block, const char *key) -{ return ConfigValueUInt(DeviceName.c_str(), block, key); } +inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) +{ return ConfigValueUInt(DeviceName, block, key); } template<> -inline al::optional ALCdevice::configValue(const char *block, const char *key) -{ return ConfigValueFloat(DeviceName.c_str(), block, key); } +inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) +{ return ConfigValueFloat(DeviceName, block, key); } template<> -inline al::optional ALCdevice::configValue(const char *block, const char *key) -{ return ConfigValueBool(DeviceName.c_str(), block, key); } +inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) +{ return ConfigValueBool(DeviceName, block, key); } + +/** Stores the latest ALC device error. */ +void alcSetError(ALCdevice *device, ALCenum errorCode); #endif diff --git a/Engine/lib/openal-soft/alc/effects/autowah.cpp b/Engine/lib/openal-soft/alc/effects/autowah.cpp index 4f874ef29..b208996e7 100644 --- a/Engine/lib/openal-soft/alc/effects/autowah.cpp +++ b/Engine/lib/openal-soft/alc/effects/autowah.cpp @@ -22,24 +22,24 @@ #include #include +#include #include -#include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "intrusive_ptr.h" +struct BufferStorage; namespace { @@ -50,35 +50,37 @@ constexpr float QFactor{5.0f}; struct AutowahState final : public EffectState { /* Effect parameters */ - float mAttackRate; - float mReleaseRate; - float mResonanceGain; - float mPeakGain; - float mFreqMinNorm; - float mBandwidthNorm; - float mEnvDelay; + float mAttackRate{}; + float mReleaseRate{}; + float mResonanceGain{}; + float mPeakGain{}; + float mFreqMinNorm{}; + float mBandwidthNorm{}; + float mEnvDelay{}; /* Filter components derived from the envelope. */ - struct { - float cos_w0; - float alpha; - } mEnv[BufferLineSize]; + struct FilterParam { + float cos_w0{}; + float alpha{}; + }; + std::array mEnv; - struct { + struct ChannelData { uint mTargetChannel{InvalidChannelIndex}; - /* Effect filters' history. */ - struct { - float z1, z2; - } mFilter; + struct FilterHistory { + float z1{}, z2{}; + }; + FilterHistory mFilter; /* Effect gains for each output channel */ - float mCurrentGain; - float mTargetGain; - } mChans[MaxAmbiChannels]; + float mCurrentGain{}; + float mTargetGain{}; + }; + std::array mChans; /* Effects buffers */ - alignas(16) float mBufferOut[BufferLineSize]; + alignas(16) FloatBufferLine mBufferOut{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -86,8 +88,6 @@ struct AutowahState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(AutowahState) }; void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -118,18 +118,19 @@ void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*) } void AutowahState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; const auto frequency = static_cast(device->Frequency); - const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)}; + const float ReleaseTime{std::clamp(props.ReleaseTime, 0.001f, 1.0f)}; - mAttackRate = std::exp(-1.0f / (props->Autowah.AttackTime*frequency)); + mAttackRate = std::exp(-1.0f / (props.AttackTime*frequency)); mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency)); /* 0-20dB Resonance Peak gain */ - mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f); - mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain / GainScale); + mResonanceGain = std::sqrt(std::log10(props.Resonance)*10.0f / 3.0f); + mPeakGain = 1.0f - std::log10(props.PeakGain / GainScale); mFreqMinNorm = MinFreq / frequency; mBandwidthNorm = (MaxFreq-MinFreq) / frequency; @@ -155,23 +156,22 @@ void AutowahState::process(const size_t samplesToDo, float env_delay{mEnvDelay}; for(size_t i{0u};i < samplesToDo;i++) { - float w0, sample, a; - /* Envelope follower described on the book: Audio Effects, Theory, * Implementation and Application. */ - sample = peak_gain * std::fabs(samplesIn[0][i]); - a = (sample > env_delay) ? attack_rate : release_rate; + const float sample{peak_gain * std::fabs(samplesIn[0][i])}; + const float a{(sample > env_delay) ? attack_rate : release_rate}; env_delay = lerpf(sample, env_delay, a); /* Calculate the cos and alpha components for this sample's filter. */ - w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * (al::numbers::pi_v*2.0f); + const float w0{std::min(bandwidth*env_delay + freq_min, 0.46f) * + (al::numbers::pi_v*2.0f)}; mEnv[i].cos_w0 = std::cos(w0); mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor); } mEnvDelay = env_delay; - auto chandata = std::begin(mChans); + auto chandata = mChans.begin(); for(const auto &insamples : samplesIn) { const size_t outidx{chandata->mTargetChannel}; @@ -194,18 +194,18 @@ void AutowahState::process(const size_t samplesToDo, { const float alpha{mEnv[i].alpha}; const float cos_w0{mEnv[i].cos_w0}; - float input, output; - float a[3], b[3]; - b[0] = 1.0f + alpha*res_gain; - b[1] = -2.0f * cos_w0; - b[2] = 1.0f - alpha*res_gain; - a[0] = 1.0f + alpha/res_gain; - a[1] = -2.0f * cos_w0; - a[2] = 1.0f - alpha/res_gain; + const std::array b{ + 1.0f + alpha*res_gain, + -2.0f * cos_w0, + 1.0f - alpha*res_gain}; + const std::array a{ + 1.0f + alpha/res_gain, + -2.0f * cos_w0, + 1.0f - alpha/res_gain}; - input = insamples[i]; - output = input*(b[0]/a[0]) + z1; + const float input{insamples[i]}; + const float 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; @@ -214,8 +214,8 @@ void AutowahState::process(const size_t samplesToDo, chandata->mFilter.z2 = z2; /* Now, mix the processed sound data to the output. */ - MixSamples({mBufferOut, samplesToDo}, samplesOut[outidx].data(), chandata->mCurrentGain, - chandata->mTargetGain, samplesToDo); + MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut[outidx], + chandata->mCurrentGain, chandata->mTargetGain, samplesToDo); ++chandata; } } diff --git a/Engine/lib/openal-soft/alc/effects/base.h b/Engine/lib/openal-soft/alc/effects/base.h index 95695857a..a64880d26 100644 --- a/Engine/lib/openal-soft/alc/effects/base.h +++ b/Engine/lib/openal-soft/alc/effects/base.h @@ -4,23 +4,27 @@ #include "core/effects/base.h" -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); +/* This is a user config option for modifying the overall output of the reverb + * effect. + */ +inline float ReverbBoost{1.0f}; -EffectStateFactory *DedicatedStateFactory_getFactory(void); -EffectStateFactory *ConvolutionStateFactory_getFactory(void); +EffectStateFactory *NullStateFactory_getFactory(); +EffectStateFactory *ReverbStateFactory_getFactory(); +EffectStateFactory *ChorusStateFactory_getFactory(); +EffectStateFactory *AutowahStateFactory_getFactory(); +EffectStateFactory *CompressorStateFactory_getFactory(); +EffectStateFactory *DistortionStateFactory_getFactory(); +EffectStateFactory *EchoStateFactory_getFactory(); +EffectStateFactory *EqualizerStateFactory_getFactory(); +EffectStateFactory *FshifterStateFactory_getFactory(); +EffectStateFactory *ModulatorStateFactory_getFactory(); +EffectStateFactory *PshifterStateFactory_getFactory(); +EffectStateFactory* VmorpherStateFactory_getFactory(); + +EffectStateFactory *DedicatedStateFactory_getFactory(); + +EffectStateFactory *ConvolutionStateFactory_getFactory(); #endif /* EFFECTS_BASE_H */ diff --git a/Engine/lib/openal-soft/alc/effects/chorus.cpp b/Engine/lib/openal-soft/alc/effects/chorus.cpp index 10ccf9f6b..4e6d36c55 100644 --- a/Engine/lib/openal-soft/alc/effects/chorus.cpp +++ b/Engine/lib/openal-soft/alc/effects/chorus.cpp @@ -22,34 +22,44 @@ #include #include -#include +#include #include -#include +#include +#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" +#include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" +#include "core/cubic_tables.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "core/resampler_limits.h" #include "intrusive_ptr.h" #include "opthelpers.h" -#include "vector.h" +struct BufferStorage; namespace { using uint = unsigned int; +constexpr auto inv_sqrt2 = static_cast(1.0 / al::numbers::sqrt2); +constexpr auto lcoeffs_pw = CalcDirectionCoeffs(std::array{-1.0f, 0.0f, 0.0f}); +constexpr auto rcoeffs_pw = CalcDirectionCoeffs(std::array{ 1.0f, 0.0f, 0.0f}); +constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, inv_sqrt2}); +constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ inv_sqrt2, 0.0f, inv_sqrt2}); + + struct ChorusState final : public EffectState { - al::vector mDelayBuffer; + std::vector mDelayBuffer; uint mOffset{0}; uint mLfoOffset{0}; @@ -58,16 +68,17 @@ struct ChorusState final : public EffectState { uint mLfoDisp{0}; /* Calculated delays to apply to the left and right outputs. */ - uint mModDelays[2][BufferLineSize]; + std::array,2> mModDelays{}; /* Temp storage for the modulated left and right outputs. */ - alignas(16) float mBuffer[2][BufferLineSize]; + alignas(16) std::array mBuffer{}; /* Gains for left and right outputs. */ - struct { - float Current[MaxAmbiChannels]{}; - float Target[MaxAmbiChannels]{}; - } mGains[2]; + struct OutGains { + std::array Current{}; + std::array Target{}; + }; + std::array mGains; /* effect parameters */ ChorusWaveform mWaveform{}; @@ -78,66 +89,70 @@ struct ChorusState final : public EffectState { void calcTriangleDelays(const size_t todo); void calcSinusoidDelays(const size_t todo); - void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; - void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, - const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span samplesIn, - const al::span samplesOut) override; + void deviceUpdate(const DeviceBase *device, const float MaxDelay); + void update(const ContextBase *context, const EffectSlot *slot, const ChorusWaveform waveform, + const float delay, const float depth, const float feedback, const float rate, + int phase, const EffectTarget target); - DEF_NEWDEL(ChorusState) + void deviceUpdate(const DeviceBase *device, const BufferStorage*) final; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, + const EffectTarget target) final; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) final; }; + void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { - constexpr float max_delay{maxf(ChorusMaxDelay, FlangerMaxDelay)}; - + constexpr auto MaxDelay = std::max(ChorusMaxDelay, FlangerMaxDelay); const auto frequency = static_cast(Device->Frequency); - const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)}; + const size_t maxlen{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)}; if(maxlen != mDelayBuffer.size()) decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer); std::fill(mDelayBuffer.begin(), mDelayBuffer.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); + e.Current.fill(0.0f); + e.Target.fill(0.0f); } } -void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot, - const EffectProps *props, const EffectTarget target) +void ChorusState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props_, const EffectTarget target) { - constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits}; + static constexpr int mindelay{MaxResamplerEdge << gCubicTable.sTableBits}; + auto &props = std::get(*props_); /* The LFO depth is scaled to be relative to the sample delay. Clamp the * delay and depth to allow enough padding for resampling. */ - const DeviceBase *device{Context->mDevice}; + const DeviceBase *device{context->mDevice}; const auto frequency = static_cast(device->Frequency); - mWaveform = props->Chorus.Waveform; + mWaveform = props.Waveform; - mDelay = maxi(float2int(props->Chorus.Delay*frequency*MixerFracOne + 0.5f), mindelay); - mDepth = minf(props->Chorus.Depth * static_cast(mDelay), + const auto stepscale = float{frequency * gCubicTable.sTableSteps}; + mDelay = std::max(float2int(std::round(props.Delay * stepscale)), mindelay); + mDepth = std::min(static_cast(mDelay) * props.Depth, static_cast(mDelay - mindelay)); - mFeedback = props->Chorus.Feedback; + mFeedback = props.Feedback; /* Gains for left and right sides */ - static constexpr auto inv_sqrt2 = static_cast(1.0 / al::numbers::sqrt2); - static constexpr auto lcoeffs_pw = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}); - static constexpr auto rcoeffs_pw = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}); - static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs({-inv_sqrt2, 0.0f, inv_sqrt2}); - static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs({ inv_sqrt2, 0.0f, inv_sqrt2}); - auto &lcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? lcoeffs_nrml : lcoeffs_pw; - auto &rcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? rcoeffs_nrml : rcoeffs_pw; + const bool ispairwise{device->mRenderMode == RenderMode::Pairwise}; + const auto lcoeffs = (!ispairwise) ? al::span{lcoeffs_nrml} : al::span{lcoeffs_pw}; + const auto rcoeffs = (!ispairwise) ? al::span{rcoeffs_nrml} : al::span{rcoeffs_pw}; + /* Attenuate the outputs by -3dB, since we duplicate a single mono input to + * separate left/right outputs. + */ + const auto gain = slot->Gain * (1.0f/al::numbers::sqrt2_v); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target); - ComputePanGains(target.Main, rcoeffs.data(), Slot->Gain, mGains[1].Target); + ComputePanGains(target.Main, lcoeffs, gain, mGains[0].Target); + ComputePanGains(target.Main, rcoeffs, gain, mGains[1].Target); - float rate{props->Chorus.Rate}; - if(!(rate > 0.0f)) + if(!(props.Rate > 0.0f)) { mLfoOffset = 0; mLfoRange = 1; @@ -149,7 +164,9 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot, /* Calculate LFO coefficient (number of samples per cycle). Limit the * max range to avoid overflow when calculating the displacement. */ - uint lfo_range{float2uint(minf(frequency/rate + 0.5f, float{INT_MAX/360 - 180}))}; + static constexpr int range_limit{std::numeric_limits::max()/360 - 180}; + const auto range = std::round(frequency / props.Rate); + const uint lfo_range{float2uint(std::min(range, float{range_limit}))}; mLfoOffset = mLfoOffset * lfo_range / mLfoRange; mLfoRange = lfo_range; @@ -164,8 +181,8 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot, } /* Calculate lfo phase displacement */ - int phase{props->Chorus.Phase}; - if(phase < 0) phase = 360 + phase; + auto phase = props.Phase; + if(phase < 0) phase += 360; mLfoDisp = (mLfoRange*static_cast(phase) + 180) / 360; } } @@ -178,9 +195,6 @@ void ChorusState::calcTriangleDelays(const size_t todo) const float depth{mDepth}; const int delay{mDelay}; - ASSUME(lfo_range > 0); - ASSUME(todo > 0); - auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { const float offset_norm{static_cast(offset) * lfo_scale}; @@ -188,25 +202,24 @@ void ChorusState::calcTriangleDelays(const size_t todo) }; uint offset{mLfoOffset}; + ASSUME(lfo_range > offset); + auto ldelays = mModDelays[0].begin(); for(size_t i{0};i < todo;) { - size_t rem{minz(todo-i, lfo_range-offset)}; - do { - mModDelays[0][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } offset = (mLfoOffset+mLfoDisp) % lfo_range; + auto rdelays = mModDelays[1].begin(); for(size_t i{0};i < todo;) { - size_t rem{minz(todo-i, lfo_range-offset)}; - do { - mModDelays[1][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; @@ -219,9 +232,6 @@ void ChorusState::calcSinusoidDelays(const size_t todo) const float depth{mDepth}; const int delay{mDelay}; - ASSUME(lfo_range > 0); - ASSUME(todo > 0); - auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { const float offset_norm{static_cast(offset) * lfo_scale}; @@ -229,25 +239,24 @@ void ChorusState::calcSinusoidDelays(const size_t todo) }; uint offset{mLfoOffset}; + ASSUME(lfo_range > offset); + auto ldelays = mModDelays[0].begin(); for(size_t i{0};i < todo;) { - size_t rem{minz(todo-i, lfo_range-offset)}; - do { - mModDelays[0][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } offset = (mLfoOffset+mLfoDisp) % lfo_range; + auto rdelays = mModDelays[1].begin(); for(size_t i{0};i < todo;) { - size_t rem{minz(todo-i, lfo_range-offset)}; - do { - mModDelays[1][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; @@ -255,10 +264,10 @@ void ChorusState::calcSinusoidDelays(const size_t todo) void ChorusState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - const size_t bufmask{mDelayBuffer.size()-1}; + const auto delaybuf = al::span{mDelayBuffer}; + const size_t bufmask{delaybuf.size()-1}; const float feedback{mFeedback}; const uint avgdelay{(static_cast(mDelay) + MixerFracHalf) >> MixerFracBits}; - float *RESTRICT delaybuf{mDelayBuffer.data()}; uint offset{mOffset}; if(mWaveform == ChorusWaveform::Sinusoid) @@ -266,35 +275,39 @@ void ChorusState::process(const size_t samplesToDo, const al::span(mBuffer[0])}; - float *RESTRICT rbuffer{al::assume_aligned<16>(mBuffer[1])}; + const auto ldelays = al::span{mModDelays[0]}; + const auto rdelays = al::span{mModDelays[1]}; + const auto lbuffer = al::span{mBuffer[0]}; + const auto rbuffer = al::span{mBuffer[1]}; for(size_t i{0u};i < samplesToDo;++i) { // Feed the buffer's input first (necessary for delays < 1). delaybuf[offset&bufmask] = samplesIn[0][i]; // Tap for the left output. - uint delay{offset - (ldelays[i]>>MixerFracBits)}; - float mu{static_cast(ldelays[i]&MixerFracMask) * (1.0f/MixerFracOne)}; - lbuffer[i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); + size_t delay{offset - (ldelays[i] >> gCubicTable.sTableBits)}; + size_t phase{ldelays[i] & gCubicTable.sTableMask}; + lbuffer[i] = delaybuf[(delay+1) & bufmask]*gCubicTable.getCoeff0(phase) + + delaybuf[(delay ) & bufmask]*gCubicTable.getCoeff1(phase) + + delaybuf[(delay-1) & bufmask]*gCubicTable.getCoeff2(phase) + + delaybuf[(delay-2) & bufmask]*gCubicTable.getCoeff3(phase); // Tap for the right output. - delay = offset - (rdelays[i]>>MixerFracBits); - mu = static_cast(rdelays[i]&MixerFracMask) * (1.0f/MixerFracOne); - rbuffer[i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); + delay = offset - (rdelays[i] >> gCubicTable.sTableBits); + phase = rdelays[i] & gCubicTable.sTableMask; + rbuffer[i] = delaybuf[(delay+1) & bufmask]*gCubicTable.getCoeff0(phase) + + delaybuf[(delay ) & bufmask]*gCubicTable.getCoeff1(phase) + + delaybuf[(delay-1) & bufmask]*gCubicTable.getCoeff2(phase) + + delaybuf[(delay-2) & bufmask]*gCubicTable.getCoeff3(phase); // Accumulate feedback from the average delay of the taps. delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback; ++offset; } - MixSamples({lbuffer, samplesToDo}, samplesOut, mGains[0].Current, mGains[0].Target, + MixSamples(lbuffer.first(samplesToDo), samplesOut, mGains[0].Current, mGains[0].Target, samplesToDo, 0); - MixSamples({rbuffer, samplesToDo}, samplesOut, mGains[1].Current, mGains[1].Target, + MixSamples(rbuffer.first(samplesToDo), samplesOut, mGains[1].Current, mGains[1].Target, samplesToDo, 0); mOffset = offset; @@ -306,15 +319,6 @@ struct ChorusStateFactory final : public EffectStateFactory { { return al::intrusive_ptr{new ChorusState{}}; } }; - -/* Flanger is basically a chorus with a really short delay. They can both use - * the same processing functions, so piggyback flanger on the chorus functions. - */ -struct FlangerStateFactory final : public EffectStateFactory { - al::intrusive_ptr create() override - { return al::intrusive_ptr{new ChorusState{}}; } -}; - } // namespace EffectStateFactory *ChorusStateFactory_getFactory() @@ -322,9 +326,3 @@ EffectStateFactory *ChorusStateFactory_getFactory() static ChorusStateFactory ChorusFactory{}; return &ChorusFactory; } - -EffectStateFactory *FlangerStateFactory_getFactory() -{ - static FlangerStateFactory FlangerFactory{}; - return &FlangerFactory; -} diff --git a/Engine/lib/openal-soft/alc/effects/compressor.cpp b/Engine/lib/openal-soft/alc/effects/compressor.cpp index 0a7ed67a7..3197119c7 100644 --- a/Engine/lib/openal-soft/alc/effects/compressor.cpp +++ b/Engine/lib/openal-soft/alc/effects/compressor.cpp @@ -32,48 +32,49 @@ #include "config.h" +#include #include +#include #include -#include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" -#include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" -#include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" +struct BufferStorage; struct ContextBase; namespace { -#define AMP_ENVELOPE_MIN 0.5f -#define AMP_ENVELOPE_MAX 2.0f +constexpr float AmpEnvelopeMin{0.5f}; +constexpr float AmpEnvelopeMax{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 */ +constexpr float AttackTime{0.1f}; /* 100ms to rise from min to max */ +constexpr float ReleaseTime{0.2f}; /* 200ms to drop from max to min */ struct CompressorState final : public EffectState { /* Effect gains for each channel */ - struct { + struct TargetGain { uint mTarget{InvalidChannelIndex}; float mGain{1.0f}; - } mChans[MaxAmbiChannels]; + }; + std::array mChans; /* Effect parameters */ bool mEnabled{true}; float mAttackMult{1.0f}; float mReleaseMult{1.0f}; float mEnvFollower{1.0f}; + alignas(16) FloatBufferLine mGains{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -81,8 +82,6 @@ struct CompressorState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(CompressorState) }; void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage*) @@ -90,20 +89,20 @@ void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage /* Number of samples to do a full attack and release (non-integer sample * counts are okay). */ - const float attackCount{static_cast(device->Frequency) * ATTACK_TIME}; - const float releaseCount{static_cast(device->Frequency) * RELEASE_TIME}; + const float attackCount{static_cast(device->Frequency) * AttackTime}; + const float releaseCount{static_cast(device->Frequency) * ReleaseTime}; /* 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); + mAttackMult = std::pow(AmpEnvelopeMax/AmpEnvelopeMin, 1.0f/attackCount); + mReleaseMult = std::pow(AmpEnvelopeMin/AmpEnvelopeMax, 1.0f/releaseCount); } void CompressorState::update(const ContextBase*, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { - mEnabled = props->Compressor.OnOff; + mEnabled = std::get(*props).OnOff; mOutTarget = target.Main->Buffer; auto set_channel = [this](size_t idx, uint outchan, float outgain) @@ -117,72 +116,62 @@ void CompressorState::update(const ContextBase*, const EffectSlot *slot, void CompressorState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - for(size_t base{0u};base < samplesToDo;) + /* Generate the per-sample gains from the signal envelope. */ + float env{mEnvFollower}; + if(mEnabled) { - float gains[256]; - const size_t td{minz(256, samplesToDo-base)}; - - /* Generate the per-sample gains from the signal envelope. */ - float env{mEnvFollower}; - if(mEnabled) + for(size_t i{0u};i < samplesToDo;++i) { - for(size_t i{0u};i < td;++i) - { - /* Clamp the absolute amplitude to the defined envelope limits, - * then attack or release the envelope to reach it. - */ - const float amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN, - AMP_ENVELOPE_MAX)}; - if(amplitude > env) - env = minf(env*mAttackMult, amplitude); - else if(amplitude < env) - env = maxf(env*mReleaseMult, amplitude); - - /* Apply the reciprocal of the envelope to normalize the volume - * (compress the dynamic range). - */ - gains[i] = 1.0f / env; - } - } - else - { - /* Same as above, except the amplitude is forced to 1. This helps - * ensure smooth gain changes when the compressor is turned on and - * off. + /* Clamp the absolute amplitude to the defined envelope limits, + * then attack or release the envelope to reach it. */ - for(size_t i{0u};i < td;++i) - { - const float amplitude{1.0f}; - if(amplitude > env) - env = minf(env*mAttackMult, amplitude); - else if(amplitude < env) - env = maxf(env*mReleaseMult, amplitude); + const float amplitude{std::clamp(std::fabs(samplesIn[0][i]), AmpEnvelopeMin, + AmpEnvelopeMax)}; + if(amplitude > env) + env = std::min(env*mAttackMult, amplitude); + else if(amplitude < env) + env = std::max(env*mReleaseMult, amplitude); - gains[i] = 1.0f / env; - } + /* Apply the reciprocal of the envelope to normalize the volume + * (compress the dynamic range). + */ + mGains[i] = 1.0f / env; } - mEnvFollower = env; - - /* Now compress the signal amplitude to output. */ - auto chan = std::cbegin(mChans); - for(const auto &input : samplesIn) + } + 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 < samplesToDo;++i) { - const size_t outidx{chan->mTarget}; - if(outidx != InvalidChannelIndex) - { - const float *RESTRICT src{input.data() + base}; - float *RESTRICT dst{samplesOut[outidx].data() + base}; - const float gain{chan->mGain}; - if(!(std::fabs(gain) > GainSilenceThreshold)) - { - for(size_t i{0u};i < td;i++) - dst[i] += src[i] * gains[i] * gain; - } - } - ++chan; - } + const float amplitude{1.0f}; + if(amplitude > env) + env = std::min(env*mAttackMult, amplitude); + else if(amplitude < env) + env = std::max(env*mReleaseMult, amplitude); - base += td; + mGains[i] = 1.0f / env; + } + } + mEnvFollower = env; + + /* Now compress the signal amplitude to output. */ + auto chan = mChans.cbegin(); + for(const auto &input : samplesIn) + { + const size_t outidx{chan->mTarget}; + if(outidx != InvalidChannelIndex) + { + const auto dst = al::span{samplesOut[outidx]}; + const float gain{chan->mGain}; + if(!(std::fabs(gain) > GainSilenceThreshold)) + { + for(size_t i{0u};i < samplesToDo;++i) + dst[i] += input[i] * mGains[i] * gain; + } + } + ++chan; } } diff --git a/Engine/lib/openal-soft/alc/effects/convolution.cpp b/Engine/lib/openal-soft/alc/effects/convolution.cpp index 7f36c4157..7eb9cd6fd 100644 --- a/Engine/lib/openal-soft/alc/effects/convolution.cpp +++ b/Engine/lib/openal-soft/alc/effects/convolution.cpp @@ -3,13 +3,15 @@ #include #include +#include +#include #include #include +#include #include -#include #include -#include -#include +#include +#include #ifdef HAVE_SSE_INTRINSICS #include @@ -17,7 +19,6 @@ #include #endif -#include "albyte.h" #include "alcomplex.h" #include "almalloc.h" #include "alnumbers.h" @@ -30,56 +31,85 @@ #include "core/context.h" #include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/splitter.h" #include "core/fmt_traits.h" #include "core/mixer.h" +#include "core/uhjfilter.h" #include "intrusive_ptr.h" +#include "opthelpers.h" +#include "pffft.h" #include "polyphase_resampler.h" +#include "vecmat.h" #include "vector.h" namespace { -/* Convolution reverb is implemented using a segmented overlap-add method. The - * impulse response is broken up into multiple segments of 128 samples, and - * each segment has an FFT applied with a 256-sample buffer (the latter half - * left silent) to get its frequency-domain response. The resulting response - * has its positive/non-mirrored frequencies saved (129 bins) in each segment. +/* Convolution is implemented using a segmented overlap-add method. The impulse + * response is split into multiple segments of 128 samples, and each segment + * has an FFT applied with a 256-sample buffer (the latter half left silent) to + * get its frequency-domain response. The resulting response has its positive/ + * non-mirrored frequencies saved (129 bins) in each segment. Note that since + * the 0- and half-frequency bins are real for a real signal, their imaginary + * components are always 0 and can be dropped, allowing their real components + * to be combined so only 128 complex values are stored for the 129 bins. * - * Input samples are similarly broken up into 128-sample segments, with an FFT - * applied to each new incoming segment to get its 129 bins. A history of FFT'd - * input segments is maintained, equal to the length of the impulse response. + * Input samples are similarly broken up into 128-sample segments, with a 256- + * sample FFT applied to each new incoming segment to get its 129 bins. A + * history of FFT'd input segments is maintained, equal to the number of + * impulse response segments. * - * To apply the reverberation, each impulse response segment is convolved with + * To apply the convolution, each impulse response segment is convolved with * its paired input segment (using complex multiplies, far cheaper than FIRs), - * accumulating into a 256-bin FFT buffer. The input history is then shifted to - * align with later impulse response segments for next time. + * accumulating into a 129-bin FFT buffer. The input history is then shifted to + * align with later impulse response segments for the next input segment. * * An inverse FFT is then applied to the accumulated FFT buffer to get a 256- * sample time-domain response for output, which is split in two halves. The * first half is the 128-sample output, and the second half is a 128-sample * (really, 127) delayed extension, which gets added to the output next time. - * Convolving two time-domain responses of lengths N and M results in a time- - * domain signal of length N+M-1, and this holds true regardless of the - * convolution being applied in the frequency domain, so these "overflow" - * samples need to be accounted for. + * Convolving two time-domain responses of length N results in a time-domain + * signal of length N*2 - 1, and this holds true regardless of the convolution + * being applied in the frequency domain, so these "overflow" samples need to + * be accounted for. * - * To avoid a delay with gathering enough input samples to apply an FFT with, - * the first segment is applied directly in the time-domain as the samples come - * in. Once enough have been retrieved, the FFT is applied on the input and - * it's paired with the remaining (FFT'd) filter segments for processing. + * To avoid a delay with gathering enough input samples for the FFT, the first + * segment is applied directly in the time-domain as the samples come in. Once + * enough have been retrieved, the FFT is applied on the input and it's paired + * with the remaining (FFT'd) filter segments for processing. */ -void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype, - const size_t samples) noexcept +template +inline void LoadSampleArray(const al::span dst, const std::byte *src, + const std::size_t channel, const std::size_t srcstep) noexcept { -#define HANDLE_FMT(T) case T: al::LoadSampleArray(dst, src, srcstep, samples); break + using TypeTraits = al::FmtTypeTraits; + using SampleType = typename TypeTraits::Type; + const auto converter = TypeTraits{}; + assert(channel < srcstep); + + const auto srcspan = al::span{reinterpret_cast(src), dst.size()*srcstep}; + auto ssrc = srcspan.cbegin(); + std::generate(dst.begin(), dst.end(), [converter,channel,srcstep,&ssrc] + { + const auto ret = converter(ssrc[channel]); + ssrc += ptrdiff_t(srcstep); + return ret; + }); +} + +void LoadSamples(const al::span dst, const std::byte *src, const size_t channel, + const size_t srcstep, const FmtType srctype) noexcept +{ +#define HANDLE_FMT(T) case T: LoadSampleArray(dst, src, channel, srcstep); break switch(srctype) { HANDLE_FMT(FmtUByte); HANDLE_FMT(FmtShort); + HANDLE_FMT(FmtInt); HANDLE_FMT(FmtFloat); HANDLE_FMT(FmtDouble); HANDLE_FMT(FmtMulaw); @@ -87,47 +117,50 @@ void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep, /* FIXME: Handle ADPCM decoding here. */ case FmtIMA4: case FmtMSADPCM: - std::fill_n(dst, samples, 0.0f); + std::fill(dst.begin(), dst.end(), 0.0f); break; } #undef HANDLE_FMT } -inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept +constexpr auto GetAmbiScales(AmbiScaling scaletype) noexcept { switch(scaletype) { - case AmbiScaling::FuMa: return AmbiScale::FromFuMa(); - case AmbiScaling::SN3D: return AmbiScale::FromSN3D(); - case AmbiScaling::UHJ: return AmbiScale::FromUHJ(); + case AmbiScaling::FuMa: return al::span{AmbiScale::FromFuMa}; + case AmbiScaling::SN3D: return al::span{AmbiScale::FromSN3D}; + case AmbiScaling::UHJ: return al::span{AmbiScale::FromUHJ}; case AmbiScaling::N3D: break; } - return AmbiScale::FromN3D(); + return al::span{AmbiScale::FromN3D}; } -inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept +constexpr auto GetAmbiLayout(AmbiLayout layouttype) noexcept { - if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa(); - return AmbiIndex::FromACN(); + if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa}; + return al::span{AmbiIndex::FromACN}; } -inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept +constexpr auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept { - if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D(); - return AmbiIndex::FromACN2D(); + if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa2D}; + return al::span{AmbiIndex::FromACN2D}; } -struct ChanMap { +constexpr float sin30{0.5f}; +constexpr float cos30{0.866025403785f}; +constexpr float sin45{al::numbers::sqrt2_v*0.5f}; +constexpr float cos45{al::numbers::sqrt2_v*0.5f}; +constexpr float sin110{ 0.939692620786f}; +constexpr float cos110{-0.342020143326f}; + +struct ChanPosMap { Channel channel; - float angle; - float elevation; + std::array pos; }; -constexpr float Deg2Rad(float x) noexcept -{ return static_cast(al::numbers::pi / 180.0 * x); } - using complex_f = std::complex; @@ -135,10 +168,11 @@ constexpr size_t ConvolveUpdateSize{256}; constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2}; -void apply_fir(al::span dst, const float *RESTRICT src, const float *RESTRICT filter) +void apply_fir(al::span dst, const al::span input, const al::span filter) { + auto src = input.begin(); #ifdef HAVE_SSE_INTRINSICS - for(float &output : dst) + std::generate(dst.begin(), dst.end(), [&src,filter] { __m128 r4{_mm_setzero_ps()}; for(size_t j{0};j < ConvolveUpdateSamples;j+=4) @@ -148,39 +182,40 @@ void apply_fir(al::span dst, const float *RESTRICT src, const float *REST r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); } + ++src; + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - output = _mm_cvtss_f32(r4); - - ++src; - } + return _mm_cvtss_f32(r4); + }); #elif defined(HAVE_NEON) - for(float &output : dst) + std::generate(dst.begin(), dst.end(), [&src,filter] { float32x4_t r4{vdupq_n_f32(0.0f)}; for(size_t j{0};j < ConvolveUpdateSamples;j+=4) r4 = vmlaq_f32(r4, vld1q_f32(&src[j]), vld1q_f32(&filter[j])); - r4 = vaddq_f32(r4, vrev64q_f32(r4)); - output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); - ++src; - } + + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + }); #else - for(float &output : dst) + std::generate(dst.begin(), dst.end(), [&src,filter] { float ret{0.0f}; for(size_t j{0};j < ConvolveUpdateSamples;++j) ret += src[j] * filter[j]; - output = ret; ++src; - } + return ret; + }); #endif } + struct ConvolutionState final : public EffectState { FmtChannels mChannels{}; AmbiLayout mAmbiLayout{}; @@ -188,11 +223,13 @@ struct ConvolutionState final : public EffectState { uint mAmbiOrder{}; size_t mFifoPos{0}; - std::array mInput{}; + alignas(16) std::array mInput{}; al::vector,16> mFilter; al::vector,16> mOutput; - alignas(16) std::array mFftBuffer{}; + PFFFTSetup mFft{}; + alignas(16) std::array mFftBuffer{}; + alignas(16) std::array mFftWorkBuffer{}; size_t mCurrentSegment{0}; size_t mNumConvolveSegs{0}; @@ -201,12 +238,11 @@ struct ConvolutionState final : public EffectState { alignas(16) FloatBufferLine mBuffer{}; float mHfScale{}, mLfScale{}; BandSplitter mFilter{}; - float Current[MAX_OUTPUT_CHANNELS]{}; - float Target[MAX_OUTPUT_CHANNELS]{}; + std::array Current{}; + std::array Target{}; }; - using ChannelDataArray = al::FlexArray; - std::unique_ptr mChans; - std::unique_ptr mComplexData; + std::vector mChans; + al::vector mComplexData; ConvolutionState() = default; @@ -222,24 +258,22 @@ struct ConvolutionState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(ConvolutionState) }; void ConvolutionState::NormalMix(const al::span samplesOut, const size_t samplesToDo) { - for(auto &chan : *mChans) - MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current, chan.Target, - samplesToDo, 0); + for(auto &chan : mChans) + MixSamples(al::span{chan.mBuffer}.first(samplesToDo), samplesOut, chan.Current, + chan.Target, samplesToDo, 0); } void ConvolutionState::UpsampleMix(const al::span samplesOut, const size_t samplesToDo) { - for(auto &chan : *mChans) + for(auto &chan : mChans) { - const al::span src{chan.mBuffer.data(), samplesToDo}; + const auto src = al::span{chan.mBuffer}.first(samplesToDo); chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale); MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0); } @@ -251,19 +285,23 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag using UhjDecoderType = UhjDecoder<512>; static constexpr auto DecoderPadding = UhjDecoderType::sInputPadding; - constexpr uint MaxConvolveAmbiOrder{1u}; + static constexpr uint MaxConvolveAmbiOrder{1u}; + + if(!mFft) + mFft = PFFFTSetup{ConvolveUpdateSize, PFFFT_REAL}; mFifoPos = 0; mInput.fill(0.0f); decltype(mFilter){}.swap(mFilter); decltype(mOutput){}.swap(mOutput); - mFftBuffer.fill(complex_f{}); + mFftBuffer.fill(0.0f); + mFftWorkBuffer.fill(0.0f); mCurrentSegment = 0; mNumConvolveSegs = 0; - mChans = nullptr; - mComplexData = nullptr; + decltype(mChans){}.swap(mChans); + decltype(mComplexData){}.swap(mComplexData); /* An empty buffer doesn't need a convolution filter. */ if(!buffer || buffer->mSampleLen < 1) return; @@ -271,14 +309,12 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag mChannels = buffer->mChannels; mAmbiLayout = IsUHJ(mChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout; mAmbiScaling = IsUHJ(mChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling; - mAmbiOrder = minu(buffer->mAmbiOrder, MaxConvolveAmbiOrder); + mAmbiOrder = std::min(buffer->mAmbiOrder, MaxConvolveAmbiOrder); - constexpr size_t m{ConvolveUpdateSize/2 + 1}; - const auto bytesPerSample = BytesFromFmt(buffer->mType); const auto realChannels = buffer->channelsFromFmt(); const auto numChannels = (mChannels == FmtUHJ2) ? 3u : ChannelsFromFmt(mChannels, mAmbiOrder); - mChans = ChannelDataArray::Create(numChannels); + mChans.resize(numChannels); /* The impulse response needs to have the same sample rate as the input and * output. The bsinc24 resampler is decent, but there is high-frequency @@ -293,7 +329,7 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag buffer->mSampleRate); const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; - for(auto &e : *mChans) + for(auto &e : mChans) e.mFilter = splitter; mFilter.resize(numChannels, {}); @@ -305,126 +341,150 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag * segment is allocated to simplify handling. */ mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples; - mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1; + mNumConvolveSegs = std::max(mNumConvolveSegs, 2_uz) - 1_uz; - const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)}; - mComplexData = std::make_unique(complex_length); - std::fill_n(mComplexData.get(), complex_length, complex_f{}); + const size_t complex_length{mNumConvolveSegs * ConvolveUpdateSize * (numChannels+1)}; + mComplexData.resize(complex_length, 0.0f); /* Load the samples from the buffer. */ const size_t srclinelength{RoundUp(buffer->mSampleLen+DecoderPadding, 16)}; - auto srcsamples = std::make_unique(srclinelength * numChannels); - std::fill_n(srcsamples.get(), srclinelength * numChannels, 0.0f); + auto srcsamples = std::vector(srclinelength * numChannels); + std::fill(srcsamples.begin(), srcsamples.end(), 0.0f); for(size_t c{0};c < numChannels && c < realChannels;++c) - LoadSamples(srcsamples.get() + srclinelength*c, buffer->mData.data() + bytesPerSample*c, - realChannels, buffer->mType, buffer->mSampleLen); + LoadSamples(al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen), + buffer->mData.data(), c, realChannels, buffer->mType); if(IsUHJ(mChannels)) { auto decoder = std::make_unique(); std::array samples{}; for(size_t c{0};c < numChannels;++c) - samples[c] = srcsamples.get() + srclinelength*c; + samples[c] = al::to_address(srcsamples.begin() + ptrdiff_t(srclinelength*c)); decoder->decode({samples.data(), numChannels}, buffer->mSampleLen, buffer->mSampleLen); } - auto ressamples = std::make_unique(buffer->mSampleLen + - (resampler ? resampledCount : 0)); - complex_f *filteriter = mComplexData.get() + mNumConvolveSegs*m; + auto ressamples = std::vector(buffer->mSampleLen + (resampler ? resampledCount : 0)); + auto ffttmp = al::vector(ConvolveUpdateSize); + auto fftbuffer = std::vector>(ConvolveUpdateSize); + + auto filteriter = mComplexData.begin() + ptrdiff_t(mNumConvolveSegs*ConvolveUpdateSize); for(size_t c{0};c < numChannels;++c) { + auto bufsamples = al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen); /* Resample to match the device. */ if(resampler) { - std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen, - ressamples.get() + resampledCount); - resampler.process(buffer->mSampleLen, ressamples.get()+resampledCount, - resampledCount, ressamples.get()); + auto restmp = al::span{ressamples}.subspan(resampledCount, buffer->mSampleLen); + std::copy(bufsamples.cbegin(), bufsamples.cend(), restmp.begin()); + resampler.process(restmp, al::span{ressamples}.first(resampledCount)); } else - std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen, ressamples.get()); + std::copy(bufsamples.cbegin(), bufsamples.cend(), ressamples.begin()); /* Store the first segment's samples in reverse in the time-domain, to * apply as a FIR filter. */ - const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)}; - std::transform(ressamples.get(), ressamples.get()+first_size, mFilter[c].rbegin(), + const size_t first_size{std::min(size_t{resampledCount}, ConvolveUpdateSamples)}; + auto sampleseg = al::span{ressamples.cbegin(), first_size}; + std::transform(sampleseg.cbegin(), sampleseg.cend(), mFilter[c].rbegin(), [](const double d) noexcept -> float { return static_cast(d); }); - auto fftbuffer = std::vector>(ConvolveUpdateSize); size_t done{first_size}; for(size_t s{0};s < mNumConvolveSegs;++s) { - const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)}; + const size_t todo{std::min(resampledCount-done, ConvolveUpdateSamples)}; + sampleseg = al::span{ressamples}.subspan(done, todo); - auto iter = std::copy_n(&ressamples[done], todo, fftbuffer.begin()); + /* Apply a double-precision forward FFT for more precise frequency + * measurements. + */ + auto iter = std::copy(sampleseg.cbegin(), sampleseg.cend(), fftbuffer.begin()); done += todo; std::fill(iter, fftbuffer.end(), std::complex{}); + forward_fft(al::span{fftbuffer}); - forward_fft(al::as_span(fftbuffer)); - filteriter = std::copy_n(fftbuffer.cbegin(), m, filteriter); + /* Convert to, and pack in, a float buffer for PFFFT. Note that the + * first bin stores the real component of the half-frequency bin in + * the imaginary component. Also scale the FFT by its length so the + * iFFT'd output will be normalized. + */ + static constexpr float fftscale{1.0f / float{ConvolveUpdateSize}}; + for(size_t i{0};i < ConvolveUpdateSamples;++i) + { + ffttmp[i*2 ] = static_cast(fftbuffer[i].real()) * fftscale; + ffttmp[i*2 + 1] = static_cast((i == 0) ? + fftbuffer[ConvolveUpdateSamples].real() : fftbuffer[i].imag()) * fftscale; + } + /* Reorder backward to make it suitable for pffft_zconvolve and the + * subsequent pffft_transform(..., PFFFT_BACKWARD). + */ + mFft.zreorder(ffttmp.data(), al::to_address(filteriter), PFFFT_BACKWARD); + filteriter += ConvolveUpdateSize; } } } void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps* /*props*/, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { - /* NOTE: Stereo and Rear are slightly different from normal mixing (as - * defined in alu.cpp). These are 45 degrees from center, rather than the - * 30 degrees used there. - * - * TODO: LFE is not mixed to output. This will require each buffer channel + /* TODO: LFE is not mixed to output. This will require each buffer channel * to have its own output target since the main mixing buffer won't have an * LFE channel (due to being B-Format). */ - static constexpr ChanMap MonoMap[1]{ - { FrontCenter, 0.0f, 0.0f } - }, StereoMap[2]{ - { FrontLeft, Deg2Rad(-45.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) } - }, RearMap[2]{ - { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) }, - { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) } - }, QuadMap[4]{ - { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }, - { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) }, - { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) } - }, X51Map[6]{ - { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, - { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, - { LFE, 0.0f, 0.0f }, - { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) }, - { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) } - }, X61Map[7]{ - { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, - { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, - { LFE, 0.0f, 0.0f }, - { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) }, - { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) }, - { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } - }, X71Map[8]{ - { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, - { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, - { LFE, 0.0f, 0.0f }, - { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) }, - { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }, - { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) }, - { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } + static constexpr std::array MonoMap{ + ChanPosMap{FrontCenter, std::array{0.0f, 0.0f, -1.0f}} + }; + static constexpr std::array StereoMap{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + }; + static constexpr std::array RearMap{ + ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, + ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, + }; + static constexpr std::array QuadMap{ + ChanPosMap{FrontLeft, std::array{-sin45, 0.0f, -cos45}}, + ChanPosMap{FrontRight, std::array{ sin45, 0.0f, -cos45}}, + ChanPosMap{BackLeft, std::array{-sin45, 0.0f, cos45}}, + ChanPosMap{BackRight, std::array{ sin45, 0.0f, cos45}}, + }; + static constexpr std::array X51Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{SideLeft, std::array{-sin110, 0.0f, -cos110}}, + ChanPosMap{SideRight, std::array{ sin110, 0.0f, -cos110}}, + }; + static constexpr std::array X61Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{BackCenter, std::array{ 0.0f, 0.0f, 1.0f} }, + ChanPosMap{SideLeft, std::array{-1.0f, 0.0f, 0.0f} }, + ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f} }, + }; + static constexpr std::array X71Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, + ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, + ChanPosMap{SideLeft, std::array{ -1.0f, 0.0f, 0.0f}}, + ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}}, }; if(mNumConvolveSegs < 1) UNLIKELY return; + auto &props = std::get(*props_); mMix = &ConvolutionState::NormalMix; - for(auto &chan : *mChans) - std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f); + for(auto &chan : mChans) + std::fill(chan.Target.begin(), chan.Target.end(), 0.0f); const float gain{slot->Gain}; if(IsAmbisonic(mChannels)) { @@ -432,49 +492,68 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot if(mChannels == FmtUHJ2 && !device->mUhjEncoder) { mMix = &ConvolutionState::UpsampleMix; - (*mChans)[0].mHfScale = 1.0f; - (*mChans)[0].mLfScale = DecoderBase::sWLFScale; - (*mChans)[1].mHfScale = 1.0f; - (*mChans)[1].mLfScale = DecoderBase::sXYLFScale; - (*mChans)[2].mHfScale = 1.0f; - (*mChans)[2].mLfScale = DecoderBase::sXYLFScale; + mChans[0].mHfScale = 1.0f; + mChans[0].mLfScale = DecoderBase::sWLFScale; + mChans[1].mHfScale = 1.0f; + mChans[1].mLfScale = DecoderBase::sXYLFScale; + mChans[2].mHfScale = 1.0f; + mChans[2].mLfScale = DecoderBase::sXYLFScale; } else if(device->mAmbiOrder > mAmbiOrder) { mMix = &ConvolutionState::UpsampleMix; const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder, device->m2DMixing); - (*mChans)[0].mHfScale = scales[0]; - (*mChans)[0].mLfScale = 1.0f; - for(size_t i{1};i < mChans->size();++i) + mChans[0].mHfScale = scales[0]; + mChans[0].mLfScale = 1.0f; + for(size_t i{1};i < mChans.size();++i) { - (*mChans)[i].mHfScale = scales[1]; - (*mChans)[i].mLfScale = 1.0f; + mChans[i].mHfScale = scales[1]; + mChans[i].mLfScale = 1.0f; } } mOutTarget = target.Main->Buffer; - auto&& scales = GetAmbiScales(mAmbiScaling); - const uint8_t *index_map{Is2DAmbisonic(mChannels) ? - GetAmbi2DLayout(mAmbiLayout).data() : - GetAmbiLayout(mAmbiLayout).data()}; + alu::Vector N{props.OrientAt[0], props.OrientAt[1], props.OrientAt[2], 0.0f}; + N.normalize(); + alu::Vector V{props.OrientUp[0], props.OrientUp[1], props.OrientUp[2], 0.0f}; + V.normalize(); + /* Build and normalize right-vector */ + alu::Vector U{N.cross_product(V)}; + U.normalize(); + + const std::array mixmatrix{ + std::array{1.0f, 0.0f, 0.0f, 0.0f}, + std::array{0.0f, U[0], -U[1], U[2]}, + std::array{0.0f, -V[0], V[1], -V[2]}, + std::array{0.0f, -N[0], N[1], -N[2]}, + }; + + const auto scales = GetAmbiScales(mAmbiScaling); + const auto index_map = Is2DAmbisonic(mChannels) ? + al::span{GetAmbi2DLayout(mAmbiLayout)}.subspan(0) : + al::span{GetAmbiLayout(mAmbiLayout)}.subspan(0); std::array coeffs{}; - for(size_t c{0u};c < mChans->size();++c) + for(size_t c{0u};c < mChans.size();++c) { const size_t acn{index_map[c]}; - coeffs[acn] = scales[acn]; - ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[c].Target); - coeffs[acn] = 0.0f; + const float scale{scales[acn]}; + + std::transform(mixmatrix[acn].cbegin(), mixmatrix[acn].cend(), coeffs.begin(), + [scale](const float in) noexcept -> float { return in * scale; }); + + ComputePanGains(target.Main, coeffs, gain, mChans[c].Target); } } else { DeviceBase *device{context->mDevice}; - al::span chanmap{}; + al::span chanmap{}; switch(mChannels) { case FmtMono: chanmap = MonoMap; break; + case FmtMonoDup: chanmap = MonoMap; break; case FmtSuperStereo: case FmtStereo: chanmap = StereoMap; break; case FmtRear: chanmap = RearMap; break; @@ -493,28 +572,55 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot mOutTarget = target.Main->Buffer; if(device->mRenderMode == RenderMode::Pairwise) { - auto ScaleAzimuthFront = [](float azimuth, float scale) -> float + /* Scales the azimuth of the given vector by 3 if it's in front. + * Effectively scales +/-30 degrees to +/-90 degrees, leaving > +90 + * and < -90 alone. + */ + auto ScaleAzimuthFront = [](std::array pos) -> std::array { - constexpr float half_pi{al::numbers::pi_v*0.5f}; - const float abs_azi{std::fabs(azimuth)}; - if(!(abs_azi >= half_pi)) - return std::copysign(minf(abs_azi*scale, half_pi), azimuth); - return azimuth; + if(pos[2] < 0.0f) + { + /* Normalize the length of the x,z components for a 2D + * vector of the azimuth angle. Negate Z since {0,0,-1} is + * angle 0. + */ + const float len2d{std::sqrt(pos[0]*pos[0] + pos[2]*pos[2])}; + float x{pos[0] / len2d}; + float z{-pos[2] / len2d}; + + /* Z > cos(pi/6) = -30 < azimuth < 30 degrees. */ + if(z > cos30) + { + /* Triple the angle represented by x,z. */ + x = x*3.0f - x*x*x*4.0f; + z = z*z*z*4.0f - z*3.0f; + + /* Scale the vector back to fit in 3D. */ + pos[0] = x * len2d; + pos[2] = -z * len2d; + } + else + { + /* If azimuth >= 30 degrees, clamp to 90 degrees. */ + pos[0] = std::copysign(len2d, pos[0]); + pos[2] = 0.0f; + } + } + return pos; }; for(size_t i{0};i < chanmap.size();++i) { if(chanmap[i].channel == LFE) continue; - const auto coeffs = CalcAngleCoeffs(ScaleAzimuthFront(chanmap[i].angle, 2.0f), - chanmap[i].elevation, 0.0f); - ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target); + const auto coeffs = CalcDirectionCoeffs(ScaleAzimuthFront(chanmap[i].pos), 0.0f); + ComputePanGains(target.Main, coeffs, gain, mChans[i].Target); } } else for(size_t i{0};i < chanmap.size();++i) { if(chanmap[i].channel == LFE) continue; - const auto coeffs = CalcAngleCoeffs(chanmap[i].angle, chanmap[i].elevation, 0.0f); - ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target); + const auto coeffs = CalcDirectionCoeffs(chanmap[i].pos, 0.0f); + ComputePanGains(target.Main, coeffs, gain, mChans[i].Target); } } } @@ -525,27 +631,26 @@ void ConvolutionState::process(const size_t samplesToDo, if(mNumConvolveSegs < 1) UNLIKELY return; - constexpr size_t m{ConvolveUpdateSize/2 + 1}; size_t curseg{mCurrentSegment}; - auto &chans = *mChans; for(size_t base{0u};base < samplesToDo;) { - const size_t todo{minz(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)}; + const size_t todo{std::min(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)}; - std::copy_n(samplesIn[0].begin() + base, todo, - mInput.begin()+ConvolveUpdateSamples+mFifoPos); + std::copy_n(samplesIn[0].begin() + ptrdiff_t(base), todo, + mInput.begin()+ptrdiff_t(ConvolveUpdateSamples+mFifoPos)); /* Apply the FIR for the newly retrieved input samples, and combine it * with the inverse FFT'd output samples. */ - for(size_t c{0};c < chans.size();++c) + for(size_t c{0};c < mChans.size();++c) { - auto buf_iter = chans[c].mBuffer.begin() + base; - apply_fir({buf_iter, todo}, mInput.data()+1 + mFifoPos, mFilter[c].data()); + auto outspan = al::span{mChans[c].mBuffer}.subspan(base, todo); + apply_fir(outspan, al::span{mInput}.subspan(1+mFifoPos), mFilter[c]); - auto fifo_iter = mOutput[c].begin() + mFifoPos; - std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{}); + auto fifospan = al::span{mOutput[c]}.subspan(mFifoPos, todo); + std::transform(fifospan.cbegin(), fifospan.cend(), outspan.cbegin(), outspan.begin(), + std::plus{}); } mFifoPos += todo; @@ -557,59 +662,51 @@ void ConvolutionState::process(const size_t samplesToDo, /* Move the newest input to the front for the next iteration's history. */ std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin()); + std::fill(mInput.begin()+ConvolveUpdateSamples, mInput.end(), 0.0f); - /* Calculate the frequency domain response and add the relevant + /* Calculate the frequency-domain response and add the relevant * frequency bins to the FFT history. */ - auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin()); - std::fill(fftiter, mFftBuffer.end(), complex_f{}); - forward_fft(al::as_span(mFftBuffer)); + mFft.transform(mInput.data(), &mComplexData[curseg*ConvolveUpdateSize], + mFftWorkBuffer.data(), PFFFT_FORWARD); - std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]); - - const complex_f *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m}; - for(size_t c{0};c < chans.size();++c) + auto filter = mComplexData.cbegin() + ptrdiff_t(mNumConvolveSegs*ConvolveUpdateSize); + for(size_t c{0};c < mChans.size();++c) { - std::fill_n(mFftBuffer.begin(), m, complex_f{}); - /* Convolve each input segment with its IR filter counterpart * (aligned in time). */ - const complex_f *RESTRICT input{&mComplexData[curseg*m]}; + mFftBuffer.fill(0.0f); + auto input = mComplexData.cbegin() + ptrdiff_t(curseg*ConvolveUpdateSize); for(size_t s{curseg};s < mNumConvolveSegs;++s) { - for(size_t i{0};i < m;++i,++input,++filter) - mFftBuffer[i] += *input * *filter; + mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), + mFftBuffer.data()); + input += ConvolveUpdateSize; + filter += ConvolveUpdateSize; } - input = mComplexData.get(); + input = mComplexData.cbegin(); for(size_t s{0};s < curseg;++s) { - for(size_t i{0};i < m;++i,++input,++filter) - mFftBuffer[i] += *input * *filter; + mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), + mFftBuffer.data()); + input += ConvolveUpdateSize; + filter += ConvolveUpdateSize; } - /* Reconstruct the mirrored/negative frequencies to do a proper - * inverse FFT. - */ - for(size_t i{m};i < ConvolveUpdateSize;++i) - mFftBuffer[i] = std::conj(mFftBuffer[ConvolveUpdateSize-i]); - /* Apply iFFT to get the 256 (really 255) samples for output. The * 128 output samples are combined with the last output's 127 * second-half samples (and this output's second half is * subsequently saved for next time). */ - inverse_fft(al::as_span(mFftBuffer)); + mFft.transform(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(), + PFFFT_BACKWARD); - /* The iFFT'd response is scaled up by the number of bins, so apply - * the inverse to normalize the output. - */ - for(size_t i{0};i < ConvolveUpdateSamples;++i) - mOutput[c][i] = - (mFftBuffer[i].real()+mOutput[c][ConvolveUpdateSamples+i]) * - (1.0f/float{ConvolveUpdateSize}); - for(size_t i{0};i < ConvolveUpdateSamples;++i) - mOutput[c][ConvolveUpdateSamples+i] = mFftBuffer[ConvolveUpdateSamples+i].real(); + /* The filter was attenuated, so the response is already scaled. */ + std::transform(mFftBuffer.cbegin(), mFftBuffer.cbegin()+ConvolveUpdateSamples, + mOutput[c].cbegin()+ConvolveUpdateSamples, mOutput[c].begin(), std::plus{}); + std::copy(mFftBuffer.cbegin()+ConvolveUpdateSamples, mFftBuffer.cend(), + mOutput[c].begin()+ConvolveUpdateSamples); } /* Shift the input history. */ diff --git a/Engine/lib/openal-soft/alc/effects/dedicated.cpp b/Engine/lib/openal-soft/alc/effects/dedicated.cpp index 047e6761a..df1bd5ed0 100644 --- a/Engine/lib/openal-soft/alc/effects/dedicated.cpp +++ b/Engine/lib/openal-soft/alc/effects/dedicated.cpp @@ -23,18 +23,19 @@ #include #include #include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alspan.h" #include "core/bufferline.h" #include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "intrusive_ptr.h" +struct BufferStorage; struct ContextBase; @@ -47,45 +48,36 @@ struct DedicatedState final : public EffectState { * gains for all possible output channels and not just the main ambisonic * buffer. */ - float mCurrentGains[MAX_OUTPUT_CHANNELS]; - float mTargetGains[MAX_OUTPUT_CHANNELS]; + std::array mCurrentGains{}; + std::array mTargetGains{}; - void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; - void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, - const EffectTarget target) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) final; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, + const EffectTarget target) final; void process(const size_t samplesToDo, const al::span samplesIn, - const al::span samplesOut) override; - - DEF_NEWDEL(DedicatedState) + const al::span samplesOut) final; }; void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*) { - std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f); + std::fill(mCurrentGains.begin(), mCurrentGains.end(), 0.0f); } void DedicatedState::update(const ContextBase*, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { - std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); + std::fill(mTargetGains.begin(), mTargetGains.end(), 0.0f); - const float Gain{slot->Gain * props->Dedicated.Gain}; + auto &props = std::get(*props_); + const float Gain{slot->Gain * props.Gain}; - if(slot->EffectType == EffectSlotType::DedicatedLFE) - { - const uint idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex}; - if(idx != InvalidChannelIndex) - { - mOutTarget = target.RealOut->Buffer; - mTargetGains[idx] = Gain; - } - } - else if(slot->EffectType == EffectSlotType::DedicatedDialog) + if(props.Target == DedicatedProps::Dialog) { /* Dialog goes to the front-center speaker if it exists, otherwise it - * plays from the front-center location. */ - const uint idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter] + * plays from the front-center location. + */ + const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter] : InvalidChannelIndex}; if(idx != InvalidChannelIndex) { @@ -94,17 +86,26 @@ void DedicatedState::update(const ContextBase*, const EffectSlot *slot, } else { - static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}); + static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains); + ComputePanGains(target.Main, coeffs, Gain, mTargetGains); + } + } + else if(props.Target == DedicatedProps::Lfe) + { + const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex}; + if(idx != InvalidChannelIndex) + { + mOutTarget = target.RealOut->Buffer; + mTargetGains[idx] = Gain; } } } void DedicatedState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains, + MixSamples(al::span{samplesIn[0]}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains, samplesToDo, 0); } diff --git a/Engine/lib/openal-soft/alc/effects/distortion.cpp b/Engine/lib/openal-soft/alc/effects/distortion.cpp index b4e2167e4..202e4fd79 100644 --- a/Engine/lib/openal-soft/alc/effects/distortion.cpp +++ b/Engine/lib/openal-soft/alc/effects/distortion.cpp @@ -22,30 +22,32 @@ #include #include +#include #include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" +#include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" +struct BufferStorage; namespace { struct DistortionState final : public EffectState { /* Effect gains for each channel */ - float mGain[MaxAmbiChannels]{}; + std::array mGain{}; /* Effect parameters */ BiquadFilter mLowpass; @@ -53,7 +55,7 @@ struct DistortionState final : public EffectState { float mAttenuation{}; float mEdgeCoeff{}; - alignas(16) float mBuffer[2][BufferLineSize]{}; + alignas(16) std::array mBuffer{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -61,8 +63,6 @@ struct DistortionState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(DistortionState) }; void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -72,16 +72,16 @@ void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*) } void DistortionState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; /* Store waveshaper edge settings. */ - const float edge{minf(std::sin(al::numbers::pi_v*0.5f * props->Distortion.Edge), - 0.99f)}; + const float edge{std::min(std::sin(al::numbers::pi_v*0.5f * props.Edge), 0.99f)}; mEdgeCoeff = 2.0f * edge / (1.0f-edge); - float cutoff{props->Distortion.LowpassCutoff}; + float cutoff{props.LowpassCutoff}; /* Bandwidth value is constant in octaves. */ float bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)}; /* Divide normalized frequency by the amount of oversampling done during @@ -90,15 +90,15 @@ void DistortionState::update(const ContextBase *context, const EffectSlot *slot, auto frequency = static_cast(device->Frequency); mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth); - cutoff = props->Distortion.EQCenter; + cutoff = props.EQCenter; /* Convert bandwidth in Hz to octaves. */ - bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f); + bandwidth = props.EQBandwidth / (cutoff * 0.67f); mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth); - static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}); + static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain); + ComputePanGains(target.Main, coeffs, slot->Gain*props.Gain, mGain); } void DistortionState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) @@ -111,7 +111,7 @@ void DistortionState::process(const size_t samplesToDo, const al::span float { - smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)); - smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f; - smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)); + smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp)); + smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp)) * -1.0f; + smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp)); return smp; }; - std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]), + std::transform(mBuffer[1].begin(), mBuffer[1].begin()+todo, mBuffer[0].begin(), proc_sample); /* Third step, do bandpass filtering of distorted signal. */ - mBandpass.process({mBuffer[0], todo}, mBuffer[1]); + mBandpass.process(al::span{mBuffer[0]}.first(todo), mBuffer[1]); todo >>= 2; - const float *outgains{mGain}; - for(FloatBufferLine &output : samplesOut) + auto outgains = mGain.cbegin(); + auto proc_bufline = [this,base,todo,&outgains](FloatBufferSpan output) { /* Fourth step, final, do attenuation and perform decimation, * storing only one sample out of four. */ const float gain{*(outgains++)}; if(!(std::fabs(gain) > GainSilenceThreshold)) - continue; + return; - for(size_t i{0u};i < todo;i++) - output[base+i] += gain * mBuffer[1][i*4]; - } + auto src = mBuffer[1].cbegin(); + const auto dst = al::span{output}.subspan(base, todo); + auto dec_sample = [gain,&src](float sample) noexcept -> float + { + sample += *src * gain; + src += 4; + return sample; + }; + std::transform(dst.begin(), dst.end(), dst.begin(), dec_sample); + }; + std::for_each(samplesOut.begin(), samplesOut.end(), proc_bufline); base += todo; } diff --git a/Engine/lib/openal-soft/alc/effects/echo.cpp b/Engine/lib/openal-soft/alc/effects/echo.cpp index a69529dc1..a303117d9 100644 --- a/Engine/lib/openal-soft/alc/effects/echo.cpp +++ b/Engine/lib/openal-soft/alc/effects/echo.cpp @@ -22,25 +22,26 @@ #include #include +#include #include -#include -#include +#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumeric.h" #include "alspan.h" +#include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "intrusive_ptr.h" #include "opthelpers.h" -#include "vector.h" +struct BufferStorage; namespace { @@ -49,33 +50,30 @@ using uint = unsigned int; constexpr float LowpassFreqRef{5000.0f}; struct EchoState final : public EffectState { - al::vector mSampleBuffer; + std::vector 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]; + std::array mDelayTap{}; size_t mOffset{0u}; /* The panning gains for the two taps */ - struct { - float Current[MaxAmbiChannels]{}; - float Target[MaxAmbiChannels]{}; - } mGains[2]; + struct OutGains { + std::array Current{}; + std::array Target{}; + }; + std::array mGains; BiquadFilter mFilter; float mFeedGain{0.0f}; - alignas(16) float mTempBuffer[2][BufferLineSize]; + alignas(16) std::array mTempBuffer{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(EchoState) }; void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) @@ -87,61 +85,62 @@ void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) const uint maxlen{NextPowerOf2(float2uint(EchoMaxDelay*frequency + 0.5f) + float2uint(EchoMaxLRDelay*frequency + 0.5f))}; if(maxlen != mSampleBuffer.size()) - al::vector(maxlen).swap(mSampleBuffer); + decltype(mSampleBuffer)(maxlen).swap(mSampleBuffer); std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); for(auto &e : mGains) { - std::fill(std::begin(e.Current), std::end(e.Current), 0.0f); - std::fill(std::begin(e.Target), std::end(e.Target), 0.0f); + std::fill(e.Current.begin(), e.Current.end(), 0.0f); + std::fill(e.Target.begin(), e.Target.end(), 0.0f); } } void EchoState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; const auto frequency = static_cast(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; + mDelayTap[0] = std::max(float2uint(std::round(props.Delay*frequency)), 1u); + mDelayTap[1] = float2uint(std::round(props.LRDelay*frequency)) + mDelayTap[0]; - const float gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */ + const float gainhf{std::max(1.0f - props.Damping, 0.0625f)}; /* Limit -24dB */ mFilter.setParamsFromSlope(BiquadType::HighShelf, LowpassFreqRef/frequency, gainhf, 1.0f); - mFeedGain = props->Echo.Feedback; + mFeedGain = props.Feedback; - /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */ - const float angle{std::asin(props->Echo.Spread)}; + /* Convert echo spread (where 0 = center, +/-1 = sides) to a 2D vector. */ + const float x{props.Spread}; /* +x = left */ + const float z{std::sqrt(1.0f - x*x)}; - const auto coeffs0 = CalcAngleCoeffs(-angle, 0.0f, 0.0f); - const auto coeffs1 = CalcAngleCoeffs( angle, 0.0f, 0.0f); + const auto coeffs0 = CalcAmbiCoeffs( x, 0.0f, z, 0.0f); + const auto coeffs1 = CalcAmbiCoeffs(-x, 0.0f, z, 0.0f); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs0.data(), slot->Gain, mGains[0].Target); - ComputePanGains(target.Main, coeffs1.data(), slot->Gain, mGains[1].Target); + ComputePanGains(target.Main, coeffs0, slot->Gain, mGains[0].Target); + ComputePanGains(target.Main, coeffs1, slot->Gain, mGains[1].Target); } void EchoState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - const size_t mask{mSampleBuffer.size()-1}; - float *RESTRICT delaybuf{mSampleBuffer.data()}; + const auto delaybuf = al::span{mSampleBuffer}; + const size_t mask{delaybuf.size()-1}; size_t offset{mOffset}; - size_t tap1{offset - mTap[0].delay}; - size_t tap2{offset - mTap[1].delay}; - float z1, z2; + size_t tap1{offset - mDelayTap[0]}; + size_t tap2{offset - mDelayTap[1]}; ASSUME(samplesToDo > 0); const BiquadFilter filter{mFilter}; - std::tie(z1, z2) = mFilter.getComponents(); + auto [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)}; + size_t td{std::min(mask+1 - std::max(offset, std::max(tap1, tap2)), samplesToDo-i)}; do { /* Feed the delay buffer's input first. */ delaybuf[offset] = samplesIn[0][i]; @@ -161,8 +160,8 @@ void EchoState::process(const size_t samplesToDo, const al::span #include +#include #include #include -#include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "intrusive_ptr.h" +struct BufferStorage; namespace { @@ -86,16 +86,17 @@ namespace { struct EqualizerState final : public EffectState { - struct { + struct OutParams { uint mTargetChannel{InvalidChannelIndex}; /* Effect parameters */ - BiquadFilter mFilter[4]; + std::array mFilter; /* Effect gains for each channel */ float mCurrentGain{}; float mTargetGain{}; - } mChans[MaxAmbiChannels]; + }; + std::array mChans; alignas(16) FloatBufferLine mSampleBuffer{}; @@ -105,8 +106,6 @@ struct EqualizerState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(EqualizerState) }; void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -114,18 +113,17 @@ void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*) for(auto &e : mChans) { e.mTargetChannel = InvalidChannelIndex; - std::for_each(std::begin(e.mFilter), std::end(e.mFilter), - std::mem_fn(&BiquadFilter::clear)); + std::for_each(e.mFilter.begin(), e.mFilter.end(), std::mem_fn(&BiquadFilter::clear)); e.mCurrentGain = 0.0f; } } void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; auto frequency = static_cast(device->Frequency); - float gain, f0norm; /* Calculate coefficients for the each type of filter. Note that the shelf * and peaking filters' gain is for the centerpoint of the transition band, @@ -133,22 +131,22 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, * property gains need their dB halved (sqrt of linear gain) for the * shelf/peak to reach the provided gain. */ - gain = std::sqrt(props->Equalizer.LowGain); - f0norm = props->Equalizer.LowCutoff / frequency; + float gain{std::sqrt(props.LowGain)}; + float f0norm{props.LowCutoff / frequency}; mChans[0].mFilter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f); - gain = std::sqrt(props->Equalizer.Mid1Gain); - f0norm = props->Equalizer.Mid1Center / frequency; + gain = std::sqrt(props.Mid1Gain); + f0norm = props.Mid1Center / frequency; mChans[0].mFilter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, - props->Equalizer.Mid1Width); + props.Mid1Width); - gain = std::sqrt(props->Equalizer.Mid2Gain); - f0norm = props->Equalizer.Mid2Center / frequency; + gain = std::sqrt(props.Mid2Gain); + f0norm = props.Mid2Center / frequency; mChans[0].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, - props->Equalizer.Mid2Width); + props.Mid2Width); - gain = std::sqrt(props->Equalizer.HighGain); - f0norm = props->Equalizer.HighCutoff / frequency; + gain = std::sqrt(props.HighGain); + f0norm = props.HighCutoff / frequency; mChans[0].mFilter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f); /* Copy the filter coefficients for the other input channels. */ @@ -171,18 +169,17 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, void EqualizerState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - const al::span buffer{mSampleBuffer.data(), samplesToDo}; - auto chan = std::begin(mChans); + const auto buffer = al::span{mSampleBuffer}.first(samplesToDo); + auto chan = mChans.begin(); for(const auto &input : samplesIn) { - const size_t outidx{chan->mTargetChannel}; - if(outidx != InvalidChannelIndex) + if(const size_t outidx{chan->mTargetChannel}; outidx != InvalidChannelIndex) { - const al::span inbuf{input.data(), samplesToDo}; - DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer.begin()); - DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer.begin()); + const auto inbuf = al::span{input}.first(samplesToDo); + DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer); + DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer); - MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain, + MixSamples(buffer, samplesOut[outidx], chan->mCurrentGain, chan->mTargetGain, samplesToDo); } ++chan; diff --git a/Engine/lib/openal-soft/alc/effects/fshifter.cpp b/Engine/lib/openal-soft/alc/effects/fshifter.cpp index 3e6a7385e..7790a2435 100644 --- a/Engine/lib/openal-soft/alc/effects/fshifter.cpp +++ b/Engine/lib/openal-soft/alc/effects/fshifter.cpp @@ -25,23 +25,25 @@ #include #include #include -#include +#include #include "alc/effects/base.h" #include "alcomplex.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" +#include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" +#include "opthelpers.h" +struct BufferStorage; namespace { @@ -57,7 +59,7 @@ constexpr size_t HilStep{HilSize / OversampleFactor}; /* Define a Hann window, used to filter the HIL input and output. */ struct Windower { - alignas(16) std::array mData; + alignas(16) std::array mData{}; Windower() { @@ -91,10 +93,11 @@ struct FshifterState final : public EffectState { alignas(16) FloatBufferLine mBufferOut{}; /* Effect gains for each output channel */ - struct { - float Current[MaxAmbiChannels]{}; - float Target[MaxAmbiChannels]{}; - } mGains[2]; + struct OutGains { + std::array Current{}; + std::array Target{}; + }; + std::array mGains; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -102,8 +105,6 @@ struct FshifterState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(FshifterState) }; void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -122,20 +123,21 @@ void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) 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); + gain.Current.fill(0.0f); + gain.Target.fill(0.0f); } } void FshifterState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - const float step{props->Fshifter.Frequency / static_cast(device->Frequency)}; - mPhaseStep[0] = mPhaseStep[1] = fastf2u(minf(step, 1.0f) * MixerFracOne); + const float step{props.Frequency / static_cast(device->Frequency)}; + mPhaseStep[0] = mPhaseStep[1] = fastf2u(std::min(step, 1.0f) * MixerFracOne); - switch(props->Fshifter.LeftDirection) + switch(props.LeftDirection) { case FShifterDirection::Down: mSign[0] = -1.0; @@ -149,7 +151,7 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot, break; } - switch(props->Fshifter.RightDirection) + switch(props.RightDirection) { case FShifterDirection::Down: mSign[1] = -1.0; @@ -164,23 +166,23 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot, } static constexpr auto inv_sqrt2 = static_cast(1.0 / al::numbers::sqrt2); - static constexpr auto lcoeffs_pw = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}); - static constexpr auto rcoeffs_pw = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}); - static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs({-inv_sqrt2, 0.0f, inv_sqrt2}); - static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs({ inv_sqrt2, 0.0f, inv_sqrt2}); + static constexpr auto lcoeffs_pw = CalcDirectionCoeffs(std::array{-1.0f, 0.0f, 0.0f}); + static constexpr auto rcoeffs_pw = CalcDirectionCoeffs(std::array{ 1.0f, 0.0f, 0.0f}); + static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, inv_sqrt2}); + static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ inv_sqrt2, 0.0f, inv_sqrt2}); auto &lcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? lcoeffs_nrml : lcoeffs_pw; auto &rcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? rcoeffs_nrml : rcoeffs_pw; mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, lcoeffs.data(), slot->Gain, mGains[0].Target); - ComputePanGains(target.Main, rcoeffs.data(), slot->Gain, mGains[1].Target); + ComputePanGains(target.Main, lcoeffs, slot->Gain, mGains[0].Target); + ComputePanGains(target.Main, rcoeffs, slot->Gain, mGains[1].Target); } void FshifterState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { for(size_t base{0u};base < samplesToDo;) { - size_t todo{minz(HilStep-mCount, samplesToDo-base)}; + size_t todo{std::min(HilStep-mCount, samplesToDo-base)}; /* Fill FIFO buffer with samples data */ const size_t pos{mPos}; @@ -218,25 +220,27 @@ void FshifterState::process(const size_t samplesToDo, const al::span(mBufferOut.data())}; for(size_t c{0};c < 2;++c) { + const double sign{mSign[c]}; const uint phase_step{mPhaseStep[c]}; uint phase_idx{mPhase[c]}; - for(size_t k{0};k < samplesToDo;++k) - { - const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)}; - BufferOut[k] = static_cast(mOutdata[k].real()*std::cos(phase) + - mOutdata[k].imag()*std::sin(phase)*mSign[c]); + std::transform(mOutdata.cbegin(), mOutdata.cbegin()+samplesToDo, mBufferOut.begin(), + [&phase_idx,phase_step,sign](const complex_d &in) -> float + { + const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)}; + const auto out = static_cast(in.real()*std::cos(phase) + + in.imag()*std::sin(phase)*sign); - phase_idx += phase_step; - phase_idx &= MixerFracMask; - } + phase_idx += phase_step; + phase_idx &= MixerFracMask; + return out; + }); mPhase[c] = phase_idx; /* Now, mix the processed sound data to the output. */ - MixSamples({BufferOut, samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target, - maxz(samplesToDo, 512), 0); + MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mGains[c].Current, + mGains[c].Target, std::max(samplesToDo, 512_uz), 0); } } diff --git a/Engine/lib/openal-soft/alc/effects/modulator.cpp b/Engine/lib/openal-soft/alc/effects/modulator.cpp index 14ee5004d..e86a3c5d8 100644 --- a/Engine/lib/openal-soft/alc/effects/modulator.cpp +++ b/Engine/lib/openal-soft/alc/effects/modulator.cpp @@ -22,75 +22,73 @@ #include #include +#include +#include #include -#include +#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "intrusive_ptr.h" +#include "opthelpers.h" +struct BufferStorage; namespace { using uint = unsigned int; -#define MAX_UPDATE_SAMPLES 128 +struct SinFunc { + static auto Get(uint index, float scale) noexcept(noexcept(std::sin(0.0f))) -> float + { return std::sin(static_cast(index) * scale); } +}; -#define WAVEFORM_FRACBITS 24 -#define WAVEFORM_FRACONE (1< float + { return static_cast(index)*scale - 1.0f; } +}; -inline float Sin(uint index) -{ - constexpr float scale{al::numbers::pi_v*2.0f / WAVEFORM_FRACONE}; - return std::sin(static_cast(index) * scale); -} +struct SquareFunc { + static constexpr auto Get(uint index, float scale) noexcept -> float + { return float(static_cast(index)*scale < 0.5f)*2.0f - 1.0f; } +}; -inline float Saw(uint index) -{ return static_cast(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; } - -inline float Square(uint index) -{ return static_cast(static_cast((index>>(WAVEFORM_FRACBITS-2))&2) - 1); } - -inline float One(uint) { return 1.0f; } - -template -void Modulate(float *RESTRICT dst, uint index, const uint step, size_t todo) -{ - for(size_t i{0u};i < todo;i++) - { - index += step; - index &= WAVEFORM_FRACMASK; - dst[i] = func(index); - } -} +struct OneFunc { + static constexpr auto Get(uint, float) noexcept -> float + { return 1.0f; } +}; struct ModulatorState final : public EffectState { - void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){}; + std::variant mSampleGen; uint mIndex{0}; - uint mStep{1}; + uint mRange{1}; + float mIndexScale{0.0f}; - struct { + alignas(16) FloatBufferLine mModSamples{}; + alignas(16) FloatBufferLine mBuffer{}; + + struct OutParams { uint mTargetChannel{InvalidChannelIndex}; BiquadFilter mFilter; float mCurrentGain{}; float mTargetGain{}; - } mChans[MaxAmbiChannels]; + }; + std::array mChans; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -98,8 +96,6 @@ struct ModulatorState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(ModulatorState) }; void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -113,24 +109,54 @@ void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*) } void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - const float step{props->Modulator.Frequency / static_cast(device->Frequency)}; - mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1})); + /* The effective frequency will be adjusted to have a whole number of + * samples per cycle (at 48khz, that allows 8000, 6857.14, 6000, 5333.33, + * 4800, etc). We could do better by using fixed-point stepping over a sin + * function, with additive synthesis for the square and sawtooth waveforms, + * but that may need a more efficient sin function since it needs to do + * many iterations per sample. + */ + const float samplesPerCycle{props.Frequency > 0.0f + ? static_cast(device->Frequency)/props.Frequency + 0.5f + : 1.0f}; + const uint range{static_cast(std::clamp(samplesPerCycle, 1.0f, + static_cast(device->Frequency)))}; + mIndex = static_cast(uint64_t{mIndex} * range / mRange); + mRange = range; - if(mStep == 0) - mGetSamples = Modulate; - else if(props->Modulator.Waveform == ModulatorWaveform::Sinusoid) - mGetSamples = Modulate; - else if(props->Modulator.Waveform == ModulatorWaveform::Sawtooth) - mGetSamples = Modulate; - else /*if(props->Modulator.Waveform == ModulatorWaveform::Square)*/ - mGetSamples = Modulate; + if(mRange == 1) + { + mIndexScale = 0.0f; + mSampleGen.emplace(); + } + else if(props.Waveform == ModulatorWaveform::Sinusoid) + { + mIndexScale = al::numbers::pi_v*2.0f / static_cast(mRange); + mSampleGen.emplace(); + } + else if(props.Waveform == ModulatorWaveform::Sawtooth) + { + mIndexScale = 2.0f / static_cast(mRange-1); + mSampleGen.emplace(); + } + else if(props.Waveform == ModulatorWaveform::Square) + { + /* For square wave, the range should be even (there should be an equal + * number of high and low samples). An odd number of samples per cycle + * would need a more complex value generator. + */ + mRange = (mRange+1) & ~1u; + mIndexScale = 1.0f / static_cast(mRange-1); + mSampleGen.emplace(); + } - float f0norm{props->Modulator.HighPassCutoff / static_cast(device->Frequency)}; - f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f); + float f0norm{props.HighPassCutoff / static_cast(device->Frequency)}; + f0norm = std::clamp(f0norm, 1.0f/512.0f, 0.49f); /* Bandwidth value is constant in octaves. */ mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); for(size_t i{1u};i < slot->Wet.Buffer.size();++i) @@ -147,34 +173,41 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, void ModulatorState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - for(size_t base{0u};base < samplesToDo;) + ASSUME(samplesToDo > 0); + + std::visit([this,samplesToDo](auto&& type) { - alignas(16) float modsamples[MAX_UPDATE_SAMPLES]; - const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; + const uint range{mRange}; + const float scale{mIndexScale}; + uint index{mIndex}; - mGetSamples(modsamples, mIndex, mStep, td); - mIndex += static_cast(mStep * td); - mIndex &= WAVEFORM_FRACMASK; + ASSUME(range > 1); - auto chandata = std::begin(mChans); - for(const auto &input : samplesIn) + for(size_t i{0};i < samplesToDo;) { - const size_t outidx{chandata->mTargetChannel}; - if(outidx != InvalidChannelIndex) - { - alignas(16) float temps[MAX_UPDATE_SAMPLES]; - - chandata->mFilter.process({&input[base], td}, temps); - for(size_t i{0u};i < td;i++) - temps[i] *= modsamples[i]; - - MixSamples({temps, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain, - chandata->mTargetGain, samplesToDo-base); - } - ++chandata; + size_t rem{std::min(samplesToDo-i, size_t{range-index})}; + do { + mModSamples[i++] = type.Get(index++, scale); + } while(--rem); + if(index == range) + index = 0; } + mIndex = index; + }, mSampleGen); - base += td; + auto chandata = mChans.begin(); + for(const auto &input : samplesIn) + { + if(const size_t outidx{chandata->mTargetChannel}; outidx != InvalidChannelIndex) + { + chandata->mFilter.process(al::span{input}.first(samplesToDo), mBuffer); + std::transform(mBuffer.cbegin(), mBuffer.cbegin()+samplesToDo, mModSamples.cbegin(), + mBuffer.begin(), std::multiplies<>{}); + + MixSamples(al::span{mBuffer}.first(samplesToDo), samplesOut[outidx], + chandata->mCurrentGain, chandata->mTargetGain, std::min(samplesToDo, 64_uz)); + } + ++chandata; } } diff --git a/Engine/lib/openal-soft/alc/effects/null.cpp b/Engine/lib/openal-soft/alc/effects/null.cpp index 1f9ae67bc..217181a8e 100644 --- a/Engine/lib/openal-soft/alc/effects/null.cpp +++ b/Engine/lib/openal-soft/alc/effects/null.cpp @@ -1,14 +1,15 @@ #include "config.h" -#include +#include -#include "almalloc.h" #include "alspan.h" #include "base.h" #include "core/bufferline.h" +#include "core/effects/base.h" #include "intrusive_ptr.h" +struct BufferStorage; struct ContextBase; struct DeviceBase; struct EffectSlot; @@ -25,8 +26,6 @@ struct NullState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(NullState) }; /* This constructs the effect state. It's called when the object is first diff --git a/Engine/lib/openal-soft/alc/effects/pshifter.cpp b/Engine/lib/openal-soft/alc/effects/pshifter.cpp index 426a2264b..8b3c6d291 100644 --- a/Engine/lib/openal-soft/alc/effects/pshifter.cpp +++ b/Engine/lib/openal-soft/alc/effects/pshifter.cpp @@ -25,22 +25,23 @@ #include #include #include -#include +#include #include "alc/effects/base.h" -#include "alcomplex.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" +#include "core/ambidefs.h" #include "core/bufferline.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" +#include "pffft.h" +struct BufferStorage; struct ContextBase; @@ -58,7 +59,7 @@ constexpr size_t StftStep{StftSize / OversampleFactor}; /* Define a Hann window, used to filter the STFT input and output. */ struct Windower { - alignas(16) std::array mData; + alignas(16) std::array mData{}; Windower() { @@ -82,27 +83,29 @@ struct FrequencyBin { struct PshifterState final : public EffectState { /* Effect parameters */ - size_t mCount; - size_t mPos; - uint mPitchShiftI; - float mPitchShift; + size_t mCount{}; + size_t mPos{}; + uint mPitchShiftI{}; + float mPitchShift{}; /* Effects buffers */ - std::array mFIFO; - std::array mLastPhase; - std::array mSumPhase; - std::array mOutputAccum; + std::array mFIFO{}; + std::array mLastPhase{}; + std::array mSumPhase{}; + std::array mOutputAccum{}; - std::array mFftBuffer; + PFFFTSetup mFft; + alignas(16) std::array mFftBuffer{}; + alignas(16) std::array mFftWorkBuffer{}; - std::array mAnalysisBuffer; - std::array mSynthesisBuffer; + std::array mAnalysisBuffer{}; + std::array mSynthesisBuffer{}; - alignas(16) FloatBufferLine mBufferOut; + alignas(16) FloatBufferLine mBufferOut{}; /* Effect gains for each output channel */ - float mCurrentGains[MaxAmbiChannels]; - float mTargetGains[MaxAmbiChannels]; + std::array mCurrentGains{}; + std::array mTargetGains{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -110,8 +113,6 @@ struct PshifterState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(PshifterState) }; void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -126,26 +127,31 @@ void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) mLastPhase.fill(0.0f); mSumPhase.fill(0.0f); mOutputAccum.fill(0.0f); - mFftBuffer.fill(complex_f{}); + mFftBuffer.fill(0.0f); mAnalysisBuffer.fill(FrequencyBin{}); mSynthesisBuffer.fill(FrequencyBin{}); - std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f); - std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); + mCurrentGains.fill(0.0f); + mTargetGains.fill(0.0f); + + if(!mFft) + mFft = PFFFTSetup{StftSize, PFFFT_REAL}; } void PshifterState::update(const ContextBase*, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { - const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune}; + auto &props = std::get(*props_); + const int tune{props.CoarseTune*100 + props.FineTune}; const float pitch{std::pow(2.0f, static_cast(tune) / 1200.0f)}; - mPitchShiftI = clampu(fastf2u(pitch*MixerFracOne), MixerFracHalf, MixerFracOne*2); + mPitchShiftI = std::clamp(fastf2u(pitch*MixerFracOne), uint{MixerFracHalf}, + uint{MixerFracOne}*2u); mPitchShift = static_cast(mPitchShiftI) * float{1.0f/MixerFracOne}; - static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}); + static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs.data(), slot->Gain, mTargetGains); + ComputePanGains(target.Main, coeffs, slot->Gain, mTargetGains); } void PshifterState::process(const size_t samplesToDo, @@ -162,7 +168,7 @@ void PshifterState::process(const size_t samplesToDo, for(size_t base{0u};base < samplesToDo;) { - const size_t todo{minz(StftStep-mCount, samplesToDo-base)}; + const size_t todo{std::min(StftStep-mCount, samplesToDo-base)}; /* Retrieve the output samples from the FIFO and fill in the new input * samples. @@ -186,15 +192,19 @@ void PshifterState::process(const size_t samplesToDo, mFftBuffer[k] = mFIFO[src] * gWindow.mData[k]; for(size_t src{0u}, k{StftSize-mPos};src < mPos;++src,++k) mFftBuffer[k] = mFIFO[src] * gWindow.mData[k]; - forward_fft(al::as_span(mFftBuffer)); + mFft.transform_ordered(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(), + PFFFT_FORWARD); /* Analyze the obtained data. Since the real FFT is symmetric, only * StftHalfSize+1 samples are needed. */ - for(size_t k{0u};k < StftHalfSize+1;k++) + for(size_t k{0u};k < StftHalfSize+1;++k) { - const float magnitude{std::abs(mFftBuffer[k])}; - const float phase{std::arg(mFftBuffer[k])}; + const auto cplx = (k == 0) ? complex_f{mFftBuffer[0]} : + (k == StftHalfSize) ? complex_f{mFftBuffer[1]} : + complex_f{mFftBuffer[k*2], mFftBuffer[k*2 + 1]}; + const float magnitude{std::abs(cplx)}; + const float phase{std::arg(cplx)}; /* Compute the phase difference from the last update and subtract * the expected phase difference for this bin. @@ -232,8 +242,8 @@ void PshifterState::process(const size_t samplesToDo, */ std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{}); - constexpr size_t bin_limit{((StftHalfSize+1)<> MixerFracBits}; @@ -266,21 +276,29 @@ void PshifterState::process(const size_t samplesToDo, tmp -= static_cast(qpd + (qpd%2)); mSumPhase[k] = tmp * al::numbers::pi_v; - mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k]); + const complex_f cplx{std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k])}; + if(k == 0) + mFftBuffer[0] = cplx.real(); + else if(k == StftHalfSize) + mFftBuffer[1] = cplx.real(); + else + { + mFftBuffer[k*2 + 0] = cplx.real(); + mFftBuffer[k*2 + 1] = cplx.imag(); + } } - for(size_t k{StftHalfSize+1};k < StftSize;++k) - mFftBuffer[k] = std::conj(mFftBuffer[StftSize-k]); /* Apply an inverse FFT to get the time-domain signal, and accumulate * for the output with windowing. */ - inverse_fft(al::as_span(mFftBuffer)); + mFft.transform_ordered(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(), + PFFFT_BACKWARD); static constexpr float scale{3.0f / OversampleFactor / StftSize}; for(size_t dst{mPos}, k{0u};dst < StftSize;++dst,++k) - mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale; + mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale; for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k) - mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale; + mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale; /* Copy out the accumulated result, then clear for the next iteration. */ std::copy_n(mOutputAccum.begin() + mPos, StftStep, mFIFO.begin() + mPos); @@ -288,8 +306,8 @@ void PshifterState::process(const size_t samplesToDo, } /* Now, mix the processed sound data to the output. */ - MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains, - maxz(samplesToDo, 512), 0); + MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains, + std::max(samplesToDo, 512_uz), 0); } diff --git a/Engine/lib/openal-soft/alc/effects/reverb.cpp b/Engine/lib/openal-soft/alc/effects/reverb.cpp index 3875bedb1..63cb629b2 100644 --- a/Engine/lib/openal-soft/alc/effects/reverb.cpp +++ b/Engine/lib/openal-soft/alc/effects/reverb.cpp @@ -22,22 +22,25 @@ #include #include +#include +#include +#include #include #include -#include #include -#include +#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" +#include "core/cubic_tables.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/filters/splitter.h" @@ -45,13 +48,9 @@ #include "core/mixer/defs.h" #include "intrusive_ptr.h" #include "opthelpers.h" -#include "vecmat.h" #include "vector.h" -/* This is a user config option for modifying the overall output of the reverb - * effect. - */ -float ReverbBoost = 1.0f; +struct BufferStorage; namespace { @@ -65,41 +64,6 @@ constexpr float DefaultModulationTime{0.25f}; #define MOD_FRACMASK (MOD_FRACONE-1) -struct CubicFilter { - static constexpr size_t sTableBits{8}; - static constexpr size_t sTableSteps{1 << sTableBits}; - static constexpr size_t sTableMask{sTableSteps - 1}; - - float mFilter[sTableSteps*2 + 1]{}; - - constexpr CubicFilter() - { - /* This creates a lookup table for a cubic spline filter, with 256 - * steps between samples. Only half the coefficients are needed, since - * Coeff2 is just Coeff1 in reverse and Coeff3 is just Coeff0 in - * reverse. - */ - for(size_t i{0};i < sTableSteps;++i) - { - const double mu{static_cast(i) / double{sTableSteps}}; - const double mu2{mu*mu}, mu3{mu2*mu}; - const double a0{-0.5*mu3 + mu2 + -0.5*mu}; - const double a1{ 1.5*mu3 + -2.5*mu2 + 1.0f}; - mFilter[i] = static_cast(a1); - mFilter[sTableSteps+i] = static_cast(a0); - } - } - - constexpr float getCoeff0(size_t i) const noexcept { return mFilter[sTableSteps+i]; } - constexpr float getCoeff1(size_t i) const noexcept { return mFilter[i]; } - constexpr float getCoeff2(size_t i) const noexcept { return mFilter[sTableSteps-i]; } - constexpr float getCoeff3(size_t i) const noexcept { return mFilter[sTableSteps*2-i]; } -}; -constexpr CubicFilter gCubicTable; - - -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. */ @@ -122,35 +86,41 @@ constexpr size_t NUM_LINES{4u}; constexpr float MODULATION_DEPTH_COEFF{0.05f}; -/* 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. +/* The B-Format to (W-normalized) A-Format conversion matrix. This produces a + * tetrahedral array of discrete signals (boosted by a factor of sqrt(3), to + * reduce the error introduced in the conversion). */ -alignas(16) constexpr float B2A[NUM_LINES][NUM_LINES]{ - { 0.5f, 0.5f, 0.5f, 0.5f }, - { 0.5f, -0.5f, -0.5f, 0.5f }, - { 0.5f, 0.5f, -0.5f, -0.5f }, - { 0.5f, -0.5f, 0.5f, -0.5f } -}; - -/* Converts A-Format to B-Format for early reflections. */ -alignas(16) constexpr std::array,NUM_LINES> EarlyA2B{{ - {{ 0.5f, 0.5f, 0.5f, 0.5f }}, - {{ 0.5f, -0.5f, 0.5f, -0.5f }}, - {{ 0.5f, -0.5f, -0.5f, 0.5f }}, - {{ 0.5f, 0.5f, -0.5f, -0.5f }} +alignas(16) constexpr std::array,NUM_LINES> B2A{{ + /* W Y Z X */ + {{ 0.5f, 0.5f, 0.5f, 0.5f }}, /* A0 */ + {{ 0.5f, -0.5f, -0.5f, 0.5f }}, /* A1 */ + {{ 0.5f, 0.5f, -0.5f, -0.5f }}, /* A2 */ + {{ 0.5f, -0.5f, 0.5f, -0.5f }} /* A3 */ }}; -/* Converts A-Format to B-Format for late reverb. */ +/* Converts (W-normalized) A-Format to B-Format for early reflections (scaled + * by 1/sqrt(3) to compensate for the boost in the B2A matrix). + */ +alignas(16) constexpr std::array,NUM_LINES> EarlyA2B{{ + /* A0 A1 A2 A3 */ + {{ 0.5f, 0.5f, 0.5f, 0.5f }}, /* W */ + {{ 0.5f, -0.5f, 0.5f, -0.5f }}, /* Y */ + {{ 0.5f, -0.5f, -0.5f, 0.5f }}, /* Z */ + {{ 0.5f, 0.5f, -0.5f, -0.5f }} /* X */ +}}; + +/* Converts (W-normalized) A-Format to B-Format for late reverb (scaled + * by 1/sqrt(3) to compensate for the boost in the B2A matrix). The response + * is rotated around Z (ambisonic X) so that the front lines are placed + * horizontally in front, and the rear lines are placed vertically in back. + */ constexpr auto InvSqrt2 = static_cast(1.0/al::numbers::sqrt2); alignas(16) constexpr std::array,NUM_LINES> LateA2B{{ - {{ 0.5f, 0.5f, 0.5f, 0.5f }}, - {{ InvSqrt2, -InvSqrt2, 0.0f, 0.0f }}, - {{ 0.0f, 0.0f, InvSqrt2, -InvSqrt2 }}, - {{ 0.5f, 0.5f, -0.5f, -0.5f }} + /* A0 A1 A2 A3 */ + {{ 0.5f, 0.5f, 0.5f, 0.5f }}, /* W */ + {{ InvSqrt2, -InvSqrt2, 0.0f, 0.0f }}, /* Y */ + {{ 0.0f, 0.0f, -InvSqrt2, InvSqrt2 }}, /* Z */ + {{ 0.5f, 0.5f, -0.5f, -0.5f }} /* X */ }}; /* The all-pass and delay lines have a variable length dependent on the @@ -251,7 +221,7 @@ constexpr std::array EARLY_ALLPASS_LENGTHS{{ * Using an average dimension of 1m, we get: */ constexpr std::array EARLY_LINE_LENGTHS{{ - 5.9850400e-4f, 1.0913150e-3f, 1.5376658e-3f, 1.9419362e-3f + 0.0000000e+0f, 4.9281100e-4f, 9.3916180e-4f, 1.3434322e-3f }}; /* The late all-pass filter lengths are based on the late line lengths: @@ -288,21 +258,17 @@ 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 *Line; - }; + al::span mLine; /* Given the allocated sample buffer, this function updates each delay line * offset. */ - void realizeLineOffset(std::array *sampleBuffer) noexcept - { Line = sampleBuffer + LineOffset; } + void realizeLineOffset(al::span sampleBuffer) noexcept + { mLine = sampleBuffer; } /* Calculate the length of a delay line and store its mask and offset. */ - uint calcLineLength(const float length, const uintptr_t offset, const float frequency, - const uint extra) + static + auto calcLineLength(const float length, const float frequency, const uint extra) -> size_t { /* All line lengths are powers of 2, calculated from their lengths in * seconds, rounded up. @@ -310,23 +276,85 @@ struct DelayLineI { uint 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; + return samples*NUM_LINES; + } +}; + +struct DelayLineU { + al::span mLine; + + void realizeLineOffset(al::span sampleBuffer) noexcept + { + assert(sampleBuffer.size() > 4 && !(sampleBuffer.size() & (sampleBuffer.size()-1))); + mLine = sampleBuffer; } - void write(size_t offset, const size_t c, const float *RESTRICT in, const size_t count) const noexcept + static + auto calcLineLength(const float length, const float frequency, const uint extra) -> size_t { - ASSUME(count > 0); + uint samples{float2uint(std::ceil(length*frequency))}; + samples = NextPowerOf2(samples + extra); + + return samples*NUM_LINES; + } + + [[nodiscard]] + auto get(size_t chan) const noexcept + { + const size_t stride{mLine.size() / NUM_LINES}; + return mLine.subspan(chan*stride, stride); + } + + void write(size_t offset, const size_t c, al::span in) const noexcept + { + const size_t stride{mLine.size() / NUM_LINES}; + const auto output = mLine.subspan(c*stride); + while(!in.empty()) + { + offset &= stride-1; + const size_t td{std::min(stride - offset, in.size())}; + std::copy_n(in.begin(), td, output.begin() + ptrdiff_t(offset)); + offset += td; + in = in.subspan(td); + } + } + + /* Writes the given input lines to the delay buffer, applying a geometric + * reflection. This effectively applies the matrix + * + * [ +1/2 -1/2 -1/2 -1/2 ] + * [ -1/2 +1/2 -1/2 -1/2 ] + * [ -1/2 -1/2 +1/2 -1/2 ] + * [ -1/2 -1/2 -1/2 +1/2 ] + * + * to the four input lines when writing to the delay buffer. The effect on + * the B-Format signal is negating W, applying a 180-degree phase shift and + * moving each response to its spatially opposite location. + */ + void writeReflected(size_t offset, const al::span in, + const size_t count) const noexcept + { + const size_t stride{mLine.size() / NUM_LINES}; for(size_t i{0u};i < count;) { - offset &= Mask; - size_t td{minz(Mask+1 - offset, count - i)}; + offset &= stride-1; + size_t td{std::min(stride - offset, count - i)}; do { - Line[offset++][c] = in[i++]; + const std::array src{in[0][i], in[1][i], in[2][i], in[3][i]}; + ++i; + + const std::array f{ + (src[0] - src[1] - src[2] - src[3]) * 0.5f, + (src[1] - src[0] - src[2] - src[3]) * 0.5f, + (src[2] - src[0] - src[1] - src[3]) * 0.5f, + (src[3] - src[0] - src[1] - src[2] ) * 0.5f + }; + mLine[0*stride + offset] = f[0]; + mLine[1*stride + offset] = f[1]; + mLine[2*stride + offset] = f[2]; + mLine[3*stride + offset] = f[3]; + ++offset; } while(--td); } } @@ -335,10 +363,19 @@ struct DelayLineI { struct VecAllpass { DelayLineI Delay; float Coeff{0.0f}; - size_t Offset[NUM_LINES]{}; + std::array Offset{}; void process(const al::span samples, size_t offset, - const float xCoeff, const float yCoeff, const size_t todo); + const float xCoeff, const float yCoeff, const size_t todo) const noexcept; +}; + +struct Allpass4 { + DelayLineU Delay; + float Coeff{0.0f}; + std::array Offset{}; + + void process(const al::span samples, const size_t offset, + const size_t todo) const noexcept; }; struct T60Filter { @@ -353,30 +390,37 @@ struct T60Filter { /* Applies the two T60 damping filter sections. */ void process(const al::span samples) - { DualBiquad{HFFilter, LFFilter}.process(samples, samples.data()); } + { DualBiquad{HFFilter, LFFilter}.process(samples, samples); } void clear() noexcept { HFFilter.clear(); LFFilter.clear(); } }; 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; + Allpass4 VecAp; /* An echo line is used to complete the second half of the early * reflections. */ - DelayLineI Delay; - size_t Offset[NUM_LINES]{}; - float Coeff[NUM_LINES]{}; + DelayLineU Delay; + std::array Offset{}; + std::array Coeff{}; /* The gain for each output channel based on 3D panning. */ - float CurrentGains[NUM_LINES][MaxAmbiChannels]{}; - float TargetGains[NUM_LINES][MaxAmbiChannels]{}; + struct OutGains { + std::array Current{}; + std::array Target{}; + + void clear() { Current.fill(0.0f); Target.fill(0.0); } + }; + std::array Gains{}; void updateLines(const float density_mult, const float diffusion, const float decayTime, const float frequency); + + void clear() + { + std::for_each(Gains.begin(), Gains.end(), std::mem_fn(&OutGains::clear)); + } }; @@ -384,22 +428,29 @@ struct Modulation { /* The vibrato time is tracked with an index over a (MOD_FRACONE) * normalized range. */ - uint Index, Step; + uint Index{0u}, Step{1u}; /* The depth of frequency change, in samples. */ - float Depth; + float Depth{0.0f}; - float ModDelays[MAX_UPDATE_SAMPLES]; + std::array ModDelays{}; void updateModulator(float modTime, float modDepth, float frequency); - void calcDelays(size_t todo); + auto calcDelays(size_t todo) -> al::span; + + void clear() noexcept + { + Index = 0u; + Step = 1u; + Depth = 0.0f; + } }; struct LateReverb { /* A recursive delay line is used fill in the reverb tail. */ - DelayLineI Delay; - size_t Offset[NUM_LINES]{}; + DelayLineU Delay; + std::array Offset{}; /* Attenuation to compensate for the modal density and decay rate of the * late lines. @@ -407,7 +458,7 @@ struct LateReverb { float DensityGain{0.0f}; /* T60 decay filters are used to simulate absorption. */ - T60Filter T60[NUM_LINES]; + std::array T60; Modulation Mod; @@ -415,40 +466,49 @@ struct LateReverb { VecAllpass VecAp; /* The gain for each output channel based on 3D panning. */ - float CurrentGains[NUM_LINES][MaxAmbiChannels]{}; - float TargetGains[NUM_LINES][MaxAmbiChannels]{}; + struct OutGains { + std::array Current{}; + std::array Target{}; + + void clear() { Current.fill(0.0f); Target.fill(0.0); } + }; + std::array Gains{}; void updateLines(const float density_mult, const float diffusion, const float lfDecayTime, const float mfDecayTime, const float hfDecayTime, const float lf0norm, const float hf0norm, const float frequency); - void clear() noexcept + void clear() { - for(auto &filter : T60) - filter.clear(); + std::for_each(T60.begin(), T60.end(), std::mem_fn(&T60Filter::clear)); + Mod.clear(); + std::for_each(Gains.begin(), Gains.end(), std::mem_fn(&OutGains::clear)); } }; struct ReverbPipeline { /* Master effect filters */ - struct { + struct FilterPair { BiquadFilter Lp; BiquadFilter Hp; - } mFilter[NUM_LINES]; + void clear() noexcept { Lp.clear(); Hp.clear(); } + }; + std::array mFilter; - /* Core delay line (early reflections and late reverb tap from this). */ - DelayLineI mEarlyDelayIn; - DelayLineI mLateDelayIn; + /* Late reverb input delay line (early reflections feed this, and late + * reverb taps from it). + */ + DelayLineU mLateDelayIn; - /* Tap points for early reflection delay. */ - size_t mEarlyDelayTap[NUM_LINES][2]{}; - float mEarlyDelayCoeff[NUM_LINES]{}; + /* Tap points for early reflection input delay. */ + std::array,NUM_LINES> mEarlyDelayTap{}; + std::array,NUM_LINES> mEarlyDelayCoeff{}; /* Tap points for late reverb feed and delay. */ - size_t mLateDelayTap[NUM_LINES][2]{}; + std::array,NUM_LINES> mLateDelayTap{}; /* Coefficients for the all-pass and line scattering matrices. */ - float mMixX{0.0f}; + float mMixX{1.0f}; float mMixY{0.0f}; EarlyReflections mEarly; @@ -459,12 +519,13 @@ struct ReverbPipeline { size_t mFadeSampleCount{1}; - void updateDelayLine(const float earlyDelay, const float lateDelay, const float density_mult, - const float decayTime, const float frequency); - void update3DPanning(const float *ReflectionsPan, const float *LateReverbPan, - const float earlyGain, const float lateGain, const bool doUpmix, const MixParams *mainMix); + void updateDelayLine(const float gain, const float earlyDelay, const float lateDelay, + const float density_mult, const float decayTime, const float frequency); + void update3DPanning(const al::span ReflectionsPan, + const al::span LateReverbPan, const float earlyGain, const float lateGain, + const bool doUpmix, const MixParams *mainMix); - void processEarly(size_t offset, const size_t samplesToDo, + void processEarly(const DelayLineU &main_delay, size_t offset, const size_t samplesToDo, const al::span tempSamples, const al::span outSamples); void processLate(size_t offset, const size_t samplesToDo, @@ -473,17 +534,15 @@ struct ReverbPipeline { void clear() noexcept { - for(auto &filter : mFilter) - { - filter.Lp.clear(); - filter.Hp.clear(); - } + std::for_each(mFilter.begin(), mFilter.end(), std::mem_fn(&FilterPair::clear)); + mEarlyDelayTap = {}; + mEarlyDelayCoeff = {}; + mLateDelayTap = {}; + mEarly.clear(); mLate.clear(); - for(auto &filters : mAmbiSplitter) - { - for(auto &filter : filters) - filter.clear(); - } + auto clear_filters = [](const al::span filters) + { std::for_each(filters.begin(), filters.end(), std::mem_fn(&BandSplitter::clear)); }; + std::for_each(mAmbiSplitter.begin(), mAmbiSplitter.end(), clear_filters); } }; @@ -491,9 +550,9 @@ struct ReverbState final : public EffectState { /* All delay lines are allocated as a single buffer to reduce memory * fragmentation and management code. */ - al::vector,16> mSampleBuffer; + al::vector mSampleBuffer; - struct { + struct Params { /* Calculated parameters which indicate if cross-fading is needed after * an update. */ @@ -506,7 +565,8 @@ struct ReverbState final : public EffectState { float ModulationDepth{0.0f}; float HFReference{5000.0f}; float LFReference{250.0f}; - } mParams; + }; + Params mParams; enum PipelineState : uint8_t { DeviceClear, @@ -516,18 +576,20 @@ struct ReverbState final : public EffectState { Normal, }; PipelineState mPipelineState{DeviceClear}; - uint8_t mCurrentPipeline{0}; + bool mCurrentPipeline{false}; - ReverbPipeline mPipelines[2]; + /* Core delay line (early reflections tap from this). */ + DelayLineU mMainDelay; + + std::array mPipelines; /* 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 mTempSamples; - }; + alignas(16) FloatBufferLine mTempLine{}; + alignas(16) std::array mTempSamples{}; + alignas(16) std::array mEarlySamples{}; alignas(16) std::array mLateSamples{}; @@ -537,48 +599,43 @@ struct ReverbState final : public EffectState { void MixOutPlain(ReverbPipeline &pipeline, const al::span samplesOut, - const size_t todo) + const size_t todo) const { - ASSUME(todo > 0); - /* When not upsampling, the panning gains convert to B-Format and pan * at the same time. */ - for(size_t c{0u};c < NUM_LINES;c++) + auto inBuffer = mEarlySamples.cbegin(); + for(auto &gains : pipeline.mEarly.Gains) { - const al::span tmpspan{mEarlySamples[c].data(), todo}; - MixSamples(tmpspan, samplesOut, pipeline.mEarly.CurrentGains[c], - pipeline.mEarly.TargetGains[c], todo, 0); + MixSamples(al::span{*inBuffer++}.first(todo), samplesOut, gains.Current, gains.Target, + todo, 0); } - for(size_t c{0u};c < NUM_LINES;c++) + inBuffer = mLateSamples.cbegin(); + for(auto &gains : pipeline.mLate.Gains) { - const al::span tmpspan{mLateSamples[c].data(), todo}; - MixSamples(tmpspan, samplesOut, pipeline.mLate.CurrentGains[c], - pipeline.mLate.TargetGains[c], todo, 0); + MixSamples(al::span{*inBuffer++}.first(todo), samplesOut, gains.Current, gains.Target, + todo, 0); } } void MixOutAmbiUp(ReverbPipeline &pipeline, const al::span samplesOut, const size_t todo) { - ASSUME(todo > 0); - auto DoMixRow = [](const al::span OutBuffer, const al::span Gains, - const float *InSamples, const size_t InStride) + const al::span InSamples) { + auto inBuffer = InSamples.cbegin(); std::fill(OutBuffer.begin(), OutBuffer.end(), 0.0f); for(const float gain : Gains) { - const float *RESTRICT input{al::assume_aligned<16>(InSamples)}; - InSamples += InStride; - - if(!(std::fabs(gain) > GainSilenceThreshold)) - continue; - - auto mix_sample = [gain](const float sample, const float in) noexcept -> float - { return sample + in*gain; }; - std::transform(OutBuffer.begin(), OutBuffer.end(), input, OutBuffer.begin(), - mix_sample); + if(std::fabs(gain) > GainSilenceThreshold) + { + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(OutBuffer.begin(), OutBuffer.end(), inBuffer->cbegin(), + OutBuffer.begin(), mix_sample); + } + ++inBuffer; } }; @@ -586,29 +643,33 @@ struct ReverbState final : public EffectState { * so the proper HF scaling can be applied to each B-Format channel. * The panning gains then pan and upsample the B-Format channels. */ - const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), todo}; - for(size_t c{0u};c < NUM_LINES;c++) + const auto tmpspan = al::span{mTempLine}.first(todo); + auto hfscale = float{mOrderScales[0]}; + auto splitter = pipeline.mAmbiSplitter[0].begin(); + auto a2bcoeffs = EarlyA2B.cbegin(); + for(auto &gains : pipeline.mEarly.Gains) { - DoMixRow(tmpspan, EarlyA2B[c], mEarlySamples[0].data(), mEarlySamples[0].size()); + DoMixRow(tmpspan, *(a2bcoeffs++), mEarlySamples); /* Apply scaling to the B-Format's HF response to "upsample" it to * higher-order output. */ - const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - pipeline.mAmbiSplitter[0][c].processHfScale(tmpspan, hfscale); + (splitter++)->processHfScale(tmpspan, hfscale); + hfscale = mOrderScales[1]; - MixSamples(tmpspan, samplesOut, pipeline.mEarly.CurrentGains[c], - pipeline.mEarly.TargetGains[c], todo, 0); + MixSamples(tmpspan, samplesOut, gains.Current, gains.Target, todo, 0); } - for(size_t c{0u};c < NUM_LINES;c++) + hfscale = mOrderScales[0]; + splitter = pipeline.mAmbiSplitter[1].begin(); + a2bcoeffs = LateA2B.cbegin(); + for(auto &gains : pipeline.mLate.Gains) { - DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size()); + DoMixRow(tmpspan, *(a2bcoeffs++), mLateSamples); - const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - pipeline.mAmbiSplitter[1][c].processHfScale(tmpspan, hfscale); + (splitter++)->processHfScale(tmpspan, hfscale); + hfscale = mOrderScales[1]; - MixSamples(tmpspan, samplesOut, pipeline.mLate.CurrentGains[c], - pipeline.mLate.TargetGains[c], todo, 0); + MixSamples(tmpspan, samplesOut, gains.Current, gains.Target, todo, 0); } } @@ -627,8 +688,6 @@ struct ReverbState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(ReverbState) }; /************************************** @@ -636,18 +695,13 @@ struct ReverbState final : public EffectState { **************************************/ inline float CalcDelayLengthMult(float density) -{ return maxf(5.0f, std::cbrt(density*DENSITY_SCALE)); } +{ return std::max(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). */ void ReverbState::allocLines(const float 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... */ @@ -657,64 +711,83 @@ void ReverbState::allocLines(const float frequency) * time and depth coefficient, and halfed for the low-to-high frequency * swing. */ - constexpr float max_mod_delay{MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f}; + static constexpr float max_mod_delay{MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f}; + std::array linelengths{}; + size_t oidx{0}; + + size_t totalSamples{0u}; + /* The main delay length includes the maximum early reflection delay and + * the largest early tap width. It must also be extended by the update size + * (BufferLineSize) for block processing. + */ + float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier}; + size_t count{mMainDelay.calcLineLength(length, frequency, BufferLineSize)}; + linelengths[oidx++] = count; + totalSamples += count; for(auto &pipeline : mPipelines) { - /* The main delay length includes the maximum early reflection delay, - * the largest early tap width, the maximum late reverb delay, and the - * largest late tap width. Finally, it must also be extended by the - * update size (BufferLineSize) for block processing. - */ - float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier}; - totalSamples += pipeline.mEarlyDelayIn.calcLineLength(length, totalSamples, frequency, - BufferLineSize); - - constexpr float LateLineDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) / + static constexpr float LateDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) / float{NUM_LINES}}; - length = ReverbMaxLateReverbDelay + LateLineDiffAvg*multiplier; - totalSamples += pipeline.mLateDelayIn.calcLineLength(length, totalSamples, frequency, - BufferLineSize); + length = ReverbMaxLateReverbDelay + LateDiffAvg*multiplier; + count = pipeline.mLateDelayIn.calcLineLength(length, frequency, BufferLineSize); + linelengths[oidx++] = count; + totalSamples += count; /* The early vector all-pass line. */ length = EARLY_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += pipeline.mEarly.VecAp.Delay.calcLineLength(length, totalSamples, frequency, - 0); + count = pipeline.mEarly.VecAp.Delay.calcLineLength(length, frequency, 0); + linelengths[oidx++] = count; + totalSamples += count; /* The early reflection line. */ length = EARLY_LINE_LENGTHS.back() * multiplier; - totalSamples += pipeline.mEarly.Delay.calcLineLength(length, totalSamples, frequency, - MAX_UPDATE_SAMPLES); + count = pipeline.mEarly.Delay.calcLineLength(length, frequency, MAX_UPDATE_SAMPLES); + linelengths[oidx++] = count; + totalSamples += count; /* The late vector all-pass line. */ length = LATE_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += pipeline.mLate.VecAp.Delay.calcLineLength(length, totalSamples, frequency, - 0); + count = pipeline.mLate.VecAp.Delay.calcLineLength(length, frequency, 0); + linelengths[oidx++] = count; + totalSamples += count; /* The late delay lines are calculated from the largest maximum density * line length, and the maximum modulation delay. Four additional * samples are needed for resampling the modulator delay. */ length = LATE_LINE_LENGTHS.back()*multiplier + max_mod_delay; - totalSamples += pipeline.mLate.Delay.calcLineLength(length, totalSamples, frequency, 4); + count = pipeline.mLate.Delay.calcLineLength(length, frequency, 4); + linelengths[oidx++] = count; + totalSamples += count; } + assert(oidx == linelengths.size()); if(totalSamples != mSampleBuffer.size()) decltype(mSampleBuffer)(totalSamples).swap(mSampleBuffer); /* Clear the sample buffer. */ - std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), decltype(mSampleBuffer)::value_type{}); + std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); /* Update all delays to reflect the new sample buffer. */ + auto bufferspan = al::span{mSampleBuffer}; + oidx = 0; + mMainDelay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); for(auto &pipeline : mPipelines) { - pipeline.mEarlyDelayIn.realizeLineOffset(mSampleBuffer.data()); - pipeline.mLateDelayIn.realizeLineOffset(mSampleBuffer.data()); - pipeline.mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); - pipeline.mEarly.Delay.realizeLineOffset(mSampleBuffer.data()); - pipeline.mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); - pipeline.mLate.Delay.realizeLineOffset(mSampleBuffer.data()); + pipeline.mLateDelayIn.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mEarly.VecAp.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mEarly.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mLate.VecAp.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mLate.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); } + assert(oidx == linelengths.size()); } void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) @@ -724,41 +797,7 @@ void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) /* Allocate the delay lines. */ allocLines(frequency); - for(auto &pipeline : mPipelines) - { - /* Clear filters and gain coefficients since the delay lines were all just - * cleared (if not reallocated). - */ - for(auto &filter : pipeline.mFilter) - { - filter.Lp.clear(); - filter.Hp.clear(); - } - - std::fill(std::begin(pipeline.mEarlyDelayCoeff),std::end(pipeline.mEarlyDelayCoeff), 0.0f); - std::fill(std::begin(pipeline.mEarlyDelayCoeff),std::end(pipeline.mEarlyDelayCoeff), 0.0f); - - pipeline.mLate.DensityGain = 0.0f; - for(auto &t60 : pipeline.mLate.T60) - { - t60.MidGain = 0.0f; - t60.HFFilter.clear(); - t60.LFFilter.clear(); - } - - pipeline.mLate.Mod.Index = 0; - pipeline.mLate.Mod.Step = 1; - pipeline.mLate.Mod.Depth = 0.0f; - - for(auto &gains : pipeline.mEarly.CurrentGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : pipeline.mEarly.TargetGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : pipeline.mLate.CurrentGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : pipeline.mLate.TargetGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); - } + std::for_each(mPipelines.begin(), mPipelines.end(), std::mem_fn(&ReverbPipeline::clear)); mPipelineState = DeviceClear; /* Reset offset base. */ @@ -774,14 +813,14 @@ void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) mUpmixOutput = false; mOrderScales.fill(1.0f); } - mPipelines[0].mAmbiSplitter[0][0].init(device->mXOverFreq / frequency); - for(auto &pipeline : mPipelines) + + auto splitter = BandSplitter{device->mXOverFreq / frequency}; + auto set_splitters = [&splitter](ReverbPipeline &pipeline) { - std::fill(pipeline.mAmbiSplitter[0].begin(), pipeline.mAmbiSplitter[0].end(), - pipeline.mAmbiSplitter[0][0]); - std::fill(pipeline.mAmbiSplitter[1].begin(), pipeline.mAmbiSplitter[1].end(), - pipeline.mAmbiSplitter[0][0]); - } + std::fill(pipeline.mAmbiSplitter[0].begin(), pipeline.mAmbiSplitter[0].end(), splitter); + std::fill(pipeline.mAmbiSplitter[1].begin(), pipeline.mAmbiSplitter[1].end(), splitter); + }; + std::for_each(mPipelines.begin(), mPipelines.end(), set_splitters); } /************************************** @@ -852,7 +891,7 @@ float CalcLimitedHfRatio(const float hfRatio, const float airAbsorptionGainHF, CalcDecayLength(airAbsorptionGainHF, decayTime)}; /* Using the limit calculated above, apply the upper bound to the HF ratio. */ - return minf(limitRatio, hfRatio); + return std::min(limitRatio, hfRatio); } @@ -908,7 +947,7 @@ void Modulation::updateModulator(float modTime, float modDepth, float frequency) * appropriate step size to generate an LFO, which will vary the feedback * delay over time. */ - Step = maxu(fastf2u(MOD_FRACONE / (frequency * modTime)), 1); + Step = std::max(fastf2u(MOD_FRACONE / (frequency * modTime)), 1u); /* The modulation depth effects the amount of frequency change over the * range of the sinus. It needs to be scaled by the modulation time so that @@ -981,7 +1020,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, * includes one sample of delay. Reduce by one to compensate. */ length = LATE_LINE_LENGTHS[i] * density_mult; - Offset[i] = maxu(float2uint(length*frequency + 0.5f), 1u) - 1u; + Offset[i] = std::max(float2uint(length*frequency + 0.5f), 1u) - 1u; /* Approximate the absorption that the vector all-pass would exhibit * given the current diffusion so we don't have to process a full T60 @@ -998,8 +1037,8 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, /* Update the offsets for the main effect delay line. */ -void ReverbPipeline::updateDelayLine(const float earlyDelay, const float lateDelay, - const float density_mult, const float decayTime, const float frequency) +void ReverbPipeline::updateDelayLine(const float gain, const float earlyDelay, + const float lateDelay, const float density_mult, const float decayTime, const float frequency) { /* Early reflection taps are decorrelated by means of an average room * reflection approximation described above the definition of the taps. @@ -1015,8 +1054,12 @@ void ReverbPipeline::updateDelayLine(const float earlyDelay, const float lateDel { float length{EARLY_TAP_LENGTHS[i]*density_mult}; mEarlyDelayTap[i][1] = float2uint((earlyDelay+length) * frequency); - mEarlyDelayCoeff[i] = CalcDecayCoeff(length, decayTime); + mEarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime) * gain; + /* Reduce the late delay tap by the shortest early delay line length to + * compensate for the late line input being fed by the delayed early + * output. + */ length = (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS.front())/float{NUM_LINES}*density_mult + lateDelay; mLateDelayTap[i][1] = float2uint(length * frequency); @@ -1028,7 +1071,7 @@ void ReverbPipeline::updateDelayLine(const float earlyDelay, const float lateDel * focal strength. This function results in a B-Format transformation matrix * that spatially focuses the signal in the desired direction. */ -std::array,4> GetTransformFromVector(const float *vec) +std::array,4> GetTransformFromVector(const al::span vec) { /* Normalize the panning vector according to the N3D scale, which has an * extra sqrt(3) term on the directional components. Converting from OpenAL @@ -1037,13 +1080,14 @@ std::array,4> GetTransformFromVector(const float *vec) * rest of OpenAL which use right-handed. This is fixed by negating Z, * which cancels out with the B-Format Z negation. */ - float norm[3]; + std::array norm{{vec[0], vec[1], vec[2]}}; float mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])}; if(mag > 1.0f) { - norm[0] = vec[0] / mag * -al::numbers::sqrt3_v; - norm[1] = vec[1] / mag * al::numbers::sqrt3_v; - norm[2] = vec[2] / mag * al::numbers::sqrt3_v; + const float scale{al::numbers::sqrt3_v / mag}; + norm[0] *= -scale; + norm[1] *= scale; + norm[2] *= scale; mag = 1.0f; } else @@ -1052,9 +1096,9 @@ std::array,4> GetTransformFromVector(const float *vec) * term. There's no need to renormalize the magnitude since it would * just be reapplied in the matrix. */ - norm[0] = vec[0] * -al::numbers::sqrt3_v; - norm[1] = vec[1] * al::numbers::sqrt3_v; - norm[2] = vec[2] * al::numbers::sqrt3_v; + norm[0] *= -al::numbers::sqrt3_v; + norm[1] *= al::numbers::sqrt3_v; + norm[2] *= al::numbers::sqrt3_v; } return std::array,4>{{ @@ -1066,51 +1110,47 @@ std::array,4> GetTransformFromVector(const float *vec) } /* Update the early and late 3D panning gains. */ -void ReverbPipeline::update3DPanning(const float *ReflectionsPan, const float *LateReverbPan, - const float earlyGain, const float lateGain, const bool doUpmix, const MixParams *mainMix) +void ReverbPipeline::update3DPanning(const al::span ReflectionsPan, + const al::span LateReverbPan, const float earlyGain, const float lateGain, + const bool doUpmix, const MixParams *mainMix) { /* Create matrices that transform a B-Format signal according to the * panning vectors. */ - const std::array,4> earlymat{GetTransformFromVector(ReflectionsPan)}; - const std::array,4> latemat{GetTransformFromVector(LateReverbPan)}; + const auto earlymat = GetTransformFromVector(ReflectionsPan); + const auto latemat = GetTransformFromVector(LateReverbPan); - if(doUpmix) - { - /* When upsampling, combine the early and late transforms with the - * first-order upsample matrix. This results in panning gains that - * apply the panning transform to first-order B-Format, which is then - * upsampled. - */ - auto mult_matrix = [](const al::span,4> mtx1) + const auto [earlycoeffs, latecoeffs] = [&]{ + if(doUpmix) { - auto&& mtx2 = AmbiScale::FirstOrderUp; - std::array,NUM_LINES> res{}; - - for(size_t i{0};i < mtx1[0].size();++i) + /* When upsampling, combine the early and late transforms with the + * first-order upsample matrix. This results in panning gains that + * apply the panning transform to first-order B-Format, which is + * then upsampled. + */ + auto mult_matrix = [](const al::span,4> mtx1) { - float *RESTRICT dst{res[i].data()}; - for(size_t k{0};k < mtx1.size();++k) + std::array,NUM_LINES> res{}; + const auto mtx2 = al::span{AmbiScale::FirstOrderUp}; + + for(size_t i{0};i < mtx1[0].size();++i) { - const float *RESTRICT src{mtx2[k].data()}; - const float a{mtx1[k][i]}; - for(size_t j{0};j < mtx2[0].size();++j) - dst[j] += a * src[j]; + const al::span dst{res[i]}; + static_assert(dst.size() >= std::tuple_size_v); + for(size_t k{0};k < mtx1.size();++k) + { + const float a{mtx1[k][i]}; + std::transform(mtx2[k].begin(), mtx2[k].end(), dst.begin(), dst.begin(), + [a](const float in, const float out) noexcept -> float + { return a*in + out; }); + } } - } - return res; - }; - auto earlycoeffs = mult_matrix(earlymat); - auto latecoeffs = mult_matrix(latemat); + return res; + }; + return std::make_pair(mult_matrix(earlymat), mult_matrix(latemat)); + } - for(size_t i{0u};i < NUM_LINES;i++) - ComputePanGains(mainMix, earlycoeffs[i].data(), earlyGain, mEarly.TargetGains[i]); - for(size_t i{0u};i < NUM_LINES;i++) - ComputePanGains(mainMix, latecoeffs[i].data(), lateGain, mLate.TargetGains[i]); - } - else - { /* When not upsampling, combine the early and late A-to-B-Format * conversions with their respective transform. This results panning * gains that convert A-Format to B-Format, which is then panned. @@ -1122,131 +1162,154 @@ void ReverbPipeline::update3DPanning(const float *ReflectionsPan, const float *L for(size_t i{0};i < mtx1[0].size();++i) { - float *RESTRICT dst{res[i].data()}; + const al::span dst{res[i]}; + static_assert(dst.size() >= std::tuple_size_v); for(size_t k{0};k < mtx1.size();++k) { const float a{mtx1[k][i]}; - for(size_t j{0};j < mtx2.size();++j) - dst[j] += a * mtx2[j][k]; + std::transform(mtx2[k].begin(), mtx2[k].end(), dst.begin(), dst.begin(), + [a](const float in, const float out) noexcept -> float + { return a*in + out; }); } } return res; }; - auto earlycoeffs = mult_matrix(EarlyA2B, earlymat); - auto latecoeffs = mult_matrix(LateA2B, latemat); + return std::make_pair(mult_matrix(EarlyA2B, earlymat), mult_matrix(LateA2B, latemat)); + }(); - for(size_t i{0u};i < NUM_LINES;i++) - ComputePanGains(mainMix, earlycoeffs[i].data(), earlyGain, mEarly.TargetGains[i]); - for(size_t i{0u};i < NUM_LINES;i++) - ComputePanGains(mainMix, latecoeffs[i].data(), lateGain, mLate.TargetGains[i]); - } + auto earlygains = mEarly.Gains.begin(); + for(auto &coeffs : earlycoeffs) + ComputePanGains(mainMix, coeffs, earlyGain, (earlygains++)->Target); + auto lategains = mLate.Gains.begin(); + for(auto &coeffs : latecoeffs) + ComputePanGains(mainMix, coeffs, lateGain, (lategains++)->Target); } void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *Device{Context->mDevice}; const auto frequency = static_cast(Device->Frequency); /* If the HF limit parameter is flagged, calculate an appropriate limit * based on the air absorption parameter. */ - float hfRatio{props->Reverb.DecayHFRatio}; - if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f) - hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF, - props->Reverb.DecayTime); + float hfRatio{props.DecayHFRatio}; + if(props.DecayHFLimit && props.AirAbsorptionGainHF < 1.0f) + hfRatio = CalcLimitedHfRatio(hfRatio, props.AirAbsorptionGainHF, props.DecayTime); /* Calculate the LF/HF decay times. */ constexpr float MinDecayTime{0.1f}, MaxDecayTime{20.0f}; - const float lfDecayTime{clampf(props->Reverb.DecayTime*props->Reverb.DecayLFRatio, - MinDecayTime, MaxDecayTime)}; - const float hfDecayTime{clampf(props->Reverb.DecayTime*hfRatio, MinDecayTime, MaxDecayTime)}; + const float lfDecayTime{std::clamp(props.DecayTime*props.DecayLFRatio, MinDecayTime, + MaxDecayTime)}; + const float hfDecayTime{std::clamp(props.DecayTime*hfRatio, MinDecayTime, MaxDecayTime)}; /* Determine if a full update is required. */ const bool fullUpdate{mPipelineState == DeviceClear || /* Density is essentially a master control for the feedback delays, so * changes the offsets of many delay lines. */ - mParams.Density != props->Reverb.Density || + mParams.Density != props.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.Diffusion != props.Diffusion || + mParams.DecayTime != props.DecayTime || mParams.HFDecayTime != hfDecayTime || mParams.LFDecayTime != lfDecayTime || /* Modulation time and depth both require fading the modulation delay. */ - mParams.ModulationTime != props->Reverb.ModulationTime || - mParams.ModulationDepth != props->Reverb.ModulationDepth || + mParams.ModulationTime != props.ModulationTime || + mParams.ModulationDepth != props.ModulationDepth || /* HF/LF References control the weighting used to calculate the density * gain. */ - mParams.HFReference != props->Reverb.HFReference || - mParams.LFReference != props->Reverb.LFReference}; + mParams.HFReference != props.HFReference || + mParams.LFReference != props.LFReference}; if(fullUpdate) { - mParams.Density = props->Reverb.Density; - mParams.Diffusion = props->Reverb.Diffusion; - mParams.DecayTime = props->Reverb.DecayTime; + mParams.Density = props.Density; + mParams.Diffusion = props.Diffusion; + mParams.DecayTime = props.DecayTime; mParams.HFDecayTime = hfDecayTime; mParams.LFDecayTime = lfDecayTime; - mParams.ModulationTime = props->Reverb.ModulationTime; - mParams.ModulationDepth = props->Reverb.ModulationDepth; - mParams.HFReference = props->Reverb.HFReference; - mParams.LFReference = props->Reverb.LFReference; + mParams.ModulationTime = props.ModulationTime; + mParams.ModulationDepth = props.ModulationDepth; + mParams.HFReference = props.HFReference; + mParams.LFReference = props.LFReference; mPipelineState = (mPipelineState != DeviceClear) ? StartFade : Normal; - mCurrentPipeline ^= 1; + mCurrentPipeline = !mCurrentPipeline; + + auto &oldpipeline = mPipelines[!mCurrentPipeline]; + for(size_t j{0};j < NUM_LINES;++j) + oldpipeline.mEarlyDelayCoeff[j][1] = 0.0f; } auto &pipeline = mPipelines[mCurrentPipeline]; + /* The density-based room size (delay length) multiplier. */ + const float density_mult{CalcDelayLengthMult(props.Density)}; + + /* Update the main effect delay and associated taps. */ + pipeline.updateDelayLine(props.Gain, props.ReflectionsDelay, props.LateReverbDelay, + density_mult, props.DecayTime, frequency); + /* Update early and late 3D panning. */ mOutTarget = target.Main->Buffer; - const float gain{props->Reverb.Gain * Slot->Gain * ReverbBoost}; - pipeline.update3DPanning(props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan, - props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, mUpmixOutput, - target.Main); + const float gain{Slot->Gain * ReverbBoost}; + pipeline.update3DPanning(props.ReflectionsPan, props.LateReverbPan, props.ReflectionsGain*gain, + props.LateReverbGain*gain, mUpmixOutput, target.Main); /* Calculate the master filters */ - float hf0norm{minf(props->Reverb.HFReference/frequency, 0.49f)}; - pipeline.mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, props->Reverb.GainHF, 1.0f); - float lf0norm{minf(props->Reverb.LFReference/frequency, 0.49f)}; - pipeline.mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, props->Reverb.GainLF, 1.0f); + float hf0norm{std::min(props.HFReference/frequency, 0.49f)}; + pipeline.mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, props.GainHF, 1.0f); + float lf0norm{std::min(props.LFReference/frequency, 0.49f)}; + pipeline.mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, props.GainLF, 1.0f); for(size_t i{1u};i < NUM_LINES;i++) { pipeline.mFilter[i].Lp.copyParamsFrom(pipeline.mFilter[0].Lp); pipeline.mFilter[i].Hp.copyParamsFrom(pipeline.mFilter[0].Hp); } - /* The density-based room size (delay length) multiplier. */ - const float density_mult{CalcDelayLengthMult(props->Reverb.Density)}; - - /* Update the main effect delay and associated taps. */ - pipeline.updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, - density_mult, props->Reverb.DecayTime, frequency); - if(fullUpdate) { /* Update the early lines. */ - pipeline.mEarly.updateLines(density_mult, props->Reverb.Diffusion, props->Reverb.DecayTime, - frequency); + pipeline.mEarly.updateLines(density_mult, props.Diffusion, props.DecayTime, frequency); /* Get the mixing matrix coefficients. */ - CalcMatrixCoeffs(props->Reverb.Diffusion, &pipeline.mMixX, &pipeline.mMixY); + CalcMatrixCoeffs(props.Diffusion, &pipeline.mMixX, &pipeline.mMixY); /* Update the modulator rate and depth. */ - pipeline.mLate.Mod.updateModulator(props->Reverb.ModulationTime, - props->Reverb.ModulationDepth, frequency); + pipeline.mLate.Mod.updateModulator(props.ModulationTime, props.ModulationDepth, frequency); /* Update the late lines. */ - pipeline.mLate.updateLines(density_mult, props->Reverb.Diffusion, lfDecayTime, - props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm, frequency); + pipeline.mLate.updateLines(density_mult, props.Diffusion, lfDecayTime, props.DecayTime, + hfDecayTime, lf0norm, hf0norm, frequency); } - const float decaySamples{(props->Reverb.ReflectionsDelay + props->Reverb.LateReverbDelay - + props->Reverb.DecayTime) * frequency}; - pipeline.mFadeSampleCount = static_cast(minf(decaySamples, 1'000'000.0f)); + /* Calculate the gain at the start of the late reverb stage, and the gain + * difference from the decay target (0.001, or -60dB). + */ + const float decayBase{props.ReflectionsGain * props.LateReverbGain}; + const float decayDiff{ReverbDecayGain / decayBase}; + + /* Given the DecayTime (the amount of time for the late reverb to decay by + * -60dB), calculate the time to decay to -60dB from the start of the late + * reverb. + * + * Otherwise, if the late reverb already starts at -60dB or less, only + * include the time to get to the late reverb. + */ + const float diffTime{!(decayDiff < 1.0f) ? 0.0f + : (std::log10(decayDiff)*(20.0f / -60.0f) * props.DecayTime)}; + + const float decaySamples{(props.ReflectionsDelay+props.LateReverbDelay+diffTime) + * frequency}; + /* Limit to 100,000 samples (a touch over 2 seconds at 48khz) to avoid + * excessive double-processing. + */ + pipeline.mFadeSampleCount = static_cast(std::min(decaySamples, 100'000.0f)); } @@ -1292,35 +1355,34 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, * 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 &RESTRICT in, - const float xCoeff, const float yCoeff) -> std::array +inline auto VectorPartialScatter(const std::array &in, const float xCoeff, + const float yCoeff) noexcept -> std::array { - return std::array{{ + return std::array{ xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]), xCoeff*in[1] + yCoeff*(-in[0] + in[2] + in[3]), xCoeff*in[2] + yCoeff*( in[0] + -in[1] + in[3]), xCoeff*in[3] + yCoeff*(-in[0] + -in[1] + -in[2] ) - }}; + }; } -/* Utilizes the above, but reverses the input channels. */ -void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const float xCoeff, - const float yCoeff, const al::span in, const size_t count) +/* Utilizes the above, but also applies a line-based reflection on the input + * channels (swapping 0<->3 and 1<->2). + */ +void VectorScatterRev(const float xCoeff, const float yCoeff, + const al::span samples, const size_t count) noexcept { ASSUME(count > 0); - for(size_t i{0u};i < count;) + for(size_t i{0u};i < count;++i) { - offset &= delay.Mask; - size_t td{minz(delay.Mask+1 - offset, count-i)}; - do { - std::array f; - for(size_t j{0u};j < NUM_LINES;j++) - f[NUM_LINES-1-j] = in[j][i]; - ++i; + std::array src{samples[0][i], samples[1][i], samples[2][i], samples[3][i]}; - delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff); - } while(--td); + src = VectorPartialScatter(std::array{src[3], src[2], src[1], src[0]}, xCoeff, yCoeff); + samples[0][i] = src[0]; + samples[1][i] = src[1]; + samples[2][i] = src[2]; + samples[3][i] = src[3]; } } @@ -1330,162 +1392,229 @@ void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const float * 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::process(const al::span samples, size_t offset, - const float xCoeff, const float yCoeff, const size_t todo) +void VecAllpass::process(const al::span samples, size_t main_offset, + const float xCoeff, const float yCoeff, const size_t todo) const noexcept { - const DelayLineI delay{Delay}; + const auto linelen = size_t{Delay.mLine.size()/NUM_LINES}; const float 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]; for(size_t i{0u};i < todo;) { - for(size_t j{0u};j < NUM_LINES;j++) - vap_offset[j] &= delay.Mask; - offset &= delay.Mask; + std::array vap_offset{}; + std::transform(Offset.cbegin(), Offset.cend(), vap_offset.begin(), + [main_offset,mask=linelen-1](const size_t delay) noexcept -> size_t + { return (main_offset-delay) & mask; }); + main_offset &= linelen-1; - 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)}; + const auto maxoff = std::accumulate(vap_offset.cbegin(), vap_offset.cend(), main_offset, + [](const size_t offset, const size_t apoffset) { return std::max(offset, apoffset); }); + size_t td{std::min(linelen - maxoff, todo - i)}; + + auto delayIn = Delay.mLine.begin(); + auto delayOut = Delay.mLine.begin() + ptrdiff_t(main_offset*NUM_LINES); + main_offset += td; do { - std::array f; + std::array f{}; for(size_t j{0u};j < NUM_LINES;j++) { const float input{samples[j][i]}; - const float out{delay.Line[vap_offset[j]++][j] - feedCoeff*input}; + const float out{delayIn[vap_offset[j]*NUM_LINES + j] - feedCoeff*input}; f[j] = input + feedCoeff*out; samples[j][i] = out; } + delayIn += NUM_LINES; ++i; - delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff); + f = VectorPartialScatter(f, xCoeff, yCoeff); + delayOut = std::copy_n(f.cbegin(), f.size(), delayOut); } while(--td); } } +/* This applies a more typical all-pass to each line, without the scattering + * matrix. + */ +void Allpass4::process(const al::span samples, const size_t offset, + const size_t todo) const noexcept +{ + const DelayLineU delay{Delay}; + const float feedCoeff{Coeff}; + + ASSUME(todo > 0); + + for(size_t j{0u};j < NUM_LINES;j++) + { + auto smpiter = samples[j].begin(); + const auto buffer = delay.get(j); + size_t dstoffset{offset}; + size_t vap_offset{offset - Offset[j]}; + for(size_t i{0u};i < todo;) + { + vap_offset &= buffer.size()-1; + dstoffset &= buffer.size()-1; + + const size_t maxoff{std::max(dstoffset, vap_offset)}; + const size_t td{std::min(buffer.size() - maxoff, todo - i)}; + + auto proc_sample = [buffer,feedCoeff,&vap_offset,&dstoffset](const float x) -> float + { + const float y{buffer[vap_offset++] - feedCoeff*x}; + buffer[dstoffset++] = x + feedCoeff*y; + return y; + }; + smpiter = std::transform(smpiter, smpiter+td, smpiter, proc_sample); + i += 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 + * The early lines are then reflected about the origin 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), + * Finally, the early response is reflected, scattered (based on diffusion), * and fed into the late reverb section of the main delay line. */ -void ReverbPipeline::processEarly(size_t offset, const size_t samplesToDo, - const al::span tempSamples, +void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset, + const size_t samplesToDo, const al::span tempSamples, const al::span outSamples) { - const DelayLineI early_delay{mEarly.Delay}; - const DelayLineI in_delay{mEarlyDelayIn}; + const DelayLineU early_delay{mEarly.Delay}; + const DelayLineU in_delay{main_delay}; const float mixX{mMixX}; const float mixY{mMixY}; - ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); for(size_t base{0};base < samplesToDo;) { - const size_t todo{minz(samplesToDo-base, MAX_UPDATE_SAMPLES)}; + const size_t todo{std::min(samplesToDo-base, MAX_UPDATE_SAMPLES)}; /* First, load decorrelated samples from the main delay line as the * primary reflections. */ - const float fadeStep{1.0f / static_cast(todo)}; - for(size_t j{0u};j < NUM_LINES;j++) + const auto fadeStep = float{1.0f / static_cast(todo)}; + for(size_t j{0_uz};j < NUM_LINES;j++) { - size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]}; - size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]}; - const float coeff{mEarlyDelayCoeff[j]}; - const float coeffStep{early_delay_tap0 != early_delay_tap1 ? coeff*fadeStep : 0.0f}; - float fadeCount{0.0f}; - - for(size_t i{0u};i < todo;) - { - early_delay_tap0 &= in_delay.Mask; - early_delay_tap1 &= in_delay.Mask; - const size_t max_tap{maxz(early_delay_tap0, early_delay_tap1)}; - size_t td{minz(in_delay.Mask+1 - max_tap, todo-i)}; - do { - const float fade0{coeff - coeffStep*fadeCount}; - const float fade1{coeffStep*fadeCount}; - fadeCount += 1.0f; - tempSamples[j][i++] = in_delay.Line[early_delay_tap0++][j]*fade0 + - in_delay.Line[early_delay_tap1++][j]*fade1; - } while(--td); - } - + const auto input = in_delay.get(j); + auto early_delay_tap0 = size_t{offset - mEarlyDelayTap[j][0]}; + auto early_delay_tap1 = size_t{offset - mEarlyDelayTap[j][1]}; mEarlyDelayTap[j][0] = mEarlyDelayTap[j][1]; + const auto coeff0 = float{mEarlyDelayCoeff[j][0]}; + const auto coeff1 = float{mEarlyDelayCoeff[j][1]}; + mEarlyDelayCoeff[j][0] = mEarlyDelayCoeff[j][1]; + auto fadeCount = float{0.0f}; + + auto tmp = tempSamples[j].begin(); + for(size_t i{0_uz};i < todo;) + { + early_delay_tap0 &= input.size()-1; + early_delay_tap1 &= input.size()-1; + const auto max_tap = size_t{std::max(early_delay_tap0, early_delay_tap1)}; + const auto td = size_t{std::min(input.size()-max_tap, todo-i)}; + const auto intap0 = input.subspan(early_delay_tap0, td); + const auto intap1 = input.subspan(early_delay_tap1, td); + + auto do_blend = [coeff0,coeff1,fadeStep,&fadeCount](const float in0, + const float in1) noexcept -> float + { + const auto ret = lerpf(in0*coeff0, in1*coeff1, fadeStep*fadeCount); + fadeCount += 1.0f; + return ret; + }; + tmp = std::transform(intap0.begin(), intap0.end(), intap1.begin(), tmp, do_blend); + early_delay_tap0 += td; + early_delay_tap1 += td; + i += td; + } + + /* Band-pass the incoming samples. */ + auto&& filter = DualBiquad{mFilter[j].Lp, mFilter[j].Hp}; + filter.process(al::span{tempSamples[j]}.first(todo), tempSamples[j]); } - /* Apply a vector all-pass, to help color the initial reflections based - * on the diffusion strength. - */ - mEarly.VecAp.process(tempSamples, offset, mixX, mixY, todo); + /* Apply an all-pass, to help color the initial reflections. */ + mEarly.VecAp.process(tempSamples, offset, 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++) - early_delay.write(offset, NUM_LINES-1-j, tempSamples[j].data(), todo); - for(size_t j{0u};j < NUM_LINES;j++) + /* Apply a delay and bounce to generate secondary reflections. */ + early_delay.writeReflected(offset, tempSamples, todo); + for(size_t j{0_uz};j < NUM_LINES;j++) { - size_t feedb_tap{offset - mEarly.Offset[j]}; - const float feedb_coeff{mEarly.Coeff[j]}; - float *RESTRICT out{al::assume_aligned<16>(outSamples[j].data() + base)}; + const auto input = early_delay.get(j); + auto feedb_tap = size_t{offset - mEarly.Offset[j]}; + const auto feedb_coeff = float{mEarly.Coeff[j]}; + auto out = outSamples[j].begin() + base; + auto tmp = tempSamples[j].begin(); - for(size_t i{0u};i < todo;) + for(size_t i{0_uz};i < todo;) { - feedb_tap &= early_delay.Mask; - size_t td{minz(early_delay.Mask+1 - feedb_tap, todo - i)}; - do { - tempSamples[j][i] += early_delay.Line[feedb_tap++][j]*feedb_coeff; - out[i] = tempSamples[j][i]; - ++i; - } while(--td); + feedb_tap &= input.size()-1; + + const auto td = size_t{std::min(input.size() - feedb_tap, todo - i)}; + const auto delaySrc = input.subspan(feedb_tap, td); + + /* Combine the main input with the attenuated delayed echo for + * the early output. + */ + out = std::transform(delaySrc.begin(), delaySrc.end(), tmp, out, + [feedb_coeff](const float delayspl, const float mainspl) noexcept -> float + { return delayspl*feedb_coeff + mainspl; }); + + /* Move the (non-attenuated) delayed echo to the temp buffer + * for feeding the late reverb. + */ + tmp = std::copy_n(delaySrc.begin(), delaySrc.size(), tmp); + feedb_tap += td; + i += td; } } - /* Finally, write the result to the late delay line input for the late - * reverb stage to pick up at the appropriate time, applying a scatter - * and bounce to improve the initial diffusion in the late reverb. + /* Finally, apply a scatter and bounce to improve the initial diffusion + * in the late reverb, writing the result to the late delay line input. */ - VectorScatterRevDelayIn(mLateDelayIn, offset, mixX, mixY, tempSamples, todo); + VectorScatterRev(mixX, mixY, tempSamples, todo); + for(size_t j{0_uz};j < NUM_LINES;j++) + mLateDelayIn.write(offset, j, al::span{tempSamples[j]}.first(todo)); base += todo; offset += todo; } } -void Modulation::calcDelays(size_t todo) +auto Modulation::calcDelays(size_t todo) -> al::span { - constexpr float mod_scale{al::numbers::pi_v * 2.0f / MOD_FRACONE}; - uint idx{Index}; - const uint step{Step}; - const float depth{Depth}; - for(size_t i{0};i < todo;++i) + auto idx = uint{Index}; + const auto step = uint{Step}; + const auto depth = float{Depth * float{gCubicTable.sTableSteps}}; + const auto delays = al::span{ModDelays}.first(todo); + std::generate(delays.begin(), delays.end(), [step,depth,&idx] { idx += step; - const float lfo{std::sin(static_cast(idx&MOD_FRACMASK) * mod_scale)}; - ModDelays[i] = (lfo+1.0f) * depth; - } + const auto x = float{static_cast(idx&MOD_FRACMASK) * (1.0f/MOD_FRACONE)}; + /* Approximate sin(x*2pi). As long as it roughly fits a sinusoid shape + * and stays within [-1...+1], it needn't be perfect. + */ + const auto lfo = float{!(idx&(MOD_FRACONE>>1)) + ? ((-16.0f * x * x) + (8.0f * x)) + : ((16.0f * x * x) + (-8.0f * x) + (-16.0f * x) + 8.0f)}; + return float2uint((lfo+1.0f) * depth); + }); Index = idx; + return delays; } @@ -1504,88 +1633,105 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo, const al::span tempSamples, const al::span outSamples) { - const DelayLineI late_delay{mLate.Delay}; - const DelayLineI in_delay{mLateDelayIn}; + const DelayLineU late_delay{mLate.Delay}; + const DelayLineU in_delay{mLateDelayIn}; const float mixX{mMixX}; const float mixY{mMixY}; - ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); for(size_t base{0};base < samplesToDo;) { - const size_t todo{minz(samplesToDo-base, minz(mLate.Offset[0], MAX_UPDATE_SAMPLES))}; + const size_t todo{std::min(std::min(mLate.Offset[0], MAX_UPDATE_SAMPLES), + samplesToDo-base)}; ASSUME(todo > 0); /* First, calculate the modulated delays for the late feedback. */ - mLate.Mod.calcDelays(todo); + const auto delays = mLate.Mod.calcDelays(todo); - /* Next, load decorrelated samples from the main and feedback delay - * lines. Filter the signal to apply its frequency-dependent decay. + /* Now load samples from the feedback delay lines. Filter the signal to + * apply its frequency-dependent decay. */ - const float fadeStep{1.0f / static_cast(todo)}; - for(size_t j{0u};j < NUM_LINES;j++) + for(size_t j{0_uz};j < NUM_LINES;++j) { - size_t late_delay_tap0{offset - mLateDelayTap[j][0]}; - size_t late_delay_tap1{offset - mLateDelayTap[j][1]}; - size_t late_feedb_tap{offset - mLate.Offset[j]}; - const float midGain{mLate.T60[j].MidGain}; - const float densityGain{mLate.DensityGain * midGain}; - const float densityStep{late_delay_tap0 != late_delay_tap1 ? - densityGain*fadeStep : 0.0f}; - float fadeCount{0.0f}; + const auto input = late_delay.get(j); + const auto midGain = float{mLate.T60[j].MidGain}; + auto late_feedb_tap = size_t{offset - mLate.Offset[j]}; + auto proc_sample = [input,midGain,&late_feedb_tap](const size_t idelay) -> float + { + /* Calculate the read sample offset and sub-sample offset + * between it and the next sample. + */ + const auto delay = size_t{late_feedb_tap - (idelay>>gCubicTable.sTableBits)}; + const auto delayoffset = size_t{idelay & gCubicTable.sTableMask}; + ++late_feedb_tap; + + /* Get the samples around the delayed offset, interpolated for + * output. + */ + const auto out0 = float{input[(delay ) & (input.size()-1)]}; + const auto out1 = float{input[(delay-1) & (input.size()-1)]}; + const auto out2 = float{input[(delay-2) & (input.size()-1)]}; + const auto out3 = float{input[(delay-3) & (input.size()-1)]}; + + const auto out = float{out0*gCubicTable.getCoeff0(delayoffset) + + out1*gCubicTable.getCoeff1(delayoffset) + + out2*gCubicTable.getCoeff2(delayoffset) + + out3*gCubicTable.getCoeff3(delayoffset)}; + return out * midGain; + }; + std::transform(delays.begin(), delays.end(), tempSamples[j].begin(), proc_sample); + + mLate.T60[j].process(al::span{tempSamples[j]}.first(todo)); + } + + /* Next load decorrelated samples from the main delay lines. */ + const float fadeStep{1.0f / static_cast(todo)}; + for(size_t j{0_uz};j < NUM_LINES;++j) + { + const auto input = in_delay.get(j); + auto late_delay_tap0 = size_t{offset - mLateDelayTap[j][0]}; + auto late_delay_tap1 = size_t{offset - mLateDelayTap[j][1]}; + mLateDelayTap[j][0] = mLateDelayTap[j][1]; + const auto densityGain = float{mLate.DensityGain}; + const auto densityStep = float{late_delay_tap0 != late_delay_tap1 + ? densityGain*fadeStep : 0.0f}; + auto fadeCount = float{0.0f}; + + auto samples = tempSamples[j].begin(); for(size_t i{0u};i < todo;) { - late_delay_tap0 &= in_delay.Mask; - late_delay_tap1 &= in_delay.Mask; - size_t td{minz(todo-i, in_delay.Mask+1 - maxz(late_delay_tap0, late_delay_tap1))}; - do { - /* Calculate the read offset and offset between it and the - * next sample. - */ - const float fdelay{mLate.Mod.ModDelays[i]}; - const size_t idelay{float2uint(fdelay * float{gCubicTable.sTableSteps})}; - const size_t delay{late_feedb_tap - (idelay>>gCubicTable.sTableBits)}; - const size_t delayoffset{idelay & gCubicTable.sTableMask}; - ++late_feedb_tap; + late_delay_tap0 &= input.size()-1; + late_delay_tap1 &= input.size()-1; + const auto td = size_t{std::min(todo - i, + input.size() - std::max(late_delay_tap0, late_delay_tap1))}; - /* Get the samples around by the delayed offset. */ - const float out0{late_delay.Line[(delay ) & late_delay.Mask][j]}; - const float out1{late_delay.Line[(delay-1) & late_delay.Mask][j]}; - const float out2{late_delay.Line[(delay-2) & late_delay.Mask][j]}; - const float out3{late_delay.Line[(delay-3) & late_delay.Mask][j]}; - - /* The output is obtained by interpolating the four samples - * that were acquired above, and combined with the main - * delay tap. - */ - const float out{out0*gCubicTable.getCoeff0(delayoffset) - + out1*gCubicTable.getCoeff1(delayoffset) - + out2*gCubicTable.getCoeff2(delayoffset) - + out3*gCubicTable.getCoeff3(delayoffset)}; - const float fade0{densityGain - densityStep*fadeCount}; - const float fade1{densityStep*fadeCount}; + auto proc_sample = [input,densityGain,densityStep,&late_delay_tap0, + &late_delay_tap1,&fadeCount](const float sample) noexcept -> float + { + const auto fade0 = float{densityGain - densityStep*fadeCount}; + const auto fade1 = float{densityStep*fadeCount}; fadeCount += 1.0f; - tempSamples[j][i] = out*midGain + - in_delay.Line[late_delay_tap0++][j]*fade0 + - in_delay.Line[late_delay_tap1++][j]*fade1; - ++i; - } while(--td); + return input[late_delay_tap0++]*fade0 + input[late_delay_tap1++]*fade1 + + sample; + }; + samples = std::transform(samples, samples+ptrdiff_t(td), samples, proc_sample); + i += td; } - mLateDelayTap[j][0] = mLateDelayTap[j][1]; - - mLate.T60[j].process({tempSamples[j].data(), todo}); } /* Apply a vector all-pass to improve micro-surface diffusion, and * write out the results for mixing. */ mLate.VecAp.process(tempSamples, offset, mixX, mixY, todo); - for(size_t j{0u};j < NUM_LINES;j++) + for(size_t j{0_uz};j < NUM_LINES;++j) std::copy_n(tempSamples[j].begin(), todo, outSamples[j].begin()+base); /* Finally, scatter and bounce the results to refeed the feedback buffer. */ - VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, tempSamples, todo); + VectorScatterRev(mixX, mixY, tempSamples, todo); + for(size_t j{0_uz};j < NUM_LINES;++j) + late_delay.write(offset, j, al::span{tempSamples[j]}.first(todo)); base += todo; offset += todo; @@ -1596,110 +1742,35 @@ void ReverbState::process(const size_t samplesToDo, const al::span 0); + ASSUME(samplesToDo <= BufferLineSize); - auto &oldpipeline = mPipelines[mCurrentPipeline^1]; + auto &oldpipeline = mPipelines[!mCurrentPipeline]; auto &pipeline = mPipelines[mCurrentPipeline]; - if(mPipelineState >= Fading) + /* Convert B-Format to A-Format for processing. */ + const size_t numInput{std::min(samplesIn.size(), NUM_LINES)}; + const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; + for(size_t c{0u};c < NUM_LINES;++c) { - /* Convert B-Format to A-Format for processing. */ - const size_t numInput{minz(samplesIn.size(), NUM_LINES)}; - const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; - for(size_t c{0u};c < NUM_LINES;c++) + std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); + for(size_t i{0};i < numInput;++i) { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - for(size_t i{0};i < numInput;++i) - { - const float gain{B2A[c][i]}; - const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; + const float gain{B2A[c][i]}; - auto mix_sample = [gain](const float sample, const float in) noexcept -> float - { return sample + in*gain; }; - std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), - mix_sample); - } - - /* Band-pass the incoming samples and feed the initial delay line. */ - auto&& filter = DualBiquad{pipeline.mFilter[c].Lp, pipeline.mFilter[c].Hp}; - filter.process(tmpspan, tmpspan.data()); - pipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(tmpspan.begin(), tmpspan.end(), samplesIn[i].begin(), tmpspan.begin(), + mix_sample); } - if(mPipelineState == Fading) - { - /* Give the old pipeline silence if it's still fading out. */ - for(size_t c{0u};c < NUM_LINES;c++) - { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - auto&& filter = DualBiquad{oldpipeline.mFilter[c].Lp, oldpipeline.mFilter[c].Hp}; - filter.process(tmpspan, tmpspan.data()); - oldpipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); - } - } + mMainDelay.write(offset, c, tmpspan); } - else - { - /* At the start of a fade, fade in input for the current pipeline, and - * fade out input for the old pipeline. - */ - const size_t numInput{minz(samplesIn.size(), NUM_LINES)}; - const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; - const float fadeStep{1.0f / static_cast(samplesToDo)}; - for(size_t c{0u};c < NUM_LINES;c++) - { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - for(size_t i{0};i < numInput;++i) - { - const float gain{B2A[c][i]}; - const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; - - auto mix_sample = [gain](const float sample, const float in) noexcept -> float - { return sample + in*gain; }; - std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), - mix_sample); - } - float stepCount{0.0f}; - for(float &sample : tmpspan) - { - stepCount += 1.0f; - sample *= stepCount*fadeStep; - } - - auto&& filter = DualBiquad{pipeline.mFilter[c].Lp, pipeline.mFilter[c].Hp}; - filter.process(tmpspan, tmpspan.data()); - pipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); - } - for(size_t c{0u};c < NUM_LINES;c++) - { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - for(size_t i{0};i < numInput;++i) - { - const float gain{B2A[c][i]}; - const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; - - auto mix_sample = [gain](const float sample, const float in) noexcept -> float - { return sample + in*gain; }; - std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), - mix_sample); - } - float stepCount{0.0f}; - for(float &sample : tmpspan) - { - stepCount += 1.0f; - sample *= 1.0f - stepCount*fadeStep; - } - - auto&& filter = DualBiquad{oldpipeline.mFilter[c].Lp, oldpipeline.mFilter[c].Hp}; - filter.process(tmpspan, tmpspan.data()); - oldpipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); - } + if(mPipelineState < Fading) mPipelineState = Fading; - } /* Process reverb for these samples. and mix them to the output. */ - pipeline.processEarly(offset, samplesToDo, mTempSamples, mEarlySamples); + pipeline.processEarly(mMainDelay, offset, samplesToDo, mTempSamples, mEarlySamples); pipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples); mixOut(pipeline, samplesOut, samplesToDo); @@ -1708,9 +1779,9 @@ void ReverbState::process(const size_t samplesToDo, const al::span= oldpipeline.mFadeSampleCount) { - for(auto &gains : oldpipeline.mEarly.TargetGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : oldpipeline.mLate.TargetGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); + for(auto &gains : oldpipeline.mEarly.Gains) + std::fill(gains.Target.begin(), gains.Target.end(), 0.0f); + for(auto &gains : oldpipeline.mLate.Gains) + std::fill(gains.Target.begin(), gains.Target.end(), 0.0f); oldpipeline.mFadeSampleCount = 0; mPipelineState = Cleanup; } @@ -1735,7 +1806,7 @@ void ReverbState::process(const size_t samplesToDo, const al::span{new ReverbState{}}; } }; -struct StdReverbStateFactory final : public EffectStateFactory { - al::intrusive_ptr create() override - { return al::intrusive_ptr{new ReverbState{}}; } -}; - } // namespace EffectStateFactory *ReverbStateFactory_getFactory() @@ -1762,9 +1828,3 @@ EffectStateFactory *ReverbStateFactory_getFactory() static ReverbStateFactory ReverbFactory{}; return &ReverbFactory; } - -EffectStateFactory *StdReverbStateFactory_getFactory() -{ - static StdReverbStateFactory ReverbFactory{}; - return &ReverbFactory; -} diff --git a/Engine/lib/openal-soft/alc/effects/vmorpher.cpp b/Engine/lib/openal-soft/alc/effects/vmorpher.cpp index 872c7add1..07b2257e5 100644 --- a/Engine/lib/openal-soft/alc/effects/vmorpher.cpp +++ b/Engine/lib/openal-soft/alc/effects/vmorpher.cpp @@ -34,68 +34,69 @@ #include #include +#include #include #include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "intrusive_ptr.h" +struct BufferStorage; namespace { using uint = unsigned int; -#define MAX_UPDATE_SAMPLES 256 -#define NUM_FORMANTS 4 -#define NUM_FILTERS 2 -#define Q_FACTOR 5.0f +constexpr size_t MaxUpdateSamples{256}; +constexpr size_t NumFormants{4}; +constexpr float RcpQFactor{1.0f / 5.0f}; +enum : size_t { + VowelAIndex, + VowelBIndex, + NumFilters +}; -#define VOWEL_A_INDEX 0 -#define VOWEL_B_INDEX 1 - -#define WAVEFORM_FRACBITS 24 -#define WAVEFORM_FRACONE (1<*2.0f / WAVEFORM_FRACONE}; + constexpr float scale{al::numbers::pi_v*2.0f / float{WaveformFracOne}}; return std::sin(static_cast(index) * scale)*0.5f + 0.5f; } inline float Saw(uint index) -{ return static_cast(index) / float{WAVEFORM_FRACONE}; } +{ return static_cast(index) / float{WaveformFracOne}; } inline float Triangle(uint index) -{ return std::fabs(static_cast(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); } +{ return std::fabs(static_cast(index)*(2.0f/WaveformFracOne) - 1.0f); } inline float Half(uint) { return 0.5f; } template -void Oscillate(float *RESTRICT dst, uint index, const uint step, size_t todo) +void Oscillate(const al::span dst, uint index, const uint step) { - for(size_t i{0u};i < todo;i++) + std::generate(dst.begin(), dst.end(), [&index,step] { index += step; - index &= WAVEFORM_FRACMASK; - dst[i] = func(index); - } + index &= WaveformFracMask; + return func(index); + }); } -struct FormantFilter -{ +struct FormantFilter { float mCoeff{0.0f}; float mGain{1.0f}; float mS1{0.0f}; @@ -106,34 +107,38 @@ struct FormantFilter : mCoeff{std::tan(al::numbers::pi_v * f0norm)}, mGain{gain} { } - inline void process(const float *samplesIn, float *samplesOut, const size_t numInput) + void process(const float *samplesIn, float *samplesOut, const size_t numInput) noexcept { /* 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 float g{mCoeff}; const float gain{mGain}; - const float h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))}; + const float h{1.0f / (1.0f + (g*RcpQFactor) + (g*g))}; + const float coeff{RcpQFactor + g}; float s1{mS1}; float s2{mS2}; - for(size_t i{0u};i < numInput;i++) - { - const float H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h}; - const float B{g*H + s1}; - const float L{g*B + s2}; + const auto input = al::span{samplesIn, numInput}; + const auto output = al::span{samplesOut, numInput}; + std::transform(input.cbegin(), input.cend(), output.cbegin(), output.begin(), + [g,gain,h,coeff,&s1,&s2](const float in, const float out) noexcept -> float + { + const float H{(in - coeff*s1 - s2)*h}; + const float B{g*H + s1}; + const float L{g*B + s2}; - s1 = g*H + B; - s2 = g*B + L; + s1 = g*H + B; + s2 = g*B + L; - // Apply peak and accumulate samples. - samplesOut[i] += B * gain; - } + // Apply peak and accumulate samples. + return out + B*gain; + }); mS1 = s1; mS2 = s2; } - inline void clear() + void clear() noexcept { mS1 = 0.0f; mS2 = 0.0f; @@ -142,26 +147,27 @@ struct FormantFilter struct VmorpherState final : public EffectState { - struct { + struct OutParams { uint mTargetChannel{InvalidChannelIndex}; /* Effect parameters */ - FormantFilter mFormants[NUM_FILTERS][NUM_FORMANTS]; + std::array,NumFilters> mFormants; /* Effect gains for each channel */ float mCurrentGain{}; float mTargetGain{}; - } mChans[MaxAmbiChannels]; + }; + std::array mChans; - void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){}; + void (*mGetSamples)(const al::span dst, uint index, const uint step){}; uint mIndex{0}; uint mStep{1}; /* Effects buffers */ - alignas(16) float mSampleBufferA[MAX_UPDATE_SAMPLES]{}; - alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{}; - alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{}; + alignas(16) std::array mSampleBufferA{}; + alignas(16) std::array mSampleBufferB{}; + alignas(16) std::array mLfo{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, @@ -169,14 +175,12 @@ struct VmorpherState final : public EffectState { void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - static std::array getFiltersByPhoneme(VMorpherPhenome phoneme, - float frequency, float pitch); - - DEF_NEWDEL(VmorpherState) + static std::array getFiltersByPhoneme(VMorpherPhenome phoneme, + float frequency, float pitch) noexcept; }; -std::array VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme, - float frequency, float pitch) +std::array VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme, + float frequency, float pitch) noexcept { /* Using soprano formant set of values to * better match mid-range frequency space. @@ -232,44 +236,43 @@ void VmorpherState::deviceUpdate(const DeviceBase*, const BufferStorage*) for(auto &e : mChans) { e.mTargetChannel = InvalidChannelIndex; - std::for_each(std::begin(e.mFormants[VOWEL_A_INDEX]), std::end(e.mFormants[VOWEL_A_INDEX]), + std::for_each(e.mFormants[VowelAIndex].begin(), e.mFormants[VowelAIndex].end(), std::mem_fn(&FormantFilter::clear)); - std::for_each(std::begin(e.mFormants[VOWEL_B_INDEX]), std::end(e.mFormants[VOWEL_B_INDEX]), + std::for_each(e.mFormants[VowelBIndex].begin(), e.mFormants[VowelBIndex].end(), std::mem_fn(&FormantFilter::clear)); e.mCurrentGain = 0.0f; } } void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; const float frequency{static_cast(device->Frequency)}; - const float step{props->Vmorpher.Rate / frequency}; - mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1})); + const float step{props.Rate / frequency}; + mStep = fastf2u(std::clamp(step*WaveformFracOne, 0.0f, WaveformFracOne-1.0f)); if(mStep == 0) mGetSamples = Oscillate; - else if(props->Vmorpher.Waveform == VMorpherWaveform::Sinusoid) + else if(props.Waveform == VMorpherWaveform::Sinusoid) mGetSamples = Oscillate; - else if(props->Vmorpher.Waveform == VMorpherWaveform::Triangle) + else if(props.Waveform == VMorpherWaveform::Triangle) mGetSamples = Oscillate; - else /*if(props->Vmorpher.Waveform == VMorpherWaveform::Sawtooth)*/ + else /*if(props.Waveform == VMorpherWaveform::Sawtooth)*/ mGetSamples = Oscillate; - const float pitchA{std::pow(2.0f, - static_cast(props->Vmorpher.PhonemeACoarseTuning) / 12.0f)}; - const float pitchB{std::pow(2.0f, - static_cast(props->Vmorpher.PhonemeBCoarseTuning) / 12.0f)}; + const float pitchA{std::pow(2.0f, static_cast(props.PhonemeACoarseTuning) / 12.0f)}; + const float pitchB{std::pow(2.0f, static_cast(props.PhonemeBCoarseTuning) / 12.0f)}; - auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA); - auto vowelB = getFiltersByPhoneme(props->Vmorpher.PhonemeB, frequency, pitchB); + auto vowelA = getFiltersByPhoneme(props.PhonemeA, frequency, pitchA); + auto vowelB = getFiltersByPhoneme(props.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].mFormants[VOWEL_A_INDEX])); - std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].mFormants[VOWEL_B_INDEX])); + std::copy(vowelA.begin(), vowelA.end(), mChans[i].mFormants[VowelAIndex].begin()); + std::copy(vowelB.begin(), vowelB.end(), mChans[i].mFormants[VowelBIndex].begin()); } mOutTarget = target.Main->Buffer; @@ -283,18 +286,20 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, void VmorpherState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { + alignas(16) std::array blended{}; + /* 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;) { - const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; + const size_t td{std::min(MaxUpdateSamples, samplesToDo-base)}; - mGetSamples(mLfo, mIndex, mStep, td); + mGetSamples(al::span{mLfo}.first(td), mIndex, mStep); mIndex += static_cast(mStep * td); - mIndex &= WAVEFORM_FRACMASK; + mIndex &= WaveformFracMask; - auto chandata = std::begin(mChans); + auto chandata = mChans.begin(); for(const auto &input : samplesIn) { const size_t outidx{chandata->mTargetChannel}; @@ -304,30 +309,29 @@ void VmorpherState::process(const size_t samplesToDo, const al::spanmFormants[VOWEL_A_INDEX]; - auto& vowelB = chandata->mFormants[VOWEL_B_INDEX]; + const auto vowelA = al::span{chandata->mFormants[VowelAIndex]}; + const auto vowelB = al::span{chandata->mFormants[VowelBIndex]}; /* Process first vowel. */ - std::fill_n(std::begin(mSampleBufferA), td, 0.0f); - 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); + std::fill_n(mSampleBufferA.begin(), td, 0.0f); + vowelA[0].process(&input[base], mSampleBufferA.data(), td); + vowelA[1].process(&input[base], mSampleBufferA.data(), td); + vowelA[2].process(&input[base], mSampleBufferA.data(), td); + vowelA[3].process(&input[base], mSampleBufferA.data(), td); /* Process second vowel. */ - std::fill_n(std::begin(mSampleBufferB), td, 0.0f); - 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); + std::fill_n(mSampleBufferB.begin(), td, 0.0f); + vowelB[0].process(&input[base], mSampleBufferB.data(), td); + vowelB[1].process(&input[base], mSampleBufferB.data(), td); + vowelB[2].process(&input[base], mSampleBufferB.data(), td); + vowelB[3].process(&input[base], mSampleBufferB.data(), td); - alignas(16) float blended[MAX_UPDATE_SAMPLES]; for(size_t i{0u};i < td;i++) blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]); /* Now, mix the processed sound data to the output. */ - MixSamples({blended, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain, - chandata->mTargetGain, samplesToDo-base); + MixSamples(al::span{blended}.first(td), al::span{samplesOut[outidx]}.subspan(base), + chandata->mCurrentGain, chandata->mTargetGain, samplesToDo-base); ++chandata; } diff --git a/Engine/lib/openal-soft/alc/events.cpp b/Engine/lib/openal-soft/alc/events.cpp new file mode 100644 index 000000000..1010a3384 --- /dev/null +++ b/Engine/lib/openal-soft/alc/events.cpp @@ -0,0 +1,95 @@ + +#include "config.h" + +#include "events.h" + +#include "alspan.h" +#include "core/logging.h" +#include "device.h" + + +namespace { + +ALCenum EnumFromEventType(const alc::EventType type) +{ + switch(type) + { + case alc::EventType::DefaultDeviceChanged: return ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT; + case alc::EventType::DeviceAdded: return ALC_EVENT_TYPE_DEVICE_ADDED_SOFT; + case alc::EventType::DeviceRemoved: return ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT; + case alc::EventType::Count: break; + } + throw std::runtime_error{"Invalid EventType: "+std::to_string(al::to_underlying(type))}; +} + +} // namespace + +namespace alc { + +std::optional GetEventType(ALCenum type) +{ + switch(type) + { + case ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT: return alc::EventType::DefaultDeviceChanged; + case ALC_EVENT_TYPE_DEVICE_ADDED_SOFT: return alc::EventType::DeviceAdded; + case ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT: return alc::EventType::DeviceRemoved; + } + return std::nullopt; +} + +void Event(EventType eventType, DeviceType deviceType, ALCdevice *device, std::string_view message) noexcept +{ + auto eventlock = std::unique_lock{EventMutex}; + if(EventCallback && EventsEnabled.test(al::to_underlying(eventType))) + EventCallback(EnumFromEventType(eventType), al::to_underlying(deviceType), device, + static_cast(message.length()), message.data(), EventUserPtr); +} + +} // namespace alc + +FORCE_ALIGN ALCboolean ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const ALCenum *events, + ALCboolean enable) noexcept +{ + if(enable != ALC_FALSE && enable != ALC_TRUE) + { + alcSetError(nullptr, ALC_INVALID_ENUM); + return ALC_FALSE; + } + if(count < 0) + { + alcSetError(nullptr, ALC_INVALID_VALUE); + return ALC_FALSE; + } + if(count == 0) + return ALC_TRUE; + if(!events) + { + alcSetError(nullptr, ALC_INVALID_VALUE); + return ALC_FALSE; + } + + alc::EventBitSet eventSet{0}; + for(ALCenum type : al::span{events, static_cast(count)}) + { + auto etype = alc::GetEventType(type); + if(!etype) + { + WARN("Invalid event type: 0x%04x\n", type); + alcSetError(nullptr, ALC_INVALID_ENUM); + return ALC_FALSE; + } + eventSet.set(al::to_underlying(*etype)); + } + + auto eventlock = std::unique_lock{alc::EventMutex}; + if(enable) alc::EventsEnabled |= eventSet; + else alc::EventsEnabled &= ~eventSet; + return ALC_TRUE; +} + +FORCE_ALIGN void ALC_APIENTRY alcEventCallbackSOFT(ALCEVENTPROCTYPESOFT callback, void *userParam) noexcept +{ + auto eventlock = std::unique_lock{alc::EventMutex}; + alc::EventCallback = callback; + alc::EventUserPtr = userParam; +} diff --git a/Engine/lib/openal-soft/alc/events.h b/Engine/lib/openal-soft/alc/events.h new file mode 100644 index 000000000..3f53ec762 --- /dev/null +++ b/Engine/lib/openal-soft/alc/events.h @@ -0,0 +1,50 @@ +#ifndef ALC_EVENTS_H +#define ALC_EVENTS_H + +#include "inprogext.h" +#include "opthelpers.h" + +#include +#include +#include +#include + + +namespace alc { + +enum class EventType : uint8_t { + DefaultDeviceChanged, + DeviceAdded, + DeviceRemoved, + + Count +}; + +std::optional GetEventType(ALCenum type); + +enum class EventSupport : ALCenum { + FullSupport = ALC_EVENT_SUPPORTED_SOFT, + NoSupport = ALC_EVENT_NOT_SUPPORTED_SOFT, +}; + +enum class DeviceType : ALCenum { + Playback = ALC_PLAYBACK_DEVICE_SOFT, + Capture = ALC_CAPTURE_DEVICE_SOFT, +}; + +using EventBitSet = std::bitset; +inline EventBitSet EventsEnabled{0}; + +inline std::mutex EventMutex; + +inline ALCEVENTPROCTYPESOFT EventCallback{}; +inline void *EventUserPtr{}; + +void Event(EventType eventType, DeviceType deviceType, ALCdevice *device, std::string_view message) noexcept; + +inline void Event(EventType eventType, DeviceType deviceType, std::string_view message) noexcept +{ Event(eventType, deviceType, nullptr, message); } + +} // namespace alc + +#endif /* ALC_EVENTS_H */ diff --git a/Engine/lib/openal-soft/alc/export_list.h b/Engine/lib/openal-soft/alc/export_list.h new file mode 100644 index 000000000..2c291ac0d --- /dev/null +++ b/Engine/lib/openal-soft/alc/export_list.h @@ -0,0 +1,917 @@ +#ifndef ALC_EXPORT_LIST_H +#define ALC_EXPORT_LIST_H + +#include "AL/alc.h" +#include "AL/al.h" +#include "AL/alext.h" + +#include "inprogext.h" +#ifdef ALSOFT_EAX +#include "context.h" +#include "al/eax/x_ram.h" +#endif + + +struct FuncExport { + const char *funcName; + void *address; +}; +#define DECL(x) FuncExport{#x, reinterpret_cast(x)} +/* NOLINTNEXTLINE(*-avoid-c-arrays) Too large for std::array auto-deduction :( */ +inline const FuncExport 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(alcReopenDeviceSOFT), + + DECL(alcEventIsSupportedSOFT), + DECL(alcEventControlSOFT), + DECL(alcEventCallbackSOFT), + + DECL(alEnable), + DECL(alDisable), + DECL(alIsEnabled), + DECL(alGetString), + DECL(alGetBooleanv), + DECL(alGetIntegerv), + DECL(alGetFloatv), + DECL(alGetDoublev), + DECL(alGetBoolean), + DECL(alGetInteger), + DECL(alGetFloat), + DECL(alGetDouble), + DECL(alGetError), + DECL(alIsExtensionPresent), + DECL(alGetProcAddress), + DECL(alGetEnumValue), + DECL(alListenerf), + DECL(alListener3f), + DECL(alListenerfv), + DECL(alListeneri), + DECL(alListener3i), + DECL(alListeneriv), + DECL(alGetListenerf), + DECL(alGetListener3f), + DECL(alGetListenerfv), + DECL(alGetListeneri), + DECL(alGetListener3i), + DECL(alGetListeneriv), + DECL(alGenSources), + DECL(alDeleteSources), + DECL(alIsSource), + DECL(alSourcef), + DECL(alSource3f), + DECL(alSourcefv), + DECL(alSourcei), + DECL(alSource3i), + DECL(alSourceiv), + DECL(alGetSourcef), + DECL(alGetSource3f), + DECL(alGetSourcefv), + DECL(alGetSourcei), + DECL(alGetSource3i), + DECL(alGetSourceiv), + DECL(alSourcePlayv), + DECL(alSourceStopv), + DECL(alSourceRewindv), + DECL(alSourcePausev), + DECL(alSourcePlay), + DECL(alSourceStop), + DECL(alSourceRewind), + DECL(alSourcePause), + DECL(alSourceQueueBuffers), + DECL(alSourceUnqueueBuffers), + DECL(alGenBuffers), + DECL(alDeleteBuffers), + DECL(alIsBuffer), + DECL(alBufferData), + DECL(alBufferf), + DECL(alBuffer3f), + DECL(alBufferfv), + DECL(alBufferi), + DECL(alBuffer3i), + DECL(alBufferiv), + DECL(alGetBufferf), + DECL(alGetBuffer3f), + DECL(alGetBufferfv), + DECL(alGetBufferi), + DECL(alGetBuffer3i), + DECL(alGetBufferiv), + DECL(alDopplerFactor), + DECL(alDopplerVelocity), + DECL(alSpeedOfSound), + DECL(alDistanceModel), + + DECL(alGenFilters), + DECL(alDeleteFilters), + DECL(alIsFilter), + DECL(alFilteri), + DECL(alFilteriv), + DECL(alFilterf), + DECL(alFilterfv), + DECL(alGetFilteri), + DECL(alGetFilteriv), + DECL(alGetFilterf), + DECL(alGetFilterfv), + DECL(alGenEffects), + DECL(alDeleteEffects), + DECL(alIsEffect), + DECL(alEffecti), + DECL(alEffectiv), + DECL(alEffectf), + DECL(alEffectfv), + DECL(alGetEffecti), + DECL(alGetEffectiv), + DECL(alGetEffectf), + DECL(alGetEffectfv), + DECL(alGenAuxiliaryEffectSlots), + DECL(alDeleteAuxiliaryEffectSlots), + DECL(alIsAuxiliaryEffectSlot), + DECL(alAuxiliaryEffectSloti), + DECL(alAuxiliaryEffectSlotiv), + DECL(alAuxiliaryEffectSlotf), + DECL(alAuxiliaryEffectSlotfv), + DECL(alGetAuxiliaryEffectSloti), + DECL(alGetAuxiliaryEffectSlotiv), + DECL(alGetAuxiliaryEffectSlotf), + DECL(alGetAuxiliaryEffectSlotfv), + + DECL(alDeferUpdatesSOFT), + DECL(alProcessUpdatesSOFT), + + DECL(alSourcedSOFT), + DECL(alSource3dSOFT), + DECL(alSourcedvSOFT), + DECL(alGetSourcedSOFT), + DECL(alGetSource3dSOFT), + DECL(alGetSourcedvSOFT), + DECL(alSourcei64SOFT), + DECL(alSource3i64SOFT), + DECL(alSourcei64vSOFT), + DECL(alGetSourcei64SOFT), + DECL(alGetSource3i64SOFT), + DECL(alGetSourcei64vSOFT), + + DECL(alGetStringiSOFT), + + DECL(alBufferStorageSOFT), + DECL(alMapBufferSOFT), + DECL(alUnmapBufferSOFT), + DECL(alFlushMappedBufferSOFT), + + DECL(alEventControlSOFT), + DECL(alEventCallbackSOFT), + DECL(alGetPointerSOFT), + DECL(alGetPointervSOFT), + + DECL(alBufferCallbackSOFT), + DECL(alGetBufferPtrSOFT), + DECL(alGetBuffer3PtrSOFT), + DECL(alGetBufferPtrvSOFT), + + DECL(alSourcePlayAtTimeSOFT), + DECL(alSourcePlayAtTimevSOFT), + + DECL(alBufferSubDataSOFT), + + DECL(alBufferDataStatic), + + DECL(alDebugMessageCallbackEXT), + DECL(alDebugMessageInsertEXT), + DECL(alDebugMessageControlEXT), + DECL(alPushDebugGroupEXT), + DECL(alPopDebugGroupEXT), + DECL(alGetDebugMessageLogEXT), + + /* Direct Context functions */ + DECL(alcGetProcAddress2), + DECL(alEnableDirect), + DECL(alDisableDirect), + DECL(alIsEnabledDirect), + DECL(alDopplerFactorDirect), + DECL(alSpeedOfSoundDirect), + DECL(alDistanceModelDirect), + DECL(alGetStringDirect), + DECL(alGetBooleanvDirect), + DECL(alGetIntegervDirect), + DECL(alGetFloatvDirect), + DECL(alGetDoublevDirect), + DECL(alGetBooleanDirect), + DECL(alGetIntegerDirect), + DECL(alGetFloatDirect), + DECL(alGetDoubleDirect), + + DECL(alGetErrorDirect), + DECL(alIsExtensionPresentDirect), + DECL(alGetProcAddress), + DECL(alGetEnumValueDirect), + + DECL(alListeneriDirect), + DECL(alListener3iDirect), + DECL(alListenerivDirect), + DECL(alListenerfDirect), + DECL(alListener3fDirect), + DECL(alListenerfvDirect), + DECL(alGetListeneriDirect), + DECL(alGetListener3iDirect), + DECL(alGetListenerivDirect), + DECL(alGetListenerfDirect), + DECL(alGetListener3fDirect), + DECL(alGetListenerfvDirect), + + DECL(alGenBuffersDirect), + DECL(alDeleteBuffersDirect), + DECL(alIsBufferDirect), + DECL(alBufferDataDirect), + DECL(alBufferiDirect), + DECL(alBuffer3iDirect), + DECL(alBufferivDirect), + DECL(alBufferfDirect), + DECL(alBuffer3fDirect), + DECL(alBufferfvDirect), + DECL(alGetBufferiDirect), + DECL(alGetBuffer3iDirect), + DECL(alGetBufferivDirect), + DECL(alGetBufferfDirect), + DECL(alGetBuffer3fDirect), + DECL(alGetBufferfvDirect), + + DECL(alGenSourcesDirect), + DECL(alDeleteSourcesDirect), + DECL(alIsSourceDirect), + DECL(alSourcePlayDirect), + DECL(alSourceStopDirect), + DECL(alSourcePauseDirect), + DECL(alSourceRewindDirect), + DECL(alSourcePlayvDirect), + DECL(alSourceStopvDirect), + DECL(alSourcePausevDirect), + DECL(alSourceRewindvDirect), + DECL(alSourceiDirect), + DECL(alSource3iDirect), + DECL(alSourceivDirect), + DECL(alSourcefDirect), + DECL(alSource3fDirect), + DECL(alSourcefvDirect), + DECL(alGetSourceiDirect), + DECL(alGetSource3iDirect), + DECL(alGetSourceivDirect), + DECL(alGetSourcefDirect), + DECL(alGetSource3fDirect), + DECL(alGetSourcefvDirect), + DECL(alSourceQueueBuffersDirect), + DECL(alSourceUnqueueBuffersDirect), + + DECL(alGenFiltersDirect), + DECL(alDeleteFiltersDirect), + DECL(alIsFilterDirect), + DECL(alFilteriDirect), + DECL(alFilterivDirect), + DECL(alFilterfDirect), + DECL(alFilterfvDirect), + DECL(alGetFilteriDirect), + DECL(alGetFilterivDirect), + DECL(alGetFilterfDirect), + DECL(alGetFilterfvDirect), + DECL(alGenEffectsDirect), + DECL(alDeleteEffectsDirect), + DECL(alIsEffectDirect), + DECL(alEffectiDirect), + DECL(alEffectivDirect), + DECL(alEffectfDirect), + DECL(alEffectfvDirect), + DECL(alGetEffectiDirect), + DECL(alGetEffectivDirect), + DECL(alGetEffectfDirect), + DECL(alGetEffectfvDirect), + DECL(alGenAuxiliaryEffectSlotsDirect), + DECL(alDeleteAuxiliaryEffectSlotsDirect), + DECL(alIsAuxiliaryEffectSlotDirect), + DECL(alAuxiliaryEffectSlotiDirect), + DECL(alAuxiliaryEffectSlotivDirect), + DECL(alAuxiliaryEffectSlotfDirect), + DECL(alAuxiliaryEffectSlotfvDirect), + DECL(alGetAuxiliaryEffectSlotiDirect), + DECL(alGetAuxiliaryEffectSlotivDirect), + DECL(alGetAuxiliaryEffectSlotfDirect), + DECL(alGetAuxiliaryEffectSlotfvDirect), + + DECL(alDeferUpdatesDirectSOFT), + DECL(alProcessUpdatesDirectSOFT), + DECL(alGetStringiDirectSOFT), + + DECL(alBufferDataStaticDirect), + DECL(alBufferCallbackDirectSOFT), + DECL(alBufferSubDataDirectSOFT), + DECL(alBufferStorageDirectSOFT), + DECL(alMapBufferDirectSOFT), + DECL(alUnmapBufferDirectSOFT), + DECL(alFlushMappedBufferDirectSOFT), + + DECL(alSourcei64DirectSOFT), + DECL(alSource3i64DirectSOFT), + DECL(alSourcei64vDirectSOFT), + DECL(alSourcedDirectSOFT), + DECL(alSource3dDirectSOFT), + DECL(alSourcedvDirectSOFT), + DECL(alGetSourcei64DirectSOFT), + DECL(alGetSource3i64DirectSOFT), + DECL(alGetSourcei64vDirectSOFT), + DECL(alGetSourcedDirectSOFT), + DECL(alGetSource3dDirectSOFT), + DECL(alGetSourcedvDirectSOFT), + DECL(alSourcePlayAtTimeDirectSOFT), + DECL(alSourcePlayAtTimevDirectSOFT), + + DECL(alEventControlDirectSOFT), + DECL(alEventCallbackDirectSOFT), + + DECL(alDebugMessageCallbackDirectEXT), + DECL(alDebugMessageInsertDirectEXT), + DECL(alDebugMessageControlDirectEXT), + DECL(alPushDebugGroupDirectEXT), + DECL(alPopDebugGroupDirectEXT), + DECL(alGetDebugMessageLogDirectEXT), + DECL(alObjectLabelEXT), + DECL(alObjectLabelDirectEXT), + DECL(alGetObjectLabelEXT), + DECL(alGetObjectLabelDirectEXT), + + /* Extra functions */ + DECL(alsoft_set_log_callback), +}; +#ifdef ALSOFT_EAX +inline const std::array eaxFunctions{ + DECL(EAXGet), + DECL(EAXSet), + DECL(EAXGetBufferMode), + DECL(EAXSetBufferMode), + + DECL(EAXGetDirect), + DECL(EAXSetDirect), + DECL(EAXGetBufferModeDirect), + DECL(EAXSetBufferModeDirect), +}; +#endif +#undef DECL + +struct EnumExport { + const char *enumName; + int value; +}; +#define DECL(x) EnumExport{#x, (x)} +/* NOLINTNEXTLINE(*-avoid-c-arrays) Too large for std::array auto-deduction :( */ +inline const EnumExport 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_DEVICE_CLOCK_SOFT), + DECL(ALC_DEVICE_LATENCY_SOFT), + DECL(ALC_DEVICE_CLOCK_LATENCY_SOFT), + DECL(AL_SAMPLE_OFFSET_CLOCK_SOFT), + DECL(AL_SEC_OFFSET_CLOCK_SOFT), + + DECL(ALC_OUTPUT_MODE_SOFT), + DECL(ALC_ANY_SOFT), + DECL(ALC_STEREO_BASIC_SOFT), + DECL(ALC_STEREO_UHJ_SOFT), + DECL(ALC_STEREO_HRTF_SOFT), + DECL(ALC_SURROUND_5_1_SOFT), + DECL(ALC_SURROUND_6_1_SOFT), + DECL(ALC_SURROUND_7_1_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(ALC_CONTEXT_FLAGS_EXT), + DECL(ALC_CONTEXT_DEBUG_BIT_EXT), + + DECL(ALC_PLAYBACK_DEVICE_SOFT), + DECL(ALC_CAPTURE_DEVICE_SOFT), + DECL(ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT), + DECL(ALC_EVENT_TYPE_DEVICE_ADDED_SOFT), + DECL(ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT), + + + 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_FORMAT_UHJ2CHN8_SOFT), + DECL(AL_FORMAT_UHJ2CHN16_SOFT), + DECL(AL_FORMAT_UHJ2CHN_FLOAT32_SOFT), + DECL(AL_FORMAT_UHJ3CHN8_SOFT), + DECL(AL_FORMAT_UHJ3CHN16_SOFT), + DECL(AL_FORMAT_UHJ3CHN_FLOAT32_SOFT), + DECL(AL_FORMAT_UHJ4CHN8_SOFT), + DECL(AL_FORMAT_UHJ4CHN16_SOFT), + DECL(AL_FORMAT_UHJ4CHN_FLOAT32_SOFT), + DECL(AL_STEREO_MODE_SOFT), + DECL(AL_NORMAL_SOFT), + DECL(AL_SUPER_STEREO_SOFT), + DECL(AL_SUPER_STEREO_WIDTH_SOFT), + + DECL(AL_FORMAT_UHJ2CHN_MULAW_SOFT), + DECL(AL_FORMAT_UHJ2CHN_ALAW_SOFT), + DECL(AL_FORMAT_UHJ2CHN_IMA4_SOFT), + DECL(AL_FORMAT_UHJ2CHN_MSADPCM_SOFT), + DECL(AL_FORMAT_UHJ3CHN_MULAW_SOFT), + DECL(AL_FORMAT_UHJ3CHN_ALAW_SOFT), + DECL(AL_FORMAT_UHJ4CHN_MULAW_SOFT), + DECL(AL_FORMAT_UHJ4CHN_ALAW_SOFT), + + DECL(AL_FORMAT_MONO_I32), + DECL(AL_FORMAT_STEREO_I32), + DECL(AL_FORMAT_REAR_I32), + DECL(AL_FORMAT_QUAD_I32), + DECL(AL_FORMAT_51CHN_I32), + DECL(AL_FORMAT_61CHN_I32), + DECL(AL_FORMAT_71CHN_I32), + DECL(AL_FORMAT_UHJ2CHN_I32_SOFT), + DECL(AL_FORMAT_UHJ3CHN_I32_SOFT), + DECL(AL_FORMAT_UHJ4CHN_I32_SOFT), + + DECL(AL_FORMAT_REAR_FLOAT32), + DECL(AL_FORMAT_QUAD_FLOAT32), + DECL(AL_FORMAT_51CHN_FLOAT32), + DECL(AL_FORMAT_61CHN_FLOAT32), + DECL(AL_FORMAT_71CHN_FLOAT32), + + 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_SAMPLE_OFFSET_LATENCY_SOFT), + DECL(AL_SEC_OFFSET_LATENCY_SOFT), + + DECL(AL_STEREO_ANGLES), + + DECL(AL_UNUSED), + DECL(AL_PENDING), + DECL(AL_PROCESSED), + + DECL(AL_NO_ERROR), + DECL(AL_INVALID_NAME), + DECL(AL_INVALID_ENUM), + DECL(AL_INVALID_VALUE), + DECL(AL_INVALID_OPERATION), + DECL(AL_OUT_OF_MEMORY), + + DECL(AL_VENDOR), + DECL(AL_VERSION), + DECL(AL_RENDERER), + DECL(AL_EXTENSIONS), + + DECL(AL_DOPPLER_FACTOR), + DECL(AL_DOPPLER_VELOCITY), + DECL(AL_DISTANCE_MODEL), + DECL(AL_SPEED_OF_SOUND), + DECL(AL_SOURCE_DISTANCE_MODEL), + DECL(AL_DEFERRED_UPDATES_SOFT), + DECL(AL_GAIN_LIMIT_SOFT), + + DECL(AL_INVERSE_DISTANCE), + DECL(AL_INVERSE_DISTANCE_CLAMPED), + DECL(AL_LINEAR_DISTANCE), + DECL(AL_LINEAR_DISTANCE_CLAMPED), + DECL(AL_EXPONENT_DISTANCE), + DECL(AL_EXPONENT_DISTANCE_CLAMPED), + + DECL(AL_FILTER_TYPE), + DECL(AL_FILTER_NULL), + DECL(AL_FILTER_LOWPASS), + DECL(AL_FILTER_HIGHPASS), + DECL(AL_FILTER_BANDPASS), + + DECL(AL_LOWPASS_GAIN), + DECL(AL_LOWPASS_GAINHF), + + DECL(AL_HIGHPASS_GAIN), + DECL(AL_HIGHPASS_GAINLF), + + DECL(AL_BANDPASS_GAIN), + DECL(AL_BANDPASS_GAINHF), + DECL(AL_BANDPASS_GAINLF), + + DECL(AL_EFFECT_TYPE), + DECL(AL_EFFECT_NULL), + DECL(AL_EFFECT_REVERB), + DECL(AL_EFFECT_EAXREVERB), + DECL(AL_EFFECT_CHORUS), + DECL(AL_EFFECT_DISTORTION), + DECL(AL_EFFECT_ECHO), + DECL(AL_EFFECT_FLANGER), + DECL(AL_EFFECT_PITCH_SHIFTER), + DECL(AL_EFFECT_FREQUENCY_SHIFTER), + DECL(AL_EFFECT_VOCAL_MORPHER), + DECL(AL_EFFECT_RING_MODULATOR), + DECL(AL_EFFECT_AUTOWAH), + DECL(AL_EFFECT_COMPRESSOR), + DECL(AL_EFFECT_EQUALIZER), + DECL(AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT), + DECL(AL_EFFECT_DEDICATED_DIALOGUE), + + DECL(AL_EFFECTSLOT_EFFECT), + DECL(AL_EFFECTSLOT_GAIN), + DECL(AL_EFFECTSLOT_AUXILIARY_SEND_AUTO), + DECL(AL_EFFECTSLOT_NULL), + + DECL(AL_EAXREVERB_DENSITY), + DECL(AL_EAXREVERB_DIFFUSION), + DECL(AL_EAXREVERB_GAIN), + DECL(AL_EAXREVERB_GAINHF), + DECL(AL_EAXREVERB_GAINLF), + DECL(AL_EAXREVERB_DECAY_TIME), + DECL(AL_EAXREVERB_DECAY_HFRATIO), + DECL(AL_EAXREVERB_DECAY_LFRATIO), + DECL(AL_EAXREVERB_REFLECTIONS_GAIN), + DECL(AL_EAXREVERB_REFLECTIONS_DELAY), + DECL(AL_EAXREVERB_REFLECTIONS_PAN), + DECL(AL_EAXREVERB_LATE_REVERB_GAIN), + DECL(AL_EAXREVERB_LATE_REVERB_DELAY), + DECL(AL_EAXREVERB_LATE_REVERB_PAN), + DECL(AL_EAXREVERB_ECHO_TIME), + DECL(AL_EAXREVERB_ECHO_DEPTH), + DECL(AL_EAXREVERB_MODULATION_TIME), + DECL(AL_EAXREVERB_MODULATION_DEPTH), + DECL(AL_EAXREVERB_AIR_ABSORPTION_GAINHF), + DECL(AL_EAXREVERB_HFREFERENCE), + DECL(AL_EAXREVERB_LFREFERENCE), + DECL(AL_EAXREVERB_ROOM_ROLLOFF_FACTOR), + DECL(AL_EAXREVERB_DECAY_HFLIMIT), + + DECL(AL_REVERB_DENSITY), + DECL(AL_REVERB_DIFFUSION), + DECL(AL_REVERB_GAIN), + DECL(AL_REVERB_GAINHF), + DECL(AL_REVERB_DECAY_TIME), + DECL(AL_REVERB_DECAY_HFRATIO), + DECL(AL_REVERB_REFLECTIONS_GAIN), + DECL(AL_REVERB_REFLECTIONS_DELAY), + DECL(AL_REVERB_LATE_REVERB_GAIN), + DECL(AL_REVERB_LATE_REVERB_DELAY), + DECL(AL_REVERB_AIR_ABSORPTION_GAINHF), + DECL(AL_REVERB_ROOM_ROLLOFF_FACTOR), + DECL(AL_REVERB_DECAY_HFLIMIT), + + DECL(AL_CHORUS_WAVEFORM), + DECL(AL_CHORUS_PHASE), + DECL(AL_CHORUS_RATE), + DECL(AL_CHORUS_DEPTH), + DECL(AL_CHORUS_FEEDBACK), + DECL(AL_CHORUS_DELAY), + + DECL(AL_DISTORTION_EDGE), + DECL(AL_DISTORTION_GAIN), + DECL(AL_DISTORTION_LOWPASS_CUTOFF), + DECL(AL_DISTORTION_EQCENTER), + DECL(AL_DISTORTION_EQBANDWIDTH), + + DECL(AL_ECHO_DELAY), + DECL(AL_ECHO_LRDELAY), + DECL(AL_ECHO_DAMPING), + DECL(AL_ECHO_FEEDBACK), + DECL(AL_ECHO_SPREAD), + + DECL(AL_FLANGER_WAVEFORM), + DECL(AL_FLANGER_PHASE), + DECL(AL_FLANGER_RATE), + DECL(AL_FLANGER_DEPTH), + DECL(AL_FLANGER_FEEDBACK), + DECL(AL_FLANGER_DELAY), + + DECL(AL_FREQUENCY_SHIFTER_FREQUENCY), + DECL(AL_FREQUENCY_SHIFTER_LEFT_DIRECTION), + DECL(AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION), + + DECL(AL_RING_MODULATOR_FREQUENCY), + DECL(AL_RING_MODULATOR_HIGHPASS_CUTOFF), + DECL(AL_RING_MODULATOR_WAVEFORM), + + DECL(AL_PITCH_SHIFTER_COARSE_TUNE), + DECL(AL_PITCH_SHIFTER_FINE_TUNE), + + DECL(AL_COMPRESSOR_ONOFF), + + DECL(AL_EQUALIZER_LOW_GAIN), + DECL(AL_EQUALIZER_LOW_CUTOFF), + DECL(AL_EQUALIZER_MID1_GAIN), + DECL(AL_EQUALIZER_MID1_CENTER), + DECL(AL_EQUALIZER_MID1_WIDTH), + DECL(AL_EQUALIZER_MID2_GAIN), + DECL(AL_EQUALIZER_MID2_CENTER), + DECL(AL_EQUALIZER_MID2_WIDTH), + DECL(AL_EQUALIZER_HIGH_GAIN), + DECL(AL_EQUALIZER_HIGH_CUTOFF), + + DECL(AL_DEDICATED_GAIN), + + DECL(AL_AUTOWAH_ATTACK_TIME), + DECL(AL_AUTOWAH_RELEASE_TIME), + DECL(AL_AUTOWAH_RESONANCE), + DECL(AL_AUTOWAH_PEAK_GAIN), + + DECL(AL_VOCAL_MORPHER_PHONEMEA), + DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), + DECL(AL_VOCAL_MORPHER_PHONEMEB), + DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), + DECL(AL_VOCAL_MORPHER_WAVEFORM), + DECL(AL_VOCAL_MORPHER_RATE), + + DECL(AL_EFFECTSLOT_TARGET_SOFT), + + DECL(AL_NUM_RESAMPLERS_SOFT), + DECL(AL_DEFAULT_RESAMPLER_SOFT), + DECL(AL_SOURCE_RESAMPLER_SOFT), + DECL(AL_RESAMPLER_NAME_SOFT), + + DECL(AL_SOURCE_SPATIALIZE_SOFT), + DECL(AL_AUTO_SOFT), + + DECL(AL_MAP_READ_BIT_SOFT), + DECL(AL_MAP_WRITE_BIT_SOFT), + DECL(AL_MAP_PERSISTENT_BIT_SOFT), + DECL(AL_PRESERVE_DATA_BIT_SOFT), + + DECL(AL_EVENT_CALLBACK_FUNCTION_SOFT), + DECL(AL_EVENT_CALLBACK_USER_PARAM_SOFT), + DECL(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT), + DECL(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT), + DECL(AL_EVENT_TYPE_DISCONNECTED_SOFT), + + DECL(AL_DROP_UNMATCHED_SOFT), + DECL(AL_REMIX_UNMATCHED_SOFT), + + DECL(AL_AMBISONIC_LAYOUT_SOFT), + DECL(AL_AMBISONIC_SCALING_SOFT), + DECL(AL_FUMA_SOFT), + DECL(AL_ACN_SOFT), + DECL(AL_SN3D_SOFT), + DECL(AL_N3D_SOFT), + + DECL(AL_BUFFER_CALLBACK_FUNCTION_SOFT), + DECL(AL_BUFFER_CALLBACK_USER_PARAM_SOFT), + + DECL(AL_UNPACK_AMBISONIC_ORDER_SOFT), + + DECL(AL_EFFECT_CONVOLUTION_SOFT), + DECL(AL_EFFECTSLOT_STATE_SOFT), + + DECL(AL_DONT_CARE_EXT), + DECL(AL_DEBUG_OUTPUT_EXT), + DECL(AL_DEBUG_CALLBACK_FUNCTION_EXT), + DECL(AL_DEBUG_CALLBACK_USER_PARAM_EXT), + DECL(AL_DEBUG_SOURCE_API_EXT), + DECL(AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT), + DECL(AL_DEBUG_SOURCE_THIRD_PARTY_EXT), + DECL(AL_DEBUG_SOURCE_APPLICATION_EXT), + DECL(AL_DEBUG_SOURCE_OTHER_EXT), + DECL(AL_DEBUG_TYPE_ERROR_EXT), + DECL(AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT), + DECL(AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT), + DECL(AL_DEBUG_TYPE_PORTABILITY_EXT), + DECL(AL_DEBUG_TYPE_PERFORMANCE_EXT), + DECL(AL_DEBUG_TYPE_MARKER_EXT), + DECL(AL_DEBUG_TYPE_PUSH_GROUP_EXT), + DECL(AL_DEBUG_TYPE_POP_GROUP_EXT), + DECL(AL_DEBUG_TYPE_OTHER_EXT), + DECL(AL_DEBUG_SEVERITY_HIGH_EXT), + DECL(AL_DEBUG_SEVERITY_MEDIUM_EXT), + DECL(AL_DEBUG_SEVERITY_LOW_EXT), + DECL(AL_DEBUG_SEVERITY_NOTIFICATION_EXT), + DECL(AL_DEBUG_LOGGED_MESSAGES_EXT), + DECL(AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT), + DECL(AL_MAX_DEBUG_MESSAGE_LENGTH_EXT), + DECL(AL_MAX_DEBUG_LOGGED_MESSAGES_EXT), + DECL(AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT), + DECL(AL_MAX_LABEL_LENGTH_EXT), + DECL(AL_STACK_OVERFLOW_EXT), + DECL(AL_STACK_UNDERFLOW_EXT), + DECL(AL_BUFFER_EXT), + DECL(AL_SOURCE_EXT), + DECL(AL_FILTER_EXT), + DECL(AL_EFFECT_EXT), + DECL(AL_AUXILIARY_EFFECT_SLOT_EXT), + + DECL(AL_PANNING_ENABLED_SOFT), + DECL(AL_PAN_SOFT), + + DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT), +}; +#ifdef ALSOFT_EAX +inline const std::array eaxEnumerations{ + DECL(AL_EAX_RAM_SIZE), + DECL(AL_EAX_RAM_FREE), + DECL(AL_STORAGE_AUTOMATIC), + DECL(AL_STORAGE_HARDWARE), + DECL(AL_STORAGE_ACCESSIBLE), +}; +#endif // ALSOFT_EAX +#undef DECL + +#endif /* ALC_EXPORT_LIST_H */ diff --git a/Engine/lib/openal-soft/alc/inprogext.h b/Engine/lib/openal-soft/alc/inprogext.h index ccb9a4bed..ba5d4351f 100644 --- a/Engine/lib/openal-soft/alc/inprogext.h +++ b/Engine/lib/openal-soft/alc/inprogext.h @@ -1,6 +1,7 @@ #ifndef INPROGEXT_H #define INPROGEXT_H +/* NOLINTBEGIN */ #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" @@ -16,15 +17,23 @@ typedef unsigned int ALbitfieldSOFT; #define AL_MAP_WRITE_BIT_SOFT 0x00000002 #define AL_MAP_PERSISTENT_BIT_SOFT 0x00000004 #define AL_PRESERVE_DATA_BIT_SOFT 0x00000008 -typedef void (AL_APIENTRY*LPALBUFFERSTORAGESOFT)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags); -typedef void* (AL_APIENTRY*LPALMAPBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access); -typedef void (AL_APIENTRY*LPALUNMAPBUFFERSOFT)(ALuint buffer); -typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length); +typedef void (AL_APIENTRY*LPALBUFFERSTORAGESOFT)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY*LPALMAPBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALUNMAPBUFFERSOFT)(ALuint buffer) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALBUFFERSTORAGEDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY*LPALMAPBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALUNMAPBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags); -AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access); -AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer); -AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length); +AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT; +AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferStorageDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT; +void* AL_APIENTRY alMapBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT; +void AL_APIENTRY alUnmapBufferDirectSOFT(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT; +void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT; #endif #endif @@ -33,20 +42,11 @@ AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, A #define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D #endif -#ifndef AL_SOFT_convolution_reverb -#define AL_SOFT_convolution_reverb -#define AL_EFFECT_CONVOLUTION_REVERB_SOFT 0xA000 -#define AL_EFFECTSLOT_STATE_SOFT 0x199D -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYSOFT)(ALuint slotid); -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYVSOFT)(ALsizei n, const ALuint *slotids); -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPSOFT)(ALuint slotid); -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPVSOFT)(ALsizei n, const ALuint *slotids); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids); -#endif +#ifndef AL_SOFT_convolution_effect +#define AL_SOFT_convolution_effect +#define AL_EFFECT_CONVOLUTION_SOFT 0xA000 +#define AL_CONVOLUTION_ORIENTATION_SOFT 0x100F /* same as AL_ORIENTATION */ +#define AL_EFFECTSLOT_STATE_SOFT 0x199E #endif #ifndef AL_SOFT_hold_on_disconnect @@ -55,19 +55,56 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint * #endif -/* Non-standard export. Not part of any extension. */ -AL_API const ALchar* AL_APIENTRY alsoft_get_version(void); +#ifndef AL_EXT_32bit_formats +#define AL_EXT_32bit_formats +#define AL_FORMAT_MONO_I32 0x19DB +#define AL_FORMAT_STEREO_I32 0x19DC +#define AL_FORMAT_REAR_I32 0x19DD +#define AL_FORMAT_REAR_FLOAT32 0x19DE +#define AL_FORMAT_QUAD_I32 0x19DF +#define AL_FORMAT_QUAD_FLOAT32 0x19E0 +#define AL_FORMAT_51CHN_I32 0x19E1 +#define AL_FORMAT_51CHN_FLOAT32 0x19E2 +#define AL_FORMAT_61CHN_I32 0x19E3 +#define AL_FORMAT_61CHN_FLOAT32 0x19E4 +#define AL_FORMAT_71CHN_I32 0x19E5 +#define AL_FORMAT_71CHN_FLOAT32 0x19E6 +#define AL_FORMAT_UHJ2CHN_I32_SOFT 0x19E7 +#define AL_FORMAT_UHJ3CHN_I32_SOFT 0x19E8 +#define AL_FORMAT_UHJ4CHN_I32_SOFT 0x19E9 +#endif + +#ifndef AL_SOFT_source_panning +#define AL_SOFT_source_panning +#define AL_PANNING_ENABLED_SOFT 0x19EA +#define AL_PAN_SOFT 0x19EB +#endif + +/* Non-standard exports. Not part of any extension. */ +AL_API const ALchar* AL_APIENTRY alsoft_get_version(void) noexcept; + +typedef void (ALC_APIENTRY*LPALSOFTLOGCALLBACK)(void *userptr, char level, const char *message, int length) noexcept; +void ALC_APIENTRY alsoft_set_log_callback(LPALSOFTLOGCALLBACK callback, void *userptr) noexcept; /* Functions from abandoned extensions. Only here for binary compatibility. */ AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, - const ALuint *buffers); + const ALuint *buffers) noexcept; -AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname); -AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values); +AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT; +ALint64SOFT AL_APIENTRY alGetInteger64DirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT; +void AL_APIENTRY alGetInteger64vDirectSOFT(ALCcontext *context, ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT; + +/* Not included in the public headers or export list, as a precaution for apps + * that check these to determine the behavior of the multi-channel *32 formats. + */ +#define AL_FORMAT_MONO32 0x1202 +#define AL_FORMAT_STEREO32 0x1203 #ifdef __cplusplus } /* extern "C" */ #endif +/* NOLINTEND */ #endif /* INPROGEXT_H */ diff --git a/Engine/lib/openal-soft/alc/panning.cpp b/Engine/lib/openal-soft/alc/panning.cpp index d118f99cf..83a410ba2 100644 --- a/Engine/lib/openal-soft/alc/panning.cpp +++ b/Engine/lib/openal-soft/alc/panning.cpp @@ -22,53 +22,61 @@ #include #include +#include #include #include #include +#include #include -#include +#include #include -#include #include -#include #include +#include #include +#include +#include +#include -#include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" -#include "al/auxeffectslot.h" -#include "albit.h" -#include "alconfig.h" #include "alc/context.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "alu.h" #include "core/ambdec.h" #include "core/ambidefs.h" #include "core/bformatdec.h" +#include "core/bufferline.h" #include "core/bs2b.h" +#include "core/context.h" #include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/nfc.h" +#include "core/filters/splitter.h" #include "core/front_stablizer.h" #include "core/hrtf.h" #include "core/logging.h" +#include "core/mixer/hrtfdefs.h" #include "core/uhjfilter.h" #include "device.h" +#include "flexarray.h" +#include "intrusive_ptr.h" #include "opthelpers.h" +#include "vector.h" namespace { -using namespace std::placeholders; +using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::nanoseconds; -inline const char *GetLabelFromChannel(Channel channel) +const char *GetLabelFromChannel(Channel channel) { switch(channel) { @@ -90,6 +98,11 @@ inline const char *GetLabelFromChannel(Channel channel) case TopBackCenter: return "top-back-center"; case TopBackRight: return "top-back-right"; + case BottomFrontLeft: return "bottom-front-left"; + case BottomFrontRight: return "bottom-front-right"; + case BottomBackLeft: return "bottom-back-left"; + case BottomBackRight: return "bottom-back-right"; + case Aux0: return "Aux0"; case Aux1: return "Aux1"; case Aux2: return "Aux2"; @@ -226,43 +239,47 @@ struct DecoderConfig { using DecoderView = DecoderConfig; -void InitNearFieldCtrl(ALCdevice *device, float ctrl_dist, uint order, bool is3d) +void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint order, const bool is3d) { - static const uint chans_per_order2d[MaxAmbiOrder+1]{ 1, 2, 2, 2 }; - static const uint chans_per_order3d[MaxAmbiOrder+1]{ 1, 3, 5, 7 }; + static const std::array chans_per_order2d{{1, 2, 2, 2}}; + static const std::array chans_per_order3d{{1, 3, 5, 7}}; /* NFC is only used when AvgSpeakerDist is greater than 0. */ if(!device->getConfigValueBool("decoder", "nfc", false) || !(ctrl_dist > 0.0f)) return; - device->AvgSpeakerDist = clampf(ctrl_dist, 0.1f, 10.0f); + device->AvgSpeakerDist = std::clamp(ctrl_dist, 0.1f, 10.0f); TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist); const float w1{SpeedOfSoundMetersPerSec / (device->AvgSpeakerDist * static_cast(device->Frequency))}; device->mNFCtrlFilter.init(w1); - auto iter = std::copy_n(is3d ? chans_per_order3d : chans_per_order2d, order+1u, - std::begin(device->NumChannelsPerOrder)); - std::fill(iter, std::end(device->NumChannelsPerOrder), 0u); + auto iter = std::copy_n(is3d ? chans_per_order3d.begin() : chans_per_order2d.begin(), order+1u, + device->NumChannelsPerOrder.begin()); + std::fill(iter, device->NumChannelsPerOrder.end(), 0u); } void InitDistanceComp(ALCdevice *device, const al::span channels, - const al::span dists) + const al::span dists) { - const float maxdist{std::accumulate(std::begin(dists), std::end(dists), 0.0f, maxf)}; + const float maxdist{std::accumulate(dists.begin(), dists.end(), 0.0f, + [](const float a, const float b) noexcept -> float { return std::max(a, b); })}; if(!device->getConfigValueBool("decoder", "distance-comp", true) || !(maxdist > 0.0f)) return; const auto distSampleScale = static_cast(device->Frequency) / SpeedOfSoundMetersPerSec; - std::vector ChanDelay; + + struct DistCoeffs { uint Length{}; float Gain{}; }; + std::vector ChanDelay; ChanDelay.reserve(device->RealOut.Buffer.size()); + size_t total{0u}; for(size_t chidx{0};chidx < channels.size();++chidx) { const Channel ch{channels[chidx]}; - const uint idx{device->RealOut.ChannelIndex[ch]}; + const size_t idx{device->RealOut.ChannelIndex[ch]}; if(idx == InvalidChannelIndex) continue; @@ -277,12 +294,12 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)}; if(delay > float{DistanceComp::MaxDelay-1}) { - ERR("Delay for channel %u (%s) exceeds buffer length (%f > %d)\n", idx, + ERR("Delay for channel %zu (%s) exceeds buffer length (%f > %d)\n", idx, GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1); delay = float{DistanceComp::MaxDelay-1}; } - ChanDelay.resize(maxz(ChanDelay.size(), idx+1)); + ChanDelay.resize(std::max(ChanDelay.size(), idx+1_uz)); ChanDelay[idx].Length = static_cast(delay); ChanDelay[idx].Gain = distance / maxdist; TRACE("Channel %s distance comp: %u samples, %f gain\n", GetLabelFromChannel(ch), @@ -297,38 +314,39 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, if(total > 0) { auto chandelays = DistanceComp::Create(total); + auto chanbuffer = chandelays->mSamples.begin(); - ChanDelay[0].Buffer = chandelays->mSamples.data(); - auto set_bufptr = [](const DistanceComp::ChanData &last, const DistanceComp::ChanData &cur) - -> DistanceComp::ChanData + auto set_bufptr = [&chanbuffer](const DistCoeffs &data) { - DistanceComp::ChanData ret{cur}; - ret.Buffer = last.Buffer + RoundUp(last.Length, 4); + DistanceComp::ChanData ret{}; + ret.Buffer = al::span{chanbuffer, data.Length}; + ret.Gain = data.Gain; + chanbuffer += ptrdiff_t(RoundUp(data.Length, 4)); return ret; }; - std::partial_sum(ChanDelay.begin(), ChanDelay.end(), chandelays->mChannels.begin(), + std::transform(ChanDelay.begin(), ChanDelay.end(), chandelays->mChannels.begin(), set_bufptr); device->ChannelDelays = std::move(chandelays); } } -inline auto& GetAmbiScales(DevAmbiScaling scaletype) noexcept +constexpr auto GetAmbiScales(DevAmbiScaling scaletype) noexcept { - if(scaletype == DevAmbiScaling::FuMa) return AmbiScale::FromFuMa(); - if(scaletype == DevAmbiScaling::SN3D) return AmbiScale::FromSN3D(); - return AmbiScale::FromN3D(); + if(scaletype == DevAmbiScaling::FuMa) return al::span{AmbiScale::FromFuMa}; + if(scaletype == DevAmbiScaling::SN3D) return al::span{AmbiScale::FromSN3D}; + return al::span{AmbiScale::FromN3D}; } -inline auto& GetAmbiLayout(DevAmbiLayout layouttype) noexcept +constexpr auto GetAmbiLayout(DevAmbiLayout layouttype) noexcept { - if(layouttype == DevAmbiLayout::FuMa) return AmbiIndex::FromFuMa(); - return AmbiIndex::FromACN(); + if(layouttype == DevAmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa}; + return al::span{AmbiIndex::FromACN}; } DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, - DecoderConfig &decoder) + DecoderConfig &decoder) { DecoderView ret{}; @@ -345,23 +363,20 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, case AmbDecScale::FuMa: decoder.mScaling = DevAmbiScaling::FuMa; break; } - std::copy_n(std::begin(conf->HFOrderGain), - std::min(al::size(conf->HFOrderGain), al::size(decoder.mOrderGain)), - std::begin(decoder.mOrderGain)); - std::copy_n(std::begin(conf->LFOrderGain), - std::min(al::size(conf->LFOrderGain), al::size(decoder.mOrderGainLF)), - std::begin(decoder.mOrderGainLF)); + const auto hfordermin = std::min(conf->HFOrderGain.size(), decoder.mOrderGain.size()); + std::copy_n(conf->HFOrderGain.begin(), hfordermin, decoder.mOrderGain.begin()); + const auto lfordermin = std::min(conf->LFOrderGain.size(), decoder.mOrderGainLF.size()); + std::copy_n(conf->LFOrderGain.begin(), lfordermin, decoder.mOrderGainLF.begin()); const auto num_coeffs = decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) : Ambi2DChannelsFromOrder(decoder.mOrder); - const auto idx_map = decoder.mIs3D ? AmbiIndex::FromACN().data() - : AmbiIndex::FromACN2D().data(); + const auto idx_map = decoder.mIs3D ? al::span{AmbiIndex::FromACN} + : al::span{AmbiIndex::FromACN2D}; const auto hfmatrix = conf->HFMatrix; const auto lfmatrix = conf->LFMatrix; uint chan_count{0}; - using const_speaker_span = al::span; - for(auto &speaker : const_speaker_span{conf->Speakers.get(), conf->NumSpeakers}) + for(auto &speaker : al::span{std::as_const(conf->Speakers)}) { /* NOTE: AmbDec does not define any standard speaker names, however * for this to work we have to by able to find the output channel @@ -380,36 +395,48 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, * RFT = Top front right * LBT = Top back left * RBT = Top back right + * LFB = Bottom front left + * RFB = Bottom front right + * LBB = Bottom back left + * RBB = Bottom back right * * Additionally, surround51 will acknowledge back speakers for side * channels, to avoid issues with an ambdec expecting 5.1 to use the * back channels. */ Channel ch{}; - if(speaker.Name == "LF") + if(speaker.Name == "LF"sv) ch = FrontLeft; - else if(speaker.Name == "RF") + else if(speaker.Name == "RF"sv) ch = FrontRight; - else if(speaker.Name == "CE") + else if(speaker.Name == "CE"sv) ch = FrontCenter; - else if(speaker.Name == "LS") + else if(speaker.Name == "LS"sv) ch = SideLeft; - else if(speaker.Name == "RS") + else if(speaker.Name == "RS"sv) ch = SideRight; - else if(speaker.Name == "LB") + else if(speaker.Name == "LB"sv) ch = (device->FmtChans == DevFmtX51) ? SideLeft : BackLeft; - else if(speaker.Name == "RB") + else if(speaker.Name == "RB"sv) ch = (device->FmtChans == DevFmtX51) ? SideRight : BackRight; - else if(speaker.Name == "CB") + else if(speaker.Name == "CB"sv) ch = BackCenter; - else if(speaker.Name == "LFT") + else if(speaker.Name == "LFT"sv) ch = TopFrontLeft; - else if(speaker.Name == "RFT") + else if(speaker.Name == "RFT"sv) ch = TopFrontRight; - else if(speaker.Name == "LBT") + else if(speaker.Name == "LBT"sv) ch = TopBackLeft; - else if(speaker.Name == "RBT") + else if(speaker.Name == "RBT"sv) ch = TopBackRight; + else if(speaker.Name == "LFB"sv) + ch = BottomFrontLeft; + else if(speaker.Name == "RFB"sv) + ch = BottomFrontRight; + else if(speaker.Name == "LBB"sv) + ch = BottomBackLeft; + else if(speaker.Name == "RBB"sv) + ch = BottomBackRight; else { int idx{}; @@ -445,13 +472,13 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, ret.mOrder = decoder.mOrder; ret.mIs3D = decoder.mIs3D; ret.mScaling = decoder.mScaling; - ret.mChannels = {decoder.mChannels.data(), chan_count}; + ret.mChannels = al::span{decoder.mChannels}.first(chan_count); ret.mOrderGain = decoder.mOrderGain; - ret.mCoeffs = {decoder.mCoeffs.data(), chan_count}; + ret.mCoeffs = al::span{decoder.mCoeffs}.first(chan_count); if(conf->FreqBands > 1) { ret.mOrderGainLF = decoder.mOrderGainLF; - ret.mCoeffsLF = {decoder.mCoeffsLF.data(), chan_count}; + ret.mCoeffsLF = al::span{decoder.mCoeffsLF}.first(chan_count); } } return ret; @@ -583,6 +610,44 @@ constexpr DecoderConfig X714Config{ {{8.80892603e-02f, -7.48948724e-02f, 9.08779842e-02f, -6.22480443e-02f}}, }} }; +constexpr DecoderConfig X7144Config{ + 1, true, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight, TopBackLeft, TopFrontLeft, TopFrontRight, TopBackRight, BottomBackLeft, BottomFrontLeft, BottomFrontRight, BottomBackRight}}, + DevAmbiScaling::N3D, + /*HF*/{{2.64575131e+0f, 1.52752523e+0f}}, + {{ + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + }} +}; void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false, DecoderView decoder={}) @@ -598,15 +663,16 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= case DevFmtX61: decoder = X61Config; break; case DevFmtX71: decoder = X71Config; break; case DevFmtX714: decoder = X714Config; break; + case DevFmtX7144: decoder = X7144Config; break; case DevFmtX3D71: decoder = X3D71Config; break; case DevFmtAmbi3D: - auto&& acnmap = GetAmbiLayout(device->mAmbiLayout); - auto&& 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 + const auto acnmap = GetAmbiLayout(device->mAmbiLayout).first(count); + const auto n3dscale = GetAmbiScales(device->mAmbiScale); + + std::transform(acnmap.cbegin(), acnmap.cend(), device->Dry.AmbiMap.begin(), + [n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig { return BFChannelConfig{1.0f/n3dscale[acn], acn}; }); AllocChannels(device, count, 0); device->m2DMixing = false; @@ -628,10 +694,10 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= const size_t ambicount{decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) : Ambi2DChannelsFromOrder(decoder.mOrder)}; const bool dual_band{hqdec && !decoder.mCoeffsLF.empty()}; - al::vector chancoeffs, chancoeffslf; + std::vector chancoeffs, chancoeffslf; for(size_t i{0u};i < decoder.mChannels.size();++i) { - const uint idx{device->channelIdxByName(decoder.mChannels[i])}; + const size_t idx{device->channelIdxByName(decoder.mChannels[i])}; if(idx == InvalidChannelIndex) { ERR("Failed to find %s channel in device\n", @@ -639,10 +705,10 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= continue; } - auto ordermap = decoder.mIs3D ? AmbiIndex::OrderFromChannel().data() - : AmbiIndex::OrderFrom2DChannel().data(); + auto ordermap = decoder.mIs3D ? al::span{AmbiIndex::OrderFromChannel} + : al::span{AmbiIndex::OrderFrom2DChannel}; - chancoeffs.resize(maxz(chancoeffs.size(), idx+1u), ChannelDec{}); + chancoeffs.resize(std::max(chancoeffs.size(), idx+1_zu), ChannelDec{}); al::span src{decoder.mCoeffs[i]}; al::span dst{chancoeffs[idx]}; for(size_t ambichan{0};ambichan < ambicount;++ambichan) @@ -651,7 +717,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= if(!dual_band) continue; - chancoeffslf.resize(maxz(chancoeffslf.size(), idx+1u), ChannelDec{}); + chancoeffslf.resize(std::max(chancoeffslf.size(), idx+1_zu), ChannelDec{}); src = decoder.mCoeffsLF[i]; dst = chancoeffslf[idx]; for(size_t ambichan{0};ambichan < ambicount;++ambichan) @@ -662,11 +728,11 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= device->mAmbiOrder = decoder.mOrder; device->m2DMixing = !decoder.mIs3D; - const al::span acnmap{decoder.mIs3D ? AmbiIndex::FromACN().data() : - AmbiIndex::FromACN2D().data(), ambicount}; - auto&& coeffscale = GetAmbiScales(decoder.mScaling); - std::transform(acnmap.begin(), acnmap.end(), std::begin(device->Dry.AmbiMap), - [&coeffscale](const uint8_t &acn) noexcept + const auto acnmap = decoder.mIs3D ? al::span{AmbiIndex::FromACN}.first(ambicount) + : al::span{AmbiIndex::FromACN2D}.first(ambicount); + const auto coeffscale = GetAmbiScales(decoder.mScaling); + std::transform(acnmap.begin(), acnmap.end(), device->Dry.AmbiMap.begin(), + [coeffscale](const uint8_t &acn) noexcept { return BFChannelConfig{1.0f/coeffscale[acn], acn}; }); AllocChannels(device, ambicount, device->channelsFromFmt()); @@ -676,7 +742,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= /* Only enable the stablizer if the decoder does not output to the * front-center channel. */ - const auto cidx = device->RealOut.ChannelIndex[FrontCenter]; + const size_t cidx{device->RealOut.ChannelIndex[FrontCenter]}; bool hasfc{false}; if(cidx < chancoeffs.size()) { @@ -707,120 +773,126 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= void InitHrtfPanning(ALCdevice *device) { - constexpr float Deg180{al::numbers::pi_v}; - constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; - constexpr float Deg_45{Deg_90 / 2.0f /* 45 degrees*/}; - constexpr float Deg135{Deg_45 * 3.0f /*135 degrees*/}; - constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/}; - constexpr float Deg_32{5.535743589e-01f /* 31~ 32 degrees*/}; - constexpr float Deg_35{6.154797087e-01f /* 35~ 36 degrees*/}; - constexpr float Deg_58{1.017221968e+00f /* 58~ 59 degrees*/}; - constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/}; - constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/}; - constexpr float Deg122{2.124370686e+00f /*121~122 degrees*/}; - static const AngularPoint AmbiPoints1O[]{ - { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, - { EvRadians{ Deg_35}, AzRadians{-Deg135} }, - { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, - { EvRadians{ Deg_35}, AzRadians{ Deg135} }, - { EvRadians{-Deg_35}, AzRadians{-Deg_45} }, - { EvRadians{-Deg_35}, AzRadians{-Deg135} }, - { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, - { EvRadians{-Deg_35}, AzRadians{ Deg135} }, - }, AmbiPoints2O[]{ - { EvRadians{-Deg_32}, AzRadians{ 0.0f} }, - { EvRadians{ 0.0f}, AzRadians{ Deg_58} }, - { EvRadians{ Deg_58}, AzRadians{ Deg_90} }, - { EvRadians{ Deg_32}, AzRadians{ 0.0f} }, - { EvRadians{ 0.0f}, AzRadians{ Deg122} }, - { EvRadians{-Deg_58}, AzRadians{-Deg_90} }, - { EvRadians{-Deg_32}, AzRadians{ Deg180} }, - { EvRadians{ 0.0f}, AzRadians{-Deg122} }, - { EvRadians{ Deg_58}, AzRadians{-Deg_90} }, - { EvRadians{ Deg_32}, AzRadians{ Deg180} }, - { EvRadians{ 0.0f}, AzRadians{-Deg_58} }, - { EvRadians{-Deg_58}, AzRadians{ Deg_90} }, - }, AmbiPoints3O[]{ - { EvRadians{ Deg_69}, AzRadians{-Deg_90} }, - { EvRadians{ Deg_69}, AzRadians{ Deg_90} }, - { EvRadians{-Deg_69}, AzRadians{-Deg_90} }, - { EvRadians{-Deg_69}, AzRadians{ Deg_90} }, - { EvRadians{ 0.0f}, AzRadians{-Deg_69} }, - { EvRadians{ 0.0f}, AzRadians{-Deg111} }, - { EvRadians{ 0.0f}, AzRadians{ Deg_69} }, - { EvRadians{ 0.0f}, AzRadians{ Deg111} }, - { EvRadians{ Deg_21}, AzRadians{ 0.0f} }, - { EvRadians{ Deg_21}, AzRadians{ Deg180} }, - { EvRadians{-Deg_21}, AzRadians{ 0.0f} }, - { EvRadians{-Deg_21}, AzRadians{ Deg180} }, - { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, - { EvRadians{ Deg_35}, AzRadians{-Deg135} }, - { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, - { EvRadians{ Deg_35}, AzRadians{ Deg135} }, - { EvRadians{-Deg_35}, AzRadians{-Deg_45} }, - { EvRadians{-Deg_35}, AzRadians{-Deg135} }, - { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, - { EvRadians{-Deg_35}, AzRadians{ Deg135} }, + static constexpr float Deg180{al::numbers::pi_v}; + static constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; + static constexpr float Deg_45{Deg_90 / 2.0f /* 45 degrees*/}; + static constexpr float Deg135{Deg_45 * 3.0f /*135 degrees*/}; + static constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/}; + static constexpr float Deg_32{5.535743589e-01f /* 31~ 32 degrees*/}; + static constexpr float Deg_35{6.154797087e-01f /* 35~ 36 degrees*/}; + static constexpr float Deg_58{1.017221968e+00f /* 58~ 59 degrees*/}; + static constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/}; + static constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/}; + static constexpr float Deg122{2.124370686e+00f /*121~122 degrees*/}; + static constexpr std::array AmbiPoints1O{ + AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg_45}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg135}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg_45}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg135}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg_45}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg135}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg_45}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg135}}, }; - static const float AmbiMatrix1O[][MaxAmbiChannels]{ - { 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[][MaxAmbiChannels]{ - { 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }, - { 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, - { 8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, - { 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }, - { 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, - { 8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, - { 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }, - { 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, - { 8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, - { 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }, - { 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, - { 8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, - }, AmbiMatrix3O[][MaxAmbiChannels]{ - { 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, - { 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, - { 5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }, - { 5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }, - { 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }, - { 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }, - { 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }, - { 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }, - { 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f, }, - { 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f, }, - { 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f, }, - { 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f, }, - { 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }, - { 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }, - { 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }, - { 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }, - { 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }, - { 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }, - { 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }, - { 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }, + static constexpr std::array AmbiPoints2O{ + AngularPoint{EvRadians{-Deg_32}, AzRadians{ 0.0f}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg_58}}, + AngularPoint{EvRadians{ Deg_58}, AzRadians{ Deg_90}}, + AngularPoint{EvRadians{ Deg_32}, AzRadians{ 0.0f}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg122}}, + AngularPoint{EvRadians{-Deg_58}, AzRadians{-Deg_90}}, + AngularPoint{EvRadians{-Deg_32}, AzRadians{ Deg180}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg122}}, + AngularPoint{EvRadians{ Deg_58}, AzRadians{-Deg_90}}, + AngularPoint{EvRadians{ Deg_32}, AzRadians{ Deg180}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg_58}}, + AngularPoint{EvRadians{-Deg_58}, AzRadians{ Deg_90}}, }; - static const float AmbiOrderHFGain1O[MaxAmbiOrder+1]{ + static constexpr std::array AmbiPoints3O{ + AngularPoint{EvRadians{ Deg_69}, AzRadians{-Deg_90}}, + AngularPoint{EvRadians{ Deg_69}, AzRadians{ Deg_90}}, + AngularPoint{EvRadians{-Deg_69}, AzRadians{-Deg_90}}, + AngularPoint{EvRadians{-Deg_69}, AzRadians{ Deg_90}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg_69}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg111}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg_69}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg111}}, + AngularPoint{EvRadians{ Deg_21}, AzRadians{ 0.0f}}, + AngularPoint{EvRadians{ Deg_21}, AzRadians{ Deg180}}, + AngularPoint{EvRadians{-Deg_21}, AzRadians{ 0.0f}}, + AngularPoint{EvRadians{-Deg_21}, AzRadians{ Deg180}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg_45}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg135}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg_45}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg135}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg_45}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg135}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg_45}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg135}}, + }; + static constexpr std::array AmbiMatrix1O{ + ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, + }; + static constexpr std::array AmbiMatrix2O{ + ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, + ChannelCoeffs{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + ChannelCoeffs{8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, + ChannelCoeffs{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + ChannelCoeffs{8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, + ChannelCoeffs{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + ChannelCoeffs{8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, + ChannelCoeffs{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + ChannelCoeffs{8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + }; + static constexpr std::array AmbiMatrix3O{ + ChannelCoeffs{5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, + ChannelCoeffs{5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, + ChannelCoeffs{5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, + ChannelCoeffs{5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, + ChannelCoeffs{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, + ChannelCoeffs{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, + ChannelCoeffs{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, + ChannelCoeffs{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, + ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f}, + ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f}, + ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f}, + ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f}, + ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, + }; + static constexpr std::array AmbiOrderHFGain1O{ /*ENRGY*/ 2.000000000e+00f, 1.154700538e+00f - }, AmbiOrderHFGain2O[MaxAmbiOrder+1]{ + }; + static constexpr std::array AmbiOrderHFGain2O{ /*ENRGY*/ 1.825741858e+00f, 1.414213562e+00f, 7.302967433e-01f /*AMP 1.000000000e+00f, 7.745966692e-01f, 4.000000000e-01f*/ /*RMS 9.128709292e-01f, 7.071067812e-01f, 3.651483717e-01f*/ - }, AmbiOrderHFGain3O[MaxAmbiOrder+1]{ + }; + static constexpr std::array AmbiOrderHFGain3O{ /*ENRGY 1.865086714e+00f, 1.606093894e+00f, 1.142055301e+00f, 5.683795528e-01f*/ /*AMP*/ 1.000000000e+00f, 8.611363116e-01f, 6.123336207e-01f, 3.047469850e-01f /*RMS 8.340921354e-01f, 7.182670250e-01f, 5.107426573e-01f, 2.541870634e-01f*/ }; - 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"); - static_assert(al::size(AmbiPoints3O) == al::size(AmbiMatrix3O), "Third-Order Ambisonic HRTF mismatch"); + static_assert(AmbiPoints1O.size() == AmbiMatrix1O.size(), "First-Order Ambisonic HRTF mismatch"); + static_assert(AmbiPoints2O.size() == AmbiMatrix2O.size(), "Second-Order Ambisonic HRTF mismatch"); + static_assert(AmbiPoints3O.size() == AmbiMatrix3O.size(), "Third-Order Ambisonic HRTF mismatch"); /* A 700hz crossover frequency provides tighter sound imaging at the sweet * spot with ambisonic decoding, as the distance between the ears is closer @@ -840,32 +912,32 @@ void InitHrtfPanning(ALCdevice *device) */ device->mRenderMode = RenderMode::Hrtf; uint ambi_order{1}; - if(auto modeopt = device->configValue(nullptr, "hrtf-mode")) + if(auto modeopt = device->configValue({}, "hrtf-mode")) { struct HrtfModeEntry { - char name[8]; + std::string_view name; RenderMode mode; uint order; }; - static const HrtfModeEntry hrtf_modes[]{ - { "full", RenderMode::Hrtf, 1 }, - { "ambi1", RenderMode::Normal, 1 }, - { "ambi2", RenderMode::Normal, 2 }, - { "ambi3", RenderMode::Normal, 3 }, + constexpr std::array hrtf_modes{ + HrtfModeEntry{"full"sv, RenderMode::Hrtf, 1}, + HrtfModeEntry{"ambi1"sv, RenderMode::Normal, 1}, + HrtfModeEntry{"ambi2"sv, RenderMode::Normal, 2}, + HrtfModeEntry{"ambi3"sv, RenderMode::Normal, 3}, }; - const char *mode{modeopt->c_str()}; - if(al::strcasecmp(mode, "basic") == 0) + std::string_view mode{*modeopt}; + if(al::case_compare(mode, "basic"sv) == 0) { - ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", mode, "ambi2"); + ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", modeopt->c_str(), "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); + { return al::case_compare(mode, entry.name) == 0; }; + auto iter = std::find_if(hrtf_modes.begin(), hrtf_modes.end(), match_entry); + if(iter == hrtf_modes.end()) + ERR("Unexpected hrtf-mode: %s\n", modeopt->c_str()); else { device->mRenderMode = iter->mode; @@ -873,17 +945,13 @@ void InitHrtfPanning(ALCdevice *device) } } 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 == RenderMode::Hrtf) ? "+ Full " : "", + GetCounterSuffix(ambi_order), (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "", device->mHrtfName.c_str()); bool perHrirMin{false}; - al::span AmbiPoints{AmbiPoints1O}; - const float (*AmbiMatrix)[MaxAmbiChannels]{AmbiMatrix1O}; - al::span AmbiOrderHFGain{AmbiOrderHFGain1O}; + auto AmbiPoints = al::span{AmbiPoints1O}.subspan(0); + auto AmbiMatrix = al::span{AmbiMatrix1O}.subspan(0); + auto AmbiOrderHFGain = al::span{AmbiOrderHFGain1O}; if(ambi_order >= 3) { perHrirMin = true; @@ -901,10 +969,9 @@ void InitHrtfPanning(ALCdevice *device) device->m2DMixing = false; const size_t count{AmbiChannelsFromOrder(ambi_order)}; - std::transform(AmbiIndex::FromACN().begin(), AmbiIndex::FromACN().begin()+count, - std::begin(device->Dry.AmbiMap), - [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); + const auto acnmap = al::span{AmbiIndex::FromACN}.first(count); + std::transform(acnmap.begin(), acnmap.end(), device->Dry.AmbiMap.begin(), + [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; }); AllocChannels(device, count, device->channelsFromFmt()); HrtfStore *Hrtf{device->mHrtf.get()}; @@ -919,21 +986,21 @@ void InitHrtfPanning(ALCdevice *device) void InitUhjPanning(ALCdevice *device) { /* UHJ is always 2D first-order. */ - constexpr size_t count{Ambi2DChannelsFromOrder(1)}; + static constexpr size_t count{Ambi2DChannelsFromOrder(1)}; device->mAmbiOrder = 1; device->m2DMixing = true; - auto acnmap_begin = AmbiIndex::FromFuMa2D().begin(); - std::transform(acnmap_begin, acnmap_begin + count, std::begin(device->Dry.AmbiMap), + const auto acnmap = al::span{AmbiIndex::FromFuMa2D}.first(); + std::transform(acnmap.cbegin(), acnmap.cend(), device->Dry.AmbiMap.begin(), [](const uint8_t &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f/AmbiScale::FromUHJ()[acn], acn}; }); + { return BFChannelConfig{1.0f/AmbiScale::FromUHJ[acn], acn}; }); AllocChannels(device, count, device->channelsFromFmt()); } } // namespace -void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional stereomode) +void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional stereomode) { /* Hold the HRTF the device last used, in case it's used again. */ HrtfStorePtr old_hrtf{std::move(device->mHrtf)}; @@ -960,6 +1027,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional> decoder_store; + std::unique_ptr> decoder_store; DecoderView decoder{}; - float speakerdists[MAX_OUTPUT_CHANNELS]{}; + std::array speakerdists{}; auto load_config = [device,&decoder_store,&decoder,&speakerdists](const char *config) { AmbDecConf conf{}; @@ -978,28 +1046,42 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalc_str()); + return false; } - else if(conf.NumSpeakers > MAX_OUTPUT_CHANNELS) - ERR("Unsupported decoder speaker count %zu (max %d)\n", conf.NumSpeakers, - MAX_OUTPUT_CHANNELS); - else if(conf.ChanMask > Ambi3OrderMask) + if(conf.Speakers.size() > MaxOutputChannels) + { + ERR("Unsupported decoder speaker count %zu (max %zu)\n", conf.Speakers.size(), + MaxOutputChannels); + return false; + } + if(conf.ChanMask > Ambi3OrderMask) + { ERR("Unsupported decoder channel mask 0x%04x (max 0x%x)\n", conf.ChanMask, Ambi3OrderMask); - else - { - device->mXOverFreq = clampf(conf.XOverFreq, 100.0f, 1000.0f); - - decoder_store = std::make_unique>(); - decoder = MakeDecoderView(device, &conf, *decoder_store); - for(size_t i{0};i < decoder.mChannels.size();++i) - speakerdists[i] = conf.Speakers[i].Distance; + return false; } + + TRACE("Using %s decoder: \"%s\"\n", DevFmtChannelsString(device->FmtChans), + conf.Description.c_str()); + device->mXOverFreq = std::clamp(conf.XOverFreq, 100.0f, 1000.0f); + + decoder_store = std::make_unique>(); + decoder = MakeDecoderView(device, &conf, *decoder_store); + + const auto confspeakers = al::span{std::as_const(conf.Speakers)} + .first(decoder.mChannels.size()); + std::transform(confspeakers.cbegin(), confspeakers.cend(), speakerdists.begin(), + std::mem_fn(&AmbDecConf::SpeakerConf::Distance)); + return true; }; + bool usingCustom{false}; if(layout) { if(auto decopt = device->configValue("decoder", layout)) - load_config(decopt->c_str()); + usingCustom = load_config(decopt->c_str()); } + if(!usingCustom && device->FmtChans != DevFmtAmbi3D) + TRACE("Using built-in %s decoder\n", DevFmtChannelsString(device->FmtChans)); /* Enable the stablizer only for formats that have front-left, front- * right, and front-center outputs. @@ -1007,8 +1089,8 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalRealOut.ChannelIndex[FrontCenter] != InvalidChannelIndex && device->RealOut.ChannelIndex[FrontLeft] != InvalidChannelIndex && device->RealOut.ChannelIndex[FrontRight] != InvalidChannelIndex - && device->getConfigValueBool(nullptr, "front-stablizer", false) != 0}; - const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", true) != 0}; + && device->getConfigValueBool({}, "front-stablizer", false)}; + const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", true)}; InitPanning(device, hqdec, stablize, decoder); if(decoder) { @@ -1049,7 +1131,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional= 0 && static_cast(hrtf_id) < device->mHrtfList.size()) { - const std::string &hrtfname = device->mHrtfList[static_cast(hrtf_id)]; + const std::string_view hrtfname{device->mHrtfList[static_cast(hrtf_id)]}; if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) { device->mHrtf = std::move(hrtf); @@ -1059,7 +1141,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalmHrtf) { - for(const auto &hrtfname : device->mHrtfList) + for(const std::string_view hrtfname : device->mHrtfList) { if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) { @@ -1076,10 +1158,10 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalmHrtf.get()}; device->mIrSize = hrtf->mIrSize; - if(auto hrtfsizeopt = device->configValue(nullptr, "hrtf-size")) + if(auto hrtfsizeopt = device->configValue({}, "hrtf-size")) { if(*hrtfsizeopt > 0 && *hrtfsizeopt < device->mIrSize) - device->mIrSize = maxu(*hrtfsizeopt, MinIrLength); + device->mIrSize = std::max(*hrtfsizeopt, MinIrLength); } InitHrtfPanning(device); @@ -1115,13 +1197,13 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalmRenderMode = RenderMode::Pairwise; if(device->Type != DeviceType::Loopback) { - if(auto cflevopt = device->configValue(nullptr, "cf_level")) + if(auto cflevopt = device->configValue({}, "cf_level")) { if(*cflevopt > 0 && *cflevopt <= 6) { - device->Bs2b = std::make_unique(); - bs2b_set_params(device->Bs2b.get(), *cflevopt, - static_cast(device->Frequency)); + auto bs2b = std::make_unique(); + bs2b->set_params(*cflevopt, static_cast(device->Frequency)); + device->Bs2b = std::move(bs2b); TRACE("BS2B enabled\n"); InitPanning(device); device->PostProcess = &ALCdevice::ProcessBs2b; @@ -1143,10 +1225,9 @@ void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context) slot->mWetBuffer.resize(count); - auto acnmap_begin = AmbiIndex::FromACN().begin(); - auto iter = std::transform(acnmap_begin, acnmap_begin + count, slot->Wet.AmbiMap.begin(), - [](const uint8_t &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f, acn}; }); + const auto acnmap = al::span{AmbiIndex::FromACN}.first(count); + const auto iter = std::transform(acnmap.cbegin(), acnmap.cend(), 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->mWetBuffer; } diff --git a/Engine/lib/openal-soft/alsoftrc.sample b/Engine/lib/openal-soft/alsoftrc.sample index 2906cca46..dd3ff8660 100644 --- a/Engine/lib/openal-soft/alsoftrc.sample +++ b/Engine/lib/openal-soft/alsoftrc.sample @@ -56,10 +56,10 @@ # Sets the default output channel configuration. If left unspecified, one will # try to be detected from the system, with a fallback to stereo. The available # values are: mono, stereo, quad, surround51, surround61, surround71, -# surround3d71, ambi1, ambi2, ambi3. Note that the ambi* configurations output -# ambisonic channels of the given order (using ACN ordering and SN3D -# normalization by default), which need to be decoded to play correctly on -# speakers. +# surround714, surround3d71, ambi1, ambi2, ambi3. Note that the ambi* +# configurations output ambisonic channels of the given order (using ACN +# ordering and SN3D normalization by default), which need to be decoded to +# play correctly on speakers. #channels = ## sample-type: @@ -179,7 +179,8 @@ # 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 +# spline - extrapolates samples using a Catmull-Rom spline +# gaussian - extrapolates samples using a 4-point Gaussian filter # 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- @@ -188,7 +189,7 @@ # between 24 and 48 points, with anti-aliasing) # fast_bsinc24 - same as bsinc24, except without interpolation between down- # sampling scales -#resampler = cubic +#resampler = gaussian ## rt-prio: (global) # Sets the real-time priority value for the mixing thread. Not all drivers may @@ -232,14 +233,15 @@ ## output-limiter: # Applies a gain limiter on the final mixed output. This reduces the volume # when the output samples would otherwise clamp, avoiding excessive clipping -# noise. -#output-limiter = true +# noise. On by default for integer sample types, and off by default for +# floating-point. +#output-limiter = ## dither: -# Applies dithering on the final mix, for 8- and 16-bit output by default. -# This replaces the distortion created by nearest-value quantization with low- -# level whitenoise. -#dither = true +# Applies dithering on the final mix, enabled by default for 8- and 16-bit +# output. This replaces the distortion created by nearest-value quantization +# with low-level whitenoise. +#dither = ## dither-depth: # Quantization bit-depth for dithered output. A value of 0 (or less) will @@ -343,6 +345,11 @@ # docs/ambdec.txt for a description of the file format. #surround71 = +## surround714: +# Decoder configuration file for 7.1.4 Surround channel output. See +# docs/ambdec.txt for a description of the file format. +#surround714 = + ## surround3d71: # Decoder configuration file for 3D7.1 Surround channel output. See # docs/ambdec.txt for a description of the file format. See also @@ -401,7 +408,7 @@ # Renders samples directly in the real-time processing callback. This allows # for lower latency and less overall CPU utilization, but can increase the # risk of underruns when increasing the amount of work the mixer needs to do. -#rt-mix = true +#rt-mix = false ## ## PulseAudio backend stuff @@ -574,6 +581,12 @@ ## [wasapi] +## spatial-api: +# Specifies whether to use a Spatial Audio stream for playback. This may +# provide expanded capabilities for surround sound and with-height speaker +# configurations. Very experimental. +#spatial-api = false + ## allow-resampler: # Specifies whether to allow an extra resampler pass on the output. Enabling # this will allow the playback device to be set to a different sample rate @@ -638,6 +651,15 @@ ## [game_compat] +## default-error: (global) +# An error value returned by alGetError when there's no current context. The +# default value is AL_INVALID_OPERATION, which lets the caller know the +# operation could not be executed. Some applications may erroneously call +# alGetError without a current context and expect 0 (AL_NO_ERROR), however +# that may cause other applications to think earlier AL calls succeeded when +# they actually failed. +#default-error = 0xA004 + ## nfc-scale: (global) # A meters-per-unit distance scale applied to NFC filters. If a game doesn't # use real-world meters for in-game units, the filters may create a too-near diff --git a/Engine/lib/openal-soft/appveyor.yml b/Engine/lib/openal-soft/appveyor.yml index aa155af43..8ad0f6bf1 100644 --- a/Engine/lib/openal-soft/appveyor.yml +++ b/Engine/lib/openal-soft/appveyor.yml @@ -1,8 +1,8 @@ version: 1.23.1.{build} environment: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - GEN: "Visual Studio 15 2017" + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + GEN: "Visual Studio 17 2022" matrix: - ARCH: Win32 CFG: Release diff --git a/Engine/lib/openal-soft/cmake/FindOpenSL.cmake b/Engine/lib/openal-soft/cmake/FindOpenSL.cmake index 004287494..3df54d447 100644 --- a/Engine/lib/openal-soft/cmake/FindOpenSL.cmake +++ b/Engine/lib/openal-soft/cmake/FindOpenSL.cmake @@ -2,8 +2,9 @@ # Find the OpenSL libraries # # This module defines the following variables and targets: -# OPENSL_FOUND - True if OPENSL was found -# OpenSL::OpenSLES - The OpenSLES target +# OPENSL_FOUND - True if OPENSL was found +# OPENSL_INCLUDE_DIRS - The OpenSL include paths +# OPENSL_LIBRARIES - The OpenSL libraries to link # #============================================================================= @@ -53,11 +54,8 @@ find_package_handle_standard_args(OpenSL REQUIRED_VARS OPENSL_LIBRARY OPENSL_INC OPENSL_ANDROID_INCLUDE_DIR) if(OPENSL_FOUND) - add_library(OpenSL::OpenSLES UNKNOWN IMPORTED) - set_target_properties(OpenSL::OpenSLES PROPERTIES - IMPORTED_LOCATION ${OPENSL_LIBRARY} - INTERFACE_INCLUDE_DIRECTORIES ${OPENSL_INCLUDE_DIR} - INTERFACE_INCLUDE_DIRECTORIES ${OPENSL_ANDROID_INCLUDE_DIR}) + 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/Engine/lib/openal-soft/cmake/FindSndIO.cmake b/Engine/lib/openal-soft/cmake/FindSndIO.cmake new file mode 100644 index 000000000..eb4a2587b --- /dev/null +++ b/Engine/lib/openal-soft/cmake/FindSndIO.cmake @@ -0,0 +1,31 @@ +# - Find SndIO includes and libraries +# +# SNDIO_FOUND - True if SNDIO_INCLUDE_DIR & SNDIO_LIBRARY are found +# SNDIO_LIBRARIES - Set when SNDIO_LIBRARY is found +# SNDIO_INCLUDE_DIRS - Set when SNDIO_INCLUDE_DIR is found +# +# SNDIO_INCLUDE_DIR - where to find sndio.h, etc. +# SNDIO_LIBRARY - the sndio library +# + +find_path(SNDIO_INCLUDE_DIR + NAMES sndio.h + DOC "The SndIO include directory" +) + +find_library(SNDIO_LIBRARY + NAMES sndio + DOC "The SndIO library" +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SndIO + REQUIRED_VARS SNDIO_LIBRARY SNDIO_INCLUDE_DIR +) + +if(SNDIO_FOUND) + set(SNDIO_LIBRARIES ${SNDIO_LIBRARY}) + set(SNDIO_INCLUDE_DIRS ${SNDIO_INCLUDE_DIR}) +endif() + +mark_as_advanced(SNDIO_INCLUDE_DIR SNDIO_LIBRARY) diff --git a/Engine/lib/openal-soft/cmake/FindSoundIO.cmake b/Engine/lib/openal-soft/cmake/FindSoundIO.cmake deleted file mode 100644 index 10450254d..000000000 --- a/Engine/lib/openal-soft/cmake/FindSoundIO.cmake +++ /dev/null @@ -1,32 +0,0 @@ -# - Find SoundIO (sndio) includes and libraries -# -# SOUNDIO_FOUND - True if SOUNDIO_INCLUDE_DIR & SOUNDIO_LIBRARY are -# found -# SOUNDIO_LIBRARIES - Set when SOUNDIO_LIBRARY is found -# SOUNDIO_INCLUDE_DIRS - Set when SOUNDIO_INCLUDE_DIR is found -# -# SOUNDIO_INCLUDE_DIR - where to find sndio.h, etc. -# SOUNDIO_LIBRARY - the sndio library -# - -find_path(SOUNDIO_INCLUDE_DIR - NAMES sndio.h - DOC "The SoundIO include directory" -) - -find_library(SOUNDIO_LIBRARY - NAMES sndio - DOC "The SoundIO library" -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(SoundIO - REQUIRED_VARS SOUNDIO_LIBRARY SOUNDIO_INCLUDE_DIR -) - -if(SOUNDIO_FOUND) - set(SOUNDIO_LIBRARIES ${SOUNDIO_LIBRARY}) - set(SOUNDIO_INCLUDE_DIRS ${SOUNDIO_INCLUDE_DIR}) -endif() - -mark_as_advanced(SOUNDIO_INCLUDE_DIR SOUNDIO_LIBRARY) diff --git a/Engine/lib/openal-soft/common/alassert.cpp b/Engine/lib/openal-soft/common/alassert.cpp new file mode 100644 index 000000000..f50642308 --- /dev/null +++ b/Engine/lib/openal-soft/common/alassert.cpp @@ -0,0 +1,38 @@ + +#include "alassert.h" + +#include +#include +#include + +namespace al { + +[[noreturn]] +void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept +{ + std::string errstr{filename}; + errstr += ':'; + errstr += std::to_string(linenum); + errstr += ": "; + errstr += funcname; + errstr += ": "; + errstr += message; + /* Calling std::terminate in a catch block hopefully causes the system to + * provide info about the caught exception in the error dialog. At least on + * Linux, this results in the process printing + * + * terminate called after throwing an instance of 'std::runtime_error' + * what(): + * + * before terminating from a SIGABRT. Hopefully Windows and Mac will do the + * appropriate things with the message for an abnormal termination. + */ + try { + throw std::runtime_error{errstr}; + } + catch(...) { + std::terminate(); + } +} + +} /* namespace al */ diff --git a/Engine/lib/openal-soft/common/alassert.h b/Engine/lib/openal-soft/common/alassert.h new file mode 100644 index 000000000..3797e96d5 --- /dev/null +++ b/Engine/lib/openal-soft/common/alassert.h @@ -0,0 +1,24 @@ +#ifndef AL_ASSERT_H +#define AL_ASSERT_H + +#include + +#include "opthelpers.h" + +namespace al { + +[[noreturn]] +void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept; + +} /* namespace al */ + +/* A custom assert macro that is not compiled out for Release/NDEBUG builds, + * making it an appropriate replacement for assert() checks that must not be + * ignored. + */ +#define alassert(cond) do { \ + if(!(cond)) UNLIKELY \ + al::do_assert("Assertion '" #cond "' failed", __LINE__, __FILE__, std::data(__func__)); \ +} while(0) + +#endif /* AL_ASSERT_H */ diff --git a/Engine/lib/openal-soft/common/albit.h b/Engine/lib/openal-soft/common/albit.h index ad5962081..98544fa52 100644 --- a/Engine/lib/openal-soft/common/albit.h +++ b/Engine/lib/openal-soft/common/albit.h @@ -1,8 +1,13 @@ #ifndef AL_BIT_H #define AL_BIT_H +#include +#ifndef __GNUC__ #include +#endif +#include #include +#include #include #if !defined(__GNUC__) && (defined(_WIN32) || defined(_WIN64)) #include @@ -10,6 +15,16 @@ namespace al { +template +std::enable_if_t + && std::is_trivially_copyable_v, +To> bit_cast(const From &src) noexcept +{ + alignas(To) std::array dst; + std::memcpy(dst.data(), &src, sizeof(To)); + return *std::launder(reinterpret_cast(dst.data())); +} + #ifdef __BYTE_ORDER__ enum class endian { little = __ORDER_LITTLE_ENDIAN__, diff --git a/Engine/lib/openal-soft/common/albyte.h b/Engine/lib/openal-soft/common/albyte.h deleted file mode 100644 index be586869d..000000000 --- a/Engine/lib/openal-soft/common/albyte.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef AL_BYTE_H -#define AL_BYTE_H - -#include -#include -#include -#include - -using uint = unsigned int; - -namespace al { - -using byte = unsigned char; - -} // namespace al - -#endif /* AL_BYTE_H */ diff --git a/Engine/lib/openal-soft/common/alcomplex.cpp b/Engine/lib/openal-soft/common/alcomplex.cpp index 4420a1bbb..954c5fadb 100644 --- a/Engine/lib/openal-soft/common/alcomplex.cpp +++ b/Engine/lib/openal-soft/common/alcomplex.cpp @@ -4,10 +4,11 @@ #include "alcomplex.h" #include +#include #include -#include #include #include +#include #include #include "albit.h" @@ -20,36 +21,38 @@ namespace { using ushort = unsigned short; using ushort2 = std::pair; +using complex_d = std::complex; -constexpr size_t BitReverseCounter(size_t log2_size) noexcept +constexpr std::size_t BitReverseCounter(std::size_t log2_size) noexcept { /* Some magic math that calculates the number of swaps needed for a * sequence of bit-reversed indices when index < reversed_index. */ - return (1u<<(log2_size-1)) - (1u<<((log2_size-1u)/2u)); + return (1_zu<<(log2_size-1)) - (1_zu<<((log2_size-1_zu)/2_zu)); } -template +template struct BitReverser { static_assert(N <= sizeof(ushort)*8, "Too many bits for the bit-reversal table."); - ushort2 mData[BitReverseCounter(N)]{}; + std::array mData{}; constexpr BitReverser() { - const size_t fftsize{1u << N}; - size_t ret_i{0}; + const std::size_t fftsize{1u << N}; + std::size_t ret_i{0}; /* Bit-reversal permutation applied to a sequence of fftsize items. */ - for(size_t idx{1u};idx < fftsize-1;++idx) + for(std::size_t idx{1u};idx < fftsize-1;++idx) { - size_t revidx{0u}, imask{idx}; - for(size_t i{0};i < N;++i) - { - revidx = (revidx<<1) | (imask&1); - imask >>= 1; - } + std::size_t revidx{idx}; + revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1); + revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2); + revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4); + revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8); + revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16); + revidx >>= 32-N; if(idx < revidx) { @@ -58,14 +61,13 @@ struct BitReverser { ++ret_i; } } - assert(ret_i == al::size(mData)); + assert(ret_i == std::size(mData)); } }; -/* These bit-reversal swap tables support up to 10-bit indices (1024 elements), - * which is the largest used by OpenAL Soft's filters and effects. Larger FFT - * requests, used by some utilities where performance is less important, will - * use a slower table-less path. +/* These bit-reversal swap tables support up to 11-bit indices (2048 elements), + * which is large enough for the filters and effects in OpenAL Soft. Larger FFT + * requests will use a slower table-less path. */ constexpr BitReverser<2> BitReverser2{}; constexpr BitReverser<3> BitReverser3{}; @@ -76,7 +78,8 @@ constexpr BitReverser<7> BitReverser7{}; constexpr BitReverser<8> BitReverser8{}; constexpr BitReverser<9> BitReverser9{}; constexpr BitReverser<10> BitReverser10{}; -constexpr std::array,11> gBitReverses{{ +constexpr BitReverser<11> BitReverser11{}; +constexpr std::array,12> gBitReverses{{ {}, {}, BitReverser2.mData, BitReverser3.mData, @@ -86,75 +89,124 @@ constexpr std::array,11> gBitReverses{{ BitReverser7.mData, BitReverser8.mData, BitReverser9.mData, - BitReverser10.mData + BitReverser10.mData, + BitReverser11.mData +}}; + +/* Lookup table for std::polar(1, pi / (1< +constexpr std::array,gBitReverses.size()-1> gArgAngle{{ + {static_cast(-1.00000000000000000e+00), static_cast(0.00000000000000000e+00)}, + {static_cast( 0.00000000000000000e+00), static_cast(1.00000000000000000e+00)}, + {static_cast( 7.07106781186547524e-01), static_cast(7.07106781186547524e-01)}, + {static_cast( 9.23879532511286756e-01), static_cast(3.82683432365089772e-01)}, + {static_cast( 9.80785280403230449e-01), static_cast(1.95090322016128268e-01)}, + {static_cast( 9.95184726672196886e-01), static_cast(9.80171403295606020e-02)}, + {static_cast( 9.98795456205172393e-01), static_cast(4.90676743274180143e-02)}, + {static_cast( 9.99698818696204220e-01), static_cast(2.45412285229122880e-02)}, + {static_cast( 9.99924701839144541e-01), static_cast(1.22715382857199261e-02)}, + {static_cast( 9.99981175282601143e-01), static_cast(6.13588464915447536e-03)}, + {static_cast( 9.99995293809576172e-01), static_cast(3.06795676296597627e-03)} }}; } // namespace -template -std::enable_if_t::value> -complex_fft(const al::span> buffer, const al::type_identity_t sign) +void complex_fft(const al::span> buffer, const double sign) { - const size_t fftsize{buffer.size()}; + const std::size_t fftsize{buffer.size()}; /* Get the number of bits used for indexing. Simplifies bit-reversal and * the main loop count. */ - const size_t log2_size{static_cast(al::countr_zero(fftsize))}; + const std::size_t log2_size{static_cast(al::countr_zero(fftsize))}; - if(log2_size >= gBitReverses.size()) UNLIKELY + if(log2_size < gBitReverses.size()) LIKELY { - for(size_t idx{1u};idx < fftsize-1;++idx) + for(auto &rev : gBitReverses[log2_size]) + std::swap(buffer[rev.first], buffer[rev.second]); + + /* Iterative form of Danielson-Lanczos lemma */ + for(std::size_t i{0};i < log2_size;++i) { - size_t revidx{0u}, imask{idx}; - for(size_t i{0};i < log2_size;++i) + const std::size_t step2{1_uz << i}; + const std::size_t step{2_uz << i}; + /* The first iteration of the inner loop would have u=1, which we + * can simplify to remove a number of complex multiplies. + */ + for(std::size_t k{0};k < fftsize;k+=step) { - revidx = (revidx<<1) | (imask&1); - imask >>= 1; - } - - if(idx < revidx) - std::swap(buffer[idx], buffer[revidx]); - } - } - else for(auto &rev : gBitReverses[log2_size]) - std::swap(buffer[rev.first], buffer[rev.second]); - - /* Iterative form of Danielson-Lanczos lemma */ - const Real pi{al::numbers::pi_v * sign}; - size_t step2{1u}; - for(size_t i{0};i < log2_size;++i) - { - const Real arg{pi / static_cast(step2)}; - - /* TODO: Would std::polar(1.0, arg) be any better? */ - const std::complex w{std::cos(arg), std::sin(arg)}; - std::complex u{1.0, 0.0}; - const size_t step{step2 << 1}; - for(size_t j{0};j < step2;j++) - { - for(size_t k{j};k < fftsize;k+=step) - { - std::complex temp{buffer[k+step2] * u}; + const complex_d temp{buffer[k+step2]}; buffer[k+step2] = buffer[k] - temp; buffer[k] += temp; } - u *= w; + const complex_d w{gArgAngle[i].real(), gArgAngle[i].imag()*sign}; + complex_d u{w}; + for(std::size_t j{1};j < step2;j++) + { + for(std::size_t k{j};k < fftsize;k+=step) + { + const complex_d temp{buffer[k+step2] * u}; + buffer[k+step2] = buffer[k] - temp; + buffer[k] += temp; + } + u *= w; + } + } + } + else + { + assert(log2_size < 32); + + for(std::size_t idx{1u};idx < fftsize-1;++idx) + { + std::size_t revidx{idx}; + revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1); + revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2); + revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4); + revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8); + revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16); + revidx >>= 32-log2_size; + + if(idx < revidx) + std::swap(buffer[idx], buffer[revidx]); } - step2 <<= 1; + const double pi{al::numbers::pi * sign}; + for(std::size_t i{0};i < log2_size;++i) + { + const std::size_t step2{1_uz << i}; + const std::size_t step{2_uz << i}; + for(std::size_t k{0};k < fftsize;k+=step) + { + const complex_d temp{buffer[k+step2]}; + buffer[k+step2] = buffer[k] - temp; + buffer[k] += temp; + } + + const double arg{pi / static_cast(step2)}; + const complex_d w{std::polar(1.0, arg)}; + complex_d u{w}; + for(std::size_t j{1};j < step2;j++) + { + for(std::size_t k{j};k < fftsize;k+=step) + { + const complex_d temp{buffer[k+step2] * u}; + buffer[k+step2] = buffer[k] - temp; + buffer[k] += temp; + } + u *= w; + } + } } } void complex_hilbert(const al::span> buffer) { - using namespace std::placeholders; - inverse_fft(buffer); const double inverse_size = 1.0/static_cast(buffer.size()); auto bufiter = buffer.begin(); - const auto halfiter = bufiter + (buffer.size()>>1); + const auto halfiter = bufiter + ptrdiff_t(buffer.size()>>1); *bufiter *= inverse_size; ++bufiter; bufiter = std::transform(bufiter, halfiter, bufiter, @@ -165,7 +217,3 @@ void complex_hilbert(const al::span> buffer) forward_fft(buffer); } - - -template void complex_fft<>(const al::span> buffer, const float sign); -template void complex_fft<>(const al::span> buffer, const double sign); diff --git a/Engine/lib/openal-soft/common/alcomplex.h b/Engine/lib/openal-soft/common/alcomplex.h index 794c35268..9f12cef32 100644 --- a/Engine/lib/openal-soft/common/alcomplex.h +++ b/Engine/lib/openal-soft/common/alcomplex.h @@ -2,7 +2,6 @@ #define ALCOMPLEX_H #include -#include #include "alspan.h" @@ -11,27 +10,21 @@ * FFT and 1 is inverse FFT. Applies the Discrete Fourier Transform (DFT) to * the data supplied in the buffer, which MUST BE power of two. */ -template -std::enable_if_t::value> -complex_fft(const al::span> buffer, const al::type_identity_t sign); +void complex_fft(const al::span> buffer, const double sign); /** * Calculate the frequency-domain response of the time-domain signal in the * provided buffer, which MUST BE power of two. */ -template -std::enable_if_t::value> -forward_fft(const al::span,N> buffer) -{ complex_fft(buffer.subspan(0), -1); } +inline void forward_fft(const al::span> buffer) +{ complex_fft(buffer, -1.0); } /** * Calculate the time-domain signal of the frequency-domain response in the * provided buffer, which MUST BE power of two. */ -template -std::enable_if_t::value> -inverse_fft(const al::span,N> buffer) -{ complex_fft(buffer.subspan(0), 1); } +inline void inverse_fft(const al::span> buffer) +{ complex_fft(buffer, +1.0); } /** * Calculate the complex helical sequence (discrete-time analytical signal) of diff --git a/Engine/lib/openal-soft/common/aldeque.h b/Engine/lib/openal-soft/common/aldeque.h deleted file mode 100644 index 3f99bf007..000000000 --- a/Engine/lib/openal-soft/common/aldeque.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef ALDEQUE_H -#define ALDEQUE_H - -#include - -#include "almalloc.h" - - -namespace al { - -template -using deque = std::deque>; - -} // namespace al - -#endif /* ALDEQUE_H */ diff --git a/Engine/lib/openal-soft/common/alfstream.cpp b/Engine/lib/openal-soft/common/alfstream.cpp deleted file mode 100644 index 8991ce035..000000000 --- a/Engine/lib/openal-soft/common/alfstream.cpp +++ /dev/null @@ -1,26 +0,0 @@ - -#include "config.h" - -#include "alfstream.h" - -#include "strutils.h" - -#ifdef _WIN32 - -namespace al { - -ifstream::ifstream(const char *filename, std::ios_base::openmode mode) - : std::ifstream{utf8_to_wstr(filename).c_str(), mode} -{ } - -void ifstream::open(const char *filename, std::ios_base::openmode mode) -{ - std::wstring wstr{utf8_to_wstr(filename)}; - std::ifstream::open(wstr.c_str(), mode); -} - -ifstream::~ifstream() = default; - -} // namespace al - -#endif diff --git a/Engine/lib/openal-soft/common/alfstream.h b/Engine/lib/openal-soft/common/alfstream.h deleted file mode 100644 index 62b2e12c7..000000000 --- a/Engine/lib/openal-soft/common/alfstream.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef AL_FSTREAM_H -#define AL_FSTREAM_H - -#ifdef _WIN32 - -#include -#include - - -namespace al { - -// Inherit from std::ifstream to accept UTF-8 filenames -class ifstream final : public std::ifstream { -public: - explicit ifstream(const char *filename, std::ios_base::openmode mode=std::ios_base::in); - explicit ifstream(const std::string &filename, std::ios_base::openmode mode=std::ios_base::in) - : ifstream{filename.c_str(), mode} { } - - explicit ifstream(const wchar_t *filename, std::ios_base::openmode mode=std::ios_base::in) - : std::ifstream{filename, mode} { } - explicit ifstream(const std::wstring &filename, std::ios_base::openmode mode=std::ios_base::in) - : ifstream{filename.c_str(), mode} { } - - void open(const char *filename, std::ios_base::openmode mode=std::ios_base::in); - void open(const std::string &filename, std::ios_base::openmode mode=std::ios_base::in) - { open(filename.c_str(), mode); } - - ~ifstream() override; -}; - -} // namespace al - -#else /* _WIN32 */ - -#include - -namespace al { - -using ifstream = std::ifstream; - -} // namespace al - -#endif /* _WIN32 */ - -#endif /* AL_FSTREAM_H */ diff --git a/Engine/lib/openal-soft/common/almalloc.cpp b/Engine/lib/openal-soft/common/almalloc.cpp deleted file mode 100644 index ad1dc6be0..000000000 --- a/Engine/lib/openal-soft/common/almalloc.cpp +++ /dev/null @@ -1,61 +0,0 @@ - -#include "config.h" - -#include "almalloc.h" - -#include -#include -#include -#include -#include -#ifdef HAVE_MALLOC_H -#include -#endif - - -void *al_malloc(size_t alignment, size_t size) -{ - assert((alignment & (alignment-1)) == 0); - alignment = std::max(alignment, alignof(std::max_align_t)); - -#if 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 - size_t total_size{size + alignment-1 + sizeof(void*)}; - void *base{std::malloc(total_size)}; - if(base != nullptr) - { - void *aligned_ptr{static_cast(base) + sizeof(void*)}; - total_size -= sizeof(void*); - - std::align(alignment, size, aligned_ptr, total_size); - *(static_cast(aligned_ptr)-1) = base; - base = aligned_ptr; - } - return base; -#endif -} - -void *al_calloc(size_t alignment, size_t size) -{ - void *ret{al_malloc(alignment, size)}; - if(ret) std::memset(ret, 0, size); - return ret; -} - -void al_free(void *ptr) noexcept -{ -#if defined(HAVE_POSIX_MEMALIGN) - std::free(ptr); -#elif defined(HAVE__ALIGNED_MALLOC) - _aligned_free(ptr); -#else - if(ptr != nullptr) - std::free(*(static_cast(ptr) - 1)); -#endif -} diff --git a/Engine/lib/openal-soft/common/almalloc.h b/Engine/lib/openal-soft/common/almalloc.h index a795fc3b7..e3a7bb521 100644 --- a/Engine/lib/openal-soft/common/almalloc.h +++ b/Engine/lib/openal-soft/common/almalloc.h @@ -3,49 +3,24 @@ #include #include -#include #include -#include #include #include #include - -#include "pragmadefs.h" +#include -void al_free(void *ptr) noexcept; -[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]] -void *al_malloc(size_t alignment, size_t size); -[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]] -void *al_calloc(size_t alignment, size_t size); +namespace gsl { +template using owner = T; +} -#define DISABLE_ALLOC() \ +#define DISABLE_ALLOC \ void *operator new(size_t) = delete; \ void *operator new[](size_t) = delete; \ void operator delete(void*) noexcept = delete; \ void operator delete[](void*) noexcept = delete; -#define DEF_NEWDEL(T) \ - void *operator new(size_t size) \ - { \ - static_assert(&operator new == &T::operator new, \ - "Incorrect container type specified"); \ - if(void *ret{al_malloc(alignof(T), size)}) \ - return ret; \ - throw std::bad_alloc(); \ - } \ - void *operator new[](size_t size) { return operator new(size); } \ - void operator delete(void *block) noexcept { al_free(block); } \ - void operator delete[](void *block) noexcept { operator delete(block); } - -#define DEF_PLACE_NEWDEL() \ - void *operator new(size_t /*size*/, void *ptr) noexcept { return ptr; } \ - 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); } \ - void operator delete[](void *block, void*) noexcept { al_free(block); } \ - void operator delete[](void *block) noexcept { al_free(block); } enum FamCount : size_t { }; @@ -58,56 +33,64 @@ enum FamCount : size_t { }; sizeof(T)); \ } \ \ - void *operator new(size_t /*size*/, FamCount count) \ + gsl::owner operator new(size_t /*size*/, FamCount count) \ { \ - if(void *ret{al_malloc(alignof(T), T::Sizeof(count))}) \ - return ret; \ - throw std::bad_alloc(); \ + const auto alignment = std::align_val_t{alignof(T)}; \ + return ::operator new[](T::Sizeof(count), alignment); \ } \ + void operator delete(gsl::owner block, FamCount) noexcept \ + { ::operator delete[](block, std::align_val_t{alignof(T)}); } \ + void operator delete(gsl::owner block) noexcept \ + { ::operator delete[](block, std::align_val_t{alignof(T)}); } \ void *operator new[](size_t /*size*/) = delete; \ - void operator delete(void *block, FamCount) { al_free(block); } \ - void operator delete(void *block) noexcept { al_free(block); } \ void operator delete[](void* /*block*/) = delete; namespace al { -template +template struct allocator { - static constexpr std::size_t alignment{std::max(Align, alignof(T))}; + static constexpr auto Alignment = std::max(AlignV, alignof(T)); + static constexpr auto AlignVal = std::align_val_t{Alignment}; - using value_type = T; - using reference = T&; - using const_reference = const T&; - using pointer = T*; - using const_pointer = const T*; + using value_type = std::remove_cv_t>; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using is_always_equal = std::true_type; - template + template = true> struct rebind { - using other = allocator; + using other = allocator; }; constexpr explicit allocator() noexcept = default; template - constexpr explicit allocator(const allocator&) noexcept { } + constexpr explicit allocator(const allocator&) noexcept + { static_assert(Alignment == allocator::Alignment); } - T *allocate(std::size_t n) + gsl::owner allocate(std::size_t n) { if(n > std::numeric_limits::max()/sizeof(T)) throw std::bad_alloc(); - if(auto p = al_malloc(alignment, n*sizeof(T))) return static_cast(p); - throw std::bad_alloc(); + return static_cast>(::operator new[](n*sizeof(T), AlignVal)); } - void deallocate(T *p, std::size_t) noexcept { al_free(p); } + void deallocate(gsl::owner p, std::size_t) noexcept + { ::operator delete[](gsl::owner{p}, AlignVal); } }; template -constexpr bool operator==(const allocator&, const allocator&) noexcept { return true; } +constexpr bool operator==(const allocator&, const allocator&) noexcept +{ return allocator::Alignment == allocator::Alignment; } template -constexpr bool operator!=(const allocator&, const allocator&) noexcept { return false; } +constexpr bool operator!=(const allocator&, const allocator&) noexcept +{ return allocator::Alignment != allocator::Alignment; } +#ifdef __cpp_lib_to_address +using std::to_address; +#else template constexpr T *to_address(T *p) noexcept { @@ -117,194 +100,55 @@ constexpr T *to_address(T *p) noexcept template constexpr auto to_address(const T &p) noexcept -{ return to_address(p.operator->()); } - +{ + return ::al::to_address(p.operator->()); +} +#endif template constexpr T* construct_at(T *ptr, Args&& ...args) - noexcept(std::is_nothrow_constructible::value) -{ return ::new(static_cast(ptr)) T{std::forward(args)...}; } - -/* 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. - */ -DIAGNOSTIC_PUSH -msc_pragma(warning(disable : 4100)) -template -constexpr std::enable_if_t::value> -destroy_at(T *ptr) noexcept(std::is_nothrow_destructible::value) -{ ptr->~T(); } -DIAGNOSTIC_POP -template -constexpr std::enable_if_t::value> -destroy_at(T *ptr) noexcept(std::is_nothrow_destructible>::value) + noexcept(std::is_nothrow_constructible_v) { - for(auto &elem : *ptr) - al::destroy_at(std::addressof(elem)); -} - -template -constexpr void destroy(T first, T end) noexcept(noexcept(al::destroy_at(std::addressof(*first)))) -{ - while(first != end) - { - al::destroy_at(std::addressof(*first)); - ++first; - } -} - -template -constexpr std::enable_if_t::value,T> -destroy_n(T first, N count) noexcept(noexcept(al::destroy_at(std::addressof(*first)))) -{ - if(count != 0) - { - do { - al::destroy_at(std::addressof(*first)); - ++first; - } while(--count); - } - return first; + /* NOLINTBEGIN(cppcoreguidelines-owning-memory) construct_at doesn't + * necessarily handle the address from an owner, while placement new + * expects to. + */ + return ::new(static_cast(ptr)) T{std::forward(args)...}; + /* NOLINTEND(cppcoreguidelines-owning-memory) */ } -template -inline std::enable_if_t::value, -T> uninitialized_default_construct_n(T first, N count) -{ - using ValueT = typename std::iterator_traits::value_type; - T current{first}; - if(count != 0) +template +class out_ptr_t { + static_assert(!std::is_same_v); + + SP &mRes; + std::variant mPtr{}; + +public: + out_ptr_t(SP &res) : mRes{res} { } + ~out_ptr_t() { - try { - do { - ::new(static_cast(std::addressof(*current))) ValueT; - ++current; - } while(--count); - } - catch(...) { - al::destroy(first, current); - throw; - } + auto set_res = [this](auto &ptr) + { mRes.reset(static_cast(ptr)); }; + std::visit(set_res, mPtr); } - return current; -} + out_ptr_t(const out_ptr_t&) = delete; + out_ptr_t& operator=(const out_ptr_t&) = delete; + operator PT*() noexcept + { return &std::get(mPtr); } -/* Storage for flexible array data. This is trivially destructible if type T is - * trivially destructible. - */ -template::value> -struct FlexArrayStorage { - const size_t mSize; - union { - char mDummy; - alignas(alignment) T mArray[1]; - }; - - static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept - { - const size_t len{sizeof(T)*count}; - return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base; - } - - FlexArrayStorage(size_t size) : mSize{size} - { al::uninitialized_default_construct_n(mArray, mSize); } - ~FlexArrayStorage() = default; - - FlexArrayStorage(const FlexArrayStorage&) = delete; - FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; + operator void**() noexcept + { return &mPtr.template emplace(); } }; -template -struct FlexArrayStorage { - const size_t mSize; - union { - char mDummy; - alignas(alignment) T mArray[1]; - }; - - static constexpr size_t Sizeof(size_t count, size_t base) noexcept - { - const size_t len{sizeof(T)*count}; - return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base; - } - - FlexArrayStorage(size_t size) : mSize{size} - { al::uninitialized_default_construct_n(mArray, mSize); } - ~FlexArrayStorage() { al::destroy_n(mArray, mSize); } - - FlexArrayStorage(const FlexArrayStorage&) = delete; - FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; -}; - -/* 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 -struct FlexArray { - using element_type = T; - using value_type = std::remove_cv_t; - 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; - using const_reverse_iterator = std::reverse_iterator; - - using Storage_t_ = FlexArrayStorage; - - Storage_t_ mStore; - - static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept - { return Storage_t_::Sizeof(count, base); } - static std::unique_ptr Create(index_type count) - { - void *ptr{al_calloc(alignof(FlexArray), Sizeof(count))}; - return std::unique_ptr{al::construct_at(static_cast(ptr), count)}; - } - - FlexArray(index_type size) : mStore{size} { } - ~FlexArray() = default; - - index_type size() const noexcept { return mStore.mSize; } - bool empty() const noexcept { return mStore.mSize == 0; } - - pointer data() noexcept { return mStore.mArray; } - const_pointer data() const noexcept { return mStore.mArray; } - - reference operator[](index_type i) noexcept { return mStore.mArray[i]; } - const_reference operator[](index_type i) const noexcept { return mStore.mArray[i]; } - - reference front() noexcept { return mStore.mArray[0]; } - const_reference front() const noexcept { return mStore.mArray[0]; } - - reference back() noexcept { return mStore.mArray[mStore.mSize-1]; } - const_reference back() const noexcept { return mStore.mArray[mStore.mSize-1]; } - - iterator begin() noexcept { return mStore.mArray; } - const_iterator begin() const noexcept { return mStore.mArray; } - const_iterator cbegin() const noexcept { return mStore.mArray; } - iterator end() noexcept { return mStore.mArray + mStore.mSize; } - const_iterator end() const noexcept { return mStore.mArray + mStore.mSize; } - const_iterator cend() const noexcept { return mStore.mArray + mStore.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() -}; +template +auto out_ptr(SP &res) +{ + using ptype = typename SP::element_type*; + return out_ptr_t{res}; +} } // namespace al diff --git a/Engine/lib/openal-soft/common/alnumbers.h b/Engine/lib/openal-soft/common/alnumbers.h index 37a554100..6d201ad4f 100644 --- a/Engine/lib/openal-soft/common/alnumbers.h +++ b/Engine/lib/openal-soft/common/alnumbers.h @@ -1,11 +1,9 @@ #ifndef COMMON_ALNUMBERS_H #define COMMON_ALNUMBERS_H -#include +#include -namespace al { - -namespace numbers { +namespace al::numbers { namespace detail_ { template @@ -13,24 +11,22 @@ namespace detail_ { } // detail_ template -static constexpr auto pi_v = detail_::as_fp(3.141592653589793238462643383279502884L); +inline constexpr auto pi_v = detail_::as_fp(3.141592653589793238462643383279502884L); template -static constexpr auto inv_pi_v = detail_::as_fp(0.318309886183790671537767526745028724L); +inline constexpr auto inv_pi_v = detail_::as_fp(0.318309886183790671537767526745028724L); template -static constexpr auto sqrt2_v = detail_::as_fp(1.414213562373095048801688724209698079L); +inline constexpr auto sqrt2_v = detail_::as_fp(1.414213562373095048801688724209698079L); template -static constexpr auto sqrt3_v = detail_::as_fp(1.732050807568877293527446341505872367L); +inline constexpr auto sqrt3_v = detail_::as_fp(1.732050807568877293527446341505872367L); -static constexpr auto pi = pi_v; -static constexpr auto inv_pi = inv_pi_v; -static constexpr auto sqrt2 = sqrt2_v; -static constexpr auto sqrt3 = sqrt3_v; +inline constexpr auto pi = pi_v; +inline constexpr auto inv_pi = inv_pi_v; +inline constexpr auto sqrt2 = sqrt2_v; +inline constexpr auto sqrt3 = sqrt3_v; -} // namespace numbers - -} // namespace al +} // namespace al::numbers #endif /* COMMON_ALNUMBERS_H */ diff --git a/Engine/lib/openal-soft/common/alnumeric.h b/Engine/lib/openal-soft/common/alnumeric.h index d6919e401..0d091166b 100644 --- a/Engine/lib/openal-soft/common/alnumeric.h +++ b/Engine/lib/openal-soft/common/alnumeric.h @@ -2,9 +2,12 @@ #define AL_NUMERIC_H #include +#include #include #include #include +#include +#include #ifdef HAVE_INTRIN_H #include #endif @@ -12,75 +15,34 @@ #include #endif +#include "albit.h" #include "altraits.h" #include "opthelpers.h" -inline constexpr int64_t operator "" _i64(unsigned long long int n) noexcept { return static_cast(n); } -inline constexpr uint64_t operator "" _u64(unsigned long long int n) noexcept { return static_cast(n); } +constexpr auto operator "" _i64(unsigned long long n) noexcept { return static_cast(n); } +constexpr auto operator "" _u64(unsigned long long n) noexcept { return static_cast(n); } + +constexpr auto operator "" _z(unsigned long long n) noexcept +{ return static_cast>(n); } +constexpr auto operator "" _uz(unsigned long long n) noexcept { return static_cast(n); } +constexpr auto operator "" _zu(unsigned long long n) noexcept { return static_cast(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)); } +constexpr auto GetCounterSuffix(size_t count) noexcept -> const char* +{ + auto &suffix = (((count%100)/10) == 1) ? "th" : + ((count%10) == 1) ? "st" : + ((count%10) == 2) ? "nd" : + ((count%10) == 3) ? "rd" : "th"; + return std::data(suffix); +} constexpr inline float lerpf(float val1, float val2, float mu) noexcept { return val1 + (val2-val1)*mu; } -constexpr inline float cubic(float val1, float val2, float val3, float val4, float mu) noexcept -{ - const float mu2{mu*mu}, mu3{mu2*mu}; - const float a0{-0.5f*mu3 + mu2 + -0.5f*mu}; - const float a1{ 1.5f*mu3 + -2.5f*mu2 + 1.0f}; - const float a2{-1.5f*mu3 + 2.0f*mu2 + 0.5f*mu}; - const float a3{ 0.5f*mu3 + -0.5f*mu2}; - return val1*a0 + val2*a1 + val3*a2 + val4*a3; -} +constexpr inline double lerpd(double val1, double val2, double mu) noexcept +{ return val1 + (val2-val1)*mu; } /** Find the next power-of-2 for non-power-of-2 numbers. */ @@ -125,21 +87,18 @@ 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) +#elif defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0 int i; __asm fld f __asm fistp i return i; -#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) +#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ + && !defined(__SSE_MATH__) 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 @@ -159,21 +118,16 @@ inline int float2int(float f) noexcept #elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0) \ || ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ && !defined(__SSE_MATH__)) - int sign, shift, mant; - union { - float f; - int i; - } conv; + const int conv_i{al::bit_cast(f)}; - conv.f = f; - sign = (conv.i>>31) | 1; - shift = ((conv.i>>23)&0xff) - (127+23); + const int sign{(conv_i>>31) | 1}; + const int shift{((conv_i>>23)&0xff) - (127+23)}; /* Over/underflow */ if(shift >= 31 || shift < -23) UNLIKELY return 0; - mant = (conv.i&0x7fffff) | 0x800000; + const int mant{(conv_i&0x7fffff) | 0x800000}; if(shift < 0) LIKELY return (mant >> -shift) * sign; return (mant << shift) * sign; @@ -195,25 +149,19 @@ inline int double2int(double d) noexcept #elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) \ || ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ && !defined(__SSE2_MATH__)) - int sign, shift; - int64_t mant; - union { - double d; - int64_t i64; - } conv; + const int64_t conv_i64{al::bit_cast(d)}; - conv.d = d; - sign = (conv.i64 >> 63) | 1; - shift = ((conv.i64 >> 52) & 0x7ff) - (1023 + 52); + const int sign{static_cast(conv_i64 >> 63) | 1}; + const int shift{(static_cast(conv_i64 >> 52) & 0x7ff) - (1023 + 52)}; /* Over/underflow */ if(shift >= 63 || shift < -52) UNLIKELY return 0; - mant = (conv.i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64; + const int64_t mant{(conv_i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64}; if(shift < 0) LIKELY - return (int)(mant >> -shift) * sign; - return (int)(mant << shift) * sign; + return static_cast(mant >> -shift) * sign; + return static_cast(mant << shift) * sign; #else @@ -246,19 +194,14 @@ inline float fast_roundf(float f) noexcept /* Integral limit, where sub-integral precision is not available for * floats. */ - static const float ilim[2]{ + static constexpr std::array ilim{ 8388608.0f /* 0x1.0p+23 */, -8388608.0f /* -0x1.0p+23 */ }; - unsigned int sign, expo; - union { - float f; - unsigned int i; - } conv; + const unsigned int conv_i{al::bit_cast(f)}; - conv.f = f; - sign = (conv.i>>31)&0x01; - expo = (conv.i>>23)&0xff; + const unsigned int sign{(conv_i>>31)&0x01}; + const unsigned int expo{(conv_i>>23)&0xff}; if(expo >= 150/*+23*/) UNLIKELY { @@ -275,20 +218,18 @@ inline float fast_roundf(float f) noexcept * 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). + * may break without __builtin_assoc_barrier support). */ +#if HAS_BUILTIN(__builtin_assoc_barrier) + return __builtin_assoc_barrier(f + ilim[sign]) - ilim[sign]; +#else f += ilim[sign]; return f - ilim[sign]; #endif +#endif } -template -constexpr const T& clamp(const T& value, const T& min_value, const T& max_value) noexcept -{ - return std::min(std::max(value, min_value), max_value); -} - // Converts level (mB) to gain. inline float level_mb_to_gain(float x) { @@ -302,7 +243,7 @@ inline float gain_to_level_mb(float x) { if (x <= 0.0f) return -10'000.0f; - return maxf(std::log10(x) * 2'000.0f, -10'000.0f); + return std::max(std::log10(x) * 2'000.0f, -10'000.0f); } #endif /* AL_NUMERIC_H */ diff --git a/Engine/lib/openal-soft/common/aloptional.h b/Engine/lib/openal-soft/common/aloptional.h deleted file mode 100644 index 6de16799e..000000000 --- a/Engine/lib/openal-soft/common/aloptional.h +++ /dev/null @@ -1,353 +0,0 @@ -#ifndef AL_OPTIONAL_H -#define AL_OPTIONAL_H - -#include -#include -#include - -#include "almalloc.h" - -namespace al { - -struct nullopt_t { }; -struct in_place_t { }; - -constexpr nullopt_t nullopt{}; -constexpr in_place_t in_place{}; - -#define NOEXCEPT_AS(...) noexcept(noexcept(__VA_ARGS__)) - -namespace detail_ { -/* Base storage struct for an optional. Defines a trivial destructor, for types - * that can be trivially destructed. - */ -template::value> -struct optstore_base { - bool mHasValue{false}; - union { - char mDummy{}; - T mValue; - }; - - constexpr optstore_base() noexcept { } - template - constexpr explicit optstore_base(in_place_t, Args&& ...args) - noexcept(std::is_nothrow_constructible::value) - : mHasValue{true}, mValue{std::forward(args)...} - { } - ~optstore_base() = default; -}; - -/* Specialization needing a non-trivial destructor. */ -template -struct optstore_base { - bool mHasValue{false}; - union { - char mDummy{}; - T mValue; - }; - - constexpr optstore_base() noexcept { } - template - constexpr explicit optstore_base(in_place_t, Args&& ...args) - noexcept(std::is_nothrow_constructible::value) - : mHasValue{true}, mValue{std::forward(args)...} - { } - ~optstore_base() { if(mHasValue) al::destroy_at(std::addressof(mValue)); } -}; - -/* Next level of storage, which defines helpers to construct and destruct the - * stored object. - */ -template -struct optstore_helper : public optstore_base { - using optstore_base::optstore_base; - - template - constexpr void construct(Args&& ...args) noexcept(std::is_nothrow_constructible::value) - { - al::construct_at(std::addressof(this->mValue), std::forward(args)...); - this->mHasValue = true; - } - - constexpr void reset() noexcept - { - if(this->mHasValue) - al::destroy_at(std::addressof(this->mValue)); - this->mHasValue = false; - } - - constexpr void assign(const optstore_helper &rhs) - noexcept(std::is_nothrow_copy_constructible::value - && std::is_nothrow_copy_assignable::value) - { - if(!rhs.mHasValue) - this->reset(); - else if(this->mHasValue) - this->mValue = rhs.mValue; - else - this->construct(rhs.mValue); - } - - constexpr void assign(optstore_helper&& rhs) - noexcept(std::is_nothrow_move_constructible::value - && std::is_nothrow_move_assignable::value) - { - if(!rhs.mHasValue) - this->reset(); - else if(this->mHasValue) - this->mValue = std::move(rhs.mValue); - else - this->construct(std::move(rhs.mValue)); - } -}; - -/* Define copy and move constructors and assignment operators, which may or may - * not be trivial. - */ -template::value, - bool trivial_move = std::is_trivially_move_constructible::value, - /* Trivial assignment is dependent on trivial construction+destruction. */ - bool = trivial_copy && std::is_trivially_copy_assignable::value - && std::is_trivially_destructible::value, - bool = trivial_move && std::is_trivially_move_assignable::value - && std::is_trivially_destructible::value> -struct optional_storage; - -/* Some versions of GCC have issues with 'this' in the following noexcept(...) - * statements, so this macro is a workaround. - */ -#define _this std::declval() - -/* Completely trivial. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage&) = default; - constexpr optional_storage& operator=(optional_storage&&) = default; -}; - -/* Non-trivial move assignment. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage&) = default; - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -/* Non-trivial move construction. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) - { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } - constexpr optional_storage& operator=(const optional_storage&) = default; - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -/* Non-trivial copy assignment. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&&) = default; -}; - -/* Non-trivial copy construction. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) - { if(rhs.mHasValue) this->construct(rhs.mValue); } - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&&) = default; -}; - -/* Non-trivial assignment. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -/* Non-trivial assignment, non-trivial move construction. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) - { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -/* Non-trivial assignment, non-trivial copy construction. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) - { if(rhs.mHasValue) this->construct(rhs.mValue); } - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -/* Completely non-trivial. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) - { if(rhs.mHasValue) this->construct(rhs.mValue); } - constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) - { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -#undef _this - -} // namespace detail_ - -#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true - -template -class optional { - using storage_t = detail_::optional_storage; - - storage_t mStore{}; - -public: - using value_type = T; - - constexpr optional() = default; - constexpr optional(const optional&) = default; - constexpr optional(optional&&) = default; - constexpr optional(nullopt_t) noexcept { } - template - constexpr explicit optional(in_place_t, Args&& ...args) - NOEXCEPT_AS(storage_t{al::in_place, std::forward(args)...}) - : mStore{al::in_place, std::forward(args)...} - { } - template::value - && !std::is_same, al::in_place_t>::value - && !std::is_same, optional>::value - && std::is_convertible::value)> - constexpr optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward(rhs)}) - : mStore{al::in_place, std::forward(rhs)} - { } - template::value - && !std::is_same, al::in_place_t>::value - && !std::is_same, optional>::value - && !std::is_convertible::value)> - constexpr explicit optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward(rhs)}) - : mStore{al::in_place, std::forward(rhs)} - { } - ~optional() = default; - - constexpr optional& operator=(const optional&) = default; - constexpr optional& operator=(optional&&) = default; - constexpr optional& operator=(nullopt_t) noexcept { mStore.reset(); return *this; } - template - constexpr std::enable_if_t::value - && std::is_assignable::value - && !std::is_same, optional>::value - && (!std::is_same, T>::value || !std::is_scalar::value), - optional&> operator=(U&& rhs) - { - if(mStore.mHasValue) - mStore.mValue = std::forward(rhs); - else - mStore.construct(std::forward(rhs)); - return *this; - } - - constexpr const T* operator->() const { return std::addressof(mStore.mValue); } - constexpr T* operator->() { return std::addressof(mStore.mValue); } - constexpr const T& operator*() const& { return mStore.mValue; } - constexpr T& operator*() & { return mStore.mValue; } - constexpr const T&& operator*() const&& { return std::move(mStore.mValue); } - constexpr T&& operator*() && { return std::move(mStore.mValue); } - - constexpr explicit operator bool() const noexcept { return mStore.mHasValue; } - constexpr bool has_value() const noexcept { return mStore.mHasValue; } - - constexpr T& value() & { return mStore.mValue; } - constexpr const T& value() const& { return mStore.mValue; } - constexpr T&& value() && { return std::move(mStore.mValue); } - constexpr const T&& value() const&& { return std::move(mStore.mValue); } - - template - constexpr T value_or(U&& defval) const& - { return bool(*this) ? **this : static_cast(std::forward(defval)); } - template - constexpr T value_or(U&& defval) && - { return bool(*this) ? std::move(**this) : static_cast(std::forward(defval)); } - - template - constexpr T& emplace(Args&& ...args) - { - mStore.reset(); - mStore.construct(std::forward(args)...); - return mStore.mValue; - } - template - constexpr std::enable_if_t&, Args&&...>::value, - T&> emplace(std::initializer_list il, Args&& ...args) - { - mStore.reset(); - mStore.construct(il, std::forward(args)...); - return mStore.mValue; - } - - constexpr void reset() noexcept { mStore.reset(); } -}; - -template -constexpr optional> make_optional(T&& arg) -{ return optional>{in_place, std::forward(arg)}; } - -template -constexpr optional make_optional(Args&& ...args) -{ return optional{in_place, std::forward(args)...}; } - -template -constexpr optional make_optional(std::initializer_list il, Args&& ...args) -{ return optional{in_place, il, std::forward(args)...}; } - -#undef REQUIRES -#undef NOEXCEPT_AS -} // namespace al - -#endif /* AL_OPTIONAL_H */ diff --git a/Engine/lib/openal-soft/common/threads.cpp b/Engine/lib/openal-soft/common/alsem.cpp similarity index 58% rename from Engine/lib/openal-soft/common/threads.cpp rename to Engine/lib/openal-soft/common/alsem.cpp index 19a6bbf04..2eeed5929 100644 --- a/Engine/lib/openal-soft/common/threads.cpp +++ b/Engine/lib/openal-soft/common/alsem.cpp @@ -20,8 +20,7 @@ #include "config.h" -#include "opthelpers.h" -#include "threads.h" +#include "alsem.h" #include @@ -32,44 +31,14 @@ #include -void althrd_setname(const char *name) -{ -#if defined(_MSC_VER) && !defined(_M_ARM) - -#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(std::numeric_limits::max())) throw std::system_error(std::make_error_code(std::errc::value_too_large)); - mSem = CreateSemaphore(nullptr, initial, std::numeric_limits::max(), nullptr); + mSem = CreateSemaphoreW(nullptr, static_cast(initial), std::numeric_limits::max(), + nullptr); if(mSem == nullptr) throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again)); } @@ -93,49 +62,8 @@ bool semaphore::try_wait() noexcept #else -#include -#ifdef HAVE_PTHREAD_NP_H -#include -#endif -#include - -namespace { - -using setname_t1 = int(*)(const char*); -using setname_t2 = int(*)(pthread_t, const char*); -using setname_t3 = void(*)(pthread_t, const char*); -using setname_t4 = int(*)(pthread_t, const char*, void*); - -void setname_caller(setname_t1 func, const char *name) -{ func(name); } - -void setname_caller(setname_t2 func, const char *name) -{ func(pthread_self(), name); } - -void setname_caller(setname_t3 func, const char *name) -{ func(pthread_self(), name); } - -void setname_caller(setname_t4 func, const char *name) -{ func(pthread_self(), "%s", static_cast(const_cast(name))); } - -} // namespace - -void althrd_setname(const char *name) -{ -#if defined(HAVE_PTHREAD_SET_NAME_NP) - setname_caller(pthread_set_name_np, name); -#elif defined(HAVE_PTHREAD_SETNAME_NP) - setname_caller(pthread_setname_np, name); -#endif - /* Avoid unused function/parameter warnings. */ - std::ignore = name; - std::ignore = static_cast(&setname_caller); - std::ignore = static_cast(&setname_caller); - std::ignore = static_cast(&setname_caller); - std::ignore = static_cast(&setname_caller); -} - -#ifdef __APPLE__ +/* Do not try using libdispatch on systems where it is absent. */ +#if defined(AL_APPLE_HAVE_DISPATCH) namespace al { diff --git a/Engine/lib/openal-soft/common/alsem.h b/Engine/lib/openal-soft/common/alsem.h new file mode 100644 index 000000000..90b393190 --- /dev/null +++ b/Engine/lib/openal-soft/common/alsem.h @@ -0,0 +1,43 @@ +#ifndef COMMON_ALSEM_H +#define COMMON_ALSEM_H + +#if defined(__APPLE__) +#include +#include +#if (((MAC_OS_X_VERSION_MIN_REQUIRED > 1050) && !defined(__ppc__)) || TARGET_OS_IOS || TARGET_OS_TV) +#include +#define AL_APPLE_HAVE_DISPATCH 1 +#else +#include /* Fallback option for Apple without a working libdispatch */ +#endif +#elif !defined(_WIN32) +#include +#endif + +namespace al { + +class semaphore { +#ifdef _WIN32 + using native_type = void*; +#elif defined(AL_APPLE_HAVE_DISPATCH) + using native_type = dispatch_semaphore_t; +#else + using native_type = sem_t; +#endif + native_type mSem{}; + +public: + semaphore(unsigned int initial=0); + semaphore(const semaphore&) = delete; + ~semaphore(); + + semaphore& operator=(const semaphore&) = delete; + + void post(); + void wait() noexcept; + bool try_wait() noexcept; +}; + +} // namespace al + +#endif /* COMMON_ALSEM_H */ diff --git a/Engine/lib/openal-soft/common/alspan.h b/Engine/lib/openal-soft/common/alspan.h index 1d6cdfe56..c60aeeb8d 100644 --- a/Engine/lib/openal-soft/common/alspan.h +++ b/Engine/lib/openal-soft/common/alspan.h @@ -1,180 +1,250 @@ #ifndef AL_SPAN_H #define AL_SPAN_H -#include +#include #include -#include #include +#include +#include #include +#include +#include "alassert.h" #include "almalloc.h" #include "altraits.h" namespace al { +/* This is here primarily to help ensure proper behavior for span's iterators, + * being an actual object with member functions instead of a raw pointer (which + * has requirements like + and - working with ptrdiff_t). This also helps + * silence clang-tidy's pointer arithmetic warnings for span and FlexArray + * iterators. It otherwise behaves like a plain pointer and should optimize + * accordingly. + * + * Shouldn't be needed once we use std::span in C++20. + */ template -constexpr auto size(const T &cont) noexcept(noexcept(cont.size())) -> decltype(cont.size()) -{ return cont.size(); } +class ptr_wrapper { + static_assert(std::is_pointer_v); + T mPointer{}; -template -constexpr size_t size(const T (&)[N]) noexcept -{ return N; } +public: + using value_type = std::remove_pointer_t; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::random_access_iterator_tag; + + explicit constexpr ptr_wrapper(T ptr) : mPointer{ptr} { } + + /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + constexpr auto operator++() noexcept -> ptr_wrapper& { ++mPointer; return *this; } + constexpr auto operator--() noexcept -> ptr_wrapper& { --mPointer; return *this; } + constexpr auto operator++(int) noexcept -> ptr_wrapper + { + auto temp = *this; + ++*this; + return temp; + } + constexpr auto operator--(int) noexcept -> ptr_wrapper + { + auto temp = *this; + --*this; + return temp; + } + + constexpr + auto operator+=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer += n; return *this; } + constexpr + auto operator-=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer -= n; return *this; } + + [[nodiscard]] constexpr auto operator*() const noexcept -> value_type& { return *mPointer; } + [[nodiscard]] constexpr auto operator->() const noexcept -> value_type* { return mPointer; } + [[nodiscard]] constexpr + auto operator[](std::size_t idx) const noexcept -> value_type& {return mPointer[idx];} + + [[nodiscard]] friend constexpr + auto operator+(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper + { return ptr_wrapper{lhs.mPointer + n}; } + [[nodiscard]] friend constexpr + auto operator+(std::ptrdiff_t n, const ptr_wrapper &rhs) noexcept -> ptr_wrapper + { return ptr_wrapper{n + rhs.mPointer}; } + [[nodiscard]] friend constexpr + auto operator-(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper + { return ptr_wrapper{lhs.mPointer - n}; } + + [[nodiscard]] friend constexpr + auto operator-(const ptr_wrapper &lhs, const ptr_wrapper &rhs)noexcept->std::ptrdiff_t + { return lhs.mPointer - rhs.mPointer; } + + [[nodiscard]] friend constexpr + auto operator==(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer == rhs.mPointer; } + [[nodiscard]] friend constexpr + auto operator!=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer != rhs.mPointer; } + [[nodiscard]] friend constexpr + auto operator<=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer <= rhs.mPointer; } + [[nodiscard]] friend constexpr + auto operator>=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer >= rhs.mPointer; } + [[nodiscard]] friend constexpr + auto operator<(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer < rhs.mPointer; } + [[nodiscard]] friend constexpr + auto operator>(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer > rhs.mPointer; } + /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ +}; -template -constexpr auto data(T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data()) -{ return cont.data(); } +inline constexpr std::size_t dynamic_extent{static_cast(-1)}; -template -constexpr auto data(const T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data()) -{ return cont.data(); } - -template -constexpr T* data(T (&arr)[N]) noexcept -{ return arr; } - -template -constexpr const T* data(std::initializer_list list) noexcept -{ return list.begin(); } - - -constexpr size_t dynamic_extent{static_cast(-1)}; - -template +template class span; namespace detail_ { - template - using void_t = void; - template struct is_span_ : std::false_type { }; - template + template struct is_span_> : std::true_type { }; template - constexpr bool is_span_v = is_span_>::value; + inline constexpr bool is_span_v = is_span_>::value; template struct is_std_array_ : std::false_type { }; - template + template struct is_std_array_> : std::true_type { }; template - constexpr bool is_std_array_v = is_std_array_>::value; + inline constexpr bool is_std_array_v = is_std_array_>::value; template - constexpr bool has_size_and_data = false; + inline constexpr bool has_size_and_data = false; template - constexpr bool has_size_and_data())), decltype(al::data(std::declval()))>> + inline constexpr bool has_size_and_data())),decltype(std::data(std::declval()))>> = true; + template + inline constexpr bool is_valid_container_type = !is_span_v && !is_std_array_v + && !std::is_array::value && has_size_and_data; + template - constexpr bool is_array_compatible = std::is_convertible::value; + inline constexpr bool is_array_compatible = std::is_convertible::value; /* NOLINT(*-avoid-c-arrays) */ template - constexpr bool is_valid_container = !is_span_v && !is_std_array_v - && !std::is_array::value && has_size_and_data - && is_array_compatible()))>,T>; + inline constexpr bool is_valid_container = is_valid_container_type + && is_array_compatible()))>,T>; } // namespace detail_ #define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true -template +template class span { public: using element_type = T; using value_type = std::remove_cv_t; - using index_type = size_t; - using difference_type = ptrdiff_t; + using size_type = std::size_t; + using difference_type = std::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 iterator = ptr_wrapper; + using const_iterator = ptr_wrapper; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - static constexpr size_t extent{E}; + static constexpr std::size_t extent{E}; template constexpr span() noexcept { } template - constexpr explicit span(U iter, index_type) : mData{to_address(iter)} { } - template::value)> - constexpr explicit span(U first, V) : mData{to_address(first)} { } + constexpr explicit span(U iter, size_type size_) : mData{::al::to_address(iter)} + { alassert(size_ == extent); } + template::value)> + constexpr explicit span(U first, V last) : mData{::al::to_address(first)} + { alassert(static_cast(last-first) == extent); } - constexpr span(type_identity_t (&arr)[E]) noexcept - : span{al::data(arr), al::size(arr)} - { } - constexpr span(std::array &arr) noexcept : span{al::data(arr), al::size(arr)} { } - template::value)> - constexpr span(const std::array &arr) noexcept - : span{al::data(arr), al::size(arr)} - { } + template + constexpr span(type_identity_t (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */ + : mData{std::data(arr)} + { static_assert(N == extent); } + template + constexpr span(std::array &arr) noexcept : mData{std::data(arr)} + { static_assert(N == extent); } + template::value)> + constexpr span(const std::array &arr) noexcept : mData{std::data(arr)} + { static_assert(N == extent); } template)> - constexpr explicit span(U&& cont) : span{al::data(cont), al::size(cont)} { } + constexpr explicit span(U&& cont) : span{std::data(cont), std::size(cont)} { } - template::value + template::value && detail_::is_array_compatible && N == dynamic_extent)> - constexpr explicit span(const span &span_) noexcept - : span{al::data(span_), al::size(span_)} - { } - template::value + constexpr explicit span(const span &span_) noexcept : mData{std::data(span_)} + { alassert(std::size(span_) == extent); } + template::value && detail_::is_array_compatible && N == extent)> - constexpr span(const span &span_) noexcept : span{al::data(span_), al::size(span_)} { } + constexpr span(const span &span_) noexcept : mData{std::data(span_)} { } constexpr span(const span&) noexcept = default; constexpr 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; } + [[nodiscard]] constexpr auto front() const -> reference { return mData[0]; } + [[nodiscard]] constexpr auto back() const -> reference { return mData[E-1]; } + [[nodiscard]] constexpr auto operator[](size_type idx) const -> reference { return mData[idx]; } + [[nodiscard]] constexpr auto data() const noexcept -> pointer { 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; } + [[nodiscard]] constexpr auto size() const noexcept -> size_type { return E; } + [[nodiscard]] constexpr auto size_bytes() const noexcept -> size_type { return E * sizeof(value_type); } + [[nodiscard]] constexpr auto empty() const noexcept -> bool { 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; } + [[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; } + [[nodiscard]] constexpr auto end() const noexcept -> iterator { return iterator{mData+E}; } + [[nodiscard]] constexpr + auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; } + [[nodiscard]] constexpr + auto cend() const noexcept -> const_iterator { return const_iterator{mData+E}; } - constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } - constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } - constexpr const_reverse_iterator crbegin() const noexcept - { return const_reverse_iterator{cend()}; } - constexpr const_reverse_iterator crend() const noexcept - { return const_reverse_iterator{cbegin()}; } + [[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); } + [[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); } + [[nodiscard]] constexpr + auto crbegin() const noexcept -> const_reverse_iterator { return cend(); } + [[nodiscard]] constexpr + auto crend() const noexcept -> const_reverse_iterator { return cbegin(); } - template - constexpr span first() const + template + [[nodiscard]] constexpr auto first() const noexcept -> span { static_assert(E >= C, "New size exceeds original capacity"); return span{mData, C}; } - template - constexpr span last() const + template + [[nodiscard]] constexpr auto last() const noexcept -> span { static_assert(E >= C, "New size exceeds original capacity"); return span{mData+(E-C), C}; } - template - constexpr auto subspan() const -> std::enable_if_t> + template + [[nodiscard]] constexpr + auto subspan() const noexcept -> std::enable_if_t> { static_assert(E >= O, "Offset exceeds extent"); static_assert(E-O >= C, "New size exceeds original capacity"); return span{mData+O, C}; } - template - constexpr auto subspan() const -> std::enable_if_t> + template + [[nodiscard]] constexpr + auto subspan() const noexcept -> std::enable_if_t> { static_assert(E >= O, "Offset exceeds extent"); return span{mData+O, E-O}; @@ -184,10 +254,13 @@ public: * defining the specialization. As a result, these methods need to be * defined later. */ - constexpr span first(size_t count) const; - constexpr span last(size_t count) const; - constexpr span subspan(size_t offset, - size_t count=dynamic_extent) const; + [[nodiscard]] constexpr + auto first(std::size_t count) const noexcept -> span; + [[nodiscard]] constexpr + auto last(std::size_t count) const noexcept -> span; + [[nodiscard]] constexpr + auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept + -> span; private: pointer mData{nullptr}; @@ -198,7 +271,7 @@ class span { public: using element_type = T; using value_type = std::remove_cv_t; - using index_type = size_t; + using size_type = std::size_t; using difference_type = ptrdiff_t; using pointer = T*; @@ -206,146 +279,175 @@ public: using reference = T&; using const_reference = const T&; - using iterator = pointer; - using const_iterator = const_pointer; + using iterator = ptr_wrapper; + using const_iterator = ptr_wrapper; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - static constexpr size_t extent{dynamic_extent}; + static constexpr std::size_t extent{dynamic_extent}; constexpr span() noexcept = default; template - constexpr span(U iter, index_type count) - : mData{to_address(iter)}, mDataEnd{to_address(iter)+count} + constexpr span(U iter, size_type count) : mData{::al::to_address(iter)}, mDataLength{count} { } - template::value)> - constexpr span(U first, V last) : span{to_address(first), static_cast(last-first)} + template::value)> + constexpr span(U first, V last) + : span{::al::to_address(first), static_cast(last-first)} { } - template - constexpr span(type_identity_t (&arr)[N]) noexcept - : span{al::data(arr), al::size(arr)} + template + constexpr span(type_identity_t (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */ + : mData{std::data(arr)}, mDataLength{std::size(arr)} { } - template - constexpr span(std::array &arr) noexcept : span{al::data(arr), al::size(arr)} { } - template::value)> + template + constexpr span(std::array &arr) noexcept + : mData{std::data(arr)}, mDataLength{std::size(arr)} + { } + template::value)> constexpr span(const std::array &arr) noexcept - : span{al::data(arr), al::size(arr)} + : mData{std::data(arr)}, mDataLength{std::size(arr)} { } template)> - constexpr span(U&& cont) : span{al::data(cont), al::size(cont)} { } + constexpr span(U&& cont) : span{std::data(cont), std::size(cont)} { } - template::value || extent != N) - && detail_::is_array_compatible)> - constexpr span(const span &span_) noexcept : span{al::data(span_), al::size(span_)} { } + template + && (!std::is_same::value || extent != N))> + constexpr span(const span &span_) noexcept : span{std::data(span_), std::size(span_)} { } constexpr span(const span&) noexcept = default; constexpr 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; } + [[nodiscard]] constexpr auto front() const -> reference { return mData[0]; } + [[nodiscard]] constexpr auto back() const -> reference { return mData[mDataLength-1]; } + [[nodiscard]] constexpr auto operator[](size_type idx) const -> reference {return mData[idx];} + [[nodiscard]] constexpr auto data() const noexcept -> pointer { return mData; } - constexpr index_type size() const noexcept { return static_cast(mDataEnd-mData); } - constexpr index_type size_bytes() const noexcept - { return static_cast(mDataEnd-mData) * sizeof(value_type); } - constexpr bool empty() const noexcept { return mData == mDataEnd; } + [[nodiscard]] constexpr auto size() const noexcept -> size_type { return mDataLength; } + [[nodiscard]] constexpr + auto size_bytes() const noexcept -> size_type { return mDataLength * sizeof(value_type); } + [[nodiscard]] constexpr auto empty() const noexcept -> bool { return mDataLength == 0; } - 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; } + [[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; } + [[nodiscard]] constexpr + auto end() const noexcept -> iterator { return iterator{mData+mDataLength}; } + [[nodiscard]] constexpr + auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; } + [[nodiscard]] constexpr + auto cend() const noexcept -> const_iterator { return const_iterator{mData+mDataLength}; } - constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } - constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } - constexpr const_reverse_iterator crbegin() const noexcept - { return const_reverse_iterator{cend()}; } - constexpr const_reverse_iterator crend() const noexcept - { return const_reverse_iterator{cbegin()}; } + [[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); } + [[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); } + [[nodiscard]] constexpr + auto crbegin() const noexcept -> const_reverse_iterator { return cend(); } + [[nodiscard]] constexpr + auto crend() const noexcept -> const_reverse_iterator { return cbegin(); } - template - constexpr span first() const - { return span{mData, C}; } - - constexpr span first(size_t count) const - { return (count >= size()) ? *this : span{mData, mData+count}; } - - template - constexpr span last() const - { return span{mDataEnd-C, C}; } - - constexpr span last(size_t count) const - { return (count >= size()) ? *this : span{mDataEnd-count, mDataEnd}; } - - template - constexpr auto subspan() const -> std::enable_if_t> - { return span{mData+O, C}; } - - template - constexpr auto subspan() const -> std::enable_if_t> - { return span{mData+O, mDataEnd}; } - - constexpr span subspan(size_t offset, size_t count=dynamic_extent) const + template + [[nodiscard]] constexpr auto first() const noexcept -> span { - return (offset > size()) ? span{} : - (count >= size()-offset) ? span{mData+offset, mDataEnd} : - span{mData+offset, mData+offset+count}; + assert(C <= mDataLength); + return span{mData, C}; + } + + [[nodiscard]] constexpr auto first(std::size_t count) const noexcept -> span + { + assert(count <= mDataLength); + return span{mData, count}; + } + + template + [[nodiscard]] constexpr auto last() const noexcept -> span + { + assert(C <= mDataLength); + return span{mData+mDataLength-C, C}; + } + + [[nodiscard]] constexpr auto last(std::size_t count) const noexcept -> span + { + assert(count <= mDataLength); + return span{mData+mDataLength-count, count}; + } + + template + [[nodiscard]] constexpr + auto subspan() const noexcept -> std::enable_if_t> + { + assert(O <= mDataLength); + assert(C <= mDataLength-O); + return span{mData+O, C}; + } + + template + [[nodiscard]] constexpr + auto subspan() const noexcept -> std::enable_if_t> + { + assert(O <= mDataLength); + return span{mData+O, mDataLength-O}; + } + + [[nodiscard]] constexpr + auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept -> span + { + assert(offset <= mDataLength); + if(count != dynamic_extent) + { + assert(count <= mDataLength-offset); + return span{mData+offset, count}; + } + return span{mData+offset, mDataLength-offset}; } private: pointer mData{nullptr}; - pointer mDataEnd{nullptr}; + size_type mDataLength{0}; }; -template -constexpr inline auto span::first(size_t count) const -> span +template +[[nodiscard]] constexpr +auto span::first(std::size_t count) const noexcept -> span { - return (count >= size()) ? span{mData, extent} : - span{mData, count}; + assert(count <= size()); + return span{mData, count}; } -template -constexpr inline auto span::last(size_t count) const -> span +template +[[nodiscard]] constexpr +auto span::last(std::size_t count) const noexcept -> span { - return (count >= size()) ? span{mData, extent} : - span{mData+extent-count, count}; + assert(count <= size()); + return span{mData+size()-count, count}; } -template -constexpr inline auto span::subspan(size_t offset, size_t count) const +template +[[nodiscard]] constexpr +auto span::subspan(std::size_t offset, std::size_t count) const noexcept -> span { - return (offset > size()) ? span{} : - (count >= size()-offset) ? span{mData+offset, mData+extent} : - span{mData+offset, mData+offset+count}; + assert(offset <= size()); + if(count != dynamic_extent) + { + assert(count <= size()-offset); + return span{mData+offset, count}; + } + return span{mData+offset, size()-offset}; } -/* Helpers to deal with the lack of user-defined deduction guides (C++17). */ -template -constexpr auto as_span(T ptr, U count_or_end) -{ - using value_type = typename std::pointer_traits::element_type; - return span{ptr, count_or_end}; -} -template -constexpr auto as_span(T (&arr)[N]) noexcept { return span{al::data(arr), al::size(arr)}; } -template -constexpr auto as_span(std::array &arr) noexcept -{ return span{al::data(arr), al::size(arr)}; } -template -constexpr auto as_span(const std::array &arr) noexcept -{ return span,N>{al::data(arr), al::size(arr)}; } -template && !detail_::is_std_array_v - && !std::is_array::value && detail_::has_size_and_data)> -constexpr auto as_span(U&& cont) -{ - using value_type = std::remove_pointer_t()))>; - return span{al::data(cont), al::size(cont)}; -} -template -constexpr auto as_span(span span_) noexcept { return span_; } + +template +span(T, EndOrSize) -> span())>>; + +template +span(T (&)[N]) -> span; /* NOLINT(*-avoid-c-arrays) */ + +template +span(std::array&) -> span; + +template +span(const std::array&) -> span; + +template)> +span(C&&) -> span()))>>; #undef REQUIRES diff --git a/Engine/lib/openal-soft/common/alstring.cpp b/Engine/lib/openal-soft/common/alstring.cpp index 4a84be1db..945dbc3ae 100644 --- a/Engine/lib/openal-soft/common/alstring.cpp +++ b/Engine/lib/openal-soft/common/alstring.cpp @@ -3,43 +3,62 @@ #include "alstring.h" +#include #include +#include +#include #include -namespace { - -int to_upper(const char ch) -{ - using char8_traits = std::char_traits; - return std::toupper(char8_traits::to_int_type(ch)); -} - -} // namespace - namespace al { -int strcasecmp(const char *str0, const char *str1) noexcept +int case_compare(const std::string_view str0, const std::string_view 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++)); + using Traits = std::string_view::traits_type; + + auto ch0 = str0.cbegin(); + auto ch1 = str1.cbegin(); + auto ch1end = ch1 + std::min(str0.size(), str1.size()); + while(ch1 != ch1end) + { + const int u0{std::toupper(Traits::to_int_type(*ch0))}; + const int u1{std::toupper(Traits::to_int_type(*ch1))}; + if(const int diff{u0-u1}) return diff; + ++ch0; ++ch1; + } + + if(str0.size() < str1.size()) return -1; + if(str0.size() > str1.size()) return 1; return 0; } +int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept +{ + using Traits = std::wstring_view::traits_type; + + auto ch0 = str0.cbegin(); + auto ch1 = str1.cbegin(); + auto ch1end = ch1 + std::min(str0.size(), str1.size()); + while(ch1 != ch1end) + { + const auto u0 = std::towupper(Traits::to_int_type(*ch0)); + const auto u1 = std::towupper(Traits::to_int_type(*ch1)); + if(const auto diff = static_cast(u0-u1)) return diff; + ++ch0; ++ch1; + } + + if(str0.size() < str1.size()) return -1; + if(str0.size() > str1.size()) return 1; + return 0; +} + +int strcasecmp(const char *str0, const char *str1) noexcept +{ return case_compare(str0, str1); } + 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; + return case_compare(std::string_view{str0, std::min(std::strlen(str0), len)}, + std::string_view{str1, std::min(std::strlen(str1), len)}); } } // namespace al diff --git a/Engine/lib/openal-soft/common/alstring.h b/Engine/lib/openal-soft/common/alstring.h index 6c5004ee1..3182889d2 100644 --- a/Engine/lib/openal-soft/common/alstring.h +++ b/Engine/lib/openal-soft/common/alstring.h @@ -1,16 +1,41 @@ #ifndef AL_STRING_H #define AL_STRING_H +#include #include -#include +#include +#include +#include namespace al { -/* These would be better served by using a string_view-like span/view with - * case-insensitive char traits. - */ +template +[[nodiscard]] constexpr +auto sizei(const std::basic_string_view str) noexcept -> int +{ return static_cast(std::min(str.size(), std::numeric_limits::max())); } + +[[nodiscard]] +constexpr bool contains(const std::string_view str0, const std::string_view str1) noexcept +{ return str0.find(str1) != std::string_view::npos; } + +[[nodiscard]] +constexpr bool starts_with(const std::string_view str0, const std::string_view str1) noexcept +{ return str0.substr(0, std::min(str0.size(), str1.size())) == str1; } + +[[nodiscard]] +constexpr bool ends_with(const std::string_view str0, const std::string_view str1) noexcept +{ return str0.substr(str0.size() - std::min(str0.size(), str1.size())) == str1; } + +[[nodiscard]] +int case_compare(const std::string_view str0, const std::string_view str1) noexcept; + +[[nodiscard]] +int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept; + +[[nodiscard]] int strcasecmp(const char *str0, const char *str1) noexcept; +[[nodiscard]] int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept; } // namespace al diff --git a/Engine/lib/openal-soft/common/althrd_setname.cpp b/Engine/lib/openal-soft/common/althrd_setname.cpp new file mode 100644 index 000000000..b92b05d1c --- /dev/null +++ b/Engine/lib/openal-soft/common/althrd_setname.cpp @@ -0,0 +1,77 @@ + +#include "config.h" + +#include "althrd_setname.h" + + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include + +void althrd_setname(const char *name [[maybe_unused]]) +{ +#if defined(_MSC_VER) && !defined(_M_ARM) + +#define MS_VC_EXCEPTION 0x406D1388 +#pragma pack(push,8) + struct InfoStruct { + 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. + }; +#pragma pack(pop) + InfoStruct info{}; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = ~DWORD{0}; + info.dwFlags = 0; + + /* FIXME: How to do this on MinGW? */ + __try { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info); + } + __except(EXCEPTION_CONTINUE_EXECUTION) { + } +#undef MS_VC_EXCEPTION +#endif +} + +#else + +#include +#ifdef HAVE_PTHREAD_NP_H +#include +#endif + +namespace { + +using setname_t1 = int(*)(const char*); +using setname_t2 = int(*)(pthread_t, const char*); +using setname_t3 = void(*)(pthread_t, const char*); +using setname_t4 = int(*)(pthread_t, const char*, void*); + +[[maybe_unused]] void setname_caller(setname_t1 func, const char *name) +{ func(name); } + +[[maybe_unused]] void setname_caller(setname_t2 func, const char *name) +{ func(pthread_self(), name); } + +[[maybe_unused]] void setname_caller(setname_t3 func, const char *name) +{ func(pthread_self(), name); } + +[[maybe_unused]] void setname_caller(setname_t4 func, const char *name) +{ func(pthread_self(), "%s", const_cast(name)); /* NOLINT(*-const-cast) */ } + +} // namespace + +void althrd_setname(const char *name [[maybe_unused]]) +{ +#if defined(HAVE_PTHREAD_SET_NAME_NP) + setname_caller(pthread_set_name_np, name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) + setname_caller(pthread_setname_np, name); +#endif +} + +#endif diff --git a/Engine/lib/openal-soft/common/althrd_setname.h b/Engine/lib/openal-soft/common/althrd_setname.h new file mode 100644 index 000000000..0e22c0a92 --- /dev/null +++ b/Engine/lib/openal-soft/common/althrd_setname.h @@ -0,0 +1,6 @@ +#ifndef COMMON_ALTHRD_SETNAME_H +#define COMMON_ALTHRD_SETNAME_H + +void althrd_setname(const char *name); + +#endif /* COMMON_ALTHRD_SETNAME_H */ diff --git a/Engine/lib/openal-soft/common/althreads.h b/Engine/lib/openal-soft/common/althreads.h new file mode 100644 index 000000000..d84917067 --- /dev/null +++ b/Engine/lib/openal-soft/common/althreads.h @@ -0,0 +1,143 @@ +#ifndef AL_THREADS_H +#define AL_THREADS_H + +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include + +#elif defined(__APPLE__) + +#include + +#else + +#include +#endif + +#include "albit.h" + +namespace al { + +template +class tss { + static_assert(sizeof(T) <= sizeof(void*)); + static_assert(std::is_trivially_destructible_v && std::is_trivially_copy_constructible_v); + + [[nodiscard]] + static auto to_ptr(const T &value) noexcept -> void* + { + if constexpr(std::is_pointer_v) + { + if constexpr(std::is_const_v>) + return const_cast(static_cast(value)); /* NOLINT(*-const-cast) */ + else + return static_cast(value); + } + else if constexpr(sizeof(T) == sizeof(void*)) + return al::bit_cast(value); + else if constexpr(std::is_integral_v) + return al::bit_cast(static_cast(value)); + } + + [[nodiscard]] + static auto from_ptr(void *ptr) noexcept -> T + { + if constexpr(std::is_pointer_v) + return static_cast(ptr); + else if constexpr(sizeof(T) == sizeof(void*)) + return al::bit_cast(ptr); + else if constexpr(std::is_integral_v) + return static_cast(al::bit_cast(ptr)); + } + +#ifdef _WIN32 + DWORD mTss{TLS_OUT_OF_INDEXES}; + +public: + tss() : mTss{TlsAlloc()} + { + if(mTss == TLS_OUT_OF_INDEXES) + throw std::runtime_error{"al::tss::tss()"}; + } + explicit tss(const T &init) : tss{} + { + if(TlsSetValue(mTss, to_ptr(init)) == FALSE) + throw std::runtime_error{"al::tss::tss(T)"}; + } + ~tss() { TlsFree(mTss); } + + void set(const T &value) const + { + if(TlsSetValue(mTss, to_ptr(value)) == FALSE) + throw std::runtime_error{"al::tss::set(T)"}; + } + + [[nodiscard]] + auto get() const noexcept -> T { return from_ptr(TlsGetValue(mTss)); } + +#elif defined(__APPLE__) + + pthread_key_t mTss{}; + +public: + tss() + { + if(int res{pthread_key_create(&mTss, nullptr)}; res != 0) + throw std::runtime_error{"al::tss::tss()"}; + } + explicit tss(const T &init) : tss{} + { + if(int res{pthread_setspecific(mTss, to_ptr(init))}; res != 0) + throw std::runtime_error{"al::tss::tss(T)"}; + } + ~tss() { pthread_key_delete(mTss); } + + void set(const T &value) const + { + if(int res{pthread_setspecific(mTss, to_ptr(value))}; res != 0) + throw std::runtime_error{"al::tss::set(T)"}; + } + + [[nodiscard]] + auto get() const noexcept -> T { return from_ptr(pthread_getspecific(mTss)); } + +#else + + tss_t mTss{}; + +public: + tss() + { + if(int res{tss_create(&mTss, nullptr)}; res != thrd_success) + throw std::runtime_error{"al::tss::tss()"}; + } + explicit tss(const T &init) : tss{} + { + if(int res{tss_set(mTss, to_ptr(init))}; res != thrd_success) + throw std::runtime_error{"al::tss::tss(T)"}; + } + ~tss() { tss_delete(mTss); } + + void set(const T &value) const + { + if(int res{tss_set(mTss, to_ptr(value))}; res != thrd_success) + throw std::runtime_error{"al::tss::set(T)"}; + } + + [[nodiscard]] + auto get() const noexcept -> T { return from_ptr(tss_get(mTss)); } +#endif /* _WIN32 */ + + tss(const tss&) = delete; + tss(tss&&) = delete; + void operator=(const tss&) = delete; + void operator=(tss&&) = delete; +}; + +} // namespace al + +#endif /* AL_THREADS_H */ diff --git a/Engine/lib/openal-soft/common/atomic.h b/Engine/lib/openal-soft/common/atomic.h index 5e9b04c69..7075b1589 100644 --- a/Engine/lib/openal-soft/common/atomic.h +++ b/Engine/lib/openal-soft/common/atomic.h @@ -2,17 +2,17 @@ #define AL_ATOMIC_H #include +#include +#include +#include "almalloc.h" -using RefCount = std::atomic; - -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) +template +auto IncrementRef(std::atomic &ref) noexcept { return ref.fetch_add(1u, std::memory_order_acq_rel)+1u; } -inline unsigned int DecrementRef(RefCount &ref) + +template +auto DecrementRef(std::atomic &ref) noexcept { return ref.fetch_sub(1u, std::memory_order_acq_rel)-1u; } @@ -30,4 +30,75 @@ inline void AtomicReplaceHead(std::atomic &head, T newhead) std::memory_order_acq_rel, std::memory_order_acquire)); } +namespace al { + +template> +class atomic_unique_ptr { + std::atomic> mPointer{}; + + using unique_ptr_t = std::unique_ptr; + +public: + atomic_unique_ptr() = default; + atomic_unique_ptr(const atomic_unique_ptr&) = delete; + explicit atomic_unique_ptr(std::nullptr_t) noexcept { } + explicit atomic_unique_ptr(gsl::owner ptr) noexcept : mPointer{ptr} { } + explicit atomic_unique_ptr(unique_ptr_t&& rhs) noexcept : mPointer{rhs.release()} { } + ~atomic_unique_ptr() + { + if(auto ptr = mPointer.exchange(nullptr, std::memory_order_relaxed)) + D{}(ptr); + } + + auto operator=(const atomic_unique_ptr&) -> atomic_unique_ptr& = delete; + auto operator=(std::nullptr_t) noexcept -> atomic_unique_ptr& + { + if(auto ptr = mPointer.exchange(nullptr)) + D{}(ptr); + return *this; + } + auto operator=(unique_ptr_t&& rhs) noexcept -> atomic_unique_ptr& + { + if(auto ptr = mPointer.exchange(rhs.release())) + D{}(ptr); + return *this; + } + + [[nodiscard]] + auto load(std::memory_order m=std::memory_order_seq_cst) const noexcept -> T* + { return mPointer.load(m); } + void store(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept + { + if(auto oldptr = mPointer.exchange(nullptr, m)) + D{}(oldptr); + } + void store(gsl::owner ptr, std::memory_order m=std::memory_order_seq_cst) noexcept + { + if(auto oldptr = mPointer.exchange(ptr, m)) + D{}(oldptr); + } + void store(unique_ptr_t&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept + { + if(auto oldptr = mPointer.exchange(ptr.release(), m)) + D{}(oldptr); + } + + [[nodiscard]] + auto exchange(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t + { return unique_ptr_t{mPointer.exchange(nullptr, m)}; } + [[nodiscard]] + auto exchange(gsl::owner ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t + { return unique_ptr_t{mPointer.exchange(ptr, m)}; } + [[nodiscard]] + auto exchange(std::unique_ptr&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t + { return unique_ptr_t{mPointer.exchange(ptr.release(), m)}; } + + [[nodiscard]] + auto is_lock_free() const noexcept -> bool { return mPointer.is_lock_free(); } + + static constexpr auto is_always_lock_free = std::atomic>::is_always_lock_free; +}; + +} // namespace al + #endif /* AL_ATOMIC_H */ diff --git a/Engine/lib/openal-soft/common/comptr.h b/Engine/lib/openal-soft/common/comptr.h index cdc6dec02..d3294396c 100644 --- a/Engine/lib/openal-soft/common/comptr.h +++ b/Engine/lib/openal-soft/common/comptr.h @@ -2,49 +2,86 @@ #define COMMON_COMPTR_H #include +#include +#include #include +#include -#include "opthelpers.h" +#define WIN32_LEAN_AND_MEAN +#include +#include + +struct ComWrapper { + HRESULT mStatus{}; + + ComWrapper(void *reserved, DWORD coinit) + : mStatus{CoInitializeEx(reserved, coinit)} + { } + ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED) + : mStatus{CoInitializeEx(nullptr, coinit)} + { } + ComWrapper(ComWrapper&& rhs) { mStatus = std::exchange(rhs.mStatus, E_FAIL); } + ComWrapper(const ComWrapper&) = delete; + ~ComWrapper() { if(SUCCEEDED(mStatus)) CoUninitialize(); } + + ComWrapper& operator=(ComWrapper&& rhs) + { + if(SUCCEEDED(mStatus)) + CoUninitialize(); + mStatus = std::exchange(rhs.mStatus, E_FAIL); + return *this; + } + ComWrapper& operator=(const ComWrapper&) = delete; + + [[nodiscard]] + HRESULT status() const noexcept { return mStatus; } + explicit operator bool() const noexcept { return SUCCEEDED(status()); } + + void uninit() + { + if(SUCCEEDED(mStatus)) + CoUninitialize(); + mStatus = E_FAIL; + } +}; template -class ComPtr { - T *mPtr{nullptr}; +struct ComPtr { + using element_type = T; + + static constexpr bool RefIsNoexcept{noexcept(std::declval().AddRef()) + && noexcept(std::declval().Release())}; -public: ComPtr() noexcept = default; - ComPtr(const ComPtr &rhs) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); } + ComPtr(const ComPtr &rhs) noexcept(RefIsNoexcept) : mPtr{rhs.mPtr} + { if(mPtr) mPtr->AddRef(); } ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; } ComPtr(std::nullptr_t) noexcept { } explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { } ~ComPtr() { if(mPtr) mPtr->Release(); } - ComPtr& operator=(const ComPtr &rhs) + /* NOLINTNEXTLINE(bugprone-unhandled-self-assignment) Yes it is. */ + ComPtr& operator=(const ComPtr &rhs) noexcept(RefIsNoexcept) { - if(!rhs.mPtr) + if constexpr(RefIsNoexcept) { - if(mPtr) - mPtr->Release(); - mPtr = nullptr; + if(rhs.mPtr) rhs.mPtr->AddRef(); + if(mPtr) mPtr->Release(); + mPtr = rhs.mPtr; + return *this; } else { - rhs.mPtr->AddRef(); - try { - if(mPtr) - mPtr->Release(); - mPtr = rhs.mPtr; - } - catch(...) { - rhs.mPtr->Release(); - throw; - } + ComPtr tmp{rhs}; + if(mPtr) mPtr->Release(); + mPtr = tmp.release(); + return *this; } - return *this; } - ComPtr& operator=(ComPtr&& rhs) + ComPtr& operator=(ComPtr&& rhs) noexcept(RefIsNoexcept) { - if(&rhs != this) LIKELY + if(&rhs != this) { if(mPtr) mPtr->Release(); mPtr = std::exchange(rhs.mPtr, nullptr); @@ -52,17 +89,25 @@ public: return *this; } + void reset(T *ptr=nullptr) noexcept(RefIsNoexcept) + { + if(mPtr) mPtr->Release(); + mPtr = ptr; + } + explicit operator bool() const noexcept { return mPtr != nullptr; } T& operator*() const noexcept { return *mPtr; } T* operator->() const noexcept { return mPtr; } T* get() const noexcept { return mPtr; } - T** getPtr() noexcept { return &mPtr; } T* release() noexcept { return std::exchange(mPtr, nullptr); } void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } + +private: + T *mPtr{nullptr}; }; #endif diff --git a/Engine/lib/openal-soft/common/dynload.cpp b/Engine/lib/openal-soft/common/dynload.cpp index f1c2a7ebb..333a9435e 100644 --- a/Engine/lib/openal-soft/common/dynload.cpp +++ b/Engine/lib/openal-soft/common/dynload.cpp @@ -3,12 +3,12 @@ #include "dynload.h" -#include "strutils.h" - #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include +#include "strutils.h" + void *LoadLib(const char *name) { std::wstring wname{utf8_to_wstr(name)}; diff --git a/Engine/lib/openal-soft/common/flexarray.h b/Engine/lib/openal-soft/common/flexarray.h new file mode 100644 index 000000000..afd6eaeee --- /dev/null +++ b/Engine/lib/openal-soft/common/flexarray.h @@ -0,0 +1,139 @@ +#ifndef AL_FLEXARRAY_H +#define AL_FLEXARRAY_H + +#include +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alspan.h" + +namespace al { + +/* Storage for flexible array data. This is trivially destructible if type T is + * trivially destructible. + */ +template::value> +struct alignas(alignment) FlexArrayStorage : al::span { + /* NOLINTBEGIN(bugprone-sizeof-expression) clang-tidy warns about the + * sizeof(T) being suspicious when T is a pointer type, which it will be + * for flexible arrays of pointers. + */ + static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept + { return sizeof(FlexArrayStorage) + sizeof(T)*count + base; } + /* NOLINTEND(bugprone-sizeof-expression) */ + + /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) Flexible + * arrays store their payloads after the end of the object, which must be + * the last in the whole parent chain. + */ + FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) + : al::span{::new(static_cast(this+1)) T[size], size} + { } + /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + ~FlexArrayStorage() = default; + + FlexArrayStorage(const FlexArrayStorage&) = delete; + FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; +}; + +template +struct alignas(alignment) FlexArrayStorage : al::span { + static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept + { return sizeof(FlexArrayStorage) + sizeof(T)*count + base; } + + /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) + : al::span{::new(static_cast(this+1)) T[size], size} + { } + /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + ~FlexArrayStorage() { std::destroy(this->begin(), this->end()); } + + FlexArrayStorage(const FlexArrayStorage&) = delete; + FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; +}; + +/* A flexible array type. Used either standalone or at the end of a parent + * struct, to have a run-time-sized array that's embedded with its size. Should + * be used delicately, ensuring there's no additional data after the FlexArray + * member. + */ +template +struct FlexArray { + using element_type = T; + using value_type = std::remove_cv_t; + 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&; + + static constexpr std::size_t StorageAlign{std::max(alignof(T), Align)}; + using Storage_t_ = FlexArrayStorage), StorageAlign)>; + + using iterator = typename Storage_t_::iterator; + using const_iterator = typename Storage_t_::const_iterator; + using reverse_iterator = typename Storage_t_::reverse_iterator; + using const_reverse_iterator = typename Storage_t_::const_reverse_iterator; + + const Storage_t_ mStore; + + static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept + { return Storage_t_::Sizeof(count, base); } + static std::unique_ptr Create(index_type count) + { return std::unique_ptr{new(FamCount{count}) FlexArray{count}}; } + + FlexArray(index_type size) noexcept(std::is_nothrow_constructible_v) + : mStore{size} + { } + ~FlexArray() = default; + + [[nodiscard]] auto size() const noexcept -> index_type { return mStore.size(); } + [[nodiscard]] auto empty() const noexcept -> bool { return mStore.empty(); } + + [[nodiscard]] auto data() noexcept -> pointer { return mStore.data(); } + [[nodiscard]] auto data() const noexcept -> const_pointer { return mStore.data(); } + + [[nodiscard]] auto operator[](index_type i) noexcept -> reference { return mStore[i]; } + [[nodiscard]] auto operator[](index_type i) const noexcept -> const_reference { return mStore[i]; } + + [[nodiscard]] auto front() noexcept -> reference { return mStore.front(); } + [[nodiscard]] auto front() const noexcept -> const_reference { return mStore.front(); } + + [[nodiscard]] auto back() noexcept -> reference { return mStore.back(); } + [[nodiscard]] auto back() const noexcept -> const_reference { return mStore.back(); } + + [[nodiscard]] auto begin() noexcept -> iterator { return mStore.begin(); } + [[nodiscard]] auto begin() const noexcept -> const_iterator { return mStore.cbegin(); } + [[nodiscard]] auto cbegin() const noexcept -> const_iterator { return mStore.cbegin(); } + [[nodiscard]] auto end() noexcept -> iterator { return mStore.end(); } + [[nodiscard]] auto end() const noexcept -> const_iterator { return mStore.cend(); } + [[nodiscard]] auto cend() const noexcept -> const_iterator { return mStore.cend(); } + + [[nodiscard]] auto rbegin() noexcept -> reverse_iterator { return mStore.rbegin(); } + [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); } + [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); } + [[nodiscard]] auto rend() noexcept -> reverse_iterator { return mStore.rend(); } + [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator { return mStore.crend(); } + [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator { return mStore.crend(); } + + gsl::owner operator new(size_t, FamCount count) + { return ::operator new[](Sizeof(count), std::align_val_t{alignof(FlexArray)}); } + void operator delete(gsl::owner block, FamCount) noexcept + { ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); } + void operator delete(gsl::owner block) noexcept + { ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); } + + void *operator new(size_t size) = delete; + void *operator new[](size_t size) = delete; + void operator delete[](void *block) = delete; +}; + +} // namespace al + +#endif /* AL_FLEXARRAY_H */ diff --git a/Engine/lib/openal-soft/common/intrusive_ptr.h b/Engine/lib/openal-soft/common/intrusive_ptr.h index 270753479..b1fa742f0 100644 --- a/Engine/lib/openal-soft/common/intrusive_ptr.h +++ b/Engine/lib/openal-soft/common/intrusive_ptr.h @@ -1,6 +1,8 @@ #ifndef INTRUSIVE_PTR_H #define INTRUSIVE_PTR_H +#include +#include #include #include "atomic.h" @@ -11,7 +13,7 @@ namespace al { template class intrusive_ref { - RefCount mRef{1u}; + std::atomic mRef{1u}; public: unsigned int add_ref() noexcept { return IncrementRef(mRef); } @@ -60,6 +62,9 @@ public: explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { } ~intrusive_ptr() { if(mPtr) mPtr->dec_ref(); } + /* NOLINTBEGIN(bugprone-unhandled-self-assignment) + * Self-assignment is handled properly here. + */ intrusive_ptr& operator=(const intrusive_ptr &rhs) noexcept { static_assert(noexcept(std::declval()->dec_ref()), "dec_ref must be noexcept"); @@ -69,6 +74,7 @@ public: mPtr = rhs.mPtr; return *this; } + /* NOLINTEND(bugprone-unhandled-self-assignment) */ intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept { if(&rhs != this) LIKELY @@ -81,9 +87,9 @@ public: explicit 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; } + [[nodiscard]] auto operator*() const noexcept -> T& { return *mPtr; } + [[nodiscard]] auto operator->() const noexcept -> T* { return mPtr; } + [[nodiscard]] auto get() const noexcept -> T* { return mPtr; } void reset(T *ptr=nullptr) noexcept { @@ -98,23 +104,6 @@ public: void swap(intrusive_ptr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } }; -#define AL_DECL_OP(op) \ -template \ -inline bool operator op(const intrusive_ptr &lhs, const T *rhs) noexcept \ -{ return lhs.get() op rhs; } \ -template \ -inline bool operator op(const T *lhs, const intrusive_ptr &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/Engine/lib/openal-soft/common/opthelpers.h b/Engine/lib/openal-soft/common/opthelpers.h index 596c24554..ae2611dad 100644 --- a/Engine/lib/openal-soft/common/opthelpers.h +++ b/Engine/lib/openal-soft/common/opthelpers.h @@ -19,10 +19,13 @@ #ifdef __GNUC__ #define force_inline [[gnu::always_inline]] inline +#define NOINLINE [[gnu::noinline]] #elif defined(_MSC_VER) #define force_inline __forceinline +#define NOINLINE __declspec(noinline) #else #define force_inline inline +#define NOINLINE #endif /* Unlike the likely attribute, ASSUME requires the condition to be true or @@ -39,7 +42,7 @@ #elif HAS_BUILTIN(__builtin_unreachable) #define ASSUME(x) do { if(x) break; __builtin_unreachable(); } while(0) #else -#define ASSUME(x) ((void)0) +#define ASSUME(x) (static_cast(0)) #endif /* This shouldn't be needed since unknown attributes are ignored, but older diff --git a/Engine/lib/openal-soft/common/pffft.cpp b/Engine/lib/openal-soft/common/pffft.cpp new file mode 100644 index 000000000..6cddcf09a --- /dev/null +++ b/Engine/lib/openal-soft/common/pffft.cpp @@ -0,0 +1,2308 @@ +//$ nobt + +/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) + * Copyright (c) 2023 Christopher Robinson + * + * Based on original fortran 77 code from FFTPACKv4 from NETLIB + * (http://www.netlib.org/fftpack), authored by Dr Paul Swarztrauber + * of NCAR, in 1985. + * + * As confirmed by the NCAR fftpack software curators, the following + * FFTPACKv5 license applies to FFTPACKv4 sources. My changes are + * released under the same terms. + * + * FFTPACK license: + * + * http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html + * + * Copyright (c) 2004 the University Corporation for Atmospheric + * Research ("UCAR"). All rights reserved. Developed by NCAR's + * Computational and Information Systems Laboratory, UCAR, + * www.cisl.ucar.edu. + * + * Redistribution and use of the Software in source and binary forms, + * with or without modification, is permitted provided that the + * following conditions are met: + * + * - Neither the names of NCAR's Computational and Information Systems + * Laboratory, the University Corporation for Atmospheric Research, + * nor the names of its sponsors or contributors may be used to + * endorse or promote products derived from this Software without + * specific prior written permission. + * + * - Redistributions of source code must retain the above copyright + * notices, this list of conditions, and the disclaimer below. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the disclaimer below in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + * SOFTWARE. + * + * + * PFFFT : a Pretty Fast FFT. + * + * This file is largerly based on the original FFTPACK implementation, modified + * in order to take advantage of SIMD instructions of modern CPUs. + */ + +#include "pffft.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albit.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "opthelpers.h" + + +using uint = unsigned int; + +namespace { + +#if defined(__GNUC__) || defined(_MSC_VER) +#define RESTRICT __restrict +#else +#define RESTRICT +#endif + +/* Vector support macros: the rest of the code is independent of + * SSE/Altivec/NEON -- adding support for other platforms with 4-element + * vectors should be limited to these macros + */ + +/* Define PFFFT_SIMD_DISABLE if you want to use scalar code instead of SIMD code */ +//#define PFFFT_SIMD_DISABLE + +#ifndef PFFFT_SIMD_DISABLE +/* + * Altivec support macros + */ +#if defined(__ppc__) || defined(__ppc64__) || defined(__powerpc__) || defined(__powerpc64__) +#include +using v4sf = vector float; +constexpr uint SimdSize{4}; +force_inline v4sf vzero() noexcept { return (vector float)vec_splat_u8(0); } +force_inline v4sf vmul(v4sf a, v4sf b) noexcept { return vec_madd(a, b, vzero()); } +force_inline v4sf vadd(v4sf a, v4sf b) noexcept { return vec_add(a, b); } +force_inline v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return vec_madd(a, b, c); } +force_inline v4sf vsub(v4sf a, v4sf b) noexcept { return vec_sub(a, b); } +force_inline v4sf ld_ps1(float a) noexcept { return vec_splats(a); } + +force_inline v4sf vset4(float a, float b, float c, float d) noexcept +{ + /* There a more efficient way to do this? */ + alignas(16) std::array vals{{a, b, c, d}}; + return vec_ld(0, vals.data()); +} +force_inline v4sf vinsert0(v4sf v, float a) noexcept { return vec_insert(a, v, 0); } +force_inline float vextract0(v4sf v) noexcept { return vec_extract(v, 0); } + +force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = vec_mergeh(in1, in2); + out2 = vec_mergel(in1, in2); +} +force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = vec_perm(in1, in2, (vector unsigned char){0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27}); + out2 = vec_perm(in1, in2, (vector unsigned char){4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31}); + out1 = tmp; +} + +force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept +{ + v4sf y0{vec_mergeh(x0, x2)}; + v4sf y1{vec_mergel(x0, x2)}; + v4sf y2{vec_mergeh(x1, x3)}; + v4sf y3{vec_mergel(x1, x3)}; + x0 = vec_mergeh(y0, y2); + x1 = vec_mergel(y0, y2); + x2 = vec_mergeh(y1, y3); + x3 = vec_mergel(y1, y3); +} + +force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept +{ return vec_perm(a,b, (vector unsigned char){16,17,18,19,20,21,22,23,8,9,10,11,12,13,14,15}); } + +/* + * SSE1 support macros + */ +#elif defined(__x86_64__) || defined(__SSE__) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 1) + +#include +using v4sf = __m128; +/* 4 floats by simd vector -- this is pretty much hardcoded in the preprocess/ + * finalize functions anyway so you will have to work if you want to enable AVX + * with its 256-bit vectors. + */ +constexpr uint SimdSize{4}; +force_inline v4sf vzero() noexcept { return _mm_setzero_ps(); } +force_inline v4sf vmul(v4sf a, v4sf b) noexcept { return _mm_mul_ps(a, b); } +force_inline v4sf vadd(v4sf a, v4sf b) noexcept { return _mm_add_ps(a, b); } +force_inline v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return _mm_add_ps(_mm_mul_ps(a,b), c); } +force_inline v4sf vsub(v4sf a, v4sf b) noexcept { return _mm_sub_ps(a, b); } +force_inline v4sf ld_ps1(float a) noexcept { return _mm_set1_ps(a); } + +force_inline v4sf vset4(float a, float b, float c, float d) noexcept +{ return _mm_setr_ps(a, b, c, d); } +force_inline v4sf vinsert0(const v4sf v, const float a) noexcept +{ return _mm_move_ss(v, _mm_set_ss(a)); } +force_inline float vextract0(v4sf v) noexcept +{ return _mm_cvtss_f32(v); } + +force_inline void interleave2(const v4sf in1, const v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = _mm_unpacklo_ps(in1, in2); + out2 = _mm_unpackhi_ps(in1, in2); +} +force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0)); + out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1)); +} + +force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept +{ _MM_TRANSPOSE4_PS(x0, x1, x2, x3); } + +force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept +{ return _mm_shuffle_ps(b, a, _MM_SHUFFLE(3,2,1,0)); } + +/* + * ARM NEON support macros + */ +#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(__arm64) || defined(_M_ARM64) + +#include +using v4sf = float32x4_t; +constexpr uint SimdSize{4}; +force_inline v4sf vzero() noexcept { return vdupq_n_f32(0.0f); } +force_inline v4sf vmul(v4sf a, v4sf b) noexcept { return vmulq_f32(a, b); } +force_inline v4sf vadd(v4sf a, v4sf b) noexcept { return vaddq_f32(a, b); } +force_inline v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return vmlaq_f32(c, a, b); } +force_inline v4sf vsub(v4sf a, v4sf b) noexcept { return vsubq_f32(a, b); } +force_inline v4sf ld_ps1(float a) noexcept { return vdupq_n_f32(a); } + +force_inline v4sf vset4(float a, float b, float c, float d) noexcept +{ + float32x4_t ret{vmovq_n_f32(a)}; + ret = vsetq_lane_f32(b, ret, 1); + ret = vsetq_lane_f32(c, ret, 2); + ret = vsetq_lane_f32(d, ret, 3); + return ret; +} +force_inline v4sf vinsert0(v4sf v, float a) noexcept +{ return vsetq_lane_f32(a, v, 0); } +force_inline float vextract0(v4sf v) noexcept +{ return vgetq_lane_f32(v, 0); } + +force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + float32x4x2_t tmp{vzipq_f32(in1, in2)}; + out1 = tmp.val[0]; + out2 = tmp.val[1]; +} +force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + float32x4x2_t tmp{vuzpq_f32(in1, in2)}; + out1 = tmp.val[0]; + out2 = tmp.val[1]; +} + +force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept +{ + /* marginally faster version: + * asm("vtrn.32 %q0, %q1;\n" + * "vtrn.32 %q2, %q3\n + * "vswp %f0, %e2\n + * "vswp %f1, %e3" + * : "+w"(x0), "+w"(x1), "+w"(x2), "+w"(x3)::); + */ + float32x4x2_t t0_{vzipq_f32(x0, x2)}; + float32x4x2_t t1_{vzipq_f32(x1, x3)}; + float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])}; + float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])}; + x0 = u0_.val[0]; + x1 = u0_.val[1]; + x2 = u1_.val[0]; + x3 = u1_.val[1]; +} + +force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept +{ return vcombine_f32(vget_low_f32(b), vget_high_f32(a)); } + +/* + * Generic GCC vector macros + */ +#elif defined(__GNUC__) + +using v4sf [[gnu::vector_size(16), gnu::aligned(16)]] = float; +constexpr uint SimdSize{4}; +force_inline constexpr v4sf vzero() noexcept { return v4sf{0.0f, 0.0f, 0.0f, 0.0f}; } +force_inline constexpr v4sf vmul(v4sf a, v4sf b) noexcept { return a * b; } +force_inline constexpr v4sf vadd(v4sf a, v4sf b) noexcept { return a + b; } +force_inline constexpr v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return a*b + c; } +force_inline constexpr v4sf vsub(v4sf a, v4sf b) noexcept { return a - b; } +force_inline constexpr v4sf ld_ps1(float a) noexcept { return v4sf{a, a, a, a}; } + +force_inline constexpr v4sf vset4(float a, float b, float c, float d) noexcept +{ return v4sf{a, b, c, d}; } +force_inline constexpr v4sf vinsert0(v4sf v, float a) noexcept +{ return v4sf{a, v[1], v[2], v[3]}; } +force_inline float vextract0(v4sf v) noexcept +{ return v[0]; } + +force_inline v4sf unpacklo(v4sf a, v4sf b) noexcept +{ return v4sf{a[0], b[0], a[1], b[1]}; } +force_inline v4sf unpackhi(v4sf a, v4sf b) noexcept +{ return v4sf{a[2], b[2], a[3], b[3]}; } + +force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = unpacklo(in1, in2); + out2 = unpackhi(in1, in2); +} +force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = v4sf{in1[0], in1[2], in2[0], in2[2]}; + out2 = v4sf{in1[1], in1[3], in2[1], in2[3]}; +} + +force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept +{ + v4sf tmp0{unpacklo(x0, x1)}; + v4sf tmp2{unpacklo(x2, x3)}; + v4sf tmp1{unpackhi(x0, x1)}; + v4sf tmp3{unpackhi(x2, x3)}; + x0 = v4sf{tmp0[0], tmp0[1], tmp2[0], tmp2[1]}; + x1 = v4sf{tmp0[2], tmp0[3], tmp2[2], tmp2[3]}; + x2 = v4sf{tmp1[0], tmp1[1], tmp3[0], tmp3[1]}; + x3 = v4sf{tmp1[2], tmp1[3], tmp3[2], tmp3[3]}; +} + +force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept +{ return v4sf{b[0], b[1], a[2], a[3]}; } + +#else + +#warning "building with simd disabled !\n"; +#define PFFFT_SIMD_DISABLE // fallback to scalar code +#endif + +#endif /* PFFFT_SIMD_DISABLE */ + +// fallback mode for situations where SIMD is not available, use scalar mode instead +#ifdef PFFFT_SIMD_DISABLE +using v4sf = float; +constexpr uint SimdSize{1}; +force_inline constexpr v4sf vmul(v4sf a, v4sf b) noexcept { return a * b; } +force_inline constexpr v4sf vadd(v4sf a, v4sf b) noexcept { return a + b; } +force_inline constexpr v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return a*b + c; } +force_inline constexpr v4sf vsub(v4sf a, v4sf b) noexcept { return a - b; } +force_inline constexpr v4sf ld_ps1(float a) noexcept { return a; } + +#else + +[[maybe_unused]] inline +auto valigned(const float *ptr) noexcept -> bool +{ + static constexpr uintptr_t alignmask{SimdSize*sizeof(float) - 1}; + return (reinterpret_cast(ptr) & alignmask) == 0; +} +#endif + +// shortcuts for complex multiplications +force_inline void vcplxmul(v4sf &ar, v4sf &ai, v4sf br, v4sf bi) noexcept +{ + v4sf tmp{vmul(ar, bi)}; + ar = vsub(vmul(ar, br), vmul(ai, bi)); + ai = vmadd(ai, br, tmp); +} +force_inline void vcplxmulconj(v4sf &ar, v4sf &ai, v4sf br, v4sf bi) noexcept +{ + v4sf tmp{vmul(ar, bi)}; + ar = vmadd(ai, bi, vmul(ar, br)); + ai = vsub(vmul(ai, br), tmp); +} + +#if !defined(PFFFT_SIMD_DISABLE) + +inline void assertv4(const al::span v_f [[maybe_unused]], + const float f0 [[maybe_unused]], const float f1 [[maybe_unused]], + const float f2 [[maybe_unused]], const float f3 [[maybe_unused]]) +{ assert(v_f[0] == f0 && v_f[1] == f1 && v_f[2] == f2 && v_f[3] == f3); } + +template +constexpr auto make_float_array(std::integer_sequence) +{ return std::array{static_cast(N)...}; } + +/* detect bugs with the vector support macros */ +[[maybe_unused]] void validate_pffft_simd() +{ + using float4 = std::array; + static constexpr auto f = make_float_array(std::make_index_sequence<16>{}); + + v4sf a0_v{vset4(f[ 0], f[ 1], f[ 2], f[ 3])}; + v4sf a1_v{vset4(f[ 4], f[ 5], f[ 6], f[ 7])}; + v4sf a2_v{vset4(f[ 8], f[ 9], f[10], f[11])}; + v4sf a3_v{vset4(f[12], f[13], f[14], f[15])}; + v4sf u_v{}; + + auto t_v = vzero(); + auto t_f = al::bit_cast(t_v); + printf("VZERO=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 0, 0, 0, 0); + + t_v = vadd(a1_v, a2_v); + t_f = al::bit_cast(t_v); + printf("VADD(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 12, 14, 16, 18); + + t_v = vmul(a1_v, a2_v); + t_f = al::bit_cast(t_v); + printf("VMUL(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 32, 45, 60, 77); + + t_v = vmadd(a1_v, a2_v, a0_v); + t_f = al::bit_cast(t_v); + printf("VMADD(4:7,8:11,0:3)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 32, 46, 62, 80); + + interleave2(a1_v, a2_v, t_v, u_v); + t_f = al::bit_cast(t_v); + auto u_f = al::bit_cast(u_v); + printf("INTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", + t_f[0], t_f[1], t_f[2], t_f[3], u_f[0], u_f[1], u_f[2], u_f[3]); + assertv4(t_f, 4, 8, 5, 9); + assertv4(u_f, 6, 10, 7, 11); + + uninterleave2(a1_v, a2_v, t_v, u_v); + t_f = al::bit_cast(t_v); + u_f = al::bit_cast(u_v); + printf("UNINTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", + t_f[0], t_f[1], t_f[2], t_f[3], u_f[0], u_f[1], u_f[2], u_f[3]); + assertv4(t_f, 4, 6, 8, 10); + assertv4(u_f, 5, 7, 9, 11); + + t_v = ld_ps1(f[15]); + t_f = al::bit_cast(t_v); + printf("LD_PS1(15)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 15, 15, 15, 15); + + t_v = vswaphl(a1_v, a2_v); + t_f = al::bit_cast(t_v); + printf("VSWAPHL(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 8, 9, 6, 7); + + vtranspose4(a0_v, a1_v, a2_v, a3_v); + auto a0_f = al::bit_cast(a0_v); + auto a1_f = al::bit_cast(a1_v); + auto a2_f = al::bit_cast(a2_v); + auto a3_f = al::bit_cast(a3_v); + printf("VTRANSPOSE4(0:3,4:7,8:11,12:15)=[%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", + a0_f[0], a0_f[1], a0_f[2], a0_f[3], a1_f[0], a1_f[1], a1_f[2], a1_f[3], + a2_f[0], a2_f[1], a2_f[2], a2_f[3], a3_f[0], a3_f[1], a3_f[2], a3_f[3]); + assertv4(a0_f, 0, 4, 8, 12); + assertv4(a1_f, 1, 5, 9, 13); + assertv4(a2_f, 2, 6, 10, 14); + assertv4(a3_f, 3, 7, 11, 15); +} +#endif //!PFFFT_SIMD_DISABLE + +/* SSE and co like 16-bytes aligned pointers */ +/* with a 64-byte alignment, we are even aligned on L2 cache lines... */ +constexpr auto V4sfAlignment = size_t(64); +constexpr auto V4sfAlignVal = std::align_val_t(V4sfAlignment); + +/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + * FIXME: Converting this from raw pointers to spans or something will probably + * need significant work to maintain performance, given non-sequential range- + * checked accesses and lack of 'restrict' to indicate non-aliased memory. At + * least, some tests should be done to check the impact of using range-checked + * spans here before blindly switching. + */ +/* + passf2 and passb2 has been merged here, fsign = -1 for passf2, +1 for passb2 +*/ +NOINLINE void passf2_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, + const float *const wa1, const float fsign) +{ + const size_t l1ido{l1*ido}; + if(ido <= 2) + { + for(size_t k{0};k < l1ido;k += ido, ch += ido, cc += 2*ido) + { + ch[0] = vadd(cc[0], cc[ido+0]); + ch[l1ido] = vsub(cc[0], cc[ido+0]); + ch[1] = vadd(cc[1], cc[ido+1]); + ch[l1ido + 1] = vsub(cc[1], cc[ido+1]); + } + } + else + { + for(size_t k{0};k < l1ido;k += ido, ch += ido, cc += 2*ido) + { + for(size_t i{0};i < ido-1;i += 2) + { + v4sf tr2{vsub(cc[i+0], cc[i+ido+0])}; + v4sf ti2{vsub(cc[i+1], cc[i+ido+1])}; + v4sf wr{ld_ps1(wa1[i])}, wi{ld_ps1(wa1[i+1]*fsign)}; + ch[i] = vadd(cc[i+0], cc[i+ido+0]); + ch[i+1] = vadd(cc[i+1], cc[i+ido+1]); + vcplxmul(tr2, ti2, wr, wi); + ch[i+l1ido] = tr2; + ch[i+l1ido+1] = ti2; + } + } + } +} + +/* + passf3 and passb3 has been merged here, fsign = -1 for passf3, +1 for passb3 +*/ +NOINLINE void passf3_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, + const float *const wa1, const float fsign) +{ + assert(ido > 2); + + const v4sf taur{ld_ps1(-0.5f)}; + const v4sf taui{ld_ps1(0.866025403784439f*fsign)}; + const size_t l1ido{l1*ido}; + const auto wa2 = wa1 + ido; + for(size_t k{0};k < l1ido;k += ido, cc += 3*ido, ch +=ido) + { + for(size_t i{0};i < ido-1;i += 2) + { + v4sf tr2{vadd(cc[i+ido], cc[i+2*ido])}; + v4sf cr2{vmadd(taur, tr2, cc[i])}; + ch[i] = vadd(tr2, cc[i]); + v4sf ti2{vadd(cc[i+ido+1], cc[i+2*ido+1])}; + v4sf ci2{vmadd(taur, ti2, cc[i+1])}; + ch[i+1] = vadd(cc[i+1], ti2); + v4sf cr3{vmul(taui, vsub(cc[i+ido], cc[i+2*ido]))}; + v4sf ci3{vmul(taui, vsub(cc[i+ido+1], cc[i+2*ido+1]))}; + v4sf dr2{vsub(cr2, ci3)}; + v4sf dr3{vadd(cr2, ci3)}; + v4sf di2{vadd(ci2, cr3)}; + v4sf di3{vsub(ci2, cr3)}; + float wr1{wa1[i]}, wi1{fsign*wa1[i+1]}, wr2{wa2[i]}, wi2{fsign*wa2[i+1]}; + vcplxmul(dr2, di2, ld_ps1(wr1), ld_ps1(wi1)); + ch[i+l1ido] = dr2; + ch[i+l1ido + 1] = di2; + vcplxmul(dr3, di3, ld_ps1(wr2), ld_ps1(wi2)); + ch[i+2*l1ido] = dr3; + ch[i+2*l1ido+1] = di3; + } + } +} /* passf3 */ + +NOINLINE void passf4_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, + const float *const wa1, const float fsign) +{ + /* fsign == -1 for forward transform and +1 for backward transform */ + const v4sf vsign{ld_ps1(fsign)}; + const size_t l1ido{l1*ido}; + if(ido == 2) + { + for(size_t k{0};k < l1ido;k += ido, ch += ido, cc += 4*ido) + { + v4sf tr1{vsub(cc[0], cc[2*ido + 0])}; + v4sf tr2{vadd(cc[0], cc[2*ido + 0])}; + v4sf ti1{vsub(cc[1], cc[2*ido + 1])}; + v4sf ti2{vadd(cc[1], cc[2*ido + 1])}; + v4sf ti4{vmul(vsub(cc[1*ido + 0], cc[3*ido + 0]), vsign)}; + v4sf tr4{vmul(vsub(cc[3*ido + 1], cc[1*ido + 1]), vsign)}; + v4sf tr3{vadd(cc[ido + 0], cc[3*ido + 0])}; + v4sf ti3{vadd(cc[ido + 1], cc[3*ido + 1])}; + + ch[0*l1ido + 0] = vadd(tr2, tr3); + ch[0*l1ido + 1] = vadd(ti2, ti3); + ch[1*l1ido + 0] = vadd(tr1, tr4); + ch[1*l1ido + 1] = vadd(ti1, ti4); + ch[2*l1ido + 0] = vsub(tr2, tr3); + ch[2*l1ido + 1] = vsub(ti2, ti3); + ch[3*l1ido + 0] = vsub(tr1, tr4); + ch[3*l1ido + 1] = vsub(ti1, ti4); + } + } + else + { + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + for(size_t k{0};k < l1ido;k += ido, ch+=ido, cc += 4*ido) + { + for(size_t i{0};i < ido-1;i+=2) + { + v4sf tr1{vsub(cc[i + 0], cc[i + 2*ido + 0])}; + v4sf tr2{vadd(cc[i + 0], cc[i + 2*ido + 0])}; + v4sf ti1{vsub(cc[i + 1], cc[i + 2*ido + 1])}; + v4sf ti2{vadd(cc[i + 1], cc[i + 2*ido + 1])}; + v4sf tr4{vmul(vsub(cc[i + 3*ido + 1], cc[i + 1*ido + 1]), vsign)}; + v4sf ti4{vmul(vsub(cc[i + 1*ido + 0], cc[i + 3*ido + 0]), vsign)}; + v4sf tr3{vadd(cc[i + ido + 0], cc[i + 3*ido + 0])}; + v4sf ti3{vadd(cc[i + ido + 1], cc[i + 3*ido + 1])}; + + ch[i] = vadd(tr2, tr3); + v4sf cr3{vsub(tr2, tr3)}; + ch[i + 1] = vadd(ti2, ti3); + v4sf ci3{vsub(ti2, ti3)}; + + v4sf cr2{vadd(tr1, tr4)}; + v4sf cr4{vsub(tr1, tr4)}; + v4sf ci2{vadd(ti1, ti4)}; + v4sf ci4{vsub(ti1, ti4)}; + float wr1{wa1[i]}, wi1{fsign*wa1[i+1]}; + vcplxmul(cr2, ci2, ld_ps1(wr1), ld_ps1(wi1)); + float wr2{wa2[i]}, wi2{fsign*wa2[i+1]}; + ch[i + l1ido] = cr2; + ch[i + l1ido + 1] = ci2; + + vcplxmul(cr3, ci3, ld_ps1(wr2), ld_ps1(wi2)); + float wr3{wa3[i]}, wi3{fsign*wa3[i+1]}; + ch[i + 2*l1ido] = cr3; + ch[i + 2*l1ido + 1] = ci3; + + vcplxmul(cr4, ci4, ld_ps1(wr3), ld_ps1(wi3)); + ch[i + 3*l1ido] = cr4; + ch[i + 3*l1ido + 1] = ci4; + } + } + } +} /* passf4 */ + +/* + * passf5 and passb5 has been merged here, fsign = -1 for passf5, +1 for passb5 + */ +NOINLINE void passf5_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, + const float *const wa1, const float fsign) +{ + const v4sf tr11{ld_ps1(0.309016994374947f)}; + const v4sf tr12{ld_ps1(-0.809016994374947f)}; + const v4sf ti11{ld_ps1(0.951056516295154f*fsign)}; + const v4sf ti12{ld_ps1(0.587785252292473f*fsign)}; + + auto cc_ref = [&cc,ido](size_t a_1, size_t a_2) noexcept -> auto& + { return cc[(a_2-1)*ido + a_1 + 1]; }; + auto ch_ref = [&ch,ido,l1](size_t a_1, size_t a_3) noexcept -> auto& + { return ch[(a_3-1)*l1*ido + a_1 + 1]; }; + + assert(ido > 2); + + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + const auto wa4 = wa3 + ido; + for(size_t k{0};k < l1;++k, cc += 5*ido, ch += ido) + { + for(size_t i{0};i < ido-1;i += 2) + { + v4sf ti5{vsub(cc_ref(i , 2), cc_ref(i , 5))}; + v4sf ti2{vadd(cc_ref(i , 2), cc_ref(i , 5))}; + v4sf ti4{vsub(cc_ref(i , 3), cc_ref(i , 4))}; + v4sf ti3{vadd(cc_ref(i , 3), cc_ref(i , 4))}; + v4sf tr5{vsub(cc_ref(i-1, 2), cc_ref(i-1, 5))}; + v4sf tr2{vadd(cc_ref(i-1, 2), cc_ref(i-1, 5))}; + v4sf tr4{vsub(cc_ref(i-1, 3), cc_ref(i-1, 4))}; + v4sf tr3{vadd(cc_ref(i-1, 3), cc_ref(i-1, 4))}; + ch_ref(i-1, 1) = vadd(cc_ref(i-1, 1), vadd(tr2, tr3)); + ch_ref(i , 1) = vadd(cc_ref(i , 1), vadd(ti2, ti3)); + v4sf cr2{vadd(cc_ref(i-1, 1), vmadd(tr11, tr2, vmul(tr12, tr3)))}; + v4sf ci2{vadd(cc_ref(i , 1), vmadd(tr11, ti2, vmul(tr12, ti3)))}; + v4sf cr3{vadd(cc_ref(i-1, 1), vmadd(tr12, tr2, vmul(tr11, tr3)))}; + v4sf ci3{vadd(cc_ref(i , 1), vmadd(tr12, ti2, vmul(tr11, ti3)))}; + v4sf cr5{vmadd(ti11, tr5, vmul(ti12, tr4))}; + v4sf ci5{vmadd(ti11, ti5, vmul(ti12, ti4))}; + v4sf cr4{vsub(vmul(ti12, tr5), vmul(ti11, tr4))}; + v4sf ci4{vsub(vmul(ti12, ti5), vmul(ti11, ti4))}; + v4sf dr3{vsub(cr3, ci4)}; + v4sf dr4{vadd(cr3, ci4)}; + v4sf di3{vadd(ci3, cr4)}; + v4sf di4{vsub(ci3, cr4)}; + v4sf dr5{vadd(cr2, ci5)}; + v4sf dr2{vsub(cr2, ci5)}; + v4sf di5{vsub(ci2, cr5)}; + v4sf di2{vadd(ci2, cr5)}; + float wr1{wa1[i]}, wi1{fsign*wa1[i+1]}, wr2{wa2[i]}, wi2{fsign*wa2[i+1]}; + float wr3{wa3[i]}, wi3{fsign*wa3[i+1]}, wr4{wa4[i]}, wi4{fsign*wa4[i+1]}; + vcplxmul(dr2, di2, ld_ps1(wr1), ld_ps1(wi1)); + ch_ref(i - 1, 2) = dr2; + ch_ref(i, 2) = di2; + vcplxmul(dr3, di3, ld_ps1(wr2), ld_ps1(wi2)); + ch_ref(i - 1, 3) = dr3; + ch_ref(i, 3) = di3; + vcplxmul(dr4, di4, ld_ps1(wr3), ld_ps1(wi3)); + ch_ref(i - 1, 4) = dr4; + ch_ref(i, 4) = di4; + vcplxmul(dr5, di5, ld_ps1(wr4), ld_ps1(wi4)); + ch_ref(i - 1, 5) = dr5; + ch_ref(i, 5) = di5; + } + } +} + +NOINLINE void radf2_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, + v4sf *RESTRICT ch, const float *const wa1) +{ + const size_t l1ido{l1*ido}; + for(size_t k{0};k < l1ido;k += ido) + { + v4sf a{cc[k]}, b{cc[k + l1ido]}; + ch[2*k] = vadd(a, b); + ch[2*(k+ido)-1] = vsub(a, b); + } + if(ido < 2) + return; + if(ido != 2) + { + for(size_t k{0};k < l1ido;k += ido) + { + for(size_t i{2};i < ido;i += 2) + { + v4sf tr2{cc[i - 1 + k + l1ido]}, ti2{cc[i + k + l1ido]}; + v4sf br{cc[i - 1 + k]}, bi{cc[i + k]}; + vcplxmulconj(tr2, ti2, ld_ps1(wa1[i - 2]), ld_ps1(wa1[i - 1])); + ch[i + 2*k] = vadd(bi, ti2); + ch[2*(k+ido) - i] = vsub(ti2, bi); + ch[i - 1 + 2*k] = vadd(br, tr2); + ch[2*(k+ido) - i -1] = vsub(br, tr2); + } + } + if((ido&1) == 1) + return; + } + const v4sf minus_one{ld_ps1(-1.0f)}; + for(size_t k{0};k < l1ido;k += ido) + { + ch[2*k + ido] = vmul(minus_one, cc[ido-1 + k + l1ido]); + ch[2*k + ido-1] = cc[k + ido-1]; + } +} /* radf2 */ + + +NOINLINE void radb2_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, + const float *const wa1) +{ + const size_t l1ido{l1*ido}; + for(size_t k{0};k < l1ido;k += ido) + { + v4sf a{cc[2*k]}; + v4sf b{cc[2*(k+ido) - 1]}; + ch[k] = vadd(a, b); + ch[k + l1ido] = vsub(a, b); + } + if(ido < 2) + return; + if(ido != 2) + { + for(size_t k{0};k < l1ido;k += ido) + { + for(size_t i{2};i < ido;i += 2) + { + v4sf a{cc[i-1 + 2*k]}; + v4sf b{cc[2*(k + ido) - i - 1]}; + v4sf c{cc[i+0 + 2*k]}; + v4sf d{cc[2*(k + ido) - i + 0]}; + ch[i-1 + k] = vadd(a, b); + v4sf tr2{vsub(a, b)}; + ch[i+0 + k] = vsub(c, d); + v4sf ti2{vadd(c, d)}; + vcplxmul(tr2, ti2, ld_ps1(wa1[i - 2]), ld_ps1(wa1[i - 1])); + ch[i-1 + k + l1ido] = tr2; + ch[i+0 + k + l1ido] = ti2; + } + } + if((ido&1) == 1) + return; + } + const v4sf minus_two{ld_ps1(-2.0f)}; + for(size_t k{0};k < l1ido;k += ido) + { + v4sf a{cc[2*k + ido-1]}; + v4sf b{cc[2*k + ido]}; + ch[k + ido-1] = vadd(a,a); + ch[k + ido-1 + l1ido] = vmul(minus_two, b); + } +} /* radb2 */ + +void radf3_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, + const float *const wa1) +{ + const v4sf taur{ld_ps1(-0.5f)}; + const v4sf taui{ld_ps1(0.866025403784439f)}; + for(size_t k{0};k < l1;++k) + { + v4sf cr2{vadd(cc[(k + l1)*ido], cc[(k + 2*l1)*ido])}; + ch[ (3*k )*ido] = vadd(cc[k*ido], cr2); + ch[ (3*k + 2)*ido] = vmul(taui, vsub(cc[(k + l1*2)*ido], cc[(k + l1)*ido])); + ch[ido-1 + (3*k + 1)*ido] = vmadd(taur, cr2, cc[k*ido]); + } + if(ido == 1) + return; + + const auto wa2 = wa1 + ido; + for(size_t k{0};k < l1;++k) + { + for(size_t i{2};i < ido;i += 2) + { + const size_t ic{ido - i}; + v4sf wr1{ld_ps1(wa1[i - 2])}; + v4sf wi1{ld_ps1(wa1[i - 1])}; + v4sf dr2{cc[i - 1 + (k + l1)*ido]}; + v4sf di2{cc[i + (k + l1)*ido]}; + vcplxmulconj(dr2, di2, wr1, wi1); + + v4sf wr2{ld_ps1(wa2[i - 2])}; + v4sf wi2{ld_ps1(wa2[i - 1])}; + v4sf dr3{cc[i - 1 + (k + l1*2)*ido]}; + v4sf di3{cc[i + (k + l1*2)*ido]}; + vcplxmulconj(dr3, di3, wr2, wi2); + + v4sf cr2{vadd(dr2, dr3)}; + v4sf ci2{vadd(di2, di3)}; + ch[i - 1 + 3*k*ido] = vadd(cc[i - 1 + k*ido], cr2); + ch[i + 3*k*ido] = vadd(cc[i + k*ido], ci2); + v4sf tr2{vmadd(taur, cr2, cc[i - 1 + k*ido])}; + v4sf ti2{vmadd(taur, ci2, cc[i + k*ido])}; + v4sf tr3{vmul(taui, vsub(di2, di3))}; + v4sf ti3{vmul(taui, vsub(dr3, dr2))}; + ch[i - 1 + (3*k + 2)*ido] = vadd(tr2, tr3); + ch[ic - 1 + (3*k + 1)*ido] = vsub(tr2, tr3); + ch[i + (3*k + 2)*ido] = vadd(ti2, ti3); + ch[ic + (3*k + 1)*ido] = vsub(ti3, ti2); + } + } +} /* radf3 */ + + +void radb3_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, + const float *const wa1) +{ + static constexpr float taur{-0.5f}; + static constexpr float taui{0.866025403784439f}; + static constexpr float taui_2{taui*2.0f}; + + const v4sf vtaur{ld_ps1(taur)}; + const v4sf vtaui_2{ld_ps1(taui_2)}; + for(size_t k{0};k < l1;++k) + { + v4sf tr2 = cc[ido-1 + (3*k + 1)*ido]; + tr2 = vadd(tr2,tr2); + v4sf cr2 = vmadd(vtaur, tr2, cc[3*k*ido]); + ch[k*ido] = vadd(cc[3*k*ido], tr2); + v4sf ci3 = vmul(vtaui_2, cc[(3*k + 2)*ido]); + ch[(k + l1)*ido] = vsub(cr2, ci3); + ch[(k + 2*l1)*ido] = vadd(cr2, ci3); + } + if(ido == 1) + return; + + const auto wa2 = wa1 + ido; + const v4sf vtaui{ld_ps1(taui)}; + for(size_t k{0};k < l1;++k) + { + for(size_t i{2};i < ido;i += 2) + { + const size_t ic{ido - i}; + v4sf tr2{vadd(cc[i - 1 + (3*k + 2)*ido], cc[ic - 1 + (3*k + 1)*ido])}; + v4sf cr2{vmadd(vtaur, tr2, cc[i - 1 + 3*k*ido])}; + ch[i - 1 + k*ido] = vadd(cc[i - 1 + 3*k*ido], tr2); + v4sf ti2{vsub(cc[i + (3*k + 2)*ido], cc[ic + (3*k + 1)*ido])}; + v4sf ci2{vmadd(vtaur, ti2, cc[i + 3*k*ido])}; + ch[i + k*ido] = vadd(cc[i + 3*k*ido], ti2); + v4sf cr3{vmul(vtaui, vsub(cc[i - 1 + (3*k + 2)*ido], cc[ic - 1 + (3*k + 1)*ido]))}; + v4sf ci3{vmul(vtaui, vadd(cc[i + (3*k + 2)*ido], cc[ic + (3*k + 1)*ido]))}; + v4sf dr2{vsub(cr2, ci3)}; + v4sf dr3{vadd(cr2, ci3)}; + v4sf di2{vadd(ci2, cr3)}; + v4sf di3{vsub(ci2, cr3)}; + vcplxmul(dr2, di2, ld_ps1(wa1[i-2]), ld_ps1(wa1[i-1])); + ch[i - 1 + (k + l1)*ido] = dr2; + ch[i + (k + l1)*ido] = di2; + vcplxmul(dr3, di3, ld_ps1(wa2[i-2]), ld_ps1(wa2[i-1])); + ch[i - 1 + (k + 2*l1)*ido] = dr3; + ch[i + (k + 2*l1)*ido] = di3; + } + } +} /* radb3 */ + +NOINLINE void radf4_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, + v4sf *RESTRICT ch, const float *const wa1) +{ + const size_t l1ido{l1*ido}; + { + const v4sf *RESTRICT cc_{cc}, *RESTRICT cc_end{cc + l1ido}; + v4sf *RESTRICT ch_{ch}; + while(cc != cc_end) + { + // this loop represents between 25% and 40% of total radf4_ps cost ! + v4sf a0{cc[0]}, a1{cc[l1ido]}; + v4sf a2{cc[2*l1ido]}, a3{cc[3*l1ido]}; + v4sf tr1{vadd(a1, a3)}; + v4sf tr2{vadd(a0, a2)}; + ch[2*ido-1] = vsub(a0, a2); + ch[2*ido ] = vsub(a3, a1); + ch[0 ] = vadd(tr1, tr2); + ch[4*ido-1] = vsub(tr2, tr1); + cc += ido; ch += 4*ido; + } + cc = cc_; + ch = ch_; + } + if(ido < 2) + return; + if(ido != 2) + { + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + + for(size_t k{0};k < l1ido;k += ido) + { + const v4sf *RESTRICT pc{cc + 1 + k}; + for(size_t i{2};i < ido;i += 2, pc += 2) + { + const size_t ic{ido - i}; + + v4sf cr2{pc[1*l1ido+0]}; + v4sf ci2{pc[1*l1ido+1]}; + v4sf wr{ld_ps1(wa1[i - 2])}; + v4sf wi{ld_ps1(wa1[i - 1])}; + vcplxmulconj(cr2,ci2,wr,wi); + + v4sf cr3{pc[2*l1ido+0]}; + v4sf ci3{pc[2*l1ido+1]}; + wr = ld_ps1(wa2[i-2]); + wi = ld_ps1(wa2[i-1]); + vcplxmulconj(cr3, ci3, wr, wi); + + v4sf cr4{pc[3*l1ido]}; + v4sf ci4{pc[3*l1ido+1]}; + wr = ld_ps1(wa3[i-2]); + wi = ld_ps1(wa3[i-1]); + vcplxmulconj(cr4, ci4, wr, wi); + + /* at this point, on SSE, five of "cr2 cr3 cr4 ci2 ci3 ci4" should be loaded in registers */ + + v4sf tr1{vadd(cr2,cr4)}; + v4sf tr4{vsub(cr4,cr2)}; + v4sf tr2{vadd(pc[0],cr3)}; + v4sf tr3{vsub(pc[0],cr3)}; + ch[i - 1 + 4*k ] = vadd(tr2,tr1); + ch[ic - 1 + 4*k + 3*ido] = vsub(tr2,tr1); // at this point tr1 and tr2 can be disposed + v4sf ti1{vadd(ci2,ci4)}; + v4sf ti4{vsub(ci2,ci4)}; + ch[i - 1 + 4*k + 2*ido] = vadd(tr3,ti4); + ch[ic - 1 + 4*k + 1*ido] = vsub(tr3,ti4); // dispose tr3, ti4 + v4sf ti2{vadd(pc[1],ci3)}; + v4sf ti3{vsub(pc[1],ci3)}; + ch[i + 4*k ] = vadd(ti1, ti2); + ch[ic + 4*k + 3*ido] = vsub(ti1, ti2); + ch[i + 4*k + 2*ido] = vadd(tr4, ti3); + ch[ic + 4*k + 1*ido] = vsub(tr4, ti3); + } + } + if((ido&1) == 1) + return; + } + const v4sf minus_hsqt2{ld_ps1(al::numbers::sqrt2_v * -0.5f)}; + for(size_t k{0};k < l1ido;k += ido) + { + v4sf a{cc[ido-1 + k + l1ido]}, b{cc[ido-1 + k + 3*l1ido]}; + v4sf c{cc[ido-1 + k]}, d{cc[ido-1 + k + 2*l1ido]}; + v4sf ti1{vmul(minus_hsqt2, vadd(b, a))}; + v4sf tr1{vmul(minus_hsqt2, vsub(b, a))}; + ch[ido-1 + 4*k ] = vadd(c, tr1); + ch[ido-1 + 4*k + 2*ido] = vsub(c, tr1); + ch[ 4*k + 1*ido] = vsub(ti1, d); + ch[ 4*k + 3*ido] = vadd(ti1, d); + } +} /* radf4 */ + + +NOINLINE void radb4_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, + v4sf *RESTRICT ch, const float *const wa1) +{ + const v4sf two{ld_ps1(2.0f)}; + const size_t l1ido{l1*ido}; + { + const v4sf *RESTRICT cc_{cc}, *RESTRICT ch_end{ch + l1ido}; + v4sf *ch_{ch}; + while(ch != ch_end) + { + v4sf a{cc[0]}, b{cc[4*ido-1]}; + v4sf c{cc[2*ido]}, d{cc[2*ido-1]}; + v4sf tr3{vmul(two,d)}; + v4sf tr2{vadd(a,b)}; + v4sf tr1{vsub(a,b)}; + v4sf tr4{vmul(two,c)}; + ch[0*l1ido] = vadd(tr2, tr3); + ch[2*l1ido] = vsub(tr2, tr3); + ch[1*l1ido] = vsub(tr1, tr4); + ch[3*l1ido] = vadd(tr1, tr4); + + cc += 4*ido; ch += ido; + } + cc = cc_; ch = ch_; + } + if(ido < 2) + return; + if(ido != 2) + { + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + + for(size_t k{0};k < l1ido;k += ido) + { + const v4sf *RESTRICT pc{cc - 1 + 4*k}; + v4sf *RESTRICT ph{ch + k + 1}; + for(size_t i{2};i < ido;i += 2) + { + v4sf tr1{vsub(pc[ i], pc[4*ido - i])}; + v4sf tr2{vadd(pc[ i], pc[4*ido - i])}; + v4sf ti4{vsub(pc[2*ido + i], pc[2*ido - i])}; + v4sf tr3{vadd(pc[2*ido + i], pc[2*ido - i])}; + ph[0] = vadd(tr2, tr3); + v4sf cr3{vsub(tr2, tr3)}; + + v4sf ti3{vsub(pc[2*ido + i + 1], pc[2*ido - i + 1])}; + v4sf tr4{vadd(pc[2*ido + i + 1], pc[2*ido - i + 1])}; + v4sf cr2{vsub(tr1, tr4)}; + v4sf cr4{vadd(tr1, tr4)}; + + v4sf ti1{vadd(pc[i + 1], pc[4*ido - i + 1])}; + v4sf ti2{vsub(pc[i + 1], pc[4*ido - i + 1])}; + + ph[1] = vadd(ti2, ti3); ph += l1ido; + v4sf ci3{vsub(ti2, ti3)}; + v4sf ci2{vadd(ti1, ti4)}; + v4sf ci4{vsub(ti1, ti4)}; + vcplxmul(cr2, ci2, ld_ps1(wa1[i-2]), ld_ps1(wa1[i-1])); + ph[0] = cr2; + ph[1] = ci2; ph += l1ido; + vcplxmul(cr3, ci3, ld_ps1(wa2[i-2]), ld_ps1(wa2[i-1])); + ph[0] = cr3; + ph[1] = ci3; ph += l1ido; + vcplxmul(cr4, ci4, ld_ps1(wa3[i-2]), ld_ps1(wa3[i-1])); + ph[0] = cr4; + ph[1] = ci4; ph = ph - 3*l1ido + 2; + } + } + if((ido&1) == 1) + return; + } + const v4sf minus_sqrt2{ld_ps1(-1.414213562373095f)}; + for(size_t k{0};k < l1ido;k += ido) + { + const size_t i0{4*k + ido}; + v4sf c{cc[i0-1]}, d{cc[i0 + 2*ido-1]}; + v4sf a{cc[i0+0]}, b{cc[i0 + 2*ido+0]}; + v4sf tr1{vsub(c,d)}; + v4sf tr2{vadd(c,d)}; + v4sf ti1{vadd(b,a)}; + v4sf ti2{vsub(b,a)}; + ch[ido-1 + k + 0*l1ido] = vadd(tr2,tr2); + ch[ido-1 + k + 1*l1ido] = vmul(minus_sqrt2, vsub(ti1, tr1)); + ch[ido-1 + k + 2*l1ido] = vadd(ti2, ti2); + ch[ido-1 + k + 3*l1ido] = vmul(minus_sqrt2, vadd(ti1, tr1)); + } +} /* radb4 */ + +void radf5_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, + const float *const wa1) +{ + const v4sf tr11{ld_ps1(0.309016994374947f)}; + const v4sf ti11{ld_ps1(0.951056516295154f)}; + const v4sf tr12{ld_ps1(-0.809016994374947f)}; + const v4sf ti12{ld_ps1(0.587785252292473f)}; + + auto cc_ref = [&cc,l1,ido](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& + { return cc[(a_3*l1 + a_2)*ido + a_1]; }; + auto ch_ref = [&ch,ido](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& + { return ch[(a_3*5 + a_2)*ido + a_1]; }; + + /* Parameter adjustments */ + ch -= 1 + ido * 6; + cc -= 1 + ido * (1 + l1); + + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + const auto wa4 = wa3 + ido; + + /* Function Body */ + for(size_t k{1};k <= l1;++k) + { + v4sf cr2{vadd(cc_ref(1, k, 5), cc_ref(1, k, 2))}; + v4sf ci5{vsub(cc_ref(1, k, 5), cc_ref(1, k, 2))}; + v4sf cr3{vadd(cc_ref(1, k, 4), cc_ref(1, k, 3))}; + v4sf ci4{vsub(cc_ref(1, k, 4), cc_ref(1, k, 3))}; + ch_ref(1, 1, k) = vadd(cc_ref(1, k, 1), vadd(cr2, cr3)); + ch_ref(ido, 2, k) = vadd(cc_ref(1, k, 1), vmadd(tr11, cr2, vmul(tr12, cr3))); + ch_ref(1, 3, k) = vmadd(ti11, ci5, vmul(ti12, ci4)); + ch_ref(ido, 4, k) = vadd(cc_ref(1, k, 1), vmadd(tr12, cr2, vmul(tr11, cr3))); + ch_ref(1, 5, k) = vsub(vmul(ti12, ci5), vmul(ti11, ci4)); + //printf("pffft: radf5, k=%d ch_ref=%f, ci4=%f\n", k, ch_ref(1, 5, k), ci4); + } + if(ido == 1) + return; + + const size_t idp2{ido + 2}; + for(size_t k{1};k <= l1;++k) + { + for(size_t i{3};i <= ido;i += 2) + { + const size_t ic{idp2 - i}; + v4sf dr2{ld_ps1(wa1[i-3])}; + v4sf di2{ld_ps1(wa1[i-2])}; + v4sf dr3{ld_ps1(wa2[i-3])}; + v4sf di3{ld_ps1(wa2[i-2])}; + v4sf dr4{ld_ps1(wa3[i-3])}; + v4sf di4{ld_ps1(wa3[i-2])}; + v4sf dr5{ld_ps1(wa4[i-3])}; + v4sf di5{ld_ps1(wa4[i-2])}; + vcplxmulconj(dr2, di2, cc_ref(i-1, k, 2), cc_ref(i, k, 2)); + vcplxmulconj(dr3, di3, cc_ref(i-1, k, 3), cc_ref(i, k, 3)); + vcplxmulconj(dr4, di4, cc_ref(i-1, k, 4), cc_ref(i, k, 4)); + vcplxmulconj(dr5, di5, cc_ref(i-1, k, 5), cc_ref(i, k, 5)); + v4sf cr2{vadd(dr2, dr5)}; + v4sf ci5{vsub(dr5, dr2)}; + v4sf cr5{vsub(di2, di5)}; + v4sf ci2{vadd(di2, di5)}; + v4sf cr3{vadd(dr3, dr4)}; + v4sf ci4{vsub(dr4, dr3)}; + v4sf cr4{vsub(di3, di4)}; + v4sf ci3{vadd(di3, di4)}; + ch_ref(i - 1, 1, k) = vadd(cc_ref(i - 1, k, 1), vadd(cr2, cr3)); + ch_ref(i, 1, k) = vsub(cc_ref(i, k, 1), vadd(ci2, ci3)); + v4sf tr2{vadd(cc_ref(i - 1, k, 1), vmadd(tr11, cr2, vmul(tr12, cr3)))}; + v4sf ti2{vsub(cc_ref(i, k, 1), vmadd(tr11, ci2, vmul(tr12, ci3)))}; + v4sf tr3{vadd(cc_ref(i - 1, k, 1), vmadd(tr12, cr2, vmul(tr11, cr3)))}; + v4sf ti3{vsub(cc_ref(i, k, 1), vmadd(tr12, ci2, vmul(tr11, ci3)))}; + v4sf tr5{vmadd(ti11, cr5, vmul(ti12, cr4))}; + v4sf ti5{vmadd(ti11, ci5, vmul(ti12, ci4))}; + v4sf tr4{vsub(vmul(ti12, cr5), vmul(ti11, cr4))}; + v4sf ti4{vsub(vmul(ti12, ci5), vmul(ti11, ci4))}; + ch_ref(i - 1, 3, k) = vsub(tr2, tr5); + ch_ref(ic - 1, 2, k) = vadd(tr2, tr5); + ch_ref(i , 3, k) = vadd(ti5, ti2); + ch_ref(ic , 2, k) = vsub(ti5, ti2); + ch_ref(i - 1, 5, k) = vsub(tr3, tr4); + ch_ref(ic - 1, 4, k) = vadd(tr3, tr4); + ch_ref(i , 5, k) = vadd(ti4, ti3); + ch_ref(ic , 4, k) = vsub(ti4, ti3); + } + } +} /* radf5 */ + +void radb5_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, + const float *const wa1) +{ + const v4sf tr11{ld_ps1(0.309016994374947f)}; + const v4sf ti11{ld_ps1(0.951056516295154f)}; + const v4sf tr12{ld_ps1(-0.809016994374947f)}; + const v4sf ti12{ld_ps1(0.587785252292473f)}; + + auto cc_ref = [&cc,ido](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& + { return cc[(a_3*5 + a_2)*ido + a_1]; }; + auto ch_ref = [&ch,ido,l1](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& + { return ch[(a_3*l1 + a_2)*ido + a_1]; }; + + /* Parameter adjustments */ + ch -= 1 + ido*(1 + l1); + cc -= 1 + ido*6; + + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + const auto wa4 = wa3 + ido; + + /* Function Body */ + for(size_t k{1};k <= l1;++k) + { + v4sf ti5{vadd(cc_ref( 1, 3, k), cc_ref(1, 3, k))}; + v4sf ti4{vadd(cc_ref( 1, 5, k), cc_ref(1, 5, k))}; + v4sf tr2{vadd(cc_ref(ido, 2, k), cc_ref(ido, 2, k))}; + v4sf tr3{vadd(cc_ref(ido, 4, k), cc_ref(ido, 4, k))}; + ch_ref(1, k, 1) = vadd(cc_ref(1, 1, k), vadd(tr2, tr3)); + v4sf cr2{vadd(cc_ref(1, 1, k), vmadd(tr11, tr2, vmul(tr12, tr3)))}; + v4sf cr3{vadd(cc_ref(1, 1, k), vmadd(tr12, tr2, vmul(tr11, tr3)))}; + v4sf ci5{vmadd(ti11, ti5, vmul(ti12, ti4))}; + v4sf ci4{vsub(vmul(ti12, ti5), vmul(ti11, ti4))}; + ch_ref(1, k, 2) = vsub(cr2, ci5); + ch_ref(1, k, 3) = vsub(cr3, ci4); + ch_ref(1, k, 4) = vadd(cr3, ci4); + ch_ref(1, k, 5) = vadd(cr2, ci5); + } + if(ido == 1) + return; + + const size_t idp2{ido + 2}; + for(size_t k{1};k <= l1;++k) + { + for(size_t i{3};i <= ido;i += 2) + { + const size_t ic{idp2 - i}; + v4sf ti5{vadd(cc_ref(i , 3, k), cc_ref(ic , 2, k))}; + v4sf ti2{vsub(cc_ref(i , 3, k), cc_ref(ic , 2, k))}; + v4sf ti4{vadd(cc_ref(i , 5, k), cc_ref(ic , 4, k))}; + v4sf ti3{vsub(cc_ref(i , 5, k), cc_ref(ic , 4, k))}; + v4sf tr5{vsub(cc_ref(i-1, 3, k), cc_ref(ic-1, 2, k))}; + v4sf tr2{vadd(cc_ref(i-1, 3, k), cc_ref(ic-1, 2, k))}; + v4sf tr4{vsub(cc_ref(i-1, 5, k), cc_ref(ic-1, 4, k))}; + v4sf tr3{vadd(cc_ref(i-1, 5, k), cc_ref(ic-1, 4, k))}; + ch_ref(i - 1, k, 1) = vadd(cc_ref(i-1, 1, k), vadd(tr2, tr3)); + ch_ref(i , k, 1) = vadd(cc_ref(i , 1, k), vadd(ti2, ti3)); + v4sf cr2{vadd(cc_ref(i-1, 1, k), vmadd(tr11, tr2, vmul(tr12, tr3)))}; + v4sf ci2{vadd(cc_ref(i , 1, k), vmadd(tr11, ti2, vmul(tr12, ti3)))}; + v4sf cr3{vadd(cc_ref(i-1, 1, k), vmadd(tr12, tr2, vmul(tr11, tr3)))}; + v4sf ci3{vadd(cc_ref(i , 1, k), vmadd(tr12, ti2, vmul(tr11, ti3)))}; + v4sf cr5{vmadd(ti11, tr5, vmul(ti12, tr4))}; + v4sf ci5{vmadd(ti11, ti5, vmul(ti12, ti4))}; + v4sf cr4{vsub(vmul(ti12, tr5), vmul(ti11, tr4))}; + v4sf ci4{vsub(vmul(ti12, ti5), vmul(ti11, ti4))}; + v4sf dr3{vsub(cr3, ci4)}; + v4sf dr4{vadd(cr3, ci4)}; + v4sf di3{vadd(ci3, cr4)}; + v4sf di4{vsub(ci3, cr4)}; + v4sf dr5{vadd(cr2, ci5)}; + v4sf dr2{vsub(cr2, ci5)}; + v4sf di5{vsub(ci2, cr5)}; + v4sf di2{vadd(ci2, cr5)}; + vcplxmul(dr2, di2, ld_ps1(wa1[i-3]), ld_ps1(wa1[i-2])); + vcplxmul(dr3, di3, ld_ps1(wa2[i-3]), ld_ps1(wa2[i-2])); + vcplxmul(dr4, di4, ld_ps1(wa3[i-3]), ld_ps1(wa3[i-2])); + vcplxmul(dr5, di5, ld_ps1(wa4[i-3]), ld_ps1(wa4[i-2])); + + ch_ref(i-1, k, 2) = dr2; ch_ref(i, k, 2) = di2; + ch_ref(i-1, k, 3) = dr3; ch_ref(i, k, 3) = di3; + ch_ref(i-1, k, 4) = dr4; ch_ref(i, k, 4) = di4; + ch_ref(i-1, k, 5) = dr5; ch_ref(i, k, 5) = di5; + } + } +} /* radb5 */ + +NOINLINE v4sf *rfftf1_ps(const size_t n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, + const float *wa, const al::span ifac) +{ + assert(work1 != work2); + + const v4sf *in{input_readonly}; + v4sf *out{in == work2 ? work1 : work2}; + const size_t nf{ifac[1]}; + size_t l2{n}; + size_t iw{n-1}; + size_t k1{1}; + while(true) + { + const size_t kh{nf - k1}; + const size_t ip{ifac[kh + 2]}; + const size_t l1{l2 / ip}; + const size_t ido{n / l2}; + iw -= (ip - 1)*ido; + switch(ip) + { + case 5: + radf5_ps(ido, l1, in, out, &wa[iw]); + break; + case 4: + radf4_ps(ido, l1, in, out, &wa[iw]); + break; + case 3: + radf3_ps(ido, l1, in, out, &wa[iw]); + break; + case 2: + radf2_ps(ido, l1, in, out, &wa[iw]); + break; + default: + assert(0); + } + if(++k1 > nf) + return out; + + l2 = l1; + if(out == work2) + { + out = work1; + in = work2; + } + else + { + out = work2; + in = work1; + } + } +} /* rfftf1 */ + +NOINLINE v4sf *rfftb1_ps(const size_t n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, + const float *wa, const al::span ifac) +{ + assert(work1 != work2); + + const v4sf *in{input_readonly}; + v4sf *out{in == work2 ? work1 : work2}; + const size_t nf{ifac[1]}; + size_t l1{1}; + size_t iw{0}; + size_t k1{1}; + while(true) + { + const size_t ip{ifac[k1 + 1]}; + const size_t l2{ip*l1}; + const size_t ido{n / l2}; + switch(ip) + { + case 5: + radb5_ps(ido, l1, in, out, &wa[iw]); + break; + case 4: + radb4_ps(ido, l1, in, out, &wa[iw]); + break; + case 3: + radb3_ps(ido, l1, in, out, &wa[iw]); + break; + case 2: + radb2_ps(ido, l1, in, out, &wa[iw]); + break; + default: + assert(0); + } + if(++k1 > nf) + return out; + + l1 = l2; + iw += (ip - 1)*ido; + + if(out == work2) + { + out = work1; + in = work2; + } + else + { + out = work2; + in = work1; + } + } +} + +v4sf *cfftf1_ps(const size_t n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, + const float *wa, const al::span ifac, const float fsign) +{ + assert(work1 != work2); + + const v4sf *in{input_readonly}; + v4sf *out{in == work2 ? work1 : work2}; + const size_t nf{ifac[1]}; + size_t l1{1}, iw{0}; + size_t k1{2}; + while(true) + { + const size_t ip{ifac[k1]}; + const size_t l2{ip*l1}; + const size_t ido{n / l2}; + const size_t idot{ido + ido}; + switch(ip) + { + case 5: + passf5_ps(idot, l1, in, out, &wa[iw], fsign); + break; + case 4: + passf4_ps(idot, l1, in, out, &wa[iw], fsign); + break; + case 3: + passf3_ps(idot, l1, in, out, &wa[iw], fsign); + break; + case 2: + passf2_ps(idot, l1, in, out, &wa[iw], fsign); + break; + default: + assert(0); + } + if(++k1 > nf+1) + return out; + + l1 = l2; + iw += (ip - 1)*idot; + if(out == work2) + { + out = work1; + in = work2; + } + else + { + out = work2; + in = work1; + } + } +} + + +uint decompose(const uint n, const al::span ifac, const al::span ntryh) +{ + uint nl{n}, nf{0}; + for(const uint ntry : ntryh) + { + while(nl != 1) + { + const uint nq{nl / ntry}; + const uint nr{nl % ntry}; + if(nr != 0) break; + + ifac[2+nf++] = ntry; + nl = nq; + if(ntry == 2 && nf != 1) + { + for(size_t i{2};i <= nf;++i) + { + size_t ib{nf - i + 2}; + ifac[ib + 1] = ifac[ib]; + } + ifac[2] = 2; + } + } + } + ifac[0] = n; + ifac[1] = nf; + return nf; +} + +void rffti1_ps(const uint n, float *wa, const al::span ifac) +{ + static constexpr std::array ntryh{4u,2u,3u,5u}; + + const uint nf{decompose(n, ifac, ntryh)}; + const double argh{2.0*al::numbers::pi / n}; + size_t is{0}; + size_t nfm1{nf - 1}; + size_t l1{1}; + for(size_t k1{0};k1 < nfm1;++k1) + { + const size_t ip{ifac[k1+2]}; + const size_t l2{l1*ip}; + const size_t ido{n / l2}; + const size_t ipm{ip - 1}; + size_t ld{0}; + for(size_t j{0};j < ipm;++j) + { + size_t i{is}; + ld += l1; + const double argld{static_cast(ld)*argh}; + double fi{0.0}; + for(size_t ii{2};ii < ido;ii += 2) + { + fi += 1.0; + wa[i++] = static_cast(std::cos(fi*argld)); + wa[i++] = static_cast(std::sin(fi*argld)); + } + is += ido; + } + l1 = l2; + } +} /* rffti1 */ + +void cffti1_ps(const uint n, float *wa, const al::span ifac) +{ + static constexpr std::array ntryh{5u,3u,4u,2u}; + + const uint nf{decompose(n, ifac, ntryh)}; + const double argh{2.0*al::numbers::pi / n}; + size_t i{1}; + size_t l1{1}; + for(size_t k1{0};k1 < nf;++k1) + { + const size_t ip{ifac[k1+2]}; + const size_t l2{l1*ip}; + const size_t ido{n / l2}; + const size_t idot{ido + ido + 2}; + const size_t ipm{ip - 1}; + size_t ld{0}; + for(size_t j{0};j < ipm;++j) + { + size_t i1{i}; + wa[i-1] = 1; + wa[i] = 0; + ld += l1; + const double argld{static_cast(ld)*argh}; + double fi{0.0}; + for(size_t ii{3};ii < idot;ii += 2) + { + fi += 1.0; + wa[++i] = static_cast(std::cos(fi*argld)); + wa[++i] = static_cast(std::sin(fi*argld)); + } + if(ip > 5) + { + wa[i1-1] = wa[i-1]; + wa[i1] = wa[i]; + } + } + l1 = l2; + } +} /* cffti1 */ + +} // namespace + +/* NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) */ +struct PFFFT_Setup { + uint N{}; + uint Ncvec{}; /* nb of complex simd vectors (N/4 if PFFFT_COMPLEX, N/8 if PFFFT_REAL) */ + std::array ifac{}; + pffft_transform_t transform{}; + + float *twiddle{}; /* N/4 elements */ + al::span e; /* N/4*3 elements */ + + alignas(V4sfAlignment) std::byte end; +}; + +PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform) +{ + assert(transform == PFFFT_REAL || transform == PFFFT_COMPLEX); + assert(N > 0); + /* unfortunately, the fft size must be a multiple of 16 for complex FFTs + * and 32 for real FFTs -- a lot of stuff would need to be rewritten to + * handle other cases (or maybe just switch to a scalar fft, I don't know..) + */ + if(transform == PFFFT_REAL) + assert((N%(2*SimdSize*SimdSize)) == 0); + else + assert((N%(SimdSize*SimdSize)) == 0); + + const uint Ncvec{(transform == PFFFT_REAL ? N/2 : N) / SimdSize}; + + const size_t storelen{std::max(offsetof(PFFFT_Setup, end) + 2_zu*Ncvec*sizeof(v4sf), + sizeof(PFFFT_Setup))}; + auto storage = static_cast>(::operator new[](storelen, V4sfAlignVal)); + al::span extrastore{&storage[offsetof(PFFFT_Setup, end)], 2_zu*Ncvec*sizeof(v4sf)}; + + PFFFTSetupPtr s{::new(storage) PFFFT_Setup{}}; + s->N = N; + s->transform = transform; + s->Ncvec = Ncvec; + + const size_t ecount{2_zu*Ncvec*(SimdSize-1)/SimdSize}; + s->e = {std::launder(reinterpret_cast(extrastore.data())), ecount}; + s->twiddle = std::launder(reinterpret_cast(&extrastore[ecount*sizeof(v4sf)])); + + if constexpr(SimdSize > 1) + { + auto e = std::vector(s->e.size()*SimdSize, 0.0f); + for(size_t k{0};k < s->Ncvec;++k) + { + const size_t i{k / SimdSize}; + const size_t j{k % SimdSize}; + for(size_t m{0};m < SimdSize-1;++m) + { + const double A{-2.0*al::numbers::pi*static_cast((m+1)*k) / N}; + e[((i*3 + m)*2 + 0)*SimdSize + j] = static_cast(std::cos(A)); + e[((i*3 + m)*2 + 1)*SimdSize + j] = static_cast(std::sin(A)); + } + } + std::memcpy(s->e.data(), e.data(), e.size()*sizeof(float)); + } + if(transform == PFFFT_REAL) + rffti1_ps(N/SimdSize, s->twiddle, s->ifac); + else + cffti1_ps(N/SimdSize, s->twiddle, s->ifac); + + /* check that N is decomposable with allowed prime factors */ + size_t m{1}; + for(size_t k{0};k < s->ifac[1];++k) + m *= s->ifac[2+k]; + + if(m != N/SimdSize) + s = nullptr; + + return s; +} + + +void pffft_destroy_setup(gsl::owner s) noexcept +{ + std::destroy_at(s); + ::operator delete[](gsl::owner{s}, V4sfAlignVal); +} + +#if !defined(PFFFT_SIMD_DISABLE) + +namespace { + +/* [0 0 1 2 3 4 5 6 7 8] -> [0 8 7 6 5 4 3 2 1] */ +void reversed_copy(const size_t N, const v4sf *in, const int in_stride, v4sf *RESTRICT out) +{ + v4sf g0, g1; + interleave2(in[0], in[1], g0, g1); + in += in_stride; + + *--out = vswaphl(g0, g1); // [g0l, g0h], [g1l g1h] -> [g1l, g0h] + for(size_t k{1};k < N;++k) + { + v4sf h0, h1; + interleave2(in[0], in[1], h0, h1); + in += in_stride; + *--out = vswaphl(g1, h0); + *--out = vswaphl(h0, h1); + g1 = h1; + } + *--out = vswaphl(g1, g0); +} + +void unreversed_copy(const size_t N, const v4sf *in, v4sf *RESTRICT out, const int out_stride) +{ + v4sf g0{in[0]}, g1{g0}; + ++in; + for(size_t k{1};k < N;++k) + { + v4sf h0{*in++}; v4sf h1{*in++}; + g1 = vswaphl(g1, h0); + h0 = vswaphl(h0, h1); + uninterleave2(h0, g1, out[0], out[1]); + out += out_stride; + g1 = h1; + } + v4sf h0{*in++}, h1{g0}; + g1 = vswaphl(g1, h0); + h0 = vswaphl(h0, h1); + uninterleave2(h0, g1, out[0], out[1]); +} + +void pffft_cplx_finalize(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, const v4sf *e) +{ + assert(in != out); + + const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks + for(size_t k{0};k < dk;++k) + { + v4sf r0{in[8*k+0]}, i0{in[8*k+1]}; + v4sf r1{in[8*k+2]}, i1{in[8*k+3]}; + v4sf r2{in[8*k+4]}, i2{in[8*k+5]}; + v4sf r3{in[8*k+6]}, i3{in[8*k+7]}; + vtranspose4(r0,r1,r2,r3); + vtranspose4(i0,i1,i2,i3); + vcplxmul(r1,i1,e[k*6+0],e[k*6+1]); + vcplxmul(r2,i2,e[k*6+2],e[k*6+3]); + vcplxmul(r3,i3,e[k*6+4],e[k*6+5]); + + v4sf sr0{vadd(r0,r2)}, dr0{vsub(r0, r2)}; + v4sf sr1{vadd(r1,r3)}, dr1{vsub(r1, r3)}; + v4sf si0{vadd(i0,i2)}, di0{vsub(i0, i2)}; + v4sf si1{vadd(i1,i3)}, di1{vsub(i1, i3)}; + + /* transformation for each column is: + * + * [1 1 1 1 0 0 0 0] [r0] + * [1 0 -1 0 0 -1 0 1] [r1] + * [1 -1 1 -1 0 0 0 0] [r2] + * [1 0 -1 0 0 1 0 -1] [r3] + * [0 0 0 0 1 1 1 1] * [i0] + * [0 1 0 -1 1 0 -1 0] [i1] + * [0 0 0 0 1 -1 1 -1] [i2] + * [0 -1 0 1 1 0 -1 0] [i3] + */ + + r0 = vadd(sr0, sr1); i0 = vadd(si0, si1); + r1 = vadd(dr0, di1); i1 = vsub(di0, dr1); + r2 = vsub(sr0, sr1); i2 = vsub(si0, si1); + r3 = vsub(dr0, di1); i3 = vadd(di0, dr1); + + *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; + *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; + } +} + +void pffft_cplx_preprocess(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, const v4sf *e) +{ + assert(in != out); + + const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks + for(size_t k{0};k < dk;++k) + { + v4sf r0{in[8*k+0]}, i0{in[8*k+1]}; + v4sf r1{in[8*k+2]}, i1{in[8*k+3]}; + v4sf r2{in[8*k+4]}, i2{in[8*k+5]}; + v4sf r3{in[8*k+6]}, i3{in[8*k+7]}; + + v4sf sr0{vadd(r0,r2)}, dr0{vsub(r0, r2)}; + v4sf sr1{vadd(r1,r3)}, dr1{vsub(r1, r3)}; + v4sf si0{vadd(i0,i2)}, di0{vsub(i0, i2)}; + v4sf si1{vadd(i1,i3)}, di1{vsub(i1, i3)}; + + r0 = vadd(sr0, sr1); i0 = vadd(si0, si1); + r1 = vsub(dr0, di1); i1 = vadd(di0, dr1); + r2 = vsub(sr0, sr1); i2 = vsub(si0, si1); + r3 = vadd(dr0, di1); i3 = vsub(di0, dr1); + + vcplxmulconj(r1,i1,e[k*6+0],e[k*6+1]); + vcplxmulconj(r2,i2,e[k*6+2],e[k*6+3]); + vcplxmulconj(r3,i3,e[k*6+4],e[k*6+5]); + + vtranspose4(r0,r1,r2,r3); + vtranspose4(i0,i1,i2,i3); + + *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; + *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; + } +} + + +force_inline void pffft_real_finalize_4x4(const v4sf *in0, const v4sf *in1, const v4sf *in, + const v4sf *e, v4sf *RESTRICT out) +{ + v4sf r0{*in0}, i0{*in1}; + v4sf r1{*in++}; v4sf i1{*in++}; + v4sf r2{*in++}; v4sf i2{*in++}; + v4sf r3{*in++}; v4sf i3{*in++}; + vtranspose4(r0,r1,r2,r3); + vtranspose4(i0,i1,i2,i3); + + /* transformation for each column is: + * + * [1 1 1 1 0 0 0 0] [r0] + * [1 0 -1 0 0 -1 0 1] [r1] + * [1 0 -1 0 0 1 0 -1] [r2] + * [1 -1 1 -1 0 0 0 0] [r3] + * [0 0 0 0 1 1 1 1] * [i0] + * [0 -1 0 1 -1 0 1 0] [i1] + * [0 -1 0 1 1 0 -1 0] [i2] + * [0 0 0 0 -1 1 -1 1] [i3] + */ + + //cerr << "matrix initial, before e , REAL:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; + //cerr << "matrix initial, before e, IMAG :\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; + + vcplxmul(r1,i1,e[0],e[1]); + vcplxmul(r2,i2,e[2],e[3]); + vcplxmul(r3,i3,e[4],e[5]); + + //cerr << "matrix initial, real part:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; + //cerr << "matrix initial, imag part:\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; + + v4sf sr0{vadd(r0,r2)}, dr0{vsub(r0,r2)}; + v4sf sr1{vadd(r1,r3)}, dr1{vsub(r3,r1)}; + v4sf si0{vadd(i0,i2)}, di0{vsub(i0,i2)}; + v4sf si1{vadd(i1,i3)}, di1{vsub(i3,i1)}; + + r0 = vadd(sr0, sr1); + r3 = vsub(sr0, sr1); + i0 = vadd(si0, si1); + i3 = vsub(si1, si0); + r1 = vadd(dr0, di1); + r2 = vsub(dr0, di1); + i1 = vsub(dr1, di0); + i2 = vadd(dr1, di0); + + *out++ = r0; + *out++ = i0; + *out++ = r1; + *out++ = i1; + *out++ = r2; + *out++ = i2; + *out++ = r3; + *out++ = i3; +} + +NOINLINE void pffft_real_finalize(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, + const v4sf *e) +{ + static constexpr float s{al::numbers::sqrt2_v/2.0f}; + + assert(in != out); + const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks + /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ + + const v4sf zero{vzero()}; + const auto cr = al::bit_cast>(in[0]); + const auto ci = al::bit_cast>(in[Ncvec*2-1]); + pffft_real_finalize_4x4(&zero, &zero, in+1, e, out); + + /* [cr0 cr1 cr2 cr3 ci0 ci1 ci2 ci3] + * + * [Xr(1)] ] [1 1 1 1 0 0 0 0] + * [Xr(N/4) ] [0 0 0 0 1 s 0 -s] + * [Xr(N/2) ] [1 0 -1 0 0 0 0 0] + * [Xr(3N/4)] [0 0 0 0 1 -s 0 s] + * [Xi(1) ] [1 -1 1 -1 0 0 0 0] + * [Xi(N/4) ] [0 0 0 0 0 -s -1 -s] + * [Xi(N/2) ] [0 -1 0 1 0 0 0 0] + * [Xi(3N/4)] [0 0 0 0 0 -s 1 -s] + */ + + const float xr0{(cr[0]+cr[2]) + (cr[1]+cr[3])}; out[0] = vinsert0(out[0], xr0); + const float xi0{(cr[0]+cr[2]) - (cr[1]+cr[3])}; out[1] = vinsert0(out[1], xi0); + const float xr2{(cr[0]-cr[2])}; out[4] = vinsert0(out[4], xr2); + const float xi2{(cr[3]-cr[1])}; out[5] = vinsert0(out[5], xi2); + const float xr1{ ci[0] + s*(ci[1]-ci[3])}; out[2] = vinsert0(out[2], xr1); + const float xi1{-ci[2] - s*(ci[1]+ci[3])}; out[3] = vinsert0(out[3], xi1); + const float xr3{ ci[0] - s*(ci[1]-ci[3])}; out[6] = vinsert0(out[6], xr3); + const float xi3{ ci[2] - s*(ci[1]+ci[3])}; out[7] = vinsert0(out[7], xi3); + + for(size_t k{1};k < dk;++k) + pffft_real_finalize_4x4(&in[8*k-1], &in[8*k+0], in + 8*k+1, e + k*6, out + k*8); +} + +force_inline void pffft_real_preprocess_4x4(const v4sf *in, const v4sf *e, v4sf *RESTRICT out, + const bool first) +{ + v4sf r0{in[0]}, i0{in[1]}, r1{in[2]}, i1{in[3]}; + v4sf r2{in[4]}, i2{in[5]}, r3{in[6]}, i3{in[7]}; + + /* transformation for each column is: + * + * [1 1 1 1 0 0 0 0] [r0] + * [1 0 0 -1 0 -1 -1 0] [r1] + * [1 -1 -1 1 0 0 0 0] [r2] + * [1 0 0 -1 0 1 1 0] [r3] + * [0 0 0 0 1 -1 1 -1] * [i0] + * [0 -1 1 0 1 0 0 1] [i1] + * [0 0 0 0 1 1 -1 -1] [i2] + * [0 1 -1 0 1 0 0 1] [i3] + */ + + v4sf sr0{vadd(r0,r3)}, dr0{vsub(r0,r3)}; + v4sf sr1{vadd(r1,r2)}, dr1{vsub(r1,r2)}; + v4sf si0{vadd(i0,i3)}, di0{vsub(i0,i3)}; + v4sf si1{vadd(i1,i2)}, di1{vsub(i1,i2)}; + + r0 = vadd(sr0, sr1); + r2 = vsub(sr0, sr1); + r1 = vsub(dr0, si1); + r3 = vadd(dr0, si1); + i0 = vsub(di0, di1); + i2 = vadd(di0, di1); + i1 = vsub(si0, dr1); + i3 = vadd(si0, dr1); + + vcplxmulconj(r1,i1,e[0],e[1]); + vcplxmulconj(r2,i2,e[2],e[3]); + vcplxmulconj(r3,i3,e[4],e[5]); + + vtranspose4(r0,r1,r2,r3); + vtranspose4(i0,i1,i2,i3); + + if(!first) + { + *out++ = r0; + *out++ = i0; + } + *out++ = r1; + *out++ = i1; + *out++ = r2; + *out++ = i2; + *out++ = r3; + *out++ = i3; +} + +NOINLINE void pffft_real_preprocess(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, + const v4sf *e) +{ + static constexpr float sqrt2{al::numbers::sqrt2_v}; + + assert(in != out); + const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks + /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ + + std::array Xr{}, Xi{}; + for(size_t k{0};k < SimdSize;++k) + { + Xr[k] = vextract0(in[2*k]); + Xi[k] = vextract0(in[2*k + 1]); + } + + pffft_real_preprocess_4x4(in, e, out+1, true); // will write only 6 values + + /* [Xr0 Xr1 Xr2 Xr3 Xi0 Xi1 Xi2 Xi3] + * + * [cr0] [1 0 2 0 1 0 0 0] + * [cr1] [1 0 0 0 -1 0 -2 0] + * [cr2] [1 0 -2 0 1 0 0 0] + * [cr3] [1 0 0 0 -1 0 2 0] + * [ci0] [0 2 0 2 0 0 0 0] + * [ci1] [0 s 0 -s 0 -s 0 -s] + * [ci2] [0 0 0 0 0 -2 0 2] + * [ci3] [0 -s 0 s 0 -s 0 -s] + */ + for(size_t k{1};k < dk;++k) + pffft_real_preprocess_4x4(in+8*k, e + k*6, out-1+k*8, false); + + const float cr0{(Xr[0]+Xi[0]) + 2*Xr[2]}; + const float cr1{(Xr[0]-Xi[0]) - 2*Xi[2]}; + const float cr2{(Xr[0]+Xi[0]) - 2*Xr[2]}; + const float cr3{(Xr[0]-Xi[0]) + 2*Xi[2]}; + out[0] = vset4(cr0, cr1, cr2, cr3); + const float ci0{ 2*(Xr[1]+Xr[3])}; + const float ci1{ sqrt2*(Xr[1]-Xr[3]) - sqrt2*(Xi[1]+Xi[3])}; + const float ci2{ 2*(Xi[3]-Xi[1])}; + const float ci3{-sqrt2*(Xr[1]-Xr[3]) - sqrt2*(Xi[1]+Xi[3])}; + out[2*Ncvec-1] = vset4(ci0, ci1, ci2, ci3); +} + + +void pffft_transform_internal(const PFFFT_Setup *setup, const v4sf *vinput, v4sf *voutput, + v4sf *scratch, const pffft_direction_t direction, const bool ordered) +{ + assert(scratch != nullptr); + assert(voutput != scratch); + + const size_t Ncvec{setup->Ncvec}; + const bool nf_odd{(setup->ifac[1]&1) != 0}; + + std::array buff{voutput, scratch}; + bool ib{nf_odd != ordered}; + if(direction == PFFFT_FORWARD) + { + /* Swap the initial work buffer for forward FFTs, which helps avoid an + * extra copy for output. + */ + ib = !ib; + if(setup->transform == PFFFT_REAL) + { + ib = (rfftf1_ps(Ncvec*2, vinput, buff[ib], buff[!ib], setup->twiddle, setup->ifac) == buff[1]); + pffft_real_finalize(Ncvec, buff[ib], buff[!ib], setup->e.data()); + } + else + { + v4sf *tmp{buff[ib]}; + for(size_t k=0; k < Ncvec; ++k) + uninterleave2(vinput[k*2], vinput[k*2+1], tmp[k*2], tmp[k*2+1]); + + ib = (cfftf1_ps(Ncvec, buff[ib], buff[!ib], buff[ib], setup->twiddle, setup->ifac, -1.0f) == buff[1]); + pffft_cplx_finalize(Ncvec, buff[ib], buff[!ib], setup->e.data()); + } + if(ordered) + pffft_zreorder(setup, reinterpret_cast(buff[!ib]), + reinterpret_cast(buff[ib]), PFFFT_FORWARD); + else + ib = !ib; + } + else + { + if(vinput == buff[ib]) + ib = !ib; // may happen when finput == foutput + + if(ordered) + { + pffft_zreorder(setup, reinterpret_cast(vinput), + reinterpret_cast(buff[ib]), PFFFT_BACKWARD); + vinput = buff[ib]; + ib = !ib; + } + if(setup->transform == PFFFT_REAL) + { + pffft_real_preprocess(Ncvec, vinput, buff[ib], setup->e.data()); + ib = (rfftb1_ps(Ncvec*2, buff[ib], buff[0], buff[1], setup->twiddle, setup->ifac) == buff[1]); + } + else + { + pffft_cplx_preprocess(Ncvec, vinput, buff[ib], setup->e.data()); + ib = (cfftf1_ps(Ncvec, buff[ib], buff[0], buff[1], setup->twiddle, setup->ifac, +1.0f) == buff[1]); + for(size_t k{0};k < Ncvec;++k) + interleave2(buff[ib][k*2], buff[ib][k*2+1], buff[ib][k*2], buff[ib][k*2+1]); + } + } + + if(buff[ib] != voutput) + { + /* extra copy required -- this situation should only happen when finput == foutput */ + assert(vinput==voutput); + for(size_t k{0};k < Ncvec;++k) + { + v4sf a{buff[ib][2*k]}, b{buff[ib][2*k+1]}; + voutput[2*k] = a; voutput[2*k+1] = b; + } + } +} + +} // namespace + +void pffft_zreorder(const PFFFT_Setup *setup, const float *in, float *out, + pffft_direction_t direction) +{ + assert(in != out); + + const size_t N{setup->N}, Ncvec{setup->Ncvec}; + const v4sf *vin{reinterpret_cast(in)}; + v4sf *RESTRICT vout{reinterpret_cast(out)}; + if(setup->transform == PFFFT_REAL) + { + const size_t dk{N/32}; + if(direction == PFFFT_FORWARD) + { + for(size_t k{0};k < dk;++k) + { + interleave2(vin[k*8 + 0], vin[k*8 + 1], vout[2*(0*dk + k) + 0], vout[2*(0*dk + k) + 1]); + interleave2(vin[k*8 + 4], vin[k*8 + 5], vout[2*(2*dk + k) + 0], vout[2*(2*dk + k) + 1]); + } + reversed_copy(dk, vin+2, 8, vout + N/SimdSize/2); + reversed_copy(dk, vin+6, 8, vout + N/SimdSize); + } + else + { + for(size_t k{0};k < dk;++k) + { + uninterleave2(vin[2*(0*dk + k) + 0], vin[2*(0*dk + k) + 1], vout[k*8 + 0], vout[k*8 + 1]); + uninterleave2(vin[2*(2*dk + k) + 0], vin[2*(2*dk + k) + 1], vout[k*8 + 4], vout[k*8 + 5]); + } + unreversed_copy(dk, vin + N/SimdSize/4, vout + N/SimdSize - 6, -8); + unreversed_copy(dk, vin + 3_uz*N/SimdSize/4, vout + N/SimdSize - 2, -8); + } + } + else + { + if(direction == PFFFT_FORWARD) + { + for(size_t k{0};k < Ncvec;++k) + { + size_t kk{(k/4) + (k%4)*(Ncvec/4)}; + interleave2(vin[k*2], vin[k*2+1], vout[kk*2], vout[kk*2+1]); + } + } + else + { + for(size_t k{0};k < Ncvec;++k) + { + size_t kk{(k/4) + (k%4)*(Ncvec/4)}; + uninterleave2(vin[kk*2], vin[kk*2+1], vout[k*2], vout[k*2+1]); + } + } + } +} + +void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *s, const float *a, const float *b, + float *ab, float scaling) +{ + const size_t Ncvec{s->Ncvec}; + const v4sf *RESTRICT va{reinterpret_cast(a)}; + const v4sf *RESTRICT vb{reinterpret_cast(b)}; + v4sf *RESTRICT vab{reinterpret_cast(ab)}; + +#ifdef __arm__ + __builtin_prefetch(va); + __builtin_prefetch(vb); + __builtin_prefetch(vab); + __builtin_prefetch(va+2); + __builtin_prefetch(vb+2); + __builtin_prefetch(vab+2); + __builtin_prefetch(va+4); + __builtin_prefetch(vb+4); + __builtin_prefetch(vab+4); + __builtin_prefetch(va+6); + __builtin_prefetch(vb+6); + __builtin_prefetch(vab+6); +#ifndef __clang__ +#define ZCONVOLVE_USING_INLINE_NEON_ASM +#endif +#endif + + const float ar1{vextract0(va[0])}; + const float ai1{vextract0(va[1])}; + const float br1{vextract0(vb[0])}; + const float bi1{vextract0(vb[1])}; + const float abr1{vextract0(vab[0])}; + const float abi1{vextract0(vab[1])}; + +#ifdef ZCONVOLVE_USING_INLINE_ASM + /* Inline asm version, unfortunately miscompiled by clang 3.2, at least on + * Ubuntu. So this will be restricted to GCC. + * + * Does it still miscompile with Clang? Is it even needed with today's + * optimizers? + */ + const float *a_{a}, *b_{b}; float *ab_{ab}; + size_t N{Ncvec}; + asm volatile("mov r8, %2 \n" + "vdup.f32 q15, %4 \n" + "1: \n" + "pld [%0,#64] \n" + "pld [%1,#64] \n" + "pld [%2,#64] \n" + "pld [%0,#96] \n" + "pld [%1,#96] \n" + "pld [%2,#96] \n" + "vld1.f32 {q0,q1}, [%0,:128]! \n" + "vld1.f32 {q4,q5}, [%1,:128]! \n" + "vld1.f32 {q2,q3}, [%0,:128]! \n" + "vld1.f32 {q6,q7}, [%1,:128]! \n" + "vld1.f32 {q8,q9}, [r8,:128]! \n" + + "vmul.f32 q10, q0, q4 \n" + "vmul.f32 q11, q0, q5 \n" + "vmul.f32 q12, q2, q6 \n" + "vmul.f32 q13, q2, q7 \n" + "vmls.f32 q10, q1, q5 \n" + "vmla.f32 q11, q1, q4 \n" + "vld1.f32 {q0,q1}, [r8,:128]! \n" + "vmls.f32 q12, q3, q7 \n" + "vmla.f32 q13, q3, q6 \n" + "vmla.f32 q8, q10, q15 \n" + "vmla.f32 q9, q11, q15 \n" + "vmla.f32 q0, q12, q15 \n" + "vmla.f32 q1, q13, q15 \n" + "vst1.f32 {q8,q9},[%2,:128]! \n" + "vst1.f32 {q0,q1},[%2,:128]! \n" + "subs %3, #2 \n" + "bne 1b \n" + : "+r"(a_), "+r"(b_), "+r"(ab_), "+r"(N) : "r"(scaling) : "r8", "q0","q1","q2","q3","q4","q5","q6","q7","q8","q9", "q10","q11","q12","q13","q15","memory"); + +#else + + /* Default routine, works fine for non-arm cpus with current compilers. */ + const v4sf vscal{ld_ps1(scaling)}; + for(size_t i{0};i < Ncvec;i += 2) + { + v4sf ar4{va[2*i+0]}, ai4{va[2*i+1]}; + v4sf br4{vb[2*i+0]}, bi4{vb[2*i+1]}; + vcplxmul(ar4, ai4, br4, bi4); + vab[2*i+0] = vmadd(ar4, vscal, vab[2*i+0]); + vab[2*i+1] = vmadd(ai4, vscal, vab[2*i+1]); + ar4 = va[2*i+2]; ai4 = va[2*i+3]; + br4 = vb[2*i+2]; bi4 = vb[2*i+3]; + vcplxmul(ar4, ai4, br4, bi4); + vab[2*i+2] = vmadd(ar4, vscal, vab[2*i+2]); + vab[2*i+3] = vmadd(ai4, vscal, vab[2*i+3]); + } +#endif + + if(s->transform == PFFFT_REAL) + { + vab[0] = vinsert0(vab[0], abr1 + ar1*br1*scaling); + vab[1] = vinsert0(vab[1], abi1 + ai1*bi1*scaling); + } +} + +void pffft_zconvolve_accumulate(const PFFFT_Setup *s, const float *a, const float *b, float *ab) +{ + const size_t Ncvec{s->Ncvec}; + const v4sf *RESTRICT va{reinterpret_cast(a)}; + const v4sf *RESTRICT vb{reinterpret_cast(b)}; + v4sf *RESTRICT vab{reinterpret_cast(ab)}; + +#ifdef __arm__ + __builtin_prefetch(va); + __builtin_prefetch(vb); + __builtin_prefetch(vab); + __builtin_prefetch(va+2); + __builtin_prefetch(vb+2); + __builtin_prefetch(vab+2); + __builtin_prefetch(va+4); + __builtin_prefetch(vb+4); + __builtin_prefetch(vab+4); + __builtin_prefetch(va+6); + __builtin_prefetch(vb+6); + __builtin_prefetch(vab+6); +#endif + + const float ar1{vextract0(va[0])}; + const float ai1{vextract0(va[1])}; + const float br1{vextract0(vb[0])}; + const float bi1{vextract0(vb[1])}; + const float abr1{vextract0(vab[0])}; + const float abi1{vextract0(vab[1])}; + + /* No inline assembly for this version. I'm not familiar enough with NEON + * assembly, and I don't know that it's needed with today's optimizers. + */ + for(size_t i{0};i < Ncvec;i += 2) + { + v4sf ar4{va[2*i+0]}, ai4{va[2*i+1]}; + v4sf br4{vb[2*i+0]}, bi4{vb[2*i+1]}; + vcplxmul(ar4, ai4, br4, bi4); + vab[2*i+0] = vadd(ar4, vab[2*i+0]); + vab[2*i+1] = vadd(ai4, vab[2*i+1]); + ar4 = va[2*i+2]; ai4 = va[2*i+3]; + br4 = vb[2*i+2]; bi4 = vb[2*i+3]; + vcplxmul(ar4, ai4, br4, bi4); + vab[2*i+2] = vadd(ar4, vab[2*i+2]); + vab[2*i+3] = vadd(ai4, vab[2*i+3]); + } + + if(s->transform == PFFFT_REAL) + { + vab[0] = vinsert0(vab[0], abr1 + ar1*br1); + vab[1] = vinsert0(vab[1], abi1 + ai1*bi1); + } +} + + +void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, + pffft_direction_t direction) +{ + assert(valigned(input) && valigned(output) && valigned(work)); + pffft_transform_internal(setup, reinterpret_cast(al::assume_aligned<16>(input)), + reinterpret_cast(al::assume_aligned<16>(output)), + reinterpret_cast(al::assume_aligned<16>(work)), direction, false); +} + +void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, + float *work, pffft_direction_t direction) +{ + assert(valigned(input) && valigned(output) && valigned(work)); + pffft_transform_internal(setup, reinterpret_cast(al::assume_aligned<16>(input)), + reinterpret_cast(al::assume_aligned<16>(output)), + reinterpret_cast(al::assume_aligned<16>(work)), direction, true); +} + +#else // defined(PFFFT_SIMD_DISABLE) + +// standard routine using scalar floats, without SIMD stuff. + +namespace { + +void pffft_transform_internal(const PFFFT_Setup *setup, const float *input, float *output, + float *scratch, const pffft_direction_t direction, bool ordered) +{ + const size_t Ncvec{setup->Ncvec}; + const bool nf_odd{(setup->ifac[1]&1) != 0}; + + assert(scratch != nullptr); + + /* z-domain data for complex transforms is already ordered without SIMD. */ + if(setup->transform == PFFFT_COMPLEX) + ordered = false; + + float *buff[2]{output, scratch}; + bool ib{nf_odd != ordered}; + if(direction == PFFFT_FORWARD) + { + if(setup->transform == PFFFT_REAL) + ib = (rfftf1_ps(Ncvec*2, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac) == buff[1]); + else + ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac, -1.0f) == buff[1]); + if(ordered) + { + pffft_zreorder(setup, buff[ib], buff[!ib], PFFFT_FORWARD); + ib = !ib; + } + } + else + { + if (input == buff[ib]) + ib = !ib; // may happen when finput == foutput + + if(ordered) + { + pffft_zreorder(setup, input, buff[ib], PFFFT_BACKWARD); + input = buff[ib]; + ib = !ib; + } + if(setup->transform == PFFFT_REAL) + ib = (rfftb1_ps(Ncvec*2, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac) == buff[1]); + else + ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac, +1.0f) == buff[1]); + } + if(buff[ib] != output) + { + // extra copy required -- this situation should happens only when finput == foutput + assert(input==output); + for(size_t k{0};k < Ncvec;++k) + { + float a{buff[ib][2*k]}, b{buff[ib][2*k+1]}; + output[2*k] = a; output[2*k+1] = b; + } + } +} + +} // namespace + +void pffft_zreorder(const PFFFT_Setup *setup, const float *in, float *RESTRICT out, + pffft_direction_t direction) +{ + const size_t N{setup->N}; + if(setup->transform == PFFFT_COMPLEX) + { + for(size_t k{0};k < 2*N;++k) + out[k] = in[k]; + return; + } + else if(direction == PFFFT_FORWARD) + { + float x_N{in[N-1]}; + for(size_t k{N-1};k > 1;--k) + out[k] = in[k-1]; + out[0] = in[0]; + out[1] = x_N; + } + else + { + float x_N{in[1]}; + for(size_t k{1};k < N-1;++k) + out[k] = in[k+1]; + out[0] = in[0]; + out[N-1] = x_N; + } +} + +void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *s, const float *a, const float *b, + float *ab, float scaling) +{ + size_t Ncvec{s->Ncvec}; + + if(s->transform == PFFFT_REAL) + { + // take care of the fftpack ordering + ab[0] += a[0]*b[0]*scaling; + ab[2*Ncvec-1] += a[2*Ncvec-1]*b[2*Ncvec-1]*scaling; + ++ab; ++a; ++b; --Ncvec; + } + for(size_t i{0};i < Ncvec;++i) + { + float ar{a[2*i+0]}, ai{a[2*i+1]}; + const float br{b[2*i+0]}, bi{b[2*i+1]}; + vcplxmul(ar, ai, br, bi); + ab[2*i+0] += ar*scaling; + ab[2*i+1] += ai*scaling; + } +} + +void pffft_zconvolve_accumulate(const PFFFT_Setup *s, const float *a, const float *b, float *ab) +{ + size_t Ncvec{s->Ncvec}; + + if(s->transform == PFFFT_REAL) + { + // take care of the fftpack ordering + ab[0] += a[0]*b[0]; + ab[2*Ncvec-1] += a[2*Ncvec-1]*b[2*Ncvec-1]; + ++ab; ++a; ++b; --Ncvec; + } + for(size_t i{0};i < Ncvec;++i) + { + float ar{a[2*i+0]}, ai{a[2*i+1]}; + const float br{b[2*i+0]}, bi{b[2*i+1]}; + vcplxmul(ar, ai, br, bi); + ab[2*i+0] += ar; + ab[2*i+1] += ai; + } +} + + +void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, + pffft_direction_t direction) +{ + pffft_transform_internal(setup, input, output, work, direction, false); +} + +void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, + float *work, pffft_direction_t direction) +{ + pffft_transform_internal(setup, input, output, work, direction, true); +} + +#endif /* defined(PFFFT_SIMD_DISABLE) */ +/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ diff --git a/Engine/lib/openal-soft/common/pffft.h b/Engine/lib/openal-soft/common/pffft.h new file mode 100644 index 000000000..0eb321285 --- /dev/null +++ b/Engine/lib/openal-soft/common/pffft.h @@ -0,0 +1,212 @@ +/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) + + Based on original fortran 77 code from FFTPACKv4 from NETLIB, + authored by Dr Paul Swarztrauber of NCAR, in 1985. + + As confirmed by the NCAR fftpack software curators, the following + FFTPACKv5 license applies to FFTPACKv4 sources. My changes are + released under the same terms. + + FFTPACK license: + + http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html + + Copyright (c) 2004 the University Corporation for Atmospheric + Research ("UCAR"). All rights reserved. Developed by NCAR's + Computational and Information Systems Laboratory, UCAR, + www.cisl.ucar.edu. + + Redistribution and use of the Software in source and binary forms, + with or without modification, is permitted provided that the + following conditions are met: + + - Neither the names of NCAR's Computational and Information Systems + Laboratory, the University Corporation for Atmospheric Research, + nor the names of its sponsors or contributors may be used to + endorse or promote products derived from this Software without + specific prior written permission. + + - Redistributions of source code must retain the above copyright + notices, this list of conditions, and the disclaimer below. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer below in the + documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + SOFTWARE. +*/ + +/* PFFFT : a Pretty Fast FFT. + * + * This is basically an adaptation of the single precision fftpack (v4) as + * found on netlib taking advantage of SIMD instructions found on CPUs such as + * Intel x86 (SSE1), PowerPC (Altivec), and Arm (NEON). + * + * For architectures where SIMD instructions aren't available, the code falls + * back to a scalar version. + * + * Restrictions: + * + * - 1D transforms only, with 32-bit single precision. + * + * - supports only transforms for inputs of length N of the form + * N=(2^a)*(3^b)*(5^c), given a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128, 144, + * 160, etc are all acceptable lengths). Performance is best for 128<=N<=8192. + * + * - all (float*) pointers for the functions below are expected to have a + * "SIMD-compatible" alignment, that is 16 bytes. + * + * You can allocate such buffers with the pffft_aligned_malloc function, and + * deallocate them with pffft_aligned_free (or with stuff like posix_memalign, + * aligned_alloc, etc). + * + * Note that for the z-domain data of real transforms, when in the canonical + * order (as interleaved complex numbers) both 0-frequency and half-frequency + * components, which are real, are assembled in the first entry as + * F(0)+i*F(n/2+1). The original fftpack placed F(n/2+1) at the end of the + * arrays instead. + */ + +#ifndef PFFFT_H +#define PFFFT_H + +#include +#include + +#include "almalloc.h" + + +/* opaque struct holding internal stuff (precomputed twiddle factors) this + * struct can be shared by many threads as it contains only read-only data. + */ +struct PFFFT_Setup; + +/* direction of the transform */ +enum pffft_direction_t { PFFFT_FORWARD, PFFFT_BACKWARD }; + +/* type of transform */ +enum pffft_transform_t { PFFFT_REAL, PFFFT_COMPLEX }; + +void pffft_destroy_setup(gsl::owner setup) noexcept; +struct PFFFTSetupDeleter { + void operator()(gsl::owner setup) const noexcept { pffft_destroy_setup(setup); } +}; +using PFFFTSetupPtr = std::unique_ptr; + +/** + * Prepare for performing transforms of size N -- the returned PFFFT_Setup + * structure is read-only so it can safely be shared by multiple concurrent + * threads. + */ +PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform); + +/** + * Perform a Fourier transform. The z-domain data is stored in the most + * efficient order for transforming back or using for convolution, and as + * such, there's no guarantee to the order of the values. If you need to have + * its content sorted in the usual way, that is as an array of interleaved + * complex numbers, either use pffft_transform_ordered, or call pffft_zreorder + * after the forward fft and before the backward fft. + * + * Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x. Typically + * you will want to scale the backward transform by 1/N. + * + * The 'work' pointer must point to an area of N (2*N for complex fft) floats, + * properly aligned. It cannot be NULL. + * + * The input and output parameters may alias. + */ +void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); + +/** + * Similar to pffft_transform, but handles the complex values in the usual form + * (interleaved complex numbers). This is similar to calling + * pffft_transform(..., PFFFT_FORWARD) followed by + * pffft_zreorder(..., PFFFT_FORWARD), or + * pffft_zreorder(..., PFFFT_BACKWARD) followed by + * pffft_transform(..., PFFFT_BACKWARD), for the given direction. + * + * The input and output parameters may alias. + */ +void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); + +/** + * Reorder the z-domain data. For PFFFT_FORWARD, it reorders from the internal + * representation to the "canonical" order (as interleaved complex numbers). + * For PFFFT_BACKWARD, it reorders from the canonical order to the internal + * order suitable for pffft_transform(..., PFFFT_BACKWARD) or + * pffft_zconvolve_accumulate. + * + * The input and output parameters should not alias. + */ +void pffft_zreorder(const PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction); + +/** + * Perform a multiplication of the z-domain data in dft_a and dft_b, and scale + * and accumulate into dft_ab. The arrays should have been obtained with + * pffft_transform(..., PFFFT_FORWARD) or pffft_zreorder(..., PFFFT_BACKWARD) + * and should *not* be in the usual order (otherwise just perform the operation + * yourself as the dft coeffs are stored as interleaved complex numbers). + * + * The operation performed is: dft_ab += (dft_a * dft_b)*scaling + * + * The dft_a, dft_b, and dft_ab parameters may alias. + */ +void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling); + +/** + * Perform a multiplication of the z-domain data in dft_a and dft_b, and + * accumulate into dft_ab. + * + * The operation performed is: dft_ab += dft_a * dft_b + * + * The dft_a, dft_b, and dft_ab parameters may alias. + */ +void pffft_zconvolve_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab); + + +struct PFFFTSetup { + PFFFTSetupPtr mSetup{}; + + PFFFTSetup() = default; + PFFFTSetup(const PFFFTSetup&) = delete; + PFFFTSetup(PFFFTSetup&& rhs) noexcept = default; + explicit PFFFTSetup(std::nullptr_t) noexcept { } + explicit PFFFTSetup(unsigned int n, pffft_transform_t transform) + : mSetup{pffft_new_setup(n, transform)} + { } + ~PFFFTSetup() = default; + + PFFFTSetup& operator=(const PFFFTSetup&) = delete; + PFFFTSetup& operator=(PFFFTSetup&& rhs) noexcept = default; + + [[nodiscard]] explicit operator bool() const noexcept { return mSetup != nullptr; } + + void transform(const float *input, float *output, float *work, pffft_direction_t direction) const + { pffft_transform(mSetup.get(), input, output, work, direction); } + + void transform_ordered(const float *input, float *output, float *work, + pffft_direction_t direction) const + { pffft_transform_ordered(mSetup.get(), input, output, work, direction); } + + void zreorder(const float *input, float *output, pffft_direction_t direction) const + { pffft_zreorder(mSetup.get(), input, output, direction); } + + void zconvolve_scale_accumulate(const float *dft_a, const float *dft_b, float *dft_ab, + float scaling) const + { pffft_zconvolve_scale_accumulate(mSetup.get(), dft_a, dft_b, dft_ab, scaling); } + + void zconvolve_accumulate(const float *dft_a, const float *dft_b, float *dft_ab) const + { pffft_zconvolve_accumulate(mSetup.get(), dft_a, dft_b, dft_ab); } +}; + +#endif // PFFFT_H diff --git a/Engine/lib/openal-soft/common/phase_shifter.h b/Engine/lib/openal-soft/common/phase_shifter.h index 0d4166bce..7febf3447 100644 --- a/Engine/lib/openal-soft/common/phase_shifter.h +++ b/Engine/lib/openal-soft/common/phase_shifter.h @@ -8,89 +8,51 @@ #endif #include -#include +#include +#include -#include "alcomplex.h" +#include "alnumbers.h" #include "alspan.h" +#include "opthelpers.h" /* Implements a wide-band +90 degree phase-shift. Note that this should be * given one sample less of a delay (FilterSize/2 - 1) compared to the direct * signal delay (FilterSize/2) to properly align. */ -template +template struct PhaseShifterT { static_assert(FilterSize >= 16, "FilterSize needs to be at least 16"); static_assert((FilterSize&(FilterSize-1)) == 0, "FilterSize needs to be power-of-two"); alignas(16) std::array mCoeffs{}; - /* Some notes on this filter construction. - * - * A wide-band phase-shift filter needs a delay to maintain linearity. A - * dirac impulse in the center of a time-domain buffer represents a filter - * passing all frequencies through as-is with a pure delay. Converting that - * to the frequency domain, adjusting the phase of each frequency bin by - * +90 degrees, then converting back to the time domain, results in a FIR - * filter that applies a +90 degree wide-band phase-shift. - * - * A particularly notable aspect of the time-domain filter response is that - * every other coefficient is 0. This allows doubling the effective size of - * the filter, by storing only the non-0 coefficients and double-stepping - * over the input to apply it. - * - * Additionally, the resulting filter is independent of the sample rate. - * The same filter can be applied regardless of the device's sample rate - * and achieve the same effect. - */ PhaseShifterT() { - using complex_d = std::complex; - constexpr size_t fft_size{FilterSize}; - constexpr size_t half_size{fft_size / 2}; - - auto fftBuffer = std::make_unique(fft_size); - std::fill_n(fftBuffer.get(), fft_size, complex_d{}); - fftBuffer[half_size] = 1.0; - - forward_fft(al::as_span(fftBuffer.get(), fft_size)); - for(size_t i{0};i < half_size+1;++i) - fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()}; - for(size_t i{half_size+1};i < fft_size;++i) - fftBuffer[i] = std::conj(fftBuffer[fft_size - i]); - inverse_fft(al::as_span(fftBuffer.get(), fft_size)); - - auto fftiter = fftBuffer.get() + half_size + (FilterSize/2 - 1); - for(float &coeff : mCoeffs) + /* Every other coefficient is 0, so we only need to calculate and store + * the non-0 terms and double-step over the input to apply it. The + * calculated coefficients are in reverse to make applying in the time- + * domain more efficient. + */ + for(std::size_t i{0};i < FilterSize/2;++i) { - coeff = static_cast(fftiter->real() / double{fft_size}); - fftiter -= 2; + const int k{static_cast(i*2 + 1) - int{FilterSize/2}}; + + /* Calculate the Blackman window value for this coefficient. */ + const double w{2.0*al::numbers::pi * static_cast(i*2 + 1) + / double{FilterSize}}; + const double window{0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) + - 0.0106411*std::cos(3.0*w)}; + + const double pk{al::numbers::pi * static_cast(k)}; + mCoeffs[i] = static_cast(window * (1.0-std::cos(pk)) / pk); } } - void process(al::span dst, const float *RESTRICT src) const; + void process(const al::span dst, const al::span src) const; private: #if defined(HAVE_NEON) - /* There doesn't seem to be NEON intrinsics to do this kind of stipple - * shuffling, so there's two custom methods for it. - */ - static auto shuffle_2020(float32x4_t a, float32x4_t b) - { - float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 0))}; - ret = vsetq_lane_f32(vgetq_lane_f32(a, 2), ret, 1); - ret = vsetq_lane_f32(vgetq_lane_f32(b, 0), ret, 2); - ret = vsetq_lane_f32(vgetq_lane_f32(b, 2), ret, 3); - return ret; - } - static auto shuffle_3131(float32x4_t a, float32x4_t b) - { - float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 1))}; - ret = vsetq_lane_f32(vgetq_lane_f32(a, 3), ret, 1); - ret = vsetq_lane_f32(vgetq_lane_f32(b, 1), ret, 2); - ret = vsetq_lane_f32(vgetq_lane_f32(b, 3), ret, 3); - return ret; - } static auto unpacklo(float32x4_t a, float32x4_t b) { float32x2x2_t result{vzip_f32(vget_low_f32(a), vget_low_f32(b))}; @@ -109,105 +71,141 @@ private: ret = vsetq_lane_f32(d, ret, 3); return ret; } + static void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3) + { + float32x4x2_t t0_{vzipq_f32(x0, x2)}; + float32x4x2_t t1_{vzipq_f32(x1, x3)}; + float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])}; + float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])}; + x0 = u0_.val[0]; + x1 = u0_.val[1]; + x2 = u1_.val[0]; + x3 = u1_.val[1]; + } #endif }; -template -inline void PhaseShifterT::process(al::span dst, const float *RESTRICT src) const +template +NOINLINE inline +void PhaseShifterT::process(const al::span dst, const al::span src) const { + auto in = src.begin(); #ifdef HAVE_SSE_INTRINSICS - if(size_t todo{dst.size()>>1}) + if(const std::size_t todo{dst.size()>>2}) { - auto *out = reinterpret_cast<__m64*>(dst.data()); - do { - __m128 r04{_mm_setzero_ps()}; - __m128 r14{_mm_setzero_ps()}; - for(size_t j{0};j < mCoeffs.size();j+=4) + auto out = al::span{reinterpret_cast<__m128*>(dst.data()), todo}; + std::generate(out.begin(), out.end(), [&in,this] + { + __m128 r0{_mm_setzero_ps()}; + __m128 r1{_mm_setzero_ps()}; + __m128 r2{_mm_setzero_ps()}; + __m128 r3{_mm_setzero_ps()}; + for(std::size_t j{0};j < mCoeffs.size();j+=4) { const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; - const __m128 s0{_mm_loadu_ps(&src[j*2])}; - const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])}; + const __m128 s0{_mm_loadu_ps(&in[j*2])}; + const __m128 s1{_mm_loadu_ps(&in[j*2 + 4])}; + const __m128 s2{_mm_movehl_ps(_mm_movelh_ps(s1, s1), s0)}; + const __m128 s3{_mm_loadh_pi(_mm_movehl_ps(s1, s1), + reinterpret_cast(&in[j*2 + 8]))}; __m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))}; - r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs)); + r0 = _mm_add_ps(r0, _mm_mul_ps(s, coeffs)); s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1)); - r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs)); + r1 = _mm_add_ps(r1, _mm_mul_ps(s, coeffs)); + + s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(2, 0, 2, 0)); + r2 = _mm_add_ps(r2, _mm_mul_ps(s, coeffs)); + + s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(3, 1, 3, 1)); + r3 = _mm_add_ps(r3, _mm_mul_ps(s, coeffs)); } - src += 2; + in += 4; - __m128 r4{_mm_add_ps(_mm_unpackhi_ps(r04, r14), _mm_unpacklo_ps(r04, r14))}; - r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - - _mm_storel_pi(out, r4); - ++out; - } while(--todo); + _MM_TRANSPOSE4_PS(r0, r1, r2, r3); + return _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3)); + }); } - if((dst.size()&1)) + if(const std::size_t todo{dst.size()&3}) { - __m128 r4{_mm_setzero_ps()}; - for(size_t j{0};j < mCoeffs.size();j+=4) + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&in,this] { - const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; - const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; - r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); - } - r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); - r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - - dst.back() = _mm_cvtss_f32(r4); + __m128 r4{_mm_setzero_ps()}; + for(std::size_t j{0};j < mCoeffs.size();j+=4) + { + const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; + const __m128 s{_mm_setr_ps(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])}; + r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); + } + ++in; + 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)); + return _mm_cvtss_f32(r4); + }); } #elif defined(HAVE_NEON) - size_t pos{0}; - if(size_t todo{dst.size()>>1}) + if(const std::size_t todo{dst.size()>>2}) { - do { - float32x4_t r04{vdupq_n_f32(0.0f)}; - float32x4_t r14{vdupq_n_f32(0.0f)}; - for(size_t j{0};j < mCoeffs.size();j+=4) + auto out = al::span{reinterpret_cast(dst.data()), todo}; + std::generate(out.begin(), out.end(), [&in,this] + { + float32x4_t r0{vdupq_n_f32(0.0f)}; + float32x4_t r1{vdupq_n_f32(0.0f)}; + float32x4_t r2{vdupq_n_f32(0.0f)}; + float32x4_t r3{vdupq_n_f32(0.0f)}; + for(std::size_t j{0};j < mCoeffs.size();j+=4) { const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; - const float32x4_t s0{vld1q_f32(&src[j*2])}; - const float32x4_t s1{vld1q_f32(&src[j*2 + 4])}; + const float32x4_t s0{vld1q_f32(&in[j*2])}; + const float32x4_t s1{vld1q_f32(&in[j*2 + 4])}; + const float32x4_t s2{vcombine_f32(vget_high_f32(s0), vget_low_f32(s1))}; + const float32x4_t s3{vcombine_f32(vget_high_f32(s1), vld1_f32(&in[j*2 + 8]))}; + const float32x4x2_t values0{vuzpq_f32(s0, s1)}; + const float32x4x2_t values1{vuzpq_f32(s2, s3)}; - r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs); - r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs); + r0 = vmlaq_f32(r0, values0.val[0], coeffs); + r1 = vmlaq_f32(r1, values0.val[1], coeffs); + r2 = vmlaq_f32(r2, values1.val[0], coeffs); + r3 = vmlaq_f32(r3, values1.val[1], coeffs); } - src += 2; + in += 4; - float32x4_t r4{vaddq_f32(unpackhi(r04, r14), unpacklo(r04, r14))}; - float32x2_t r2{vadd_f32(vget_low_f32(r4), vget_high_f32(r4))}; - - vst1_f32(&dst[pos], r2); - pos += 2; - } while(--todo); + vtranspose4(r0, r1, r2, r3); + return vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3)); + }); } - if((dst.size()&1)) + if(const std::size_t todo{dst.size()&3}) { - float32x4_t r4{vdupq_n_f32(0.0f)}; - for(size_t j{0};j < mCoeffs.size();j+=4) + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&in,this] { - const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; - const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; - r4 = vmlaq_f32(r4, s, coeffs); - } - r4 = vaddq_f32(r4, vrev64q_f32(r4)); - dst[pos] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + float32x4_t r4{vdupq_n_f32(0.0f)}; + for(std::size_t j{0};j < mCoeffs.size();j+=4) + { + const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; + const float32x4_t s{load4(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])}; + r4 = vmlaq_f32(r4, s, coeffs); + } + ++in; + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + }); } #else - for(float &output : dst) + std::generate(dst.begin(), dst.end(), [&in,this] { float ret{0.0f}; - for(size_t j{0};j < mCoeffs.size();++j) - ret += src[j*2] * mCoeffs[j]; - - output = ret; - ++src; - } + for(std::size_t j{0};j < mCoeffs.size();++j) + ret += in[j*2] * mCoeffs[j]; + ++in; + return ret; + }); #endif } diff --git a/Engine/lib/openal-soft/common/polyphase_resampler.cpp b/Engine/lib/openal-soft/common/polyphase_resampler.cpp index 14f7e40d8..6aed84edb 100644 --- a/Engine/lib/openal-soft/common/polyphase_resampler.cpp +++ b/Engine/lib/openal-soft/common/polyphase_resampler.cpp @@ -3,16 +3,61 @@ #include #include +#include +#include +#include #include "alnumbers.h" #include "opthelpers.h" +using uint = unsigned int; + namespace { constexpr double Epsilon{1e-9}; -using uint = unsigned int; +#if __cpp_lib_math_special_functions >= 201603L +using std::cyl_bessel_i; + +#else + +/* 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 + * + * This implementation only handles nu = 0, and isn't the most precise (it + * starts with the largest value and accumulates successively smaller values, + * compounding the rounding and precision error), but it's good enough. + */ +template +U cyl_bessel_i(T nu, U x) +{ + if(nu != T{0}) + throw std::runtime_error{"cyl_bessel_i: nu != 0"}; + + /* Start at k=1 since k=0 is trivial. */ + const double x2{x/2.0}; + double term{1.0}; + double sum{1.0}; + int k{1}; + + /* Let the integration converge until the term of the sum is no longer + * significant. + */ + double last_sum{}; + do { + const double y{x2 / k}; + ++k; + last_sum = sum; + term *= y * y; + sum += term; + } while(sum != last_sum); + return static_cast(sum); +} +#endif /* This is the normalized cardinal sine (sinc) function. * @@ -26,33 +71,6 @@ double Sinc(const double x) return std::sin(al::numbers::pi*x) / (al::numbers::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 - */ -constexpr double BesselI_0(const double x) -{ - // Start at k=1 since k=0 is trivial. - const double x2{x/2.0}; - double term{1.0}; - double sum{1.0}; - int k{1}; - - // Let the integration converge until the term of the sum is no longer - // significant. - double last_sum{}; - do { - const double 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]. * @@ -67,23 +85,11 @@ constexpr double BesselI_0(const double x) * * k = 2 i / M - 1, where 0 <= i <= M. */ -double Kaiser(const double b, const double k) +double Kaiser(const double beta, const double k, const double besseli_0_beta) { 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. -constexpr uint Gcd(uint x, uint y) -{ - while(y > 0) - { - const uint z{y}; - y = x % y; - x = z; - } - return x; + return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; } /* Calculates the size (order) of the Kaiser window. Rejection is in dB and @@ -124,11 +130,11 @@ constexpr double CalcKaiserBeta(const double rejection) * p -- gain compensation factor when sampling * f_t -- normalized center frequency (or cutoff; 0.5 is nyquist) */ -double SincFilter(const uint l, const double b, const double gain, const double cutoff, - const uint i) +double SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain, + const double cutoff, const uint i) { const double x{static_cast(i) - l}; - return Kaiser(b, x / l) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x); + return Kaiser(beta, x/l, besseli_0_beta) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x); } } // namespace @@ -137,7 +143,7 @@ double SincFilter(const uint l, const double b, const double gain, const double // that's used to cut frequencies above the destination nyquist. void PPhaseResampler::init(const uint srcRate, const uint dstRate) { - const uint gcd{Gcd(srcRate, dstRate)}; + const uint gcd{std::gcd(srcRate, dstRate)}; mP = dstRate / gcd; mQ = srcRate / gcd; @@ -145,78 +151,70 @@ void PPhaseResampler::init(const uint srcRate, const uint dstRate) * ends before the nyquist (0.5). Both are scaled by the downsampling * factor. */ - double cutoff, width; - if(mP > mQ) - { - cutoff = 0.475 / mP; - width = 0.05 / mP; - } - else - { - cutoff = 0.475 / mQ; - width = 0.05 / mQ; - } + const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.475 / mP, 0.05 / mP) + : std::make_tuple(0.475 / mQ, 0.05 / 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)}; + const double besseli_0_beta{cyl_bessel_i(0, beta)}; mM = l*2 + 1; mL = l; mF.resize(mM); for(uint i{0};i < mM;i++) - mF[i] = SincFilter(l, beta, mP, cutoff, i); + mF[i] = SincFilter(l, beta, besseli_0_beta, mP, cutoff, i); } // Perform the upsample-filter-downsample resampling operation using a // polyphase filter implementation. -void PPhaseResampler::process(const uint inN, const double *in, const uint outN, double *out) +void PPhaseResampler::process(const al::span in, const al::span out) { - if(outN == 0) UNLIKELY + if(out.empty()) UNLIKELY return; // Handle in-place operation. std::vector workspace; - double *work{out}; - if(work == in) UNLIKELY + al::span work{out}; + if(work.data() == in.data()) UNLIKELY { - workspace.resize(outN); - work = workspace.data(); + workspace.resize(out.size()); + work = workspace; } // Resample the input. const uint p{mP}, q{mQ}, m{mM}, l{mL}; - const double *f{mF.data()}; - for(uint i{0};i < outN;i++) + const al::span f{mF}; + for(uint i{0};i < out.size();i++) { // Input starts at l to compensate for the filter delay. This will // drop any build-up from the first half of the filter. - size_t j_f{(l + q*i) % p}; - size_t j_s{(l + q*i) / p}; + std::size_t j_f{(l + q*i) % p}; + std::size_t j_s{(l + q*i) / p}; - // Only take input when 0 <= j_s < inN. + // Only take input when 0 <= j_s < in.size(). double r{0.0}; if(j_f < m) LIKELY { - size_t filt_len{(m-j_f+p-1) / p}; - if(j_s+1 > inN) LIKELY + std::size_t filt_len{(m-j_f+p-1) / p}; + if(j_s+1 > in.size()) LIKELY { - size_t skip{std::min(j_s+1 - inN, filt_len)}; + std::size_t skip{std::min(j_s+1 - in.size(), filt_len)}; j_f += p*skip; j_s -= skip; filt_len -= skip; } - if(size_t todo{std::min(j_s+1, filt_len)}) LIKELY + std::size_t todo{std::min(j_s+1, filt_len)}; + while(todo) { - do { - r += f[j_f] * in[j_s]; - j_f += p; - --j_s; - } while(--todo); + r += f[j_f] * in[j_s]; + j_f += p; --j_s; + --todo; } } work[i] = r; } // Clean up after in-place operation. - if(work != out) - std::copy_n(work, outN, out); + if(work.data() != out.data()) + std::copy(work.cbegin(), work.cend(), out.begin()); } diff --git a/Engine/lib/openal-soft/common/polyphase_resampler.h b/Engine/lib/openal-soft/common/polyphase_resampler.h index 557485bb2..0795c80d8 100644 --- a/Engine/lib/openal-soft/common/polyphase_resampler.h +++ b/Engine/lib/openal-soft/common/polyphase_resampler.h @@ -3,6 +3,8 @@ #include +#include "alspan.h" + using uint = unsigned int; @@ -35,12 +37,12 @@ using uint = unsigned int; struct PPhaseResampler { void init(const uint srcRate, const uint dstRate); - void process(const uint inN, const double *in, const uint outN, double *out); + void process(const al::span in, const al::span out); explicit operator bool() const noexcept { return !mF.empty(); } private: - uint mP, mQ, mM, mL; + uint mP{}, mQ{}, mM{}, mL{}; std::vector mF; }; diff --git a/Engine/lib/openal-soft/common/ringbuffer.cpp b/Engine/lib/openal-soft/common/ringbuffer.cpp index 0aec1d497..f4fa49bf9 100644 --- a/Engine/lib/openal-soft/common/ringbuffer.cpp +++ b/Engine/lib/openal-soft/common/ringbuffer.cpp @@ -23,202 +23,150 @@ #include "ringbuffer.h" #include -#include +#include +#include #include +#include -#include "almalloc.h" +#include "alnumeric.h" -RingBufferPtr RingBuffer::Create(size_t sz, size_t elem_sz, int limit_writes) +auto RingBuffer::Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> RingBufferPtr { - size_t power_of_two{0u}; + std::size_t power_of_two{0u}; if(sz > 0) { - power_of_two = sz; + power_of_two = sz - 1; 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 + if constexpr(sizeof(size_t) > sizeof(uint32_t)) + power_of_two |= power_of_two>>32; } ++power_of_two; - if(power_of_two <= sz || power_of_two > std::numeric_limits::max()/elem_sz) + if(power_of_two < sz || power_of_two > std::numeric_limits::max()>>1 + || power_of_two > std::numeric_limits::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; + const std::size_t bufbytes{power_of_two * elem_sz}; + RingBufferPtr rb{new(FamCount(bufbytes)) RingBuffer{limit_writes ? sz : power_of_two, + power_of_two-1, elem_sz, bufbytes}}; 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{}); + mWriteCount.store(0, std::memory_order_relaxed); + mReadCount.store(0, std::memory_order_relaxed); + std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, std::byte{}); } -size_t RingBuffer::read(void *dest, size_t cnt) noexcept +auto RingBuffer::read(void *dest, std::size_t count) noexcept -> std::size_t { - const size_t free_cnt{readSpace()}; - if(free_cnt == 0) return 0; + const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; + const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; + const std::size_t readable{w - r}; + if(readable == 0) return 0; - const size_t to_read{std::min(cnt, free_cnt)}; - size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask}; + const std::size_t to_read{std::min(count, readable)}; + const std::size_t read_idx{r & 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; - } + const std::size_t rdend{read_idx + to_read}; + const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz) + : std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask); - auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize, - static_cast(dest)); - read_ptr += n1; + auto dstbytes = al::span{static_cast(dest), count*mElemSize}; + auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize, + dstbytes.begin()); if(n2 > 0) - { std::copy_n(mBuffer.begin(), n2*mElemSize, outiter); - read_ptr += n2; - } - mReadPtr.store(read_ptr, std::memory_order_release); + mReadCount.store(r+n1+n2, std::memory_order_release); return to_read; } -size_t RingBuffer::peek(void *dest, size_t cnt) const noexcept +auto RingBuffer::peek(void *dest, std::size_t count) const noexcept -> std::size_t { - const size_t free_cnt{readSpace()}; - if(free_cnt == 0) return 0; + const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; + const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; + const std::size_t readable{w - r}; + if(readable == 0) return 0; - const size_t to_read{std::min(cnt, free_cnt)}; - size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask}; + const std::size_t to_read{std::min(count, readable)}; + const std::size_t read_idx{r & 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; - } + const std::size_t rdend{read_idx + to_read}; + const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz) + : std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask); - auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize, - static_cast(dest)); + auto dstbytes = al::span{static_cast(dest), count*mElemSize}; + auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize, + dstbytes.begin()); 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 +auto RingBuffer::write(const void *src, std::size_t count) noexcept -> std::size_t { - const size_t free_cnt{writeSpace()}; - if(free_cnt == 0) return 0; + const std::size_t w{mWriteCount.load(std::memory_order_relaxed)}; + const std::size_t r{mReadCount.load(std::memory_order_acquire)}; + const std::size_t writable{mWriteSize - (w - r)}; + if(writable == 0) return 0; - const size_t to_write{std::min(cnt, free_cnt)}; - size_t write_ptr{mWritePtr.load(std::memory_order_relaxed) & mSizeMask}; + const std::size_t to_write{std::min(count, writable)}; + const std::size_t write_idx{w & 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; - } + const std::size_t wrend{write_idx + to_write}; + const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::make_tuple(to_write, 0_uz) + : std::make_tuple(mSizeMask+1 - write_idx, wrend&mSizeMask); - auto srcbytes = static_cast(src); - std::copy_n(srcbytes, n1*mElemSize, mBuffer.begin() + write_ptr*mElemSize); - write_ptr += n1; + auto srcbytes = al::span{static_cast(src), count*mElemSize}; + std::copy_n(srcbytes.cbegin(), n1*mElemSize, mBuffer.begin() + ptrdiff_t(write_idx*mElemSize)); if(n2 > 0) - { - std::copy_n(srcbytes + n1*mElemSize, n2*mElemSize, mBuffer.begin()); - write_ptr += n2; - } - mWritePtr.store(write_ptr, std::memory_order_release); + std::copy_n(srcbytes.cbegin() + ptrdiff_t(n1*mElemSize), n2*mElemSize, mBuffer.begin()); + mWriteCount.store(w+n1+n2, std::memory_order_release); return to_write; } -auto RingBuffer::getReadVector() const noexcept -> DataPair +auto RingBuffer::getReadVector() noexcept -> DataPair { - DataPair ret; + const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; + const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; + const std::size_t readable{w - r}; + const std::size_t read_idx{r & mSizeMask}; - 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) + const std::size_t rdend{read_idx + readable}; + if(rdend > 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(mBuffer.data() + r*mElemSize); - ret.first.len = mSizeMask+1 - r; - ret.second.buf = const_cast(mBuffer.data()); - ret.second.len = cnt2 & mSizeMask; + * plus some from the start of the buffer. + */ + return DataPair{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx}, + {mBuffer.data(), rdend&mSizeMask}}; } - else - { - /* Single part vector: just the rest of the buffer */ - ret.first.buf = const_cast(mBuffer.data() + r*mElemSize); - ret.first.len = free_cnt; - ret.second.buf = nullptr; - ret.second.len = 0; - } - - return ret; + return DataPair{{mBuffer.data() + read_idx*mElemSize, readable}, {}}; } -auto RingBuffer::getWriteVector() const noexcept -> DataPair +auto RingBuffer::getWriteVector() noexcept -> DataPair { - DataPair ret; + const std::size_t w{mWriteCount.load(std::memory_order_relaxed)}; + const std::size_t r{mReadCount.load(std::memory_order_acquire)}; + const std::size_t writable{mWriteSize - (w - r)}; + const std::size_t write_idx{w & mSizeMask}; - 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) + const std::size_t wrend{write_idx + writable}; + if(wrend > 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(mBuffer.data() + w*mElemSize); - ret.first.len = mSizeMask+1 - w; - ret.second.buf = const_cast(mBuffer.data()); - ret.second.len = cnt2 & mSizeMask; + * plus some from the start of the buffer. + */ + return DataPair{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx}, + {mBuffer.data(), wrend&mSizeMask}}; } - else - { - ret.first.buf = const_cast(mBuffer.data() + w*mElemSize); - ret.first.len = free_cnt; - ret.second.buf = nullptr; - ret.second.len = 0; - } - - return ret; + return DataPair{{mBuffer.data() + write_idx*mElemSize, writable}, {}}; } diff --git a/Engine/lib/openal-soft/common/ringbuffer.h b/Engine/lib/openal-soft/common/ringbuffer.h index 2a3797b05..4493474cd 100644 --- a/Engine/lib/openal-soft/common/ringbuffer.h +++ b/Engine/lib/openal-soft/common/ringbuffer.h @@ -2,111 +2,133 @@ #define RINGBUFFER_H #include +#include +#include #include -#include +#include #include -#include "albyte.h" #include "almalloc.h" +#include "flexarray.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 + * size or count are in 'elements', not bytes. Additionally, it only supports * single-consumer/single-provider operation. */ struct RingBuffer { private: - std::atomic mWritePtr{0u}; - std::atomic mReadPtr{0u}; - size_t mWriteSize{0u}; - size_t mSizeMask{0u}; - size_t mElemSize{0u}; +#if defined(__cpp_lib_hardware_interference_size) && !defined(_LIBCPP_VERSION) + static constexpr std::size_t sCacheAlignment{std::hardware_destructive_interference_size}; +#else + /* Assume a 64-byte cache line, the most common/likely value. */ + static constexpr std::size_t sCacheAlignment{64}; +#endif + alignas(sCacheAlignment) std::atomic mWriteCount{0u}; + alignas(sCacheAlignment) std::atomic mReadCount{0u}; - al::FlexArray mBuffer; + alignas(sCacheAlignment) const std::size_t mWriteSize; + const std::size_t mSizeMask; + const std::size_t mElemSize; + + al::FlexArray mBuffer; public: struct Data { - al::byte *buf; - size_t len; + std::byte *buf; + std::size_t len; }; using DataPair = std::pair; - - RingBuffer(const size_t count) : mBuffer{count} { } + RingBuffer(const std::size_t writesize, const std::size_t mask, const std::size_t elemsize, + const std::size_t numbytes) + : mWriteSize{writesize}, mSizeMask{mask}, mElemSize{elemsize}, mBuffer{numbytes} + { } /** Reset the read and write pointers to zero. This is not thread safe. */ - void reset() noexcept; + auto reset() noexcept -> void; + + /** + * 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. + */ + [[nodiscard]] auto readSpace() const noexcept -> std::size_t + { + const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; + const std::size_t r{mReadCount.load(std::memory_order_acquire)}; + /* mWriteCount is never more than mWriteSize greater than mReadCount. */ + return w - r; + } + + /** + * The copying data reader. Copy at most `count' elements into `dest'. + * Returns the actual number of elements copied. + */ + [[nodiscard]] auto read(void *dest, std::size_t count) noexcept -> std::size_t; + /** + * The copying data reader w/o read pointer advance. Copy at most `count' + * elements into `dest'. Returns the actual number of elements copied. + */ + [[nodiscard]] auto peek(void *dest, std::size_t count) const noexcept -> std::size_t; /** * 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. */ - DataPair getReadVector() const noexcept; + [[nodiscard]] auto getReadVector() noexcept -> DataPair; + /** Advance the read pointer `count' places. */ + auto readAdvance(std::size_t count) noexcept -> void + { + const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; + const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; + [[maybe_unused]] const std::size_t readable{w - r}; + assert(readable >= count); + mReadCount.store(r+count, std::memory_order_release); + } + + + /** + * Return the number of elements available for writing. This is the total + * number of writable elements excluding what's readable (already written). + */ + [[nodiscard]] auto writeSpace() const noexcept -> std::size_t + { return mWriteSize - readSpace(); } + + /** + * The copying data writer. Copy at most `count' elements from `src'. Returns + * the actual number of elements copied. + */ + [[nodiscard]] auto write(const void *src, std::size_t count) noexcept -> std::size_t; + /** * 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. */ - DataPair 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 + [[nodiscard]] auto getWriteVector() noexcept -> DataPair; + /** Advance the write pointer `count' places. */ + auto writeAdvance(std::size_t count) noexcept -> void { - const size_t w{mWritePtr.load(std::memory_order_acquire)}; - const size_t r{mReadPtr.load(std::memory_order_acquire)}; - return (w-r) & mSizeMask; + const std::size_t w{mWriteCount.load(std::memory_order_relaxed)}; + const std::size_t r{mReadCount.load(std::memory_order_acquire)}; + [[maybe_unused]] const std::size_t writable{mWriteSize - (w - r)}; + assert(writable >= count); + mWriteCount.store(w+count, std::memory_order_release); } - /** - * 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 - { mReadPtr.fetch_add(cnt, std::memory_order_acq_rel); } - - - /** - * 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 - { - const size_t w{mWritePtr.load(std::memory_order_acquire)}; - const size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask}; - return (r-w-1) & mSizeMask; - } - - /** - * 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 - { mWritePtr.fetch_add(cnt, std::memory_order_acq_rel); } - - size_t getElemSize() const noexcept { return mElemSize; } + [[nodiscard]] auto getElemSize() const noexcept -> std::size_t { return mElemSize; } /** * 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). + * bytes. The number of elements is rounded up to a power of two. If + * `limit_writes' is true, the writable space will be limited to `sz' + * elements regardless of the rounded size. */ - static std::unique_ptr Create(size_t sz, size_t elem_sz, int limit_writes); + [[nodiscard]] static + auto Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> std::unique_ptr; DEF_FAM_NEWDEL(RingBuffer, mBuffer) }; diff --git a/Engine/lib/openal-soft/common/strutils.cpp b/Engine/lib/openal-soft/common/strutils.cpp index d0418eff2..4bcd41194 100644 --- a/Engine/lib/openal-soft/common/strutils.cpp +++ b/Engine/lib/openal-soft/common/strutils.cpp @@ -5,36 +5,37 @@ #include - #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include -std::string wstr_to_utf8(const WCHAR *wstr) +#include "alstring.h" + +std::string wstr_to_utf8(std::wstring_view wstr) { std::string ret; - int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); + const int len{WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), nullptr, 0, + nullptr, nullptr)}; if(len > 0) { - ret.resize(len); - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &ret[0], len, nullptr, nullptr); - ret.pop_back(); + ret.resize(static_cast(len)); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), ret.data(), len, + nullptr, nullptr); } return ret; } -std::wstring utf8_to_wstr(const char *str) +std::wstring utf8_to_wstr(std::string_view str) { std::wstring ret; - int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); + const int len{MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), nullptr, 0)}; if(len > 0) { - ret.resize(len); - MultiByteToWideChar(CP_UTF8, 0, str, -1, &ret[0], len); - ret.pop_back(); + ret.resize(static_cast(len)); + MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), ret.data(), len); } return ret; @@ -43,21 +44,25 @@ std::wstring utf8_to_wstr(const char *str) namespace al { -al::optional getenv(const char *envname) +std::optional getenv(const char *envname) { +#ifdef _GAMING_XBOX + const char *str{::getenv(envname)}; +#else const char *str{std::getenv(envname)}; - if(str && str[0] != '\0') +#endif + if(str && *str != '\0') return str; - return al::nullopt; + return std::nullopt; } #ifdef _WIN32 -al::optional getenv(const WCHAR *envname) +std::optional getenv(const WCHAR *envname) { const WCHAR *str{_wgetenv(envname)}; - if(str && str[0] != L'\0') + if(str && *str != L'\0') return str; - return al::nullopt; + return std::nullopt; } #endif diff --git a/Engine/lib/openal-soft/common/strutils.h b/Engine/lib/openal-soft/common/strutils.h index 0c7a0e226..3644847c2 100644 --- a/Engine/lib/openal-soft/common/strutils.h +++ b/Engine/lib/openal-soft/common/strutils.h @@ -1,22 +1,22 @@ #ifndef AL_STRUTILS_H #define AL_STRUTILS_H +#include #include -#include "aloptional.h" - #ifdef _WIN32 -#include +#include +#include -std::string wstr_to_utf8(const wchar_t *wstr); -std::wstring utf8_to_wstr(const char *str); +std::string wstr_to_utf8(std::wstring_view wstr); +std::wstring utf8_to_wstr(std::string_view str); #endif namespace al { -al::optional getenv(const char *envname); +std::optional getenv(const char *envname); #ifdef _WIN32 -al::optional getenv(const wchar_t *envname); +std::optional getenv(const wchar_t *envname); #endif } // namespace al diff --git a/Engine/lib/openal-soft/common/threads.h b/Engine/lib/openal-soft/common/threads.h deleted file mode 100644 index 1cdb5d8f6..000000000 --- a/Engine/lib/openal-soft/common/threads.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef AL_THREADS_H -#define AL_THREADS_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 - * thread functions will be properly aligned on the stack, even though GCC may - * generate code with the assumption that it is. */ -#define FORCE_ALIGN __attribute__((force_align_arg_pointer)) -#else -#define FORCE_ALIGN -#endif - -#if defined(__APPLE__) -#include -#elif !defined(_WIN32) -#include -#endif - -void althrd_setname(const char *name); - -namespace al { - -class semaphore { -#ifdef _WIN32 - using native_type = void*; -#elif defined(__APPLE__) - using native_type = dispatch_semaphore_t; -#else - using native_type = sem_t; -#endif - native_type mSem; - -public: - semaphore(unsigned int initial=0); - semaphore(const semaphore&) = delete; - ~semaphore(); - - semaphore& operator=(const semaphore&) = delete; - - void post(); - void wait() noexcept; - bool try_wait() noexcept; -}; - -} // namespace al - -#endif /* AL_THREADS_H */ diff --git a/Engine/lib/openal-soft/common/vecmat.h b/Engine/lib/openal-soft/common/vecmat.h index a45f262f6..90b5d1469 100644 --- a/Engine/lib/openal-soft/common/vecmat.h +++ b/Engine/lib/openal-soft/common/vecmat.h @@ -1,6 +1,7 @@ #ifndef COMMON_VECMAT_H #define COMMON_VECMAT_H +#include #include #include #include @@ -11,22 +12,24 @@ namespace alu { -template -class VectorR { - static_assert(std::is_floating_point::value, "Must use floating-point types"); - alignas(16) T mVals[4]; +class Vector { + alignas(16) std::array mVals{}; public: - constexpr VectorR() noexcept = default; - constexpr VectorR(const VectorR&) noexcept = default; - constexpr explicit VectorR(T a, T b, T c, T d) noexcept : mVals{a, b, c, d} { } + constexpr Vector() noexcept = default; + constexpr Vector(const Vector&) noexcept = default; + constexpr Vector(Vector&&) noexcept = default; + constexpr explicit Vector(float a, float b, float c, float d) noexcept : mVals{{a,b,c,d}} { } - constexpr VectorR& operator=(const VectorR&) noexcept = default; + constexpr auto operator=(const Vector&) noexcept -> Vector& = default; + constexpr auto operator=(Vector&&) noexcept -> Vector& = default; - constexpr T& operator[](size_t idx) noexcept { return mVals[idx]; } - constexpr const T& operator[](size_t idx) const noexcept { return mVals[idx]; } + [[nodiscard]] constexpr + auto operator[](std::size_t idx) noexcept -> float& { return mVals[idx]; } + [[nodiscard]] constexpr + auto operator[](std::size_t idx) const noexcept -> const float& { return mVals[idx]; } - constexpr VectorR& operator+=(const VectorR &rhs) noexcept + constexpr auto operator+=(const Vector &rhs) noexcept -> Vector& { mVals[0] += rhs.mVals[0]; mVals[1] += rhs.mVals[1]; @@ -35,85 +38,85 @@ public: return *this; } - constexpr VectorR operator-(const VectorR &rhs) const noexcept + [[nodiscard]] constexpr + auto operator-(const Vector &rhs) const noexcept -> Vector { - return VectorR{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1], + return Vector{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1], mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]}; } - constexpr T normalize(T limit = std::numeric_limits::epsilon()) + constexpr auto normalize(float limit = std::numeric_limits::epsilon()) -> float { - limit = std::max(limit, std::numeric_limits::epsilon()); - const T length_sqr{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; + limit = std::max(limit, std::numeric_limits::epsilon()); + const auto length_sqr = float{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; if(length_sqr > limit*limit) { - const T length{std::sqrt(length_sqr)}; - T inv_length{T{1}/length}; + const auto length = float{std::sqrt(length_sqr)}; + auto inv_length = float{1.0f / length}; mVals[0] *= inv_length; mVals[1] *= inv_length; mVals[2] *= inv_length; return length; } - mVals[0] = mVals[1] = mVals[2] = T{0}; - return T{0}; + mVals[0] = mVals[1] = mVals[2] = 0.0f; + return 0.0f; } - constexpr VectorR cross_product(const alu::VectorR &rhs) const noexcept + [[nodiscard]] constexpr auto cross_product(const Vector &rhs) const noexcept -> Vector { - return VectorR{ + return Vector{ mVals[1]*rhs.mVals[2] - mVals[2]*rhs.mVals[1], mVals[2]*rhs.mVals[0] - mVals[0]*rhs.mVals[2], mVals[0]*rhs.mVals[1] - mVals[1]*rhs.mVals[0], - T{0}}; + 0.0f}; } - constexpr T dot_product(const alu::VectorR &rhs) const noexcept + [[nodiscard]] constexpr auto dot_product(const Vector &rhs) const noexcept -> float { return mVals[0]*rhs.mVals[0] + mVals[1]*rhs.mVals[1] + mVals[2]*rhs.mVals[2]; } }; -using Vector = VectorR; -template -class MatrixR { - static_assert(std::is_floating_point::value, "Must use floating-point types"); - alignas(16) T mVals[16]; +class Matrix { + alignas(16) std::array mVals{}; public: - constexpr MatrixR() noexcept = default; - constexpr MatrixR(const MatrixR&) noexcept = default; - constexpr explicit MatrixR( - T aa, T ab, T ac, T ad, - T ba, T bb, T bc, T bd, - T ca, T cb, T cc, T cd, - T da, T db, T dc, T dd) noexcept - : mVals{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd} + constexpr Matrix() noexcept = default; + constexpr Matrix(const Matrix&) noexcept = default; + constexpr Matrix(Matrix&&) noexcept = default; + constexpr explicit 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}} { } - constexpr MatrixR& operator=(const MatrixR&) noexcept = default; + constexpr auto operator=(const Matrix&) noexcept -> Matrix& = default; + constexpr auto operator=(Matrix&&) noexcept -> Matrix& = default; - constexpr auto operator[](size_t idx) noexcept { return al::span{&mVals[idx*4], 4}; } - constexpr auto operator[](size_t idx) const noexcept - { return al::span{&mVals[idx*4], 4}; } + [[nodiscard]] constexpr auto operator[](std::size_t idx) noexcept + { return al::span{&mVals[idx*4], 4}; } + [[nodiscard]] constexpr auto operator[](std::size_t idx) const noexcept + { return al::span{&mVals[idx*4], 4}; } - static constexpr MatrixR Identity() noexcept + [[nodiscard]] static constexpr auto Identity() noexcept -> Matrix { - return MatrixR{ - T{1}, T{0}, T{0}, T{0}, - T{0}, T{1}, T{0}, T{0}, - T{0}, T{0}, T{1}, T{0}, - T{0}, T{0}, T{0}, T{1}}; + return Matrix{ + 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}; + } + + [[nodiscard]] friend constexpr + auto operator*(const Matrix &mtx, const Vector &vec) noexcept -> Vector + { + return 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]}; } }; -using Matrix = MatrixR; - -template -constexpr VectorR operator*(const MatrixR &mtx, const VectorR &vec) noexcept -{ - return VectorR{ - 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]}; -} } // namespace alu diff --git a/Engine/lib/openal-soft/common/vector.h b/Engine/lib/openal-soft/common/vector.h index 1b69d6a77..364735c67 100644 --- a/Engine/lib/openal-soft/common/vector.h +++ b/Engine/lib/openal-soft/common/vector.h @@ -1,13 +1,14 @@ #ifndef AL_VECTOR_H #define AL_VECTOR_H +#include #include #include "almalloc.h" namespace al { -template +template using vector = std::vector>; } // namespace al diff --git a/Engine/lib/openal-soft/common/win_main_utf8.h b/Engine/lib/openal-soft/common/win_main_utf8.h index 077af533d..81734242f 100644 --- a/Engine/lib/openal-soft/common/win_main_utf8.h +++ b/Engine/lib/openal-soft/common/win_main_utf8.h @@ -20,14 +20,20 @@ #define STATIC_CAST(...) static_cast<__VA_ARGS__> #define REINTERPRET_CAST(...) reinterpret_cast<__VA_ARGS__> +#define MAYBE_UNUSED [[maybe_unused]] #else #define STATIC_CAST(...) (__VA_ARGS__) #define REINTERPRET_CAST(...) (__VA_ARGS__) +#ifdef __GNUC__ +#define MAYBE_UNUSED __attribute__((__unused__)) +#else +#define MAYBE_UNUSED +#endif #endif -static FILE *my_fopen(const char *fname, const char *mode) +MAYBE_UNUSED static FILE *my_fopen(const char *fname, const char *mode) { wchar_t *wname=NULL, *wmode=NULL; int namelen, modelen; @@ -44,10 +50,11 @@ static FILE *my_fopen(const char *fname, const char *mode) } #ifdef __cplusplus - auto strbuf = std::make_unique(static_cast(namelen)+modelen); + auto strbuf = std::make_unique(static_cast(namelen) + + static_cast(modelen)); wname = strbuf.get(); #else - wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)namelen + modelen); + wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)namelen + (size_t)modelen); #endif wmode = wname + namelen; MultiByteToWideChar(CP_UTF8, 0, fname, -1, wname, namelen); diff --git a/Engine/lib/openal-soft/config.h.in b/Engine/lib/openal-soft/config.h.in index 477d8c77b..9013c4731 100644 --- a/Engine/lib/openal-soft/config.h.in +++ b/Engine/lib/openal-soft/config.h.in @@ -1,21 +1,12 @@ -/* Define if deprecated EAX extensions are enabled */ -#cmakedefine ALSOFT_EAX +/* Define the alignment attribute for externally callable functions. */ +#define FORCE_ALIGN @ALSOFT_FORCE_ALIGN@ /* Define if HRTF data is embedded in the library */ #cmakedefine ALSOFT_EMBED_HRTF_DATA -/* Define if we have the posix_memalign function */ -#cmakedefine HAVE_POSIX_MEMALIGN - -/* Define if we have the _aligned_malloc function */ -#cmakedefine HAVE__ALIGNED_MALLOC - /* Define if we have the proc_pidpath function */ #cmakedefine HAVE_PROC_PIDPATH -/* Define if we have the getopt function */ -#cmakedefine HAVE_GETOPT - /* Define if we have DBus/RTKit */ #cmakedefine HAVE_RTKIT @@ -82,9 +73,6 @@ /* Define if we have pthread_np.h */ #cmakedefine HAVE_PTHREAD_NP_H -/* Define if we have malloc.h */ -#cmakedefine HAVE_MALLOC_H - /* Define if we have cpuid.h */ #cmakedefine HAVE_CPUID_H @@ -94,9 +82,6 @@ /* Define if we have guiddef.h */ #cmakedefine HAVE_GUIDDEF_H -/* Define if we have initguid.h */ -#cmakedefine HAVE_INITGUID_H - /* Define if we have GCC's __get_cpuid() */ #cmakedefine HAVE_GCC_GET_CPUID @@ -117,3 +102,6 @@ /* Define the installation data directory */ #cmakedefine ALSOFT_INSTALL_DATADIR "@ALSOFT_INSTALL_DATADIR@" + +/* Define whether build alsoft for winuwp */ +#cmakedefine ALSOFT_UWP diff --git a/Engine/lib/openal-soft/core/ambdec.cpp b/Engine/lib/openal-soft/core/ambdec.cpp index 8ca182c49..ba3a4b8b3 100644 --- a/Engine/lib/openal-soft/core/ambdec.cpp +++ b/Engine/lib/openal-soft/core/ambdec.cpp @@ -8,12 +8,13 @@ #include #include #include +#include +#include #include #include #include #include "albit.h" -#include "alfstream.h" #include "alspan.h" #include "opthelpers.h" @@ -42,21 +43,22 @@ enum class ReaderScope { HFMatrix, }; -#ifdef __USE_MINGW_ANSI_STDIO -[[gnu::format(gnu_printf,2,3)]] +#ifdef __MINGW32__ +[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] #else [[gnu::format(printf,2,3)]] #endif -al::optional make_error(size_t linenum, const char *fmt, ...) +std::optional make_error(size_t linenum, const char *fmt, ...) { - al::optional ret; + std::optional ret; auto &str = ret.emplace(); str.resize(256); - int printed{std::snprintf(const_cast(str.data()), str.length(), "Line %zu: ", linenum)}; + int printed{std::snprintf(str.data(), str.length(), "Line %zu: ", linenum)}; if(printed < 0) printed = 0; auto plen = std::min(static_cast(printed), str.length()); + /* NOLINTBEGIN(*-array-to-pointer-decay) */ std::va_list args, args2; va_start(args, fmt); va_copy(args2, args); @@ -68,6 +70,7 @@ al::optional make_error(size_t linenum, const char *fmt, ...) } va_end(args2); va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ return ret; } @@ -77,9 +80,9 @@ al::optional make_error(size_t linenum, const char *fmt, ...) AmbDecConf::~AmbDecConf() = default; -al::optional AmbDecConf::load(const char *fname) noexcept +std::optional AmbDecConf::load(const char *fname) noexcept { - al::ifstream f{fname}; + std::ifstream f{std::filesystem::u8path(fname)}; if(!f.is_open()) return std::string("Failed to open file \"")+fname+"\""; @@ -111,7 +114,7 @@ al::optional AmbDecConf::load(const char *fname) noexcept { if(command == "add_spkr") { - if(speaker_pos == NumSpeakers) + if(speaker_pos == Speakers.size()) return make_error(linenum, "Too many speakers specified"); AmbDecConf::SpeakerConf &spkr = Speakers[speaker_pos++]; @@ -127,7 +130,7 @@ al::optional AmbDecConf::load(const char *fname) noexcept else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix) { auto &gains = (scope == ReaderScope::LFMatrix) ? LFOrderGain : HFOrderGain; - auto *matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix; + auto matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix; auto &pos = (scope == ReaderScope::LFMatrix) ? lfmatrix_pos : hfmatrix_pos; if(command == "order_gain") @@ -139,13 +142,13 @@ al::optional AmbDecConf::load(const char *fname) noexcept { --toread; istr >> value; - if(curgain < al::size(gains)) + if(curgain < std::size(gains)) gains[curgain++] = value; } } else if(command == "add_row") { - if(pos == NumSpeakers) + if(pos == Speakers.size()) return make_error(linenum, "Too many matrix rows specified"); unsigned int mask{ChanMask}; @@ -205,12 +208,13 @@ al::optional AmbDecConf::load(const char *fname) noexcept } else if(command == "/dec/speakers") { - if(NumSpeakers) + if(!Speakers.empty()) return make_error(linenum, "Duplicate speakers"); - istr >> NumSpeakers; - if(!NumSpeakers) - return make_error(linenum, "Invalid speakers: %zu", NumSpeakers); - Speakers = std::make_unique(NumSpeakers); + size_t numspeakers{}; + istr >> numspeakers; + if(!numspeakers) + return make_error(linenum, "Invalid speakers: %zu", numspeakers); + Speakers.resize(numspeakers); } else if(command == "/dec/coeff_scale") { @@ -243,22 +247,22 @@ al::optional AmbDecConf::load(const char *fname) noexcept } else if(command == "/speakers/{") { - if(!NumSpeakers) + if(Speakers.empty()) return make_error(linenum, "Speakers defined without a count"); scope = ReaderScope::Speakers; } else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{") { - if(!NumSpeakers) + if(Speakers.empty()) return make_error(linenum, "Matrix defined without a speaker count"); if(!ChanMask) return make_error(linenum, "Matrix defined without a channel mask"); - if(!Matrix) + if(Matrix.empty()) { - Matrix = std::make_unique(NumSpeakers * FreqBands); - LFMatrix = Matrix.get(); - HFMatrix = LFMatrix + NumSpeakers*(FreqBands-1); + Matrix.resize(Speakers.size() * FreqBands); + LFMatrix = al::span{Matrix}.first(Speakers.size()); + HFMatrix = al::span{Matrix}.subspan(Speakers.size()*(FreqBands-1)); } if(FreqBands == 1) @@ -285,13 +289,13 @@ al::optional AmbDecConf::load(const char *fname) noexcept if(!is_at_end(buffer, endpos)) return make_error(linenum, "Extra junk on end: %s", buffer.substr(endpos).c_str()); - if(speaker_pos < NumSpeakers || hfmatrix_pos < NumSpeakers - || (FreqBands == 2 && lfmatrix_pos < NumSpeakers)) + if(speaker_pos < Speakers.size() || hfmatrix_pos < Speakers.size() + || (FreqBands == 2 && lfmatrix_pos < Speakers.size())) return make_error(linenum, "Incomplete decoder definition"); if(CoeffScale == AmbDecScale::Unset) return make_error(linenum, "No coefficient scaling defined"); - return al::nullopt; + return std::nullopt; } else return make_error(linenum, "Unexpected command: %s", command.c_str()); diff --git a/Engine/lib/openal-soft/core/ambdec.h b/Engine/lib/openal-soft/core/ambdec.h index 7f7397817..587a30c66 100644 --- a/Engine/lib/openal-soft/core/ambdec.h +++ b/Engine/lib/openal-soft/core/ambdec.h @@ -3,9 +3,11 @@ #include #include +#include #include +#include -#include "aloptional.h" +#include "alspan.h" #include "core/ambidefs.h" /* Helpers to read .ambdec configuration files. */ @@ -34,22 +36,21 @@ struct AmbDecConf { float Elevation{0.0f}; std::string Connection; }; - size_t NumSpeakers{0}; - std::unique_ptr Speakers; + std::vector Speakers; using CoeffArray = std::array; - std::unique_ptr Matrix; + std::vector Matrix; /* Unused when FreqBands == 1 */ - float LFOrderGain[MaxAmbiOrder+1]{}; - CoeffArray *LFMatrix; + std::array LFOrderGain{}; + al::span LFMatrix; - float HFOrderGain[MaxAmbiOrder+1]{}; - CoeffArray *HFMatrix; + std::array HFOrderGain{}; + al::span HFMatrix; ~AmbDecConf(); - al::optional load(const char *fname) noexcept; + std::optional load(const char *fname) noexcept; }; #endif /* CORE_AMBDEC_H */ diff --git a/Engine/lib/openal-soft/core/ambidefs.cpp b/Engine/lib/openal-soft/core/ambidefs.cpp index 70d6f356e..2389ce6bd 100644 --- a/Engine/lib/openal-soft/core/ambidefs.cpp +++ b/Engine/lib/openal-soft/core/ambidefs.cpp @@ -21,25 +21,25 @@ constexpr auto inv_sqrt3f = static_cast(1.0/al::numbers::sqrt3); * will result in that channel being subsequently decoded for second-order as * if it was a first-order decoder for that same speaker array. */ -constexpr std::array,MaxAmbiOrder+1> HFScales{{ - {{ 4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }}, - {{ 4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }}, - {{ 2.981423970e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }}, - {{ 2.359168820e+00f, 2.031565936e+00f, 1.444598386e+00f, 7.189495850e-01f }}, - /* 1.947005434e+00f, 1.764337084e+00f, 1.424707344e+00f, 9.755104127e-01f, 4.784482742e-01f */ -}}; +constexpr std::array HFScales{ + std::array{4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f}, + std::array{4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f}, + std::array{2.981423970e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f}, + std::array{2.359168820e+00f, 2.031565936e+00f, 1.444598386e+00f, 7.189495850e-01f}, + /*std::array{1.947005434e+00f, 1.764337084e+00f, 1.424707344e+00f, 9.755104127e-01f, 4.784482742e-01f}, */ +}; /* Same as above, but using a 10-point horizontal-only speaker array. Should * only be used when the device is mixing in 2D B-Format for horizontal-only * output. */ -constexpr std::array,MaxAmbiOrder+1> HFScales2D{{ - {{ 2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }}, - {{ 2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }}, - {{ 1.825741858e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }}, - {{ 1.581138830e+00f, 1.460781803e+00f, 1.118033989e+00f, 6.050756345e-01f }}, - /* 1.414213562e+00f, 1.344997024e+00f, 1.144122806e+00f, 8.312538756e-01f, 4.370160244e-01f */ -}}; +constexpr std::array HFScales2D{ + std::array{2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f}, + std::array{2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f}, + std::array{1.825741858e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f}, + std::array{1.581138830e+00f, 1.460781803e+00f, 1.118033989e+00f, 6.050756345e-01f}, + /*std::array{1.414213562e+00f, 1.344997024e+00f, 1.144122806e+00f, 8.312538756e-01f, 4.370160244e-01f}, */ +}; /* This calculates a first-order "upsampler" matrix. It combines a first-order @@ -49,17 +49,17 @@ constexpr std::array,MaxAmbiOrder+1> HFScales2D * signal. While not perfect, this should accurately encode a lower-order * signal into a higher-order signal. */ -constexpr std::array,8> FirstOrderDecoder{{ - {{ 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, }}, -}}; -constexpr std::array FirstOrderEncoder{{ +constexpr std::array FirstOrderDecoder{ + std::array{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, + std::array{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, + std::array{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, + std::array{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, + std::array{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, + std::array{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, + std::array{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, + std::array{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, +}; +constexpr std::array FirstOrderEncoder{ CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs(-inv_sqrt3f, inv_sqrt3f, inv_sqrt3f), @@ -68,25 +68,25 @@ constexpr std::array FirstOrderEncoder{{ CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs(-inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs(-inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), -}}; +}; static_assert(FirstOrderDecoder.size() == FirstOrderEncoder.size(), "First-order mismatch"); /* This calculates a 2D first-order "upsampler" matrix. Same as the first-order * matrix, just using a more optimized speaker array for horizontal-only * content. */ -constexpr std::array,4> FirstOrder2DDecoder{{ - {{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, 2.041241452e-01f, }}, - {{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, -2.041241452e-01f, }}, - {{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, 2.041241452e-01f, }}, - {{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, -2.041241452e-01f, }}, -}}; -constexpr std::array FirstOrder2DEncoder{{ +constexpr std::array FirstOrder2DDecoder{ + std::array{2.500000000e-01f, 2.041241452e-01f, 0.0f, 2.041241452e-01f}, + std::array{2.500000000e-01f, 2.041241452e-01f, 0.0f, -2.041241452e-01f}, + std::array{2.500000000e-01f, -2.041241452e-01f, 0.0f, 2.041241452e-01f}, + std::array{2.500000000e-01f, -2.041241452e-01f, 0.0f, -2.041241452e-01f}, +}; +constexpr std::array FirstOrder2DEncoder{ CalcAmbiCoeffs( inv_sqrt2f, 0.0f, inv_sqrt2f), CalcAmbiCoeffs( inv_sqrt2f, 0.0f, -inv_sqrt2f), CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, inv_sqrt2f), CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, -inv_sqrt2f), -}}; +}; static_assert(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-order 2D mismatch"); @@ -94,21 +94,21 @@ static_assert(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-o * matrix, just using a slightly more dense speaker array suitable for second- * order content. */ -constexpr std::array,12> SecondOrderDecoder{{ - {{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }}, - {{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, - {{ 8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, - {{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }}, - {{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, - {{ 8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, - {{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }}, - {{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, - {{ 8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, - {{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }}, - {{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, - {{ 8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, -}}; -constexpr std::array SecondOrderEncoder{{ +constexpr std::array SecondOrderDecoder{ + std::array{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, + std::array{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + std::array{8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + std::array{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, + std::array{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + std::array{8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + std::array{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, + std::array{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + std::array{8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + std::array{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, + std::array{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + std::array{8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, +}; +constexpr std::array SecondOrderEncoder{ CalcAmbiCoeffs( 0.000000000e+00f, -5.257311121e-01f, 8.506508084e-01f), CalcAmbiCoeffs(-8.506508084e-01f, 0.000000000e+00f, 5.257311121e-01f), CalcAmbiCoeffs(-5.257311121e-01f, 8.506508084e-01f, 0.000000000e+00f), @@ -121,29 +121,29 @@ constexpr std::array SecondOrderEncoder{{ CalcAmbiCoeffs( 0.000000000e+00f, 5.257311121e-01f, -8.506508084e-01f), CalcAmbiCoeffs( 8.506508084e-01f, 0.000000000e+00f, 5.257311121e-01f), CalcAmbiCoeffs(-5.257311121e-01f, -8.506508084e-01f, 0.000000000e+00f), -}}; +}; static_assert(SecondOrderDecoder.size() == SecondOrderEncoder.size(), "Second-order mismatch"); /* This calculates a 2D second-order "upsampler" matrix. Same as the second- * order matrix, just using a more optimized speaker array for horizontal-only * content. */ -constexpr std::array,6> SecondOrder2DDecoder{{ - {{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, - {{ 1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }}, - {{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, - {{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, - {{ 1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }}, - {{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, -}}; -constexpr std::array SecondOrder2DEncoder{{ +constexpr std::array SecondOrder2DDecoder{ + std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, + std::array{1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f}, + std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, + std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, + std::array{1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f}, + std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, +}; +constexpr std::array SecondOrder2DEncoder{ CalcAmbiCoeffs(-0.50000000000f, 0.0f, 0.86602540379f), CalcAmbiCoeffs(-1.00000000000f, 0.0f, 0.00000000000f), CalcAmbiCoeffs(-0.50000000000f, 0.0f, -0.86602540379f), CalcAmbiCoeffs( 0.50000000000f, 0.0f, -0.86602540379f), CalcAmbiCoeffs( 1.00000000000f, 0.0f, 0.00000000000f), CalcAmbiCoeffs( 0.50000000000f, 0.0f, 0.86602540379f), -}}; +}; static_assert(SecondOrder2DDecoder.size() == SecondOrder2DEncoder.size(), "Second-order 2D mismatch"); @@ -152,29 +152,29 @@ static_assert(SecondOrder2DDecoder.size() == SecondOrder2DEncoder.size(), * matrix, just using a more dense speaker array suitable for third-order * content. */ -constexpr std::array,20> ThirdOrderDecoder{{ - {{ 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }}, - {{ 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }}, - {{ 5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }}, - {{ 5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }}, - {{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }}, - {{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }}, - {{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }}, - {{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }}, - {{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f, }}, - {{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f, }}, - {{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f, }}, - {{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f, }}, - {{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }}, - {{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }}, - {{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }}, - {{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }}, - {{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }}, - {{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }}, - {{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }}, - {{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }}, -}}; -constexpr std::array ThirdOrderEncoder{{ +constexpr std::array ThirdOrderDecoder{ + std::array{5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, + std::array{5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, + std::array{5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, + std::array{5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, + std::array{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, + std::array{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, + std::array{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, + std::array{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, + std::array{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f}, + std::array{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f}, + std::array{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f}, + std::array{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f}, + std::array{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, + std::array{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, + std::array{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, + std::array{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, + std::array{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, + std::array{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, + std::array{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, + std::array{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, +}; +constexpr std::array ThirdOrderEncoder{ CalcAmbiCoeffs( 0.35682208976f, 0.93417235897f, 0.00000000000f), CalcAmbiCoeffs(-0.35682208976f, 0.93417235897f, 0.00000000000f), CalcAmbiCoeffs( 0.35682208976f, -0.93417235897f, 0.00000000000f), @@ -195,24 +195,24 @@ constexpr std::array ThirdOrderEncoder{{ CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs( -inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs( -inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), -}}; +}; static_assert(ThirdOrderDecoder.size() == ThirdOrderEncoder.size(), "Third-order mismatch"); /* This calculates a 2D third-order "upsampler" matrix. Same as the third-order * matrix, just using a more optimized speaker array for horizontal-only * content. */ -constexpr std::array,8> ThirdOrder2DDecoder{{ - {{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, 1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }}, - {{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, 5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }}, - {{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, -5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }}, - {{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, -1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }}, - {{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, -1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }}, - {{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, -5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }}, - {{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, 5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }}, - {{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, 1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }}, -}}; -constexpr std::array ThirdOrder2DEncoder{{ +constexpr std::array ThirdOrder2DDecoder{ + std::array{1.250000000e-01f, -5.523559567e-02f, 0.0f, 1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f}, + std::array{1.250000000e-01f, -1.333505242e-01f, 0.0f, 5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f}, + std::array{1.250000000e-01f, -1.333505242e-01f, 0.0f, -5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f}, + std::array{1.250000000e-01f, -5.523559567e-02f, 0.0f, -1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f}, + std::array{1.250000000e-01f, 5.523559567e-02f, 0.0f, -1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f}, + std::array{1.250000000e-01f, 1.333505242e-01f, 0.0f, -5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f}, + std::array{1.250000000e-01f, 1.333505242e-01f, 0.0f, 5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f}, + std::array{1.250000000e-01f, 5.523559567e-02f, 0.0f, 1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f}, +}; +constexpr std::array ThirdOrder2DEncoder{ CalcAmbiCoeffs(-0.38268343237f, 0.0f, 0.92387953251f), CalcAmbiCoeffs(-0.92387953251f, 0.0f, 0.38268343237f), CalcAmbiCoeffs(-0.92387953251f, 0.0f, -0.38268343237f), @@ -221,7 +221,7 @@ constexpr std::array ThirdOrder2DEncoder{{ CalcAmbiCoeffs( 0.92387953251f, 0.0f, -0.38268343237f), CalcAmbiCoeffs( 0.92387953251f, 0.0f, 0.38268343237f), CalcAmbiCoeffs( 0.38268343237f, 0.0f, 0.92387953251f), -}}; +}; static_assert(ThirdOrder2DDecoder.size() == ThirdOrder2DEncoder.size(), "Third-order 2D mismatch"); @@ -230,19 +230,19 @@ static_assert(ThirdOrder2DDecoder.size() == ThirdOrder2DEncoder.size(), "Third-o * the foreseeable future. This is only necessary for mixing horizontal-only * fourth-order content to 3D. */ -constexpr std::array,10> FourthOrder2DDecoder{{ - {{ 1.000000000e-01f, 3.568220898e-02f, 0.0f, 1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, - {{ 1.000000000e-01f, 9.341723590e-02f, 0.0f, 6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, - {{ 1.000000000e-01f, 1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, -9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f, }}, - {{ 1.000000000e-01f, 9.341723590e-02f, 0.0f, -6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, - {{ 1.000000000e-01f, 3.568220898e-02f, 0.0f, -1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, - {{ 1.000000000e-01f, -3.568220898e-02f, 0.0f, -1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, - {{ 1.000000000e-01f, -9.341723590e-02f, 0.0f, -6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, - {{ 1.000000000e-01f, -1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, 9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f, }}, - {{ 1.000000000e-01f, -9.341723590e-02f, 0.0f, 6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, - {{ 1.000000000e-01f, -3.568220898e-02f, 0.0f, 1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, -}}; -constexpr std::array FourthOrder2DEncoder{{ +constexpr std::array FourthOrder2DDecoder{ + std::array{1.000000000e-01f, 3.568220898e-02f, 0.0f, 1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, + std::array{1.000000000e-01f, 9.341723590e-02f, 0.0f, 6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, + std::array{1.000000000e-01f, 1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, -9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f}, + std::array{1.000000000e-01f, 9.341723590e-02f, 0.0f, -6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, + std::array{1.000000000e-01f, 3.568220898e-02f, 0.0f, -1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, + std::array{1.000000000e-01f, -3.568220898e-02f, 0.0f, -1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, + std::array{1.000000000e-01f, -9.341723590e-02f, 0.0f, -6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, + std::array{1.000000000e-01f, -1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, 9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f}, + std::array{1.000000000e-01f, -9.341723590e-02f, 0.0f, 6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, + std::array{1.000000000e-01f, -3.568220898e-02f, 0.0f, 1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, +}; +constexpr std::array FourthOrder2DEncoder{ CalcAmbiCoeffs( 3.090169944e-01f, 0.000000000e+00f, 9.510565163e-01f), CalcAmbiCoeffs( 8.090169944e-01f, 0.000000000e+00f, 5.877852523e-01f), CalcAmbiCoeffs( 1.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f), @@ -253,12 +253,12 @@ constexpr std::array FourthOrder2DEncoder{{ CalcAmbiCoeffs(-1.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f), CalcAmbiCoeffs(-8.090169944e-01f, 0.000000000e+00f, 5.877852523e-01f), CalcAmbiCoeffs(-3.090169944e-01f, 0.000000000e+00f, 9.510565163e-01f), -}}; +}; static_assert(FourthOrder2DDecoder.size() == FourthOrder2DEncoder.size(), "Fourth-order 2D mismatch"); template -auto CalcAmbiUpsampler(const std::array,M> &decoder, +constexpr auto CalcAmbiUpsampler(const std::array,M> &decoder, const std::array &encoder) { std::array res{}; @@ -279,13 +279,13 @@ auto CalcAmbiUpsampler(const std::array,M> &decoder, } // namespace -const std::array AmbiScale::FirstOrderUp{CalcAmbiUpsampler(FirstOrderDecoder, FirstOrderEncoder)}; -const std::array AmbiScale::FirstOrder2DUp{CalcAmbiUpsampler(FirstOrder2DDecoder, FirstOrder2DEncoder)}; -const std::array AmbiScale::SecondOrderUp{CalcAmbiUpsampler(SecondOrderDecoder, SecondOrderEncoder)}; -const std::array AmbiScale::SecondOrder2DUp{CalcAmbiUpsampler(SecondOrder2DDecoder, SecondOrder2DEncoder)}; -const std::array AmbiScale::ThirdOrderUp{CalcAmbiUpsampler(ThirdOrderDecoder, ThirdOrderEncoder)}; -const std::array AmbiScale::ThirdOrder2DUp{CalcAmbiUpsampler(ThirdOrder2DDecoder, ThirdOrder2DEncoder)}; -const std::array AmbiScale::FourthOrder2DUp{CalcAmbiUpsampler(FourthOrder2DDecoder, FourthOrder2DEncoder)}; +const std::array,4> AmbiScale::FirstOrderUp{CalcAmbiUpsampler(FirstOrderDecoder, FirstOrderEncoder)}; +const std::array,4> AmbiScale::FirstOrder2DUp{CalcAmbiUpsampler(FirstOrder2DDecoder, FirstOrder2DEncoder)}; +const std::array,9> AmbiScale::SecondOrderUp{CalcAmbiUpsampler(SecondOrderDecoder, SecondOrderEncoder)}; +const std::array,9> AmbiScale::SecondOrder2DUp{CalcAmbiUpsampler(SecondOrder2DDecoder, SecondOrder2DEncoder)}; +const std::array,16> AmbiScale::ThirdOrderUp{CalcAmbiUpsampler(ThirdOrderDecoder, ThirdOrderEncoder)}; +const std::array,16> AmbiScale::ThirdOrder2DUp{CalcAmbiUpsampler(ThirdOrder2DDecoder, ThirdOrder2DEncoder)}; +const std::array,25> AmbiScale::FourthOrder2DUp{CalcAmbiUpsampler(FourthOrder2DDecoder, FourthOrder2DEncoder)}; std::array AmbiScale::GetHFOrderScales(const uint src_order, diff --git a/Engine/lib/openal-soft/core/ambidefs.h b/Engine/lib/openal-soft/core/ambidefs.h index b7d2bcd1f..1faf8eb68 100644 --- a/Engine/lib/openal-soft/core/ambidefs.h +++ b/Engine/lib/openal-soft/core/ambidefs.h @@ -2,8 +2,8 @@ #define CORE_AMBIDEFS_H #include -#include -#include +#include +#include #include "alnumbers.h" @@ -14,104 +14,88 @@ using uint = unsigned int; * 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. */ -constexpr uint8_t MaxAmbiOrder{3}; -constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept +inline constexpr auto MaxAmbiOrder = std::uint8_t{3}; +inline constexpr auto AmbiChannelsFromOrder(std::size_t order) noexcept -> std::size_t { return (order+1) * (order+1); } -constexpr size_t MaxAmbiChannels{AmbiChannelsFromOrder(MaxAmbiOrder)}; +inline constexpr auto MaxAmbiChannels = size_t{AmbiChannelsFromOrder(MaxAmbiOrder)}; /* 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). */ -constexpr uint Ambi0OrderMask{0x00000001}; -constexpr uint Ambi1OrderMask{0x0000000f}; -constexpr uint Ambi2OrderMask{0x000001ff}; -constexpr uint Ambi3OrderMask{0x0000ffff}; -constexpr uint Ambi4OrderMask{0x01ffffff}; +inline constexpr uint Ambi0OrderMask{0x00000001}; +inline constexpr uint Ambi1OrderMask{0x0000000f}; +inline constexpr uint Ambi2OrderMask{0x000001ff}; +inline constexpr uint Ambi3OrderMask{0x0000ffff}; +inline constexpr uint Ambi4OrderMask{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. */ -constexpr uint AmbiPeriphonicMask{0xfe7ce4}; +inline constexpr uint AmbiPeriphonicMask{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 +inline constexpr auto Ambi2DChannelsFromOrder(std::size_t order) noexcept -> std::size_t { return order*2 + 1; } -constexpr size_t MaxAmbi2DChannels{Ambi2DChannelsFromOrder(MaxAmbiOrder)}; +inline constexpr auto MaxAmbi2DChannels = std::size_t{Ambi2DChannelsFromOrder(MaxAmbiOrder)}; /* 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 auto& FromN3D() noexcept - { - static constexpr const std::array ret{{ - 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 - }}; - return ret; - } - static auto& FromSN3D() noexcept - { - static constexpr const std::array ret{{ - 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) */ - }}; - return ret; - } - static auto& FromFuMa() noexcept - { - static constexpr const std::array ret{{ - 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) */ - }}; - return ret; - } - static auto& FromUHJ() noexcept - { - static constexpr const std::array ret{{ - 1.000000000f, /* ACN 0 (W), sqrt(1) */ - 1.224744871f, /* ACN 1 (Y), sqrt(3/2) */ - 1.224744871f, /* ACN 2 (Z), sqrt(3/2) */ - 1.224744871f, /* ACN 3 (X), sqrt(3/2) */ - /* Higher orders not relevant for UHJ. */ - 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, - }}; - return ret; - } + static inline constexpr std::array 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 inline constexpr std::array 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 inline constexpr std::array 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) */ + }}; + static inline constexpr std::array FromUHJ{{ + 1.000000000f, /* ACN 0 (W), sqrt(1) */ + 1.224744871f, /* ACN 1 (Y), sqrt(3/2) */ + 1.224744871f, /* ACN 2 (Z), sqrt(3/2) */ + 1.224744871f, /* ACN 3 (X), sqrt(3/2) */ + /* Higher orders not relevant for UHJ. */ + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, + }}; /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ static std::array GetHFOrderScales(const uint src_order, @@ -127,72 +111,49 @@ struct AmbiScale { }; struct AmbiIndex { - static auto& FromFuMa() noexcept - { - static constexpr const std::array ret{{ - 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 */ - }}; - return ret; - } - static auto& FromFuMa2D() noexcept - { - static constexpr const std::array ret{{ - 0, /* W */ - 3, /* X */ - 1, /* Y */ - 8, /* U */ - 4, /* V */ - 15, /* P */ - 9, /* Q */ - }}; - return ret; - } + static inline constexpr std::array 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 inline constexpr std::array FromFuMa2D{{ + 0, /* W */ + 3, /* X */ + 1, /* Y */ + 8, /* U */ + 4, /* V */ + 15, /* P */ + 9, /* Q */ + }}; - static auto& FromACN() noexcept - { - static constexpr const std::array ret{{ - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15 - }}; - return ret; - } - static auto& FromACN2D() noexcept - { - static constexpr const std::array ret{{ - 0, 1,3, 4,8, 9,15 - }}; - return ret; - } + static inline constexpr std::array FromACN{{ + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15 + }}; + static inline constexpr std::array FromACN2D{{ + 0, 1,3, 4,8, 9,15 + }}; - static auto& OrderFromChannel() noexcept - { - static constexpr const std::array ret{{ - 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, - }}; - return ret; - } - static auto& OrderFrom2DChannel() noexcept - { - static constexpr const std::array ret{{ - 0, 1,1, 2,2, 3,3, - }}; - return ret; - } + + static inline constexpr std::array OrderFromChannel{{ + 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, + }}; + static inline constexpr std::array OrderFrom2DChannel{{ + 0, 1,1, 2,2, 3,3, + }}; }; diff --git a/Engine/lib/openal-soft/core/async_event.h b/Engine/lib/openal-soft/core/async_event.h index 5a2f5f91a..f865fd8a8 100644 --- a/Engine/lib/openal-soft/core/async_event.h +++ b/Engine/lib/openal-soft/core/async_event.h @@ -1,6 +1,11 @@ #ifndef CORE_EVENT_H #define CORE_EVENT_H +#include +#include +#include +#include + #include "almalloc.h" struct EffectState; @@ -8,48 +13,53 @@ struct EffectState; using uint = unsigned int; -struct AsyncEvent { - enum : uint { - /* User event types. */ - SourceStateChange, - BufferCompleted, - Disconnected, - UserEventCount, - - /* Internal events, always processed. */ - ReleaseEffectState = 128, - - /* End event thread processing. */ - KillThread, - }; - - enum class SrcState { - Reset, - Stop, - Play, - Pause - }; - - const uint EnumType; - union { - char dummy; - struct { - uint id; - SrcState state; - } srcstate; - struct { - uint id; - uint count; - } bufcomp; - struct { - char msg[244]; - } disconnect; - EffectState *mEffectState; - } u{}; - - constexpr AsyncEvent(uint type) noexcept : EnumType{type} { } - - DISABLE_ALLOC() +enum class AsyncEnableBits : std::uint8_t { + SourceState, + BufferCompleted, + Disconnected, + Count }; + +enum class AsyncSrcState : std::uint8_t { + Reset, + Stop, + Play, + Pause +}; + +using AsyncKillThread = std::monostate; + +struct AsyncSourceStateEvent { + uint mId; + AsyncSrcState mState; +}; + +struct AsyncBufferCompleteEvent { + uint mId; + uint mCount; +}; + +struct AsyncDisconnectEvent { + std::string msg; +}; + +struct AsyncEffectReleaseEvent { + EffectState *mEffectState; +}; + +using AsyncEvent = std::variant; + +template +auto &InitAsyncEvent(std::byte *evtbuf, Args&& ...args) +{ + auto *evt = al::construct_at(reinterpret_cast(evtbuf), std::in_place_type, + std::forward(args)...); + return std::get(*evt); +} + #endif diff --git a/Engine/lib/openal-soft/core/bformatdec.cpp b/Engine/lib/openal-soft/core/bformatdec.cpp index 129b99762..f572d4569 100644 --- a/Engine/lib/openal-soft/core/bformatdec.cpp +++ b/Engine/lib/openal-soft/core/bformatdec.cpp @@ -6,114 +6,122 @@ #include #include #include +#include #include -#include "almalloc.h" #include "alnumbers.h" +#include "bufferline.h" #include "filters/splitter.h" +#include "flexarray.h" #include "front_stablizer.h" #include "mixer.h" #include "opthelpers.h" +namespace { + +template +struct overloaded : Ts... { using Ts::operator()...; }; + +template +overloaded(Ts...) -> overloaded; + +} // namespace + BFormatDec::BFormatDec(const size_t inchans, const al::span coeffs, const al::span coeffslf, const float xover_f0norm, std::unique_ptr stablizer) - : mStablizer{std::move(stablizer)}, mDualBand{!coeffslf.empty()}, mChannelDec{inchans} + : mStablizer{std::move(stablizer)} { - if(!mDualBand) + if(coeffslf.empty()) { - for(size_t j{0};j < mChannelDec.size();++j) + auto &decoder = mChannelDec.emplace>(inchans); + for(size_t j{0};j < decoder.size();++j) { - float *outcoeffs{mChannelDec[j].mGains.Single}; - for(const ChannelDec &incoeffs : coeffs) - *(outcoeffs++) = incoeffs[j]; + std::transform(coeffs.cbegin(), coeffs.cend(), decoder[j].mGains.begin(), + [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); } } else { - mChannelDec[0].mXOver.init(xover_f0norm); - for(size_t j{1};j < mChannelDec.size();++j) - mChannelDec[j].mXOver = mChannelDec[0].mXOver; + auto &decoder = mChannelDec.emplace>(inchans); + decoder[0].mXOver.init(xover_f0norm); + for(size_t j{1};j < decoder.size();++j) + decoder[j].mXOver = decoder[0].mXOver; - for(size_t j{0};j < mChannelDec.size();++j) + for(size_t j{0};j < decoder.size();++j) { - float *outcoeffs{mChannelDec[j].mGains.Dual[sHFBand]}; - for(const ChannelDec &incoeffs : coeffs) - *(outcoeffs++) = incoeffs[j]; + std::transform(coeffs.cbegin(), coeffs.cend(), decoder[j].mGains[sHFBand].begin(), + [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); - outcoeffs = mChannelDec[j].mGains.Dual[sLFBand]; - for(const ChannelDec &incoeffs : coeffslf) - *(outcoeffs++) = incoeffs[j]; + std::transform(coeffslf.cbegin(), coeffslf.cend(), decoder[j].mGains[sLFBand].begin(), + [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); } } } void BFormatDec::process(const al::span OutBuffer, - const FloatBufferLine *InSamples, const size_t SamplesToDo) + const al::span InSamples, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); - if(mDualBand) + auto decode_dualband = [=](std::vector &decoder) { - const al::span hfSamples{mSamples[sHFBand].data(), SamplesToDo}; - const al::span lfSamples{mSamples[sLFBand].data(), SamplesToDo}; - for(auto &chandec : mChannelDec) + auto input = InSamples.cbegin(); + const auto hfSamples = al::span{mSamples[sHFBand]}.first(SamplesToDo); + const auto lfSamples = al::span{mSamples[sLFBand]}.first(SamplesToDo); + for(auto &chandec : decoder) { - chandec.mXOver.process({InSamples->data(), SamplesToDo}, hfSamples.data(), - lfSamples.data()); - MixSamples(hfSamples, OutBuffer, chandec.mGains.Dual[sHFBand], - chandec.mGains.Dual[sHFBand], 0, 0); - MixSamples(lfSamples, OutBuffer, chandec.mGains.Dual[sLFBand], - chandec.mGains.Dual[sLFBand], 0, 0); - ++InSamples; + chandec.mXOver.process(al::span{*input++}.first(SamplesToDo), hfSamples, lfSamples); + MixSamples(hfSamples, OutBuffer, chandec.mGains[sHFBand], chandec.mGains[sHFBand],0,0); + MixSamples(lfSamples, OutBuffer, chandec.mGains[sLFBand], chandec.mGains[sLFBand],0,0); } - } - else + }; + auto decode_singleband = [=](std::vector &decoder) { - for(auto &chandec : mChannelDec) + auto input = InSamples.cbegin(); + for(auto &chandec : decoder) { - MixSamples({InSamples->data(), SamplesToDo}, OutBuffer, chandec.mGains.Single, - chandec.mGains.Single, 0, 0); - ++InSamples; + MixSamples(al::span{*input++}.first(SamplesToDo), OutBuffer, chandec.mGains, + chandec.mGains, 0, 0); } - } + }; + + std::visit(overloaded{decode_dualband, decode_singleband}, mChannelDec); } void BFormatDec::processStablize(const al::span OutBuffer, - const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx, - const size_t SamplesToDo) + const al::span InSamples, const size_t lidx, const size_t ridx, + const size_t cidx, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); /* Move the existing direct L/R signal out so it doesn't get processed by * the stablizer. */ - float *RESTRICT mid{al::assume_aligned<16>(mStablizer->MidDirect.data())}; - float *RESTRICT side{al::assume_aligned<16>(mStablizer->Side.data())}; - for(size_t i{0};i < SamplesToDo;++i) - { - mid[i] = OutBuffer[lidx][i] + OutBuffer[ridx][i]; - side[i] = OutBuffer[lidx][i] - OutBuffer[ridx][i]; - } - std::fill_n(OutBuffer[lidx].begin(), SamplesToDo, 0.0f); - std::fill_n(OutBuffer[ridx].begin(), SamplesToDo, 0.0f); + const auto leftout = al::span{OutBuffer[lidx]}.first(SamplesToDo); + const auto rightout = al::span{OutBuffer[ridx]}.first(SamplesToDo); + const al::span mid{al::assume_aligned<16>(mStablizer->MidDirect.data()), SamplesToDo}; + const al::span side{al::assume_aligned<16>(mStablizer->Side.data()), SamplesToDo}; + std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), mid.begin(),std::plus{}); + std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), side.begin(),std::minus{}); + std::fill_n(leftout.begin(), leftout.size(), 0.0f); + std::fill_n(rightout.begin(), rightout.size(), 0.0f); /* Decode the B-Format input to OutBuffer. */ process(OutBuffer, InSamples, SamplesToDo); /* Include the decoded side signal with the direct side signal. */ for(size_t i{0};i < SamplesToDo;++i) - side[i] += OutBuffer[lidx][i] - OutBuffer[ridx][i]; + side[i] += leftout[i] - rightout[i]; /* Get the decoded mid signal and band-split it. */ - std::transform(OutBuffer[lidx].cbegin(), OutBuffer[lidx].cbegin()+SamplesToDo, - OutBuffer[ridx].cbegin(), mStablizer->Temp.begin(), - [](const float l, const float r) noexcept { return l + r; }); + const auto tmpsamples = al::span{mStablizer->Temp}.first(SamplesToDo); + std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), tmpsamples.begin(), + std::plus{}); - mStablizer->MidFilter.process({mStablizer->Temp.data(), SamplesToDo}, mStablizer->MidHF.data(), - mStablizer->MidLF.data()); + mStablizer->MidFilter.process(tmpsamples, mStablizer->MidHF, mStablizer->MidLF); /* Apply an all-pass to all channels to match the band-splitter's phase * shift. This is to keep the phase synchronized between the existing @@ -126,9 +134,9 @@ void BFormatDec::processStablize(const al::span OutBuffer, * and substitute the direct mid signal and direct+decoded side signal. */ if(i == lidx) - mStablizer->ChannelFilters[i].processAllPass({mid, SamplesToDo}); + mStablizer->ChannelFilters[i].processAllPass(mid); else if(i == ridx) - mStablizer->ChannelFilters[i].processAllPass({side, SamplesToDo}); + mStablizer->ChannelFilters[i].processAllPass(side); else mStablizer->ChannelFilters[i].processAllPass({OutBuffer[i].data(), SamplesToDo}); } @@ -142,6 +150,7 @@ void BFormatDec::processStablize(const al::span OutBuffer, const float cos_hf{std::cos(1.0f/4.0f * (al::numbers::pi_v*0.5f))}; const float sin_lf{std::sin(1.0f/3.0f * (al::numbers::pi_v*0.5f))}; const float sin_hf{std::sin(1.0f/4.0f * (al::numbers::pi_v*0.5f))}; + const auto centerout = al::span{OutBuffer[cidx]}.first(SamplesToDo); for(size_t i{0};i < SamplesToDo;i++) { /* Add the direct mid signal to the processed mid signal so it can be @@ -154,9 +163,9 @@ void BFormatDec::processStablize(const al::span OutBuffer, /* The generated center channel signal adds to the existing signal, * while the modified left and right channels replace. */ - OutBuffer[lidx][i] = (m + s) * 0.5f; - OutBuffer[ridx][i] = (m - s) * 0.5f; - OutBuffer[cidx][i] += c * 0.5f; + leftout[i] = (m + s) * 0.5f; + rightout[i] = (m - s) * 0.5f; + centerout[i] += c * 0.5f; } } diff --git a/Engine/lib/openal-soft/core/bformatdec.h b/Engine/lib/openal-soft/core/bformatdec.h index 7a27a5a42..5bb2d3b40 100644 --- a/Engine/lib/openal-soft/core/bformatdec.h +++ b/Engine/lib/openal-soft/core/bformatdec.h @@ -4,16 +4,15 @@ #include #include #include +#include +#include -#include "almalloc.h" #include "alspan.h" #include "ambidefs.h" #include "bufferline.h" #include "devformat.h" #include "filters/splitter.h" -#include "vector.h" - -struct FrontStablizer; +#include "front_stablizer.h" using ChannelDec = std::array; @@ -23,49 +22,40 @@ class BFormatDec { static constexpr size_t sLFBand{1}; static constexpr size_t sNumBands{2}; - struct ChannelDecoder { - union MatrixU { - float Dual[sNumBands][MAX_OUTPUT_CHANNELS]; - float Single[MAX_OUTPUT_CHANNELS]; - } mGains{}; - - /* NOTE: BandSplitter filter is unused with single-band decoding. */ - BandSplitter mXOver; + struct ChannelDecoderSingle { + std::array mGains{}; }; - alignas(16) std::array mSamples; + struct ChannelDecoderDual { + BandSplitter mXOver; + std::array,sNumBands> mGains{}; + }; + + alignas(16) std::array mSamples{}; const std::unique_ptr mStablizer; - const bool mDualBand{false}; - /* TODO: This should ideally be a FlexArray, since ChannelDecoder is rather - * small and only a few are needed (3, 4, 5, 7, typically). But that can - * only be used in a standard layout struct, and a std::unique_ptr member - * (mStablizer) causes GCC and Clang to warn it's not. - */ - al::vector mChannelDec; + std::variant,std::vector> mChannelDec; public: BFormatDec(const size_t inchans, const al::span coeffs, const al::span coeffslf, const float xover_f0norm, std::unique_ptr stablizer); - bool hasStablizer() const noexcept { return mStablizer != nullptr; } + [[nodiscard]] auto hasStablizer() const noexcept -> bool { return mStablizer != nullptr; } /* Decodes the ambisonic input to the given output channels. */ - void process(const al::span OutBuffer, const FloatBufferLine *InSamples, - const size_t SamplesToDo); + void process(const al::span OutBuffer, + const al::span InSamples, const size_t SamplesToDo); /* Decodes the ambisonic input to the given output channels with stablization. */ void processStablize(const al::span OutBuffer, - const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx, - const size_t SamplesToDo); + const al::span InSamples, const size_t lidx, const size_t ridx, + const size_t cidx, const size_t SamplesToDo); static std::unique_ptr Create(const size_t inchans, const al::span coeffs, const al::span coeffslf, const float xover_f0norm, std::unique_ptr stablizer); - - DEF_NEWDEL(BFormatDec) }; #endif /* CORE_BFORMATDEC_H */ diff --git a/Engine/lib/openal-soft/core/bs2b.cpp b/Engine/lib/openal-soft/core/bs2b.cpp index 303bf9bd9..6e292b147 100644 --- a/Engine/lib/openal-soft/core/bs2b.cpp +++ b/Engine/lib/openal-soft/core/bs2b.cpp @@ -26,72 +26,75 @@ #include #include #include +#include #include "alnumbers.h" +#include "alspan.h" #include "bs2b.h" +namespace { /* Set up all data. */ -static void init(struct bs2b *bs2b) +void init(Bs2b::bs2b *bs2b) { float Fc_lo, Fc_hi; float G_lo, G_hi; - float x, g; switch(bs2b->level) { - case BS2B_LOW_CLEVEL: /* Low crossfeed level */ + case Bs2b::LowCLevel: /* Low crossfeed level */ Fc_lo = 360.0f; Fc_hi = 501.0f; G_lo = 0.398107170553497f; G_hi = 0.205671765275719f; break; - case BS2B_MIDDLE_CLEVEL: /* Middle crossfeed level */ + case Bs2b::MiddleCLevel: /* Middle crossfeed level */ Fc_lo = 500.0f; Fc_hi = 711.0f; G_lo = 0.459726988530872f; G_hi = 0.228208484414988f; break; - case BS2B_HIGH_CLEVEL: /* High crossfeed level (virtual speakers are closer to itself) */ + case Bs2b::HighCLevel: /* High crossfeed level (virtual speakers are closer to itself) */ Fc_lo = 700.0f; Fc_hi = 1021.0f; G_lo = 0.530884444230988f; G_hi = 0.250105790667544f; break; - case BS2B_LOW_ECLEVEL: /* Low easy crossfeed level */ + case Bs2b::LowECLevel: /* Low easy crossfeed level */ Fc_lo = 360.0f; Fc_hi = 494.0f; G_lo = 0.316227766016838f; G_hi = 0.168236228897329f; break; - case BS2B_MIDDLE_ECLEVEL: /* Middle easy crossfeed level */ + case Bs2b::MiddleECLevel: /* Middle easy crossfeed level */ Fc_lo = 500.0f; Fc_hi = 689.0f; G_lo = 0.354813389233575f; G_hi = 0.187169483835901f; break; - default: /* High easy crossfeed level */ - bs2b->level = BS2B_HIGH_ECLEVEL; + case Bs2b::HighECLevel: /* High easy crossfeed level */ + default: + bs2b->level = Bs2b::HighECLevel; Fc_lo = 700.0f; Fc_hi = 975.0f; G_lo = 0.398107170553497f; G_hi = 0.205671765275719f; break; - } /* switch */ + } - g = 1.0f / (1.0f - G_hi + G_lo); + float g{1.0f / (1.0f - G_hi + G_lo)}; /* $fc = $Fc / $s; * $d = 1 / 2 / pi / $fc; * $x = exp(-1 / $d); */ - x = std::exp(-al::numbers::pi_v*2.0f*Fc_lo/static_cast(bs2b->srate)); + float x{ std::exp(-al::numbers::pi_v*2.0f*Fc_lo/static_cast(bs2b->srate))}; bs2b->b1_lo = x; bs2b->a0_lo = G_lo * (1.0f - x) * g; @@ -99,85 +102,88 @@ static void init(struct bs2b *bs2b) bs2b->b1_hi = x; bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g; bs2b->a1_hi = -x * g; -} /* init */ +} +} // namespace /* Exported functions. * See descriptions in "bs2b.h" */ +namespace Bs2b { -void bs2b_set_params(struct bs2b *bs2b, int level, int srate) +void bs2b::set_params(int level_, int srate_) { - if(srate <= 0) srate = 1; + if(srate_ < 1) + throw std::runtime_error{"BS2B srate < 1"}; - bs2b->level = level; - bs2b->srate = srate; - init(bs2b); -} /* bs2b_set_params */ + level = level_; + srate = srate_; + init(this); +} -int bs2b_get_level(struct bs2b *bs2b) +void bs2b::clear() { - return bs2b->level; -} /* bs2b_get_level */ + history.fill(bs2b::t_last_sample{}); +} -int bs2b_get_srate(struct bs2b *bs2b) +void bs2b::cross_feed(float *Left, float *Right, size_t SamplesToDo) { - return bs2b->srate; -} /* bs2b_get_srate */ + const float a0lo{a0_lo}; + const float b1lo{b1_lo}; + const float a0hi{a0_hi}; + const float a1hi{a1_hi}; + const float b1hi{b1_hi}; + std::array,128> samples{}; + al::span lsamples{Left, SamplesToDo}; + al::span rsamples{Right, SamplesToDo}; -void bs2b_clear(struct bs2b *bs2b) -{ - std::fill(std::begin(bs2b->history), std::end(bs2b->history), bs2b::t_last_sample{}); -} /* bs2b_clear */ - -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]; - - for(size_t base{0};base < SamplesToDo;) + while(!lsamples.empty()) { - const size_t todo{std::min(128, SamplesToDo-base)}; + const size_t todo{std::min(samples.size(), lsamples.size())}; /* Process left input */ - 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] = a0_lo*Left[i] + z_lo; - z_lo = b1_lo*lsamples[i][0]; + float z_lo{history[0].lo}; + float z_hi{history[0].hi}; + std::transform(lsamples.cbegin(), lsamples.cbegin()+ptrdiff_t(todo), samples.begin(), + [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x) -> std::array + { + float y0{a0hi*x + z_hi}; + z_hi = a1hi*x + b1hi*y0; - lsamples[i][1] = a0_hi*Left[i] + z_hi; - z_hi = a1_hi*Left[i] + b1_hi*lsamples[i][1]; - } - bs2b->history[0].lo = z_lo; - bs2b->history[0].hi = z_hi; + float y1{a0lo*x + z_lo}; + z_lo = b1lo*y1; + + return {y0, y1}; + }); + history[0].lo = z_lo; + history[0].hi = z_hi; /* Process right input */ - z_lo = bs2b->history[1].lo; - z_hi = bs2b->history[1].hi; - for(size_t i{0};i < todo;i++) - { - rsamples[i][0] = a0_lo*Right[i] + z_lo; - z_lo = b1_lo*rsamples[i][0]; + z_lo = history[1].lo; + z_hi = history[1].hi; + std::transform(rsamples.cbegin(), rsamples.cbegin()+ptrdiff_t(todo), samples.begin(), + samples.begin(), + [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x, const std::array out) -> std::array + { + float y0{a0lo*x + z_lo}; + z_lo = b1lo*y0; - rsamples[i][1] = a0_hi*Right[i] + z_hi; - z_hi = a1_hi*Right[i] + b1_hi*rsamples[i][1]; - } - bs2b->history[1].lo = z_lo; - bs2b->history[1].hi = z_hi; + float y1{a0hi*x + z_hi}; + z_hi = a1hi*x + b1hi*y1; - /* Crossfeed */ - for(size_t i{0};i < todo;i++) - *(Left++) = lsamples[i][1] + rsamples[i][0]; - for(size_t i{0};i < todo;i++) - *(Right++) = rsamples[i][1] + lsamples[i][0]; + return {out[0]+y0, out[1]+y1}; + }); + history[1].lo = z_lo; + history[1].hi = z_hi; - base += todo; + auto iter = std::transform(samples.cbegin(), samples.cbegin()+todo, lsamples.begin(), + [](const std::array &in) { return in[0]; }); + lsamples = {iter, lsamples.end()}; + + iter = std::transform(samples.cbegin(), samples.cbegin()+todo, rsamples.begin(), + [](const std::array &in) { return in[1]; }); + rsamples = {iter, rsamples.end()}; } -} /* bs2b_cross_feed */ +} + +} // namespace Bs2b diff --git a/Engine/lib/openal-soft/core/bs2b.h b/Engine/lib/openal-soft/core/bs2b.h index 4d0b9dd8e..32c77e101 100644 --- a/Engine/lib/openal-soft/core/bs2b.h +++ b/Engine/lib/openal-soft/core/bs2b.h @@ -24,66 +24,66 @@ #ifndef CORE_BS2B_H #define CORE_BS2B_H -#include "almalloc.h" +#include +#include -/* Number of crossfeed levels */ -#define BS2B_CLEVELS 3 +namespace Bs2b { -/* Normal crossfeed levels */ -#define BS2B_HIGH_CLEVEL 3 -#define BS2B_MIDDLE_CLEVEL 2 -#define BS2B_LOW_CLEVEL 1 +enum { + /* Normal crossfeed levels */ + LowCLevel = 1, + MiddleCLevel = 2, + HighCLevel = 3, -/* Easy crossfeed levels */ -#define BS2B_HIGH_ECLEVEL BS2B_HIGH_CLEVEL + BS2B_CLEVELS -#define BS2B_MIDDLE_ECLEVEL BS2B_MIDDLE_CLEVEL + BS2B_CLEVELS -#define BS2B_LOW_ECLEVEL BS2B_LOW_CLEVEL + BS2B_CLEVELS + /* Easy crossfeed levels */ + LowECLevel = 4, + MiddleECLevel = 5, + HighECLevel = 6, -/* Default crossfeed levels */ -#define BS2B_DEFAULT_CLEVEL BS2B_HIGH_ECLEVEL -/* Default sample rate (Hz) */ -#define BS2B_DEFAULT_SRATE 44100 + DefaultCLevel = HighECLevel +}; struct bs2b { - int level; /* Crossfeed level */ - int srate; /* Sample rate (Hz) */ + int level{}; /* Crossfeed level */ + int srate{}; /* Sample rate (Hz) */ /* Lowpass IIR filter coefficients */ - float a0_lo; - float b1_lo; + float a0_lo{}; + float b1_lo{}; /* Highboost IIR filter coefficients */ - float a0_hi; - float a1_hi; - float b1_hi; + float a0_hi{}; + float a1_hi{}; + float b1_hi{}; /* Buffer of filter history * [0] - first channel, [1] - second channel */ struct t_last_sample { - float lo; - float hi; - } history[2]; + float lo{}; + float hi{}; + }; + std::array history{}; - DEF_NEWDEL(bs2b) + /* Clear buffers and set new coefficients with new crossfeed level and + * sample rate values. + * level - crossfeed level of *Level enum values. + * srate - sample rate by Hz. + */ + void set_params(int level, int srate); + + /* Return current crossfeed level value */ + [[nodiscard]] auto get_level() const noexcept -> int { return level; } + + /* Return current sample rate value */ + [[nodiscard]] auto get_srate() const noexcept -> int { return srate; } + + /* Clear buffer */ + void clear(); + + void cross_feed(float *Left, float *Right, std::size_t SamplesToDo); }; -/* Clear buffers and set new coefficients with new crossfeed level and sample - * rate values. - * level - crossfeed level of *LEVEL values. - * srate - sample rate by Hz. - */ -void bs2b_set_params(bs2b *bs2b, int level, int srate); - -/* Return current crossfeed level value */ -int bs2b_get_level(bs2b *bs2b); - -/* Return current sample rate value */ -int bs2b_get_srate(bs2b *bs2b); - -/* Clear buffer */ -void bs2b_clear(bs2b *bs2b); - -void bs2b_cross_feed(bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo); +} // namespace Bs2b #endif /* CORE_BS2B_H */ diff --git a/Engine/lib/openal-soft/core/bsinc_tables.cpp b/Engine/lib/openal-soft/core/bsinc_tables.cpp index 693645f43..74f47a129 100644 --- a/Engine/lib/openal-soft/core/bsinc_tables.cpp +++ b/Engine/lib/openal-soft/core/bsinc_tables.cpp @@ -5,18 +5,63 @@ #include #include #include +#include #include -#include #include +#include #include "alnumbers.h" -#include "core/mixer/defs.h" +#include "alnumeric.h" +#include "alspan.h" +#include "bsinc_defs.h" +#include "resampler_limits.h" namespace { using uint = unsigned int; +#if __cpp_lib_math_special_functions >= 201603L +using std::cyl_bessel_i; + +#else + +/* 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 + * + * This implementation only handles nu = 0, and isn't the most precise (it + * starts with the largest value and accumulates successively smaller values, + * compounding the rounding and precision error), but it's good enough. + */ +template +U cyl_bessel_i(T nu, U x) +{ + if(nu != T{0}) + throw std::runtime_error{"cyl_bessel_i: nu != 0"}; + + /* Start at k=1 since k=0 is trivial. */ + const double x2{x/2.0}; + double term{1.0}; + double sum{1.0}; + int k{1}; + + /* Let the integration converge until the term of the sum is no longer + * significant. + */ + double last_sum{}; + do { + const double y{x2 / k}; + ++k; + last_sum = sum; + term *= y * y; + sum += term; + } while(sum != last_sum); + return static_cast(sum); +} +#endif /* This is the normalized cardinal sine (sinc) function. * @@ -31,35 +76,6 @@ constexpr double Sinc(const double x) return std::sin(al::numbers::pi*x) / (al::numbers::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 - */ -constexpr double BesselI_0(const double x) noexcept -{ - /* Start at k=1 since k=0 is trivial. */ - const double x2{x / 2.0}; - double term{1.0}; - double sum{1.0}; - double last_sum{}; - int k{1}; - - /* Let the integration converge until the term of the sum is no longer - * significant. - */ - do { - const double 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]. * @@ -78,7 +94,7 @@ constexpr double Kaiser(const double beta, const double k, const double besseli_ { if(!(k >= -1.0 && k <= 1.0)) return 0.0; - return BesselI_0(beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; + return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; } /* Calculates the (normalized frequency) transition width of the Kaiser window. @@ -97,7 +113,7 @@ constexpr double CalcKaiserBeta(const double rejection) { if(rejection > 50.0) return 0.1102 * (rejection-8.7); - else if(rejection >= 21.0) + if(rejection >= 21.0) return (0.5842 * std::pow(rejection-21.0, 0.4)) + (0.07886 * (rejection-21.0)); return 0.0; } @@ -107,24 +123,18 @@ struct BSincHeader { double width{}; double beta{}; double scaleBase{}; - double scaleRange{}; - double besseli_0_beta{}; - uint a[BSincScaleCount]{}; + std::array a{}; uint total_size{}; constexpr BSincHeader(uint Rejection, uint Order) noexcept + : width{CalcKaiserWidth(Rejection, Order)}, beta{CalcKaiserBeta(Rejection)} + , scaleBase{width / 2.0} { - width = CalcKaiserWidth(Rejection, Order); - beta = CalcKaiserBeta(Rejection); - scaleBase = width / 2.0; - scaleRange = 1.0 - scaleBase; - besseli_0_beta = BesselI_0(beta); - uint num_points{Order+1}; for(uint si{0};si < BSincScaleCount;++si) { - const double scale{scaleBase + (scaleRange * (si+1) / BSincScaleCount)}; + const double scale{lerpd(scaleBase, 1.0, (si+1) / double{BSincScaleCount})}; const uint a_{std::min(static_cast(num_points / 2.0 / scale), num_points)}; const uint m{2 * a_}; @@ -142,37 +152,19 @@ constexpr BSincHeader bsinc12_hdr{60, 11}; constexpr BSincHeader bsinc24_hdr{60, 23}; -/* NOTE: GCC 5 has an issue with BSincHeader objects being in an anonymous - * namespace while also being used as non-type template parameters. - */ -#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 6 - -/* The number of sample points is double the a value (rounded up to a multiple - * of 4), and scale index 0 includes the doubling for downsampling. bsinc24 is - * currently the highest quality filter, and will use the most sample points. - */ -constexpr uint BSincPointsMax{(bsinc24_hdr.a[0]*2 + 3) & ~3u}; -static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small"); - -template -struct BSincFilterArray { - alignas(16) std::array mTable; - const BSincHeader &hdr; - - BSincFilterArray(const BSincHeader &hdr_) : hdr{hdr_} - { -#else template struct BSincFilterArray { alignas(16) std::array mTable{}; BSincFilterArray() { - constexpr uint BSincPointsMax{(hdr.a[0]*2 + 3) & ~3u}; + static constexpr uint BSincPointsMax{(hdr.a[0]*2u + 3u) & ~3u}; static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small"); -#endif - using filter_type = double[BSincPhaseCount+1][BSincPointsMax]; - auto filter = std::make_unique(BSincScaleCount); + + using filter_type = std::array,BSincPhaseCount>; + auto filter = std::vector(BSincScaleCount); + + const double besseli_0_beta{cyl_bessel_i(0, hdr.beta)}; /* Calculate the Kaiser-windowed Sinc filter coefficients for each * scale and phase index. @@ -181,22 +173,19 @@ struct BSincFilterArray { { const uint m{hdr.a[si] * 2}; const size_t o{(BSincPointsMax-m) / 2}; - const double scale{hdr.scaleBase + (hdr.scaleRange * (si+1) / BSincScaleCount)}; + const double scale{lerpd(hdr.scaleBase, 1.0, (si+1) / double{BSincScaleCount})}; const double cutoff{scale - (hdr.scaleBase * std::max(1.0, scale*2.0))}; const auto a = static_cast(hdr.a[si]); const double l{a - 1.0/BSincPhaseCount}; - /* Do one extra phase index so that the phase delta has a proper - * target for its last index. - */ - for(uint pi{0};pi <= BSincPhaseCount;++pi) + for(uint pi{0};pi < BSincPhaseCount;++pi) { const double phase{std::floor(l) + (pi/double{BSincPhaseCount})}; for(uint i{0};i < m;++i) { const double x{i - phase}; - filter[si][pi][o+i] = Kaiser(hdr.beta, x/l, hdr.besseli_0_beta) * cutoff * + filter[si][pi][o+i] = Kaiser(hdr.beta, x/l, besseli_0_beta) * cutoff * Sinc(cutoff*x); } } @@ -219,59 +208,82 @@ struct BSincFilterArray { /* Linear interpolation between phases is simplified by pre- * calculating the delta (b - a) in: x = a + f (b - a) */ - for(size_t i{0};i < m;++i) + if(pi < BSincPhaseCount-1) { - const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]}; - mTable[idx++] = static_cast(phDelta); + for(size_t i{0};i < m;++i) + { + const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]}; + mTable[idx++] = static_cast(phDelta); + } + } + else + { + /* The delta target for the last phase index is the first + * phase index with the coefficients offset by one. The + * first delta targets 0, as it represents a coefficient + * for a sample that won't be part of the filter. + */ + mTable[idx++] = static_cast(0.0 - filter[si][pi][o]); + for(size_t i{1};i < m;++i) + { + const double phDelta{filter[si][0][o+i-1] - filter[si][pi][o+i]}; + mTable[idx++] = static_cast(phDelta); + } } } - /* Calculate and write out each phase index's filter quality scale - * deltas. The last scale index doesn't have any scale or scale- - * phase deltas. + + /* Now write out each phase index's scale and phase+scale deltas, + * to complete the bilinear equation for the combination of phase + * and scale. */ - if(si == BSincScaleCount-1) + if(si < BSincScaleCount-1) { + for(size_t pi{0};pi < BSincPhaseCount;++pi) + { + for(size_t i{0};i < m;++i) + { + const double scDelta{filter[si+1][pi][o+i] - filter[si][pi][o+i]}; + mTable[idx++] = static_cast(scDelta); + } + + if(pi < BSincPhaseCount-1) + { + for(size_t i{0};i < m;++i) + { + const double spDelta{(filter[si+1][pi+1][o+i]-filter[si+1][pi][o+i]) - + (filter[si][pi+1][o+i]-filter[si][pi][o+i])}; + mTable[idx++] = static_cast(spDelta); + } + } + else + { + mTable[idx++] = static_cast((0.0 - filter[si+1][pi][o]) - + (0.0 - filter[si][pi][o])); + for(size_t i{1};i < m;++i) + { + const double spDelta{(filter[si+1][0][o+i-1] - filter[si+1][pi][o+i]) - + (filter[si][0][o+i-1] - filter[si][pi][o+i])}; + mTable[idx++] = static_cast(spDelta); + } + } + } + } + else + { + /* The last scale index doesn't have scale-related deltas. */ for(size_t i{0};i < BSincPhaseCount*m*2;++i) mTable[idx++] = 0.0f; } - else for(size_t pi{0};pi < BSincPhaseCount;++pi) - { - /* Linear interpolation between scales is also simplified. - * - * Given a difference in the number of points between scales, - * the destination points will be 0, thus: x = a + f (-a) - */ - for(size_t i{0};i < m;++i) - { - const double scDelta{filter[si+1][pi][o+i] - filter[si][pi][o+i]}; - mTable[idx++] = static_cast(scDelta); - } - - /* This last simplification is done to complete the bilinear - * equation for the combination of phase and scale. - */ - for(size_t i{0};i < m;++i) - { - const double spDelta{(filter[si+1][pi+1][o+i] - filter[si+1][pi][o+i]) - - (filter[si][pi+1][o+i] - filter[si][pi][o+i])}; - mTable[idx++] = static_cast(spDelta); - } - } } assert(idx == hdr.total_size); } - constexpr const BSincHeader &getHeader() const noexcept { return hdr; } - constexpr const float *getTable() const noexcept { return &mTable.front(); } + [[nodiscard]] constexpr auto getHeader() const noexcept -> const BSincHeader& { return hdr; } + [[nodiscard]] constexpr auto getTable() const noexcept { return al::span{mTable}; } }; -#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 6 -const BSincFilterArray bsinc12_filter{bsinc12_hdr}; -const BSincFilterArray bsinc24_filter{bsinc24_hdr}; -#else const BSincFilterArray bsinc12_filter{}; const BSincFilterArray bsinc24_filter{}; -#endif template constexpr BSincTable GenerateBSincTable(const T &filter) @@ -279,7 +291,7 @@ constexpr BSincTable GenerateBSincTable(const T &filter) BSincTable ret{}; const BSincHeader &hdr = filter.getHeader(); ret.scaleBase = static_cast(hdr.scaleBase); - ret.scaleRange = static_cast(1.0 / hdr.scaleRange); + ret.scaleRange = static_cast(1.0 / (1.0 - hdr.scaleBase)); for(size_t i{0};i < BSincScaleCount;++i) ret.m[i] = ((hdr.a[i]*2) + 3) & ~3u; ret.filterOffset[0] = 0; diff --git a/Engine/lib/openal-soft/core/bsinc_tables.h b/Engine/lib/openal-soft/core/bsinc_tables.h index aca4b2744..eab894ce9 100644 --- a/Engine/lib/openal-soft/core/bsinc_tables.h +++ b/Engine/lib/openal-soft/core/bsinc_tables.h @@ -1,14 +1,17 @@ #ifndef CORE_BSINC_TABLES_H #define CORE_BSINC_TABLES_H +#include + +#include "alspan.h" #include "bsinc_defs.h" struct BSincTable { float scaleBase, scaleRange; - unsigned int m[BSincScaleCount]; - unsigned int filterOffset[BSincScaleCount]; - const float *Tab; + std::array m; + std::array filterOffset; + al::span Tab; }; extern const BSincTable gBSinc12; diff --git a/Engine/lib/openal-soft/core/buffer_storage.cpp b/Engine/lib/openal-soft/core/buffer_storage.cpp index 98ca2c1b3..780e388ad 100644 --- a/Engine/lib/openal-soft/core/buffer_storage.cpp +++ b/Engine/lib/openal-soft/core/buffer_storage.cpp @@ -3,79 +3,3 @@ #include "buffer_storage.h" -#include - - -const char *NameFromFormat(FmtType type) noexcept -{ - switch(type) - { - case FmtUByte: return "UInt8"; - case FmtShort: return "Int16"; - case FmtFloat: return "Float"; - case FmtDouble: return "Double"; - case FmtMulaw: return "muLaw"; - case FmtAlaw: return "aLaw"; - case FmtIMA4: return "IMA4 ADPCM"; - case FmtMSADPCM: return "MS ADPCM"; - } - return ""; -} - -const char *NameFromFormat(FmtChannels channels) noexcept -{ - switch(channels) - { - case FmtMono: return "Mono"; - case FmtStereo: return "Stereo"; - case FmtRear: return "Rear"; - case FmtQuad: return "Quadraphonic"; - case FmtX51: return "Surround 5.1"; - case FmtX61: return "Surround 6.1"; - case FmtX71: return "Surround 7.1"; - case FmtBFormat2D: return "B-Format 2D"; - case FmtBFormat3D: return "B-Format 3D"; - case FmtUHJ2: return "UHJ2"; - case FmtUHJ3: return "UHJ3"; - case FmtUHJ4: return "UHJ4"; - case FmtSuperStereo: return "Super Stereo"; - } - return ""; -} - -uint BytesFromFmt(FmtType type) noexcept -{ - switch(type) - { - case FmtUByte: return sizeof(uint8_t); - case FmtShort: return sizeof(int16_t); - case FmtFloat: return sizeof(float); - case FmtDouble: return sizeof(double); - case FmtMulaw: return sizeof(uint8_t); - case FmtAlaw: return sizeof(uint8_t); - case FmtIMA4: break; - case FmtMSADPCM: break; - } - return 0; -} - -uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept -{ - switch(chans) - { - case FmtMono: return 1; - case FmtStereo: return 2; - case FmtRear: return 2; - case FmtQuad: return 4; - case FmtX51: return 6; - case FmtX61: return 7; - case FmtX71: return 8; - case FmtBFormat2D: return (ambiorder*2) + 1; - case FmtBFormat3D: return (ambiorder+1) * (ambiorder+1); - case FmtUHJ2: return 2; - case FmtUHJ3: return 3; - case FmtUHJ4: return 4; - case FmtSuperStereo: return 2; - } - return 0; -} diff --git a/Engine/lib/openal-soft/core/buffer_storage.h b/Engine/lib/openal-soft/core/buffer_storage.h index 282d5b53e..ab097cf12 100644 --- a/Engine/lib/openal-soft/core/buffer_storage.h +++ b/Engine/lib/openal-soft/core/buffer_storage.h @@ -2,61 +2,16 @@ #define CORE_BUFFER_STORAGE_H #include +#include -#include "albyte.h" #include "alnumeric.h" #include "alspan.h" #include "ambidefs.h" +#include "storage_formats.h" using uint = unsigned int; -/* Storable formats */ -enum FmtType : unsigned char { - FmtUByte, - FmtShort, - FmtFloat, - FmtDouble, - FmtMulaw, - FmtAlaw, - FmtIMA4, - FmtMSADPCM, -}; -enum FmtChannels : unsigned char { - FmtMono, - FmtStereo, - FmtRear, - FmtQuad, - FmtX51, /* (WFX order) */ - FmtX61, /* (WFX order) */ - FmtX71, /* (WFX order) */ - FmtBFormat2D, - FmtBFormat3D, - FmtUHJ2, /* 2-channel UHJ, aka "BHJ", stereo-compatible */ - FmtUHJ3, /* 3-channel UHJ, aka "THJ" */ - FmtUHJ4, /* 4-channel UHJ, aka "PHJ" */ - FmtSuperStereo, /* Stereo processed with Super Stereo. */ -}; - -enum class AmbiLayout : unsigned char { - FuMa, - ACN, -}; -enum class AmbiScaling : unsigned char { - FuMa, - SN3D, - N3D, - UHJ, -}; - -const char *NameFromFormat(FmtType type) noexcept; -const char *NameFromFormat(FmtChannels channels) noexcept; - -uint BytesFromFmt(FmtType type) noexcept; -uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept; -inline uint FrameSizeFromFmt(FmtChannels chans, FmtType type, uint ambiorder) noexcept -{ return ChannelsFromFmt(chans, ambiorder) * BytesFromFmt(type); } - constexpr bool IsBFormat(FmtChannels chans) noexcept { return chans == FmtBFormat2D || chans == FmtBFormat3D; } @@ -85,7 +40,7 @@ struct BufferStorage { CallbackType mCallback{nullptr}; void *mUserData{nullptr}; - al::span mData; + al::span mData; uint mSampleRate{0u}; FmtChannels mChannels{FmtMono}; @@ -97,19 +52,20 @@ struct BufferStorage { AmbiScaling mAmbiScaling{AmbiScaling::FuMa}; uint mAmbiOrder{0u}; - inline uint bytesFromFmt() const noexcept { return BytesFromFmt(mType); } - inline uint channelsFromFmt() const noexcept + [[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromFmt(mType); } + [[nodiscard]] auto channelsFromFmt() const noexcept -> uint { return ChannelsFromFmt(mChannels, mAmbiOrder); } - inline uint frameSizeFromFmt() const noexcept { return channelsFromFmt() * bytesFromFmt(); } + [[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint + { return channelsFromFmt() * bytesFromFmt(); } - inline uint blockSizeFromFmt() const noexcept + [[nodiscard]] auto blockSizeFromFmt() const noexcept -> uint { if(mType == FmtIMA4) return ((mBlockAlign-1)/2 + 4) * channelsFromFmt(); if(mType == FmtMSADPCM) return ((mBlockAlign-2)/2 + 7) * channelsFromFmt(); return frameSizeFromFmt(); }; - inline bool isBFormat() const noexcept { return IsBFormat(mChannels); } + [[nodiscard]] auto isBFormat() const noexcept -> bool { return IsBFormat(mChannels); } }; #endif /* CORE_BUFFER_STORAGE_H */ diff --git a/Engine/lib/openal-soft/core/bufferline.h b/Engine/lib/openal-soft/core/bufferline.h index 8b445f3ff..309fb778f 100644 --- a/Engine/lib/openal-soft/core/bufferline.h +++ b/Engine/lib/openal-soft/core/bufferline.h @@ -9,7 +9,7 @@ * more memory and are harder on cache, while smaller values may need more * iterations for mixing. */ -constexpr int BufferLineSize{1024}; +inline constexpr size_t BufferLineSize{1024}; using FloatBufferLine = std::array; using FloatBufferSpan = al::span; diff --git a/Engine/lib/openal-soft/core/context.cpp b/Engine/lib/openal-soft/core/context.cpp index d68d8327d..cff0e40cf 100644 --- a/Engine/lib/openal-soft/core/context.cpp +++ b/Engine/lib/openal-soft/core/context.cpp @@ -2,7 +2,11 @@ #include "config.h" #include +#include +#include #include +#include +#include #include "async_event.h" #include "context.h" @@ -23,52 +27,23 @@ ContextBase::ContextBase(DeviceBase *device) : mDevice{device} ContextBase::~ContextBase() { - size_t count{0}; - ContextProps *cprops{mParams.ContextUpdate.exchange(nullptr, std::memory_order_relaxed)}; - if(cprops) - { - ++count; - delete cprops; - } - cprops = mFreeContextProps.exchange(nullptr, std::memory_order_acquire); - while(cprops) - { - std::unique_ptr old{cprops}; - cprops = old->next.load(std::memory_order_relaxed); - ++count; - } - TRACE("Freed %zu context property object%s\n", count, (count==1)?"":"s"); - - count = 0; - EffectSlotProps *eprops{mFreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)}; - while(eprops) - { - std::unique_ptr old{eprops}; - eprops = old->next.load(std::memory_order_relaxed); - ++count; - } - TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s"); - - if(EffectSlotArray *curarray{mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed)}) - { - al::destroy_n(curarray->end(), curarray->size()); - delete curarray; - } - - delete mVoices.exchange(nullptr, std::memory_order_relaxed); + mActiveAuxSlots.store(nullptr, std::memory_order_relaxed); + mVoices.store(nullptr, std::memory_order_relaxed); if(mAsyncEvents) { - count = 0; + size_t count{0}; auto evt_vec = mAsyncEvents->getReadVector(); if(evt_vec.first.len > 0) { - al::destroy_n(reinterpret_cast(evt_vec.first.buf), evt_vec.first.len); + std::destroy_n(std::launder(reinterpret_cast(evt_vec.first.buf)), + evt_vec.first.len); count += evt_vec.first.len; } if(evt_vec.second.len > 0) { - al::destroy_n(reinterpret_cast(evt_vec.second.buf), evt_vec.second.len); + std::destroy_n(std::launder(reinterpret_cast(evt_vec.second.buf)), + evt_vec.second.len); count += evt_vec.second.len; } if(count > 0) @@ -80,85 +55,131 @@ ContextBase::~ContextBase() void ContextBase::allocVoiceChanges() { - constexpr size_t clustersize{128}; + static constexpr size_t clustersize{std::tuple_size_v}; + + VoiceChangeCluster clusterptr{std::make_unique()}; + const auto cluster = al::span{*clusterptr}; - VoiceChangeCluster cluster{std::make_unique(clustersize)}; for(size_t i{1};i < clustersize;++i) cluster[i-1].mNext.store(std::addressof(cluster[i]), std::memory_order_relaxed); cluster[clustersize-1].mNext.store(mVoiceChangeTail, std::memory_order_relaxed); - mVoiceChangeClusters.emplace_back(std::move(cluster)); - mVoiceChangeTail = mVoiceChangeClusters.back().get(); + mVoiceChangeClusters.emplace_back(std::move(clusterptr)); + mVoiceChangeTail = mVoiceChangeClusters.back()->data(); } void ContextBase::allocVoiceProps() { - constexpr size_t clustersize{32}; + static constexpr size_t clustersize{std::tuple_size_v}; TRACE("Increasing allocated voice properties to %zu\n", (mVoicePropClusters.size()+1) * clustersize); - VoicePropsCluster cluster{std::make_unique(clustersize)}; + auto clusterptr = std::make_unique(); + auto cluster = al::span{*clusterptr}; for(size_t i{1};i < clustersize;++i) cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed); - mVoicePropClusters.emplace_back(std::move(cluster)); + mVoicePropClusters.emplace_back(std::move(clusterptr)); VoicePropsItem *oldhead{mFreeVoiceProps.load(std::memory_order_acquire)}; do { - mVoicePropClusters.back()[clustersize-1].next.store(oldhead, std::memory_order_relaxed); - } while(mFreeVoiceProps.compare_exchange_weak(oldhead, mVoicePropClusters.back().get(), + mVoicePropClusters.back()->back().next.store(oldhead, std::memory_order_relaxed); + } while(mFreeVoiceProps.compare_exchange_weak(oldhead, mVoicePropClusters.back()->data(), std::memory_order_acq_rel, std::memory_order_acquire) == false); } void ContextBase::allocVoices(size_t addcount) { - constexpr size_t clustersize{32}; + static constexpr size_t clustersize{std::tuple_size_v}; /* Convert element count to cluster count. */ addcount = (addcount+(clustersize-1)) / clustersize; + if(!addcount) + { + if(!mVoiceClusters.empty()) + return; + ++addcount; + } + if(addcount >= std::numeric_limits::max()/clustersize - mVoiceClusters.size()) throw std::runtime_error{"Allocating too many voices"}; const size_t totalcount{(mVoiceClusters.size()+addcount) * clustersize}; TRACE("Increasing allocated voices to %zu\n", totalcount); - auto newarray = VoiceArray::Create(totalcount); while(addcount) { - mVoiceClusters.emplace_back(std::make_unique(clustersize)); + mVoiceClusters.emplace_back(std::make_unique()); --addcount; } + auto newarray = VoiceArray::Create(totalcount); auto voice_iter = newarray->begin(); for(VoiceCluster &cluster : mVoiceClusters) - { - for(size_t i{0};i < clustersize;++i) - *(voice_iter++) = &cluster[i]; - } + voice_iter = std::transform(cluster->begin(), cluster->end(), voice_iter, + [](Voice &voice) noexcept -> Voice* { return &voice; }); - if(auto *oldvoices = mVoices.exchange(newarray.release(), std::memory_order_acq_rel)) - { - mDevice->waitForMix(); - delete oldvoices; - } + if(auto oldvoices = mVoices.exchange(std::move(newarray), std::memory_order_acq_rel)) + std::ignore = mDevice->waitForMix(); } +void ContextBase::allocEffectSlotProps() +{ + static constexpr size_t clustersize{std::tuple_size_v}; + + TRACE("Increasing allocated effect slot properties to %zu\n", + (mEffectSlotPropClusters.size()+1) * clustersize); + + auto clusterptr = std::make_unique(); + auto cluster = al::span{*clusterptr}; + for(size_t i{1};i < clustersize;++i) + cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed); + auto *newcluster = mEffectSlotPropClusters.emplace_back(std::move(clusterptr)).get(); + + EffectSlotProps *oldhead{mFreeEffectSlotProps.load(std::memory_order_acquire)}; + do { + newcluster->back().next.store(oldhead, std::memory_order_relaxed); + } while(mFreeEffectSlotProps.compare_exchange_weak(oldhead, newcluster->data(), + std::memory_order_acq_rel, std::memory_order_acquire) == false); +} + EffectSlot *ContextBase::getEffectSlot() { - for(auto& cluster : mEffectSlotClusters) + for(auto& clusterptr : mEffectSlotClusters) { - for(size_t i{0};i < EffectSlotClusterSize;++i) - { - if(!cluster[i].InUse) - return &cluster[i]; - } + const auto cluster = al::span{*clusterptr}; + auto iter = std::find_if_not(cluster.begin(), cluster.end(), + std::mem_fn(&EffectSlot::InUse)); + if(iter != cluster.end()) return al::to_address(iter); } - if(1 >= std::numeric_limits::max()/EffectSlotClusterSize - mEffectSlotClusters.size()) + auto clusterptr = std::make_unique(); + if(1 >= std::numeric_limits::max()/clusterptr->size() - mEffectSlotClusters.size()) throw std::runtime_error{"Allocating too many effect slots"}; - const size_t totalcount{(mEffectSlotClusters.size()+1) * EffectSlotClusterSize}; + const size_t totalcount{(mEffectSlotClusters.size()+1) * clusterptr->size()}; TRACE("Increasing allocated effect slots to %zu\n", totalcount); - mEffectSlotClusters.emplace_back(std::make_unique(EffectSlotClusterSize)); - return getEffectSlot(); + mEffectSlotClusters.emplace_back(std::move(clusterptr)); + return mEffectSlotClusters.back()->data(); +} + + +void ContextBase::allocContextProps() +{ + static constexpr size_t clustersize{std::tuple_size_v}; + + TRACE("Increasing allocated context properties to %zu\n", + (mContextPropClusters.size()+1) * clustersize); + + auto clusterptr = std::make_unique(); + auto cluster = al::span{*clusterptr}; + for(size_t i{1};i < clustersize;++i) + cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed); + auto *newcluster = mContextPropClusters.emplace_back(std::move(clusterptr)).get(); + + ContextProps *oldhead{mFreeContextProps.load(std::memory_order_acquire)}; + do { + newcluster->back().next.store(oldhead, std::memory_order_relaxed); + } while(mFreeContextProps.compare_exchange_weak(oldhead, newcluster->data(), + std::memory_order_acq_rel, std::memory_order_acquire) == false); } diff --git a/Engine/lib/openal-soft/core/context.h b/Engine/lib/openal-soft/core/context.h index 9723eac38..1a1852e33 100644 --- a/Engine/lib/openal-soft/core/context.h +++ b/Engine/lib/openal-soft/core/context.h @@ -7,15 +7,16 @@ #include #include #include +#include #include "almalloc.h" +#include "alsem.h" #include "alspan.h" #include "async_event.h" #include "atomic.h" -#include "bufferline.h" -#include "threads.h" +#include "flexarray.h" +#include "opthelpers.h" #include "vecmat.h" -#include "vector.h" struct DeviceBase; struct EffectSlot; @@ -25,12 +26,10 @@ struct Voice; struct VoiceChange; struct VoicePropsItem; -using uint = unsigned int; +inline constexpr float SpeedOfSoundMetersPerSec{343.3f}; -constexpr float SpeedOfSoundMetersPerSec{343.3f}; - -constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */ +inline constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */ enum class DistanceModel : unsigned char { Disable, @@ -58,8 +57,6 @@ struct ContextProps { DistanceModel mDistanceModel; std::atomic next; - - DEF_NEWDEL(ContextProps) }; struct ContextParams { @@ -87,7 +84,7 @@ struct ContextBase { /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit * indicates if updates are currently happening). */ - RefCount mUpdateCount{0u}; + std::atomic mUpdateCount{0u}; std::atomic mHoldUpdates{false}; std::atomic mStopVoicesOnDisconnect{true}; @@ -98,7 +95,7 @@ struct ContextBase { */ std::atomic mFreeContextProps{nullptr}; std::atomic mFreeVoiceProps{nullptr}; - std::atomic mFreeEffectslotProps{nullptr}; + std::atomic mFreeEffectSlotProps{nullptr}; /* The voice change tail is the beginning of the "free" elements, up to and * *excluding* the current. If tail==current, there's no free elements and @@ -110,21 +107,22 @@ struct ContextBase { void allocVoiceChanges(); void allocVoiceProps(); - + void allocEffectSlotProps(); + void allocContextProps(); ContextParams mParams; using VoiceArray = al::FlexArray; - std::atomic mVoices{}; + al::atomic_unique_ptr mVoices{}; std::atomic mActiveVoiceCount{}; void allocVoices(size_t addcount); - al::span getVoicesSpan() const noexcept + [[nodiscard]] auto getVoicesSpan() const noexcept -> al::span { return {mVoices.load(std::memory_order_relaxed)->data(), mActiveVoiceCount.load(std::memory_order_relaxed)}; } - al::span getVoicesSpanAcquired() const noexcept + [[nodiscard]] auto getVoicesSpanAcquired() const noexcept -> al::span { return {mVoices.load(std::memory_order_acquire)->data(), mActiveVoiceCount.load(std::memory_order_acquire)}; @@ -132,12 +130,16 @@ struct ContextBase { using EffectSlotArray = al::FlexArray; - std::atomic mActiveAuxSlots{nullptr}; + /* This array is split in half. The front half is the list of activated + * effect slots as set by the app, and the back half is the same list but + * sorted to ensure later effect slots are fed by earlier ones. + */ + al::atomic_unique_ptr mActiveAuxSlots; std::thread mEventThread; al::semaphore mEventSem; std::unique_ptr mAsyncEvents; - using AsyncEventBitset = std::bitset; + using AsyncEventBitset = std::bitset; std::atomic mEnabledEvts{0u}; /* Asynchronous voice change actions are processed as a linked list of @@ -145,21 +147,29 @@ struct ContextBase { * However, to avoid allocating each object individually, they're allocated * in clusters that are stored in a vector for easy automatic cleanup. */ - using VoiceChangeCluster = std::unique_ptr; - al::vector mVoiceChangeClusters; + using VoiceChangeCluster = std::unique_ptr>; + std::vector mVoiceChangeClusters; - using VoiceCluster = std::unique_ptr; - al::vector mVoiceClusters; + using VoiceCluster = std::unique_ptr>; + std::vector mVoiceClusters; - using VoicePropsCluster = std::unique_ptr; - al::vector mVoicePropClusters; + using VoicePropsCluster = std::unique_ptr>; + std::vector mVoicePropClusters; - static constexpr size_t EffectSlotClusterSize{4}; EffectSlot *getEffectSlot(); - using EffectSlotCluster = std::unique_ptr; - al::vector mEffectSlotClusters; + using EffectSlotCluster = std::unique_ptr>; + std::vector mEffectSlotClusters; + + using EffectSlotPropsCluster = std::unique_ptr>; + std::vector mEffectSlotPropClusters; + + /* This could be greater than 2, but there should be no way there can be + * more than two context property updates in use simultaneously. + */ + using ContextPropsCluster = std::unique_ptr>; + std::vector mContextPropClusters; ContextBase(DeviceBase *device); diff --git a/Engine/lib/openal-soft/core/converter.cpp b/Engine/lib/openal-soft/core/converter.cpp index a51414484..97102536f 100644 --- a/Engine/lib/openal-soft/core/converter.cpp +++ b/Engine/lib/openal-soft/core/converter.cpp @@ -6,12 +6,12 @@ #include #include #include +#include #include #include -#include +#include #include "albit.h" -#include "albyte.h" #include "alnumeric.h" #include "fpu_ctrl.h" @@ -24,43 +24,46 @@ static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for Buffer static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize, "MaxPitch and/or BufferLineSize are too large for MixerFracBits!"); -/* Base template left undefined. Should be marked =delete, but Clang 3.8.1 - * chokes on that given the inline specializations. - */ template -inline float LoadSample(DevFmtType_t val) noexcept; +constexpr float LoadSample(DevFmtType_t val) noexcept = delete; -template<> inline float LoadSample(DevFmtType_t val) noexcept -{ return val * (1.0f/128.0f); } -template<> inline float LoadSample(DevFmtType_t val) noexcept -{ return val * (1.0f/32768.0f); } -template<> inline float LoadSample(DevFmtType_t val) noexcept +template<> constexpr float LoadSample(DevFmtType_t val) noexcept +{ return float(val) * (1.0f/128.0f); } +template<> constexpr float LoadSample(DevFmtType_t val) noexcept +{ return float(val) * (1.0f/32768.0f); } +template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return static_cast(val) * (1.0f/2147483648.0f); } -template<> inline float LoadSample(DevFmtType_t val) noexcept +template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return val; } -template<> inline float LoadSample(DevFmtType_t val) noexcept +template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return LoadSample(static_cast(val - 128)); } -template<> inline float LoadSample(DevFmtType_t val) noexcept +template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return LoadSample(static_cast(val - 32768)); } -template<> inline float LoadSample(DevFmtType_t val) noexcept +template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return LoadSample(static_cast(val - 2147483648u)); } template -inline void LoadSampleArray(float *RESTRICT dst, const void *src, const size_t srcstep, - const size_t samples) noexcept +inline void LoadSampleArray(const al::span dst, const void *src, const size_t channel, + const size_t srcstep) noexcept { - const DevFmtType_t *ssrc = static_cast*>(src); - for(size_t i{0u};i < samples;i++) - dst[i] = LoadSample(ssrc[i*srcstep]); + assert(channel < srcstep); + const auto srcspan = al::span{static_cast*>(src), dst.size()*srcstep}; + auto ssrc = srcspan.cbegin(); + std::generate(dst.begin(), dst.end(), [&ssrc,channel,srcstep] + { + const float ret{LoadSample(ssrc[channel])}; + ssrc += ptrdiff_t(srcstep); + return ret; + }); } -void LoadSamples(float *dst, const void *src, const size_t srcstep, const DevFmtType srctype, - const size_t samples) noexcept +void LoadSamples(const al::span dst, const void *src, const size_t channel, + const size_t srcstep, const DevFmtType srctype) noexcept { #define HANDLE_FMT(T) \ - case T: LoadSampleArray(dst, src, srcstep, samples); break + case T: LoadSampleArray(dst, src, channel, srcstep); break switch(srctype) { HANDLE_FMT(DevFmtByte); @@ -81,11 +84,11 @@ inline DevFmtType_t StoreSample(float) noexcept; template<> inline float StoreSample(float val) noexcept { return val; } template<> inline int32_t StoreSample(float val) noexcept -{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } +{ return fastf2i(std::clamp(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } template<> inline int16_t StoreSample(float val) noexcept -{ return static_cast(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); } +{ return static_cast(fastf2i(std::clamp(val*32768.0f, -32768.0f, 32767.0f))); } template<> inline int8_t StoreSample(float val) noexcept -{ return static_cast(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); } +{ return static_cast(fastf2i(std::clamp(val*128.0f, -128.0f, 127.0f))); } /* Define unsigned output variations. */ template<> inline uint32_t StoreSample(float val) noexcept @@ -96,20 +99,25 @@ template<> inline uint8_t StoreSample(float val) noexcept { return static_cast(StoreSample(val) + 128); } template -inline void StoreSampleArray(void *dst, const float *RESTRICT src, const size_t dststep, - const size_t samples) noexcept +inline void StoreSampleArray(void *dst, const al::span src, const size_t channel, + const size_t dststep) noexcept { - DevFmtType_t *sdst = static_cast*>(dst); - for(size_t i{0u};i < samples;i++) - sdst[i*dststep] = StoreSample(src[i]); + assert(channel < dststep); + const auto dstspan = al::span{static_cast*>(dst), src.size()*dststep}; + auto sdst = dstspan.begin(); + std::for_each(src.cbegin(), src.cend(), [&sdst,channel,dststep](const float in) + { + sdst[channel] = StoreSample(in); + sdst += ptrdiff_t(dststep); + }); } -void StoreSamples(void *dst, const float *src, const size_t dststep, const DevFmtType dsttype, - const size_t samples) noexcept +void StoreSamples(void *dst, const al::span src, const size_t channel, + const size_t dststep, const DevFmtType dsttype) noexcept { #define HANDLE_FMT(T) \ - case T: StoreSampleArray(dst, src, dststep, samples); break + case T: StoreSampleArray(dst, src, channel, dststep); break switch(dsttype) { HANDLE_FMT(DevFmtByte); @@ -125,30 +133,35 @@ void StoreSamples(void *dst, const float *src, const size_t dststep, const DevFm template -void Mono2Stereo(float *RESTRICT dst, const void *src, const size_t frames) noexcept +void Mono2Stereo(const al::span dst, const void *src) noexcept { - const DevFmtType_t *ssrc = static_cast*>(src); - for(size_t i{0u};i < frames;i++) - dst[i*2 + 1] = dst[i*2 + 0] = LoadSample(ssrc[i]) * 0.707106781187f; + const auto srcspan = al::span{static_cast*>(src), dst.size()>>1}; + auto sdst = dst.begin(); + std::for_each(srcspan.cbegin(), srcspan.cend(), [&sdst](const auto in) + { sdst = std::fill_n(sdst, 2, LoadSample(in)*0.707106781187f); }); } template -void Multi2Mono(uint chanmask, const size_t step, const float scale, float *RESTRICT dst, - const void *src, const size_t frames) noexcept +void Multi2Mono(uint chanmask, const size_t step, const float scale, const al::span dst, + const void *src) noexcept { - const DevFmtType_t *ssrc = static_cast*>(src); - std::fill_n(dst, frames, 0.0f); + const auto srcspan = al::span{static_cast*>(src), step*dst.size()}; + std::fill_n(dst.begin(), dst.size(), 0.0f); for(size_t c{0};chanmask;++c) { if((chanmask&1)) LIKELY { - for(size_t i{0u};i < frames;i++) - dst[i] += LoadSample(ssrc[i*step + c]); + auto ssrc = srcspan.cbegin(); + std::for_each(dst.begin(), dst.end(), [&ssrc,step,c](float &sample) + { + const float s{LoadSample(ssrc[c])}; + ssrc += ptrdiff_t(step); + sample += s; + }); } chanmask >>= 1; } - for(size_t i{0u};i < frames;i++) - dst[i] *= scale; + std::for_each(dst.begin(), dst.end(), [scale](float &sample) noexcept { sample *= scale; }); } } // namespace @@ -168,19 +181,19 @@ SampleConverterPtr SampleConverter::Create(DevFmtType srcType, DevFmtType dstTyp converter->mSrcPrepCount = MaxResamplerPadding; converter->mFracOffset = 0; for(auto &chan : converter->mChan) - { - const al::span buffer{chan.PrevSamples}; - std::fill(buffer.begin(), buffer.end(), 0.0f); - } + chan.PrevSamples.fill(0.0f); /* Have to set the mixer FPU mode since that's what the resampler code expects. */ FPUCtl mixer_mode{}; - auto step = static_cast( - mind(srcRate*double{MixerFracOne}/dstRate + 0.5, MaxPitch*MixerFracOne)); - converter->mIncrement = maxu(step, 1); + const auto step = std::min(std::round(srcRate*double{MixerFracOne}/dstRate), + MaxPitch*double{MixerFracOne}); + converter->mIncrement = std::max(static_cast(step), 1u); if(converter->mIncrement == MixerFracOne) - converter->mResample = [](const InterpState*, const float *RESTRICT src, uint, const uint, - const al::span dst) { std::copy_n(src, dst.size(), dst.begin()); }; + { + converter->mResample = [](const InterpState*, const al::span src, uint, + const uint, const al::span dst) + { std::copy_n(src.begin()+MaxResamplerEdge, dst.size(), dst.begin()); }; + } else converter->mResample = PrepareResampler(resampler, converter->mIncrement, &converter->mState); @@ -210,24 +223,25 @@ uint SampleConverter::availableOut(uint srcframes) const DataSize64 -= mFracOffset; /* If we have a full prep, we can generate at least one sample. */ - return static_cast(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1, - std::numeric_limits::max())); + return static_cast(std::clamp((DataSize64 + mIncrement-1)/mIncrement, 1_u64, + uint64_t{std::numeric_limits::max()})); } uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint dstframes) { - const uint SrcFrameSize{static_cast(mChan.size()) * mSrcTypeSize}; - const uint DstFrameSize{static_cast(mChan.size()) * mDstTypeSize}; + const size_t SrcFrameSize{mChan.size() * mSrcTypeSize}; + const size_t DstFrameSize{mChan.size() * mDstTypeSize}; const uint increment{mIncrement}; - auto SamplesIn = static_cast(*src); uint NumSrcSamples{*srcframes}; + auto SamplesIn = al::span{static_cast(*src), NumSrcSamples*SrcFrameSize}; + auto SamplesOut = al::span{static_cast(dst), dstframes*DstFrameSize}; FPUCtl mixer_mode{}; uint pos{0}; while(pos < dstframes && NumSrcSamples > 0) { const uint prepcount{mSrcPrepCount}; - const uint readable{minu(NumSrcSamples, BufferLineSize - prepcount)}; + const uint readable{std::min(NumSrcSamples, uint{BufferLineSize} - prepcount)}; if(prepcount < MaxResamplerPadding && MaxResamplerPadding-prepcount >= readable) { @@ -235,16 +249,16 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint * 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, readable); + LoadSamples(al::span{mChan[chan].PrevSamples}.subspan(prepcount, readable), + SamplesIn.data(), chan, mChan.size(), mSrcType); mSrcPrepCount = prepcount + readable; NumSrcSamples = 0; break; } - float *RESTRICT SrcData{mSrcSamples}; - float *RESTRICT DstData{mDstSamples}; + const auto SrcData = al::span{mSrcSamples}; + const auto DstData = al::span{mDstSamples}; uint DataPosFrac{mFracOffset}; uint64_t DataSize64{prepcount}; DataSize64 += readable; @@ -253,39 +267,36 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint DataSize64 -= DataPosFrac; /* If we have a full prep, we can generate at least one sample. */ - auto DstSize = static_cast( - clampu64((DataSize64 + increment-1)/increment, 1, BufferLineSize)); - DstSize = minu(DstSize, dstframes-pos); + auto DstSize = static_cast(std::clamp((DataSize64 + increment-1)/increment, 1_u64, + uint64_t{BufferLineSize})); + DstSize = std::min(DstSize, dstframes-pos); const uint DataPosEnd{DstSize*increment + DataPosFrac}; const uint SrcDataEnd{DataPosEnd>>MixerFracBits}; assert(prepcount+readable >= SrcDataEnd); - const uint nextprep{minu(prepcount + readable - SrcDataEnd, MaxResamplerPadding)}; + const uint nextprep{std::min(prepcount+readable-SrcDataEnd, MaxResamplerPadding)}; for(size_t chan{0u};chan < mChan.size();chan++) { - const al::byte *SrcSamples{SamplesIn + mSrcTypeSize*chan}; - al::byte *DstSamples = static_cast(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, readable); + std::copy_n(mChan[chan].PrevSamples.cbegin(), prepcount, SrcData.begin()); + LoadSamples(SrcData.subspan(prepcount, readable), SamplesIn.data(), chan, mChan.size(), + mSrcType); /* Store as many prep samples for next time as possible, given the * number of output samples being generated. */ - std::copy_n(SrcData+SrcDataEnd, nextprep, mChan[chan].PrevSamples); - std::fill(std::begin(mChan[chan].PrevSamples)+nextprep, - std::end(mChan[chan].PrevSamples), 0.0f); + auto previter = std::copy_n(SrcData.begin()+ptrdiff_t(SrcDataEnd), nextprep, + mChan[chan].PrevSamples.begin()); + std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f); /* Now resample, and store the result in the output buffer. */ - mResample(&mState, SrcData+MaxResamplerEdge, DataPosFrac, increment, - {DstData, DstSize}); + mResample(&mState, SrcData, DataPosFrac, increment, DstData.first(DstSize)); - StoreSamples(DstSamples, DstData, mChan.size(), mDstType, DstSize); + StoreSamples(SamplesOut.data(), DstData.first(DstSize), chan, mChan.size(), mDstType); } /* Update the number of prep samples still available, as well as the @@ -295,15 +306,115 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint mFracOffset = DataPosEnd & MixerFracMask; /* Update the src and dst pointers in case there's still more to do. */ - const uint srcread{minu(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)}; - SamplesIn += SrcFrameSize*srcread; + const uint srcread{std::min(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)}; + SamplesIn = SamplesIn.subspan(SrcFrameSize*srcread); + NumSrcSamples -= srcread; + + SamplesOut = SamplesOut.subspan(DstFrameSize*DstSize); + pos += DstSize; + } + + *src = SamplesIn.data(); + *srcframes = NumSrcSamples; + + return pos; +} + +uint SampleConverter::convertPlanar(const void **src, uint *srcframes, void *const*dst, uint dstframes) +{ + const auto srcs = al::span{src, mChan.size()}; + const auto dsts = al::span{dst, mChan.size()}; + const uint increment{mIncrement}; + uint NumSrcSamples{*srcframes}; + + FPUCtl mixer_mode{}; + uint pos{0}; + while(pos < dstframes && NumSrcSamples > 0) + { + const uint prepcount{mSrcPrepCount}; + const uint readable{std::min(NumSrcSamples, uint{BufferLineSize} - prepcount)}; + + if(prepcount < MaxResamplerPadding && MaxResamplerPadding-prepcount >= readable) + { + /* 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++) + { + auto samples = al::span{static_cast(srcs[chan]), + NumSrcSamples*size_t{mSrcTypeSize}}; + LoadSamples(al::span{mChan[chan].PrevSamples}.subspan(prepcount, readable), + samples.data(), 0, 1, mSrcType); + srcs[chan] = samples.subspan(size_t{mSrcTypeSize}*readable).data(); + } + + mSrcPrepCount = prepcount + readable; + NumSrcSamples = 0; + break; + } + + const auto SrcData = al::span{mSrcSamples}; + const auto DstData = al::span{mDstSamples}; + uint DataPosFrac{mFracOffset}; + uint64_t DataSize64{prepcount}; + DataSize64 += readable; + DataSize64 -= MaxResamplerPadding; + DataSize64 <<= MixerFracBits; + DataSize64 -= DataPosFrac; + + /* If we have a full prep, we can generate at least one sample. */ + auto DstSize = static_cast(std::clamp((DataSize64 + increment-1)/increment, 1_u64, + uint64_t{BufferLineSize})); + DstSize = std::min(DstSize, dstframes-pos); + + const uint DataPosEnd{DstSize*increment + DataPosFrac}; + const uint SrcDataEnd{DataPosEnd>>MixerFracBits}; + + assert(prepcount+readable >= SrcDataEnd); + const uint nextprep{std::min(prepcount+readable-SrcDataEnd, MaxResamplerPadding)}; + + for(size_t chan{0u};chan < mChan.size();chan++) + { + /* Load the previous samples into the source data first, then the + * new samples from the input buffer. + */ + auto srciter = std::copy_n(mChan[chan].PrevSamples.cbegin(),prepcount,SrcData.begin()); + LoadSamples({srciter, readable}, srcs[chan], 0, 1, mSrcType); + + /* Store as many prep samples for next time as possible, given the + * number of output samples being generated. + */ + auto previter = std::copy_n(SrcData.begin()+ptrdiff_t(SrcDataEnd), nextprep, + mChan[chan].PrevSamples.begin()); + std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f); + + /* Now resample, and store the result in the output buffer. */ + mResample(&mState, SrcData, DataPosFrac, increment, DstData.first(DstSize)); + + auto DstSamples = al::span{static_cast(dsts[chan]), + size_t{mDstTypeSize}*dstframes}.subspan(pos*size_t{mDstTypeSize}); + StoreSamples(DstSamples.data(), DstData.first(DstSize), 0, 1, mDstType); + } + + /* Update the number of prep samples still available, as well as the + * fractional offset. + */ + mSrcPrepCount = nextprep; + mFracOffset = DataPosEnd & MixerFracMask; + + /* Update the src and dst pointers in case there's still more to do. */ + const uint srcread{std::min(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)}; + std::for_each(srcs.begin(), srcs.end(), [this,NumSrcSamples,srcread](const void *&srcref) + { + auto srcspan = al::span{static_cast(srcref), + size_t{mSrcTypeSize}*NumSrcSamples}; + srcref = srcspan.subspan(size_t{mSrcTypeSize}*srcread).data(); + }); NumSrcSamples -= srcread; - dst = static_cast(dst) + DstFrameSize*DstSize; pos += DstSize; } - *src = SamplesIn; *srcframes = NumSrcSamples; return pos; @@ -317,7 +428,7 @@ void ChannelConverter::convert(const void *src, float *dst, uint frames) const const float scale{std::sqrt(1.0f / static_cast(al::popcount(mChanMask)))}; switch(mSrcType) { -#define HANDLE_FMT(T) case T: Multi2Mono(mChanMask, mSrcStep, scale, dst, src, frames); break +#define HANDLE_FMT(T) case T: Multi2Mono(mChanMask, mSrcStep, scale, {dst, frames}, src); break HANDLE_FMT(DevFmtByte); HANDLE_FMT(DevFmtUByte); HANDLE_FMT(DevFmtShort); @@ -332,7 +443,7 @@ void ChannelConverter::convert(const void *src, float *dst, uint frames) const { switch(mSrcType) { -#define HANDLE_FMT(T) case T: Mono2Stereo(dst, src, frames); break +#define HANDLE_FMT(T) case T: Mono2Stereo({dst, frames*2_uz}, src); break HANDLE_FMT(DevFmtByte); HANDLE_FMT(DevFmtUByte); HANDLE_FMT(DevFmtShort); diff --git a/Engine/lib/openal-soft/core/converter.h b/Engine/lib/openal-soft/core/converter.h index 01becea28..01649c361 100644 --- a/Engine/lib/openal-soft/core/converter.h +++ b/Engine/lib/openal-soft/core/converter.h @@ -7,7 +7,9 @@ #include "almalloc.h" #include "devformat.h" +#include "flexarray.h" #include "mixer/defs.h" +#include "resampler_limits.h" using uint = unsigned int; @@ -25,21 +27,22 @@ struct SampleConverter { InterpState mState{}; ResamplerFunc mResample{}; - alignas(16) float mSrcSamples[BufferLineSize]{}; - alignas(16) float mDstSamples[BufferLineSize]{}; + alignas(16) FloatBufferLine mSrcSamples{}; + alignas(16) FloatBufferLine mDstSamples{}; struct ChanSamples { - alignas(16) float PrevSamples[MaxResamplerPadding]; + alignas(16) std::array PrevSamples; }; al::FlexArray mChan; SampleConverter(size_t numchans) : mChan{numchans} { } - uint convert(const void **src, uint *srcframes, void *dst, uint dstframes); - uint availableOut(uint srcframes) const; + [[nodiscard]] auto convert(const void **src, uint *srcframes, void *dst, uint dstframes) -> uint; + [[nodiscard]] auto convertPlanar(const void **src, uint *srcframes, void *const*dst, uint dstframes) -> uint; + [[nodiscard]] auto availableOut(uint srcframes) const -> uint; using SampleOffset = std::chrono::duration>; - SampleOffset currentInputDelay() const noexcept + [[nodiscard]] auto currentInputDelay() const noexcept -> SampleOffset { const int64_t prep{int64_t{mSrcPrepCount} - MaxResamplerEdge}; return SampleOffset{(prep< bool { return mChanMask != 0; } void convert(const void *src, float *dst, uint frames) const; }; diff --git a/Engine/lib/openal-soft/core/cpu_caps.cpp b/Engine/lib/openal-soft/core/cpu_caps.cpp index d4b4d86c8..1a064cf46 100644 --- a/Engine/lib/openal-soft/core/cpu_caps.cpp +++ b/Engine/lib/openal-soft/core/cpu_caps.cpp @@ -17,6 +17,7 @@ #include #endif +#include #include #include #include @@ -50,14 +51,14 @@ inline std::array get_cpuid(unsigned int f) } // namespace -al::optional GetCPUInfo() +std::optional GetCPUInfo() { CPUInfo ret; #ifdef CAN_GET_CPUID auto cpuregs = get_cpuid(0); if(cpuregs[0] == 0) - return al::nullopt; + return std::nullopt; const reg_type maxfunc{cpuregs[0]}; diff --git a/Engine/lib/openal-soft/core/cpu_caps.h b/Engine/lib/openal-soft/core/cpu_caps.h index ffd671d0d..0826a49bd 100644 --- a/Engine/lib/openal-soft/core/cpu_caps.h +++ b/Engine/lib/openal-soft/core/cpu_caps.h @@ -1,10 +1,9 @@ #ifndef CORE_CPU_CAPS_H #define CORE_CPU_CAPS_H +#include #include -#include "aloptional.h" - extern int CPUCapFlags; enum { @@ -21,6 +20,6 @@ struct CPUInfo { int mCaps{0}; }; -al::optional GetCPUInfo(); +std::optional GetCPUInfo(); #endif /* CORE_CPU_CAPS_H */ diff --git a/Engine/lib/openal-soft/core/cubic_defs.h b/Engine/lib/openal-soft/core/cubic_defs.h index 33751c974..a38208164 100644 --- a/Engine/lib/openal-soft/core/cubic_defs.h +++ b/Engine/lib/openal-soft/core/cubic_defs.h @@ -1,13 +1,15 @@ #ifndef CORE_CUBIC_DEFS_H #define CORE_CUBIC_DEFS_H +#include + /* The number of distinct phase intervals within the cubic filter tables. */ constexpr unsigned int CubicPhaseBits{5}; constexpr unsigned int CubicPhaseCount{1 << CubicPhaseBits}; struct CubicCoefficients { - float mCoeffs[4]; - float mDeltas[4]; + alignas(16) std::array mCoeffs; + alignas(16) std::array mDeltas; }; #endif /* CORE_CUBIC_DEFS_H */ diff --git a/Engine/lib/openal-soft/core/cubic_tables.cpp b/Engine/lib/openal-soft/core/cubic_tables.cpp index 73ec6b3f6..cf469f9c0 100644 --- a/Engine/lib/openal-soft/core/cubic_tables.cpp +++ b/Engine/lib/openal-soft/core/cubic_tables.cpp @@ -1,59 +1,121 @@ #include "cubic_tables.h" -#include #include -#include #include -#include -#include -#include +#include #include "alnumbers.h" -#include "core/mixer/defs.h" - +#include "alnumeric.h" +#include "cubic_defs.h" +/* These gaussian filter tables are inspired by the gaussian-like filter found + * in the SNES. This is based on the public domain code developed by Near, with + * the help of Ryphecha and nocash, from the nesdev.org forums. + * + * + * + * Additional changes were made here, the most obvious being that is has full + * floating-point precision instead of 11-bit fixed-point, but also an offset + * adjustment for the coefficients to better preserve phase. + */ namespace { -using uint = unsigned int; - -struct SplineFilterArray { - alignas(16) CubicCoefficients mTable[CubicPhaseCount]{}; - - constexpr SplineFilterArray() - { - /* Fill in the main coefficients. */ - for(size_t pi{0};pi < CubicPhaseCount;++pi) - { - const double mu{static_cast(pi) / CubicPhaseCount}; - const double mu2{mu*mu}, mu3{mu2*mu}; - mTable[pi].mCoeffs[0] = static_cast(-0.5*mu3 + mu2 + -0.5*mu); - mTable[pi].mCoeffs[1] = static_cast( 1.5*mu3 + -2.5*mu2 + 1.0); - mTable[pi].mCoeffs[2] = static_cast(-1.5*mu3 + 2.0*mu2 + 0.5*mu); - mTable[pi].mCoeffs[3] = static_cast( 0.5*mu3 + -0.5*mu2); - } - - /* Fill in the coefficient deltas. */ - for(size_t pi{0};pi < CubicPhaseCount-1;++pi) - { - mTable[pi].mDeltas[0] = mTable[pi+1].mCoeffs[0] - mTable[pi].mCoeffs[0]; - mTable[pi].mDeltas[1] = mTable[pi+1].mCoeffs[1] - mTable[pi].mCoeffs[1]; - mTable[pi].mDeltas[2] = mTable[pi+1].mCoeffs[2] - mTable[pi].mCoeffs[2]; - mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3]; - } - - const size_t pi{CubicPhaseCount - 1}; - mTable[pi].mDeltas[0] = -mTable[pi].mCoeffs[0]; - mTable[pi].mDeltas[1] = -mTable[pi].mCoeffs[1]; - mTable[pi].mDeltas[2] = 1.0f - mTable[pi].mCoeffs[2]; - mTable[pi].mDeltas[3] = -mTable[pi].mCoeffs[3]; - } - - constexpr auto getTable() const noexcept { return al::as_span(mTable); } -}; - -constexpr SplineFilterArray SplineFilter{}; +[[nodiscard]] +auto GetCoeff(double idx) noexcept -> double +{ + const double k{0.5 + idx}; + if(k > 512.0) return 0.0; + const double s{ std::sin(al::numbers::pi*1.280/1024 * k)}; + const double t{(std::cos(al::numbers::pi*2.000/1023 * k) - 1.0) * 0.50}; + const double u{(std::cos(al::numbers::pi*4.000/1023 * k) - 1.0) * 0.08}; + return s * (t + u + 1.0) / k; +} } // namespace -const CubicTable gCubicSpline{SplineFilter.getTable()}; +GaussianTable::GaussianTable() +{ + static constexpr double IndexScale{512.0 / double{CubicPhaseCount*2}}; + /* Fill in the main coefficients. */ + for(std::size_t pi{0};pi < CubicPhaseCount;++pi) + { + const double coeff0{GetCoeff(static_cast(CubicPhaseCount + pi)*IndexScale)}; + const double coeff1{GetCoeff(static_cast(pi)*IndexScale)}; + const double coeff2{GetCoeff(static_cast(CubicPhaseCount - pi)*IndexScale)}; + const double coeff3{GetCoeff(static_cast(CubicPhaseCount*2_uz-pi)*IndexScale)}; + + const double scale{1.0 / (coeff0 + coeff1 + coeff2 + coeff3)}; + mTable[pi].mCoeffs[0] = static_cast(coeff0 * scale); + mTable[pi].mCoeffs[1] = static_cast(coeff1 * scale); + mTable[pi].mCoeffs[2] = static_cast(coeff2 * scale); + mTable[pi].mCoeffs[3] = static_cast(coeff3 * scale); + } + + /* Fill in the coefficient deltas. */ + for(std::size_t pi{0};pi < CubicPhaseCount-1;++pi) + { + mTable[pi].mDeltas[0] = mTable[pi+1].mCoeffs[0] - mTable[pi].mCoeffs[0]; + mTable[pi].mDeltas[1] = mTable[pi+1].mCoeffs[1] - mTable[pi].mCoeffs[1]; + mTable[pi].mDeltas[2] = mTable[pi+1].mCoeffs[2] - mTable[pi].mCoeffs[2]; + mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3]; + } + + const std::size_t pi{CubicPhaseCount - 1}; + mTable[pi].mDeltas[0] = 0.0f - mTable[pi].mCoeffs[0]; + mTable[pi].mDeltas[1] = mTable[0].mCoeffs[0] - mTable[pi].mCoeffs[1]; + mTable[pi].mDeltas[2] = mTable[0].mCoeffs[1] - mTable[pi].mCoeffs[2]; + mTable[pi].mDeltas[3] = mTable[0].mCoeffs[2] - mTable[pi].mCoeffs[3]; +} + +SplineTable::SplineTable() +{ + /* This filter table is based on a Catmull-Rom spline. It retains more of + * the original high-frequency content, at the cost of increased harmonics. + */ + for(std::size_t pi{0};pi < CubicPhaseCount;++pi) + { + const double mu{static_cast(pi) / double{CubicPhaseCount}}; + const double mu2{mu*mu}, mu3{mu2*mu}; + mTable[pi].mCoeffs[0] = static_cast(-0.5*mu3 + mu2 + -0.5*mu); + mTable[pi].mCoeffs[1] = static_cast( 1.5*mu3 + -2.5*mu2 + 1.0); + mTable[pi].mCoeffs[2] = static_cast(-1.5*mu3 + 2.0*mu2 + 0.5*mu); + mTable[pi].mCoeffs[3] = static_cast( 0.5*mu3 + -0.5*mu2); + } + + for(std::size_t pi{0};pi < CubicPhaseCount-1;++pi) + { + mTable[pi].mDeltas[0] = mTable[pi+1].mCoeffs[0] - mTable[pi].mCoeffs[0]; + mTable[pi].mDeltas[1] = mTable[pi+1].mCoeffs[1] - mTable[pi].mCoeffs[1]; + mTable[pi].mDeltas[2] = mTable[pi+1].mCoeffs[2] - mTable[pi].mCoeffs[2]; + mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3]; + } + + const std::size_t pi{CubicPhaseCount - 1}; + mTable[pi].mDeltas[0] = 0.0f - mTable[pi].mCoeffs[0]; + mTable[pi].mDeltas[1] = mTable[0].mCoeffs[0] - mTable[pi].mCoeffs[1]; + mTable[pi].mDeltas[2] = mTable[0].mCoeffs[1] - mTable[pi].mCoeffs[2]; + mTable[pi].mDeltas[3] = mTable[0].mCoeffs[2] - mTable[pi].mCoeffs[3]; +} + + +CubicFilter::CubicFilter() +{ + static constexpr double IndexScale{512.0 / double{sTableSteps*2}}; + /* Only half the coefficients need to be iterated here, since Coeff2 and + * Coeff3 are just Coeff1 and Coeff0 in reverse respectively. + */ + for(size_t i{0};i < sTableSteps/2 + 1;++i) + { + const double coeff0{GetCoeff(static_cast(sTableSteps + i)*IndexScale)}; + const double coeff1{GetCoeff(static_cast(i)*IndexScale)}; + const double coeff2{GetCoeff(static_cast(sTableSteps - i)*IndexScale)}; + const double coeff3{GetCoeff(static_cast(sTableSteps*2_uz - i)*IndexScale)}; + + const double scale{1.0 / (coeff0 + coeff1 + coeff2 + coeff3)}; + mFilter[sTableSteps + i] = static_cast(coeff0 * scale); + mFilter[i] = static_cast(coeff1 * scale); + mFilter[sTableSteps - i] = static_cast(coeff2 * scale); + mFilter[sTableSteps*2 - i] = static_cast(coeff3 * scale); + } +} diff --git a/Engine/lib/openal-soft/core/cubic_tables.h b/Engine/lib/openal-soft/core/cubic_tables.h index 88097ae2f..2106a1331 100644 --- a/Engine/lib/openal-soft/core/cubic_tables.h +++ b/Engine/lib/openal-soft/core/cubic_tables.h @@ -1,17 +1,41 @@ #ifndef CORE_CUBIC_TABLES_H #define CORE_CUBIC_TABLES_H -#include "alspan.h" +#include +#include + #include "cubic_defs.h" struct CubicTable { - al::span Tab; + std::array mTable{}; }; -/* A Catmull-Rom spline. The spline passes through the center two samples, - * ensuring no discontinuity while moving through a series of samples. - */ -extern const CubicTable gCubicSpline; +struct GaussianTable : CubicTable { GaussianTable(); }; +inline const GaussianTable gGaussianFilter; + +struct SplineTable : CubicTable { SplineTable(); }; +inline const SplineTable gSplineFilter; + + +struct CubicFilter { + static constexpr std::size_t sTableBits{8}; + static constexpr std::size_t sTableSteps{1 << sTableBits}; + static constexpr std::size_t sTableMask{sTableSteps - 1}; + + std::array mFilter{}; + + CubicFilter(); + + [[nodiscard]] constexpr + auto getCoeff0(std::size_t i) const noexcept -> float { return mFilter[sTableSteps+i]; } + [[nodiscard]] constexpr + auto getCoeff1(std::size_t i) const noexcept -> float { return mFilter[i]; } + [[nodiscard]] constexpr + auto getCoeff2(std::size_t i) const noexcept -> float { return mFilter[sTableSteps-i]; } + [[nodiscard]] constexpr + auto getCoeff3(std::size_t i) const noexcept -> float { return mFilter[sTableSteps*2-i]; } +}; +inline const CubicFilter gCubicTable; #endif /* CORE_CUBIC_TABLES_H */ diff --git a/Engine/lib/openal-soft/core/dbus_wrap.cpp b/Engine/lib/openal-soft/core/dbus_wrap.cpp index 7f2217066..05d9fc063 100644 --- a/Engine/lib/openal-soft/core/dbus_wrap.cpp +++ b/Engine/lib/openal-soft/core/dbus_wrap.cpp @@ -11,14 +11,16 @@ #include "logging.h" -void *dbus_handle{nullptr}; -#define DECL_FUNC(x) decltype(p##x) p##x{}; -DBUS_FUNCTIONS(DECL_FUNC) -#undef DECL_FUNC - void PrepareDBus() { - static constexpr char libname[] = "libdbus-1.so.3"; + const char *libname{"libdbus-1.so.3"}; + + dbus_handle = LoadLib(libname); + if(!dbus_handle) + { + WARN("Failed to load %s\n", libname); + return; + } auto load_func = [](auto &f, const char *name) -> void { f = reinterpret_cast>(GetSymbol(dbus_handle, name)); }; @@ -33,14 +35,8 @@ void PrepareDBus() } \ } while(0); - dbus_handle = LoadLib(libname); - if(!dbus_handle) - { - WARN("Failed to load %s\n", libname); - return; - } + DBUS_FUNCTIONS(LOAD_FUNC) -DBUS_FUNCTIONS(LOAD_FUNC) #undef LOAD_FUNC } #endif diff --git a/Engine/lib/openal-soft/core/dbus_wrap.h b/Engine/lib/openal-soft/core/dbus_wrap.h index 09eaacf93..afc27d842 100644 --- a/Engine/lib/openal-soft/core/dbus_wrap.h +++ b/Engine/lib/openal-soft/core/dbus_wrap.h @@ -28,8 +28,8 @@ MAGIC(dbus_message_iter_get_arg_type) \ MAGIC(dbus_message_iter_get_basic) \ MAGIC(dbus_set_error_from_message) -extern void *dbus_handle; -#define DECL_FUNC(x) extern decltype(x) *p##x; +inline void *dbus_handle{}; +#define DECL_FUNC(x) inline decltype(x) *p##x{}; DBUS_FUNCTIONS(DECL_FUNC) #undef DECL_FUNC @@ -70,9 +70,16 @@ namespace dbus { struct Error { Error() { dbus_error_init(&mError); } + Error(const Error&) = delete; + Error(Error&&) = delete; ~Error() { dbus_error_free(&mError); } + + void operator=(const Error&) = delete; + void operator=(Error&&) = delete; + DBusError* operator->() { return &mError; } DBusError &get() { return mError; } + private: DBusError mError{}; }; diff --git a/Engine/lib/openal-soft/core/devformat.cpp b/Engine/lib/openal-soft/core/devformat.cpp index acdabc4f1..57d1d8ea7 100644 --- a/Engine/lib/openal-soft/core/devformat.cpp +++ b/Engine/lib/openal-soft/core/devformat.cpp @@ -29,6 +29,7 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept case DevFmtX61: return 7; case DevFmtX71: return 8; case DevFmtX714: return 12; + case DevFmtX7144: return 16; case DevFmtX3D71: return 8; case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1); } @@ -60,6 +61,7 @@ const char *DevFmtChannelsString(DevFmtChannels chans) noexcept case DevFmtX61: return "6.1 Surround"; case DevFmtX71: return "7.1 Surround"; case DevFmtX714: return "7.1.4 Surround"; + case DevFmtX7144: return "7.1.4.4 Surround"; case DevFmtX3D71: return "3D7.1 Surround"; case DevFmtAmbi3D: return "Ambisonic 3D"; } diff --git a/Engine/lib/openal-soft/core/devformat.h b/Engine/lib/openal-soft/core/devformat.h index 485826a39..ca59df5e5 100644 --- a/Engine/lib/openal-soft/core/devformat.h +++ b/Engine/lib/openal-soft/core/devformat.h @@ -2,6 +2,7 @@ #define CORE_DEVFORMAT_H #include +#include using uint = unsigned int; @@ -25,6 +26,11 @@ enum Channel : unsigned char { TopBackCenter, TopBackRight, + BottomFrontLeft, + BottomFrontRight, + BottomBackLeft, + BottomBackRight, + Aux0, Aux1, Aux2, @@ -66,12 +72,13 @@ enum DevFmtChannels : unsigned char { DevFmtX61, DevFmtX71, DevFmtX714, + DevFmtX7144, DevFmtX3D71, DevFmtAmbi3D, DevFmtChannelsDefault = DevFmtStereo }; -#define MAX_OUTPUT_CHANNELS 16 +inline constexpr std::size_t MaxOutputChannels{16}; /* DevFmtType traits, providing the type, etc given a DevFmtType. */ template diff --git a/Engine/lib/openal-soft/core/device.cpp b/Engine/lib/openal-soft/core/device.cpp index 2766c5e4e..795a96016 100644 --- a/Engine/lib/openal-soft/core/device.cpp +++ b/Engine/lib/openal-soft/core/device.cpp @@ -9,15 +9,12 @@ #include "mastering.h" -al::FlexArray DeviceBase::sEmptyContextArray{0u}; +static_assert(std::atomic::is_always_lock_free); -DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{&sEmptyContextArray} +DeviceBase::DeviceBase(DeviceType type) + : Type{type}, mContexts{al::FlexArray::Create(0)} { } -DeviceBase::~DeviceBase() -{ - auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed); - if(oldarray != &sEmptyContextArray) delete oldarray; -} +DeviceBase::~DeviceBase() = default; diff --git a/Engine/lib/openal-soft/core/device.h b/Engine/lib/openal-soft/core/device.h index 9aaf7adb8..a62863189 100644 --- a/Engine/lib/openal-soft/core/device.h +++ b/Engine/lib/openal-soft/core/device.h @@ -1,14 +1,13 @@ #ifndef CORE_DEVICE_H #define CORE_DEVICE_H -#include - #include #include #include #include +#include +#include #include -#include #include #include "almalloc.h" @@ -18,6 +17,7 @@ #include "bufferline.h" #include "devformat.h" #include "filters/nfc.h" +#include "flexarray.h" #include "intrusive_ptr.h" #include "mixer/hrtfdefs.h" #include "opthelpers.h" @@ -26,8 +26,10 @@ #include "vector.h" class BFormatDec; +namespace Bs2b { struct bs2b; -struct Compressor; +} // namespace Bs2b +class Compressor; struct ContextBase; struct DirectHrtfState; struct HrtfStore; @@ -35,28 +37,28 @@ struct HrtfStore; using uint = unsigned int; -#define MIN_OUTPUT_RATE 8000 -#define MAX_OUTPUT_RATE 192000 -#define DEFAULT_OUTPUT_RATE 48000 +inline constexpr std::size_t MinOutputRate{8000}; +inline constexpr std::size_t MaxOutputRate{192000}; +inline constexpr std::size_t DefaultOutputRate{48000}; -#define DEFAULT_UPDATE_SIZE 960 /* 20ms */ -#define DEFAULT_NUM_UPDATES 3 +inline constexpr std::size_t DefaultUpdateSize{960}; /* 20ms */ +inline constexpr std::size_t DefaultNumUpdates{3}; -enum class DeviceType : unsigned char { +enum class DeviceType : std::uint8_t { Playback, Capture, Loopback }; -enum class RenderMode : unsigned char { +enum class RenderMode : std::uint8_t { Normal, Pairwise, Hrtf }; -enum class StereoEncoding : unsigned char { +enum class StereoEncoding : std::uint8_t { Basic, Uhj, Hrtf, @@ -78,24 +80,23 @@ struct DistanceComp { static constexpr uint MaxDelay{1024}; struct ChanData { + al::span Buffer{}; /* Valid size is [0...MaxDelay). */ float Gain{1.0f}; - uint Length{0u}; /* Valid range is [0...MaxDelay). */ - float *Buffer{nullptr}; }; - std::array mChannels; + std::array mChannels; al::FlexArray mSamples; - DistanceComp(size_t count) : mSamples{count} { } + DistanceComp(std::size_t count) : mSamples{count} { } - static std::unique_ptr Create(size_t numsamples) + static std::unique_ptr Create(std::size_t numsamples) { return std::unique_ptr{new(FamCount(numsamples)) DistanceComp{numsamples}}; } DEF_FAM_NEWDEL(DistanceComp, mSamples) }; -constexpr uint InvalidChannelIndex{~0u}; +constexpr auto InvalidChannelIndex = static_cast(~0u); struct BFChannelConfig { float Scale; @@ -113,24 +114,24 @@ struct MixParams { * source is expected to be a 3D ACN/N3D ambisonic buffer, and for each * channel [0...count), the given functor is called with the source channel * index, destination channel index, and the gain for that channel. If the - * destination channel is INVALID_CHANNEL_INDEX, the given source channel - * is not used for output. + * destination channel is InvalidChannelIndex, the given source channel is + * not used for output. */ template void setAmbiMixParams(const MixParams &inmix, const float gainbase, F func) const { - const size_t numIn{inmix.Buffer.size()}; - const size_t numOut{Buffer.size()}; - for(size_t i{0};i < numIn;++i) + const std::size_t numIn{inmix.Buffer.size()}; + const std::size_t numOut{Buffer.size()}; + for(std::size_t i{0};i < numIn;++i) { - auto idx = InvalidChannelIndex; - auto gain = 0.0f; + std::uint8_t idx{InvalidChannelIndex}; + float gain{0.0f}; - for(size_t j{0};j < numOut;++j) + for(std::size_t j{0};j < numOut;++j) { if(AmbiMap[j].Index == inmix.AmbiMap[i].Index) { - idx = static_cast(j); + idx = static_cast(j); gain = AmbiMap[j].Scale * gainbase; break; } @@ -142,7 +143,7 @@ struct MixParams { struct RealMixParams { al::span RemixMap; - std::array ChannelIndex{}; + std::array ChannelIndex{}; al::span Buffer; }; @@ -159,22 +160,26 @@ enum { // Specifies if the DSP is paused at user request DevicePaused, - // Specifies if the device is currently running - DeviceRunning, // Specifies if the output plays directly on/in ears (headphones, headset, // ear buds, etc). DirectEar, + /* Specifies if output is using speaker virtualization (e.g. Windows + * Spatial Audio). + */ + Virtualization, + DeviceFlagsCount }; -struct DeviceBase { - /* To avoid extraneous allocations, a 0-sized FlexArray is - * defined globally as a sharable object. - */ - static al::FlexArray sEmptyContextArray; +enum class DeviceState : std::uint8_t { + Unprepared, + Configured, + Playing +}; +struct DeviceBase { std::atomic Connected{true}; const DeviceType Type{}; @@ -198,6 +203,7 @@ struct DeviceBase { // Device flags std::bitset Flags{}; + DeviceState mDeviceState{DeviceState::Unprepared}; uint NumAuxSends{}; @@ -214,35 +220,31 @@ struct DeviceBase { */ NfcFilter mNFCtrlFilter{}; - uint SamplesDone{0u}; - std::chrono::nanoseconds ClockBase{0}; + std::atomic mSamplesDone{0u}; + std::atomic mClockBase{std::chrono::nanoseconds{}}; std::chrono::nanoseconds FixedLatency{0}; AmbiRotateMatrix mAmbiRotateMatrix{}; AmbiRotateMatrix mAmbiRotateMatrix2{}; /* Temp storage used for mixer processing. */ - static constexpr size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding}; - static constexpr size_t MixerChannelsMax{16}; - using MixerBufferLine = std::array; - alignas(16) std::array mSampleData; - alignas(16) std::array mResampleData; + static constexpr std::size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding}; + static constexpr std::size_t MixerChannelsMax{16}; + alignas(16) std::array mSampleData{}; + alignas(16) std::array mResampleData{}; - alignas(16) float FilteredData[BufferLineSize]; - union { - alignas(16) float HrtfSourceData[BufferLineSize + HrtfHistoryLength]; - alignas(16) float NfcSampleData[BufferLineSize]; - }; + alignas(16) std::array FilteredData{}; + alignas(16) std::array ExtraSampleData{}; /* Persistent storage for HRTF mixing. */ - alignas(16) float2 HrtfAccumData[BufferLineSize + HrirLength]; + alignas(16) std::array HrtfAccumData{}; /* Mixing buffer used by the Dry mix and Real output. */ al::vector MixBuffer; /* The "dry" path corresponds to the main output. */ MixParams Dry; - uint NumChannelsPerOrder[MaxAmbiOrder+1]{}; + std::array NumChannelsPerOrder{}; /* "Real" output, which will be written to the device buffer. May alias the * dry buffer. @@ -261,7 +263,7 @@ struct DeviceBase { std::unique_ptr AmbiDecoder; /* Stereo-to-binaural filter */ - std::unique_ptr Bs2b; + std::unique_ptr Bs2b; using PostProc = void(DeviceBase::*)(const size_t SamplesToDo); PostProc PostProcess{nullptr}; @@ -280,10 +282,10 @@ struct DeviceBase { * 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}; + std::atomic mMixCount{0u}; // Contexts created on this device - std::atomic*> mContexts{nullptr}; + al::atomic_unique_ptr> mContexts; DeviceBase(DeviceType type); @@ -291,33 +293,69 @@ struct DeviceBase { DeviceBase& operator=(const DeviceBase&) = delete; ~DeviceBase(); - uint bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); } - uint channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } - uint frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); } + [[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromDevFmt(FmtType); } + [[nodiscard]] auto channelsFromFmt() const noexcept -> uint { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } + [[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint { return bytesFromFmt() * channelsFromFmt(); } - uint waitForMix() const noexcept + struct MixLock { + DeviceBase *const self; + const uint mEndVal; + + MixLock(DeviceBase *device, const uint endval) noexcept : self{device}, mEndVal{endval} { } + MixLock(const MixLock&) = delete; + void operator=(const MixLock&) = delete; + /* Update the mix count when the lock goes out of scope to "release" it + * (lsb should be 0). + */ + ~MixLock() { self->mMixCount.store(mEndVal, std::memory_order_release); } + }; + auto getWriteMixLock() noexcept -> MixLock { - uint refcount; - while((refcount=MixCount.load(std::memory_order_acquire))&1) { - } + /* Increment the mix count at the start of mixing and writing clock + * info (lsb should be 1). + */ + auto mixCount = mMixCount.load(std::memory_order_relaxed); + mMixCount.store(++mixCount, std::memory_order_release); + return MixLock{this, ++mixCount}; + } + + /** Waits for the mixer to not be mixing or updating the clock. */ + [[nodiscard]] auto waitForMix() const noexcept -> uint + { + uint refcount{mMixCount.load(std::memory_order_acquire)}; + while((refcount&1)) refcount = mMixCount.load(std::memory_order_acquire); return refcount; } - void ProcessHrtf(const size_t SamplesToDo); - void ProcessAmbiDec(const size_t SamplesToDo); - void ProcessAmbiDecStablized(const size_t SamplesToDo); - void ProcessUhj(const size_t SamplesToDo); - void ProcessBs2b(const size_t SamplesToDo); + /** + * Helper to get the current clock time from the device's ClockBase, and + * SamplesDone converted from the sample rate. Should only be called while + * watching the MixCount. + */ + [[nodiscard]] auto getClockTime() const noexcept -> std::chrono::nanoseconds + { + using std::chrono::seconds; + using std::chrono::nanoseconds; - inline void postProcess(const size_t SamplesToDo) + auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / Frequency; + return mClockBase.load(std::memory_order_relaxed) + ns; + } + + void ProcessHrtf(const std::size_t SamplesToDo); + void ProcessAmbiDec(const std::size_t SamplesToDo); + void ProcessAmbiDecStablized(const std::size_t SamplesToDo); + void ProcessUhj(const std::size_t SamplesToDo); + void ProcessBs2b(const std::size_t SamplesToDo); + + inline void postProcess(const std::size_t SamplesToDo) { if(PostProcess) LIKELY (this->*PostProcess)(SamplesToDo); } void renderSamples(const al::span outBuffers, const uint numSamples); - void renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep); + void renderSamples(void *outBuffer, const uint numSamples, const std::size_t frameStep); /* Caller must lock the device state, and the mixer must not be running. */ -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf,2,3)]] +#ifdef __MINGW32__ + [[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] #else [[gnu::format(printf,2,3)]] #endif @@ -325,21 +363,21 @@ struct DeviceBase { /** * Returns the index for the given channel name (e.g. FrontCenter), or - * INVALID_CHANNEL_INDEX if it doesn't exist. + * InvalidChannelIndex if it doesn't exist. */ - uint channelIdxByName(Channel chan) const noexcept + [[nodiscard]] auto channelIdxByName(Channel chan) const noexcept -> std::uint8_t { return RealOut.ChannelIndex[chan]; } - DISABLE_ALLOC() - private: uint renderSamples(const uint numSamples); }; /* Must be less than 15 characters (16 including terminating null) for * compatibility with pthread_setname_np limitations. */ -#define MIXER_THREAD_NAME "alsoft-mixer" +[[nodiscard]] constexpr +auto GetMixerThreadName() noexcept -> const char* { return "alsoft-mixer"; } -#define RECORD_THREAD_NAME "alsoft-record" +[[nodiscard]] constexpr +auto GetRecordThreadName() noexcept -> const char* { return "alsoft-record"; } #endif /* CORE_DEVICE_H */ diff --git a/Engine/lib/openal-soft/core/effects/base.h b/Engine/lib/openal-soft/core/effects/base.h index 4ee19f37c..b0d267167 100644 --- a/Engine/lib/openal-soft/core/effects/base.h +++ b/Engine/lib/openal-soft/core/effects/base.h @@ -1,12 +1,11 @@ #ifndef CORE_EFFECTS_BASE_H #define CORE_EFFECTS_BASE_H -#include +#include +#include +#include -#include "albyte.h" -#include "almalloc.h" #include "alspan.h" -#include "atomic.h" #include "core/bufferline.h" #include "intrusive_ptr.h" @@ -19,21 +18,21 @@ struct RealMixParams; /** Target gain for the reverb decay feedback reaching the decay time. */ -constexpr float ReverbDecayGain{0.001f}; /* -60 dB */ +inline constexpr float ReverbDecayGain{0.001f}; /* -60 dB */ -constexpr float ReverbMaxReflectionsDelay{0.3f}; -constexpr float ReverbMaxLateReverbDelay{0.1f}; +inline constexpr float ReverbMaxReflectionsDelay{0.3f}; +inline constexpr float ReverbMaxLateReverbDelay{0.1f}; enum class ChorusWaveform { Sinusoid, Triangle }; -constexpr float ChorusMaxDelay{0.016f}; -constexpr float FlangerMaxDelay{0.004f}; +inline constexpr float ChorusMaxDelay{0.016f}; +inline constexpr float FlangerMaxDelay{0.004f}; -constexpr float EchoMaxDelay{0.207f}; -constexpr float EchoMaxLRDelay{0.404f}; +inline constexpr float EchoMaxDelay{0.207f}; +inline constexpr float EchoMaxLRDelay{0.404f}; enum class FShifterDirection { Down, @@ -59,115 +58,135 @@ enum class VMorpherWaveform { Sawtooth }; -union EffectProps { - struct { - float Density; - float Diffusion; - float Gain; - float GainHF; - float GainLF; - float DecayTime; - float DecayHFRatio; - float DecayLFRatio; - float ReflectionsGain; - float ReflectionsDelay; - float ReflectionsPan[3]; - float LateReverbGain; - float LateReverbDelay; - float LateReverbPan[3]; - float EchoTime; - float EchoDepth; - float ModulationTime; - float ModulationDepth; - float AirAbsorptionGainHF; - float HFReference; - float LFReference; - float RoomRolloffFactor; - bool DecayHFLimit; - } Reverb; - - struct { - float AttackTime; - float ReleaseTime; - float Resonance; - float PeakGain; - } Autowah; - - struct { - ChorusWaveform Waveform; - int Phase; - float Rate; - float Depth; - float Feedback; - float Delay; - } Chorus; /* Also Flanger */ - - struct { - bool OnOff; - } Compressor; - - struct { - float Edge; - float Gain; - float LowpassCutoff; - float EQCenter; - float EQBandwidth; - } Distortion; - - struct { - float Delay; - float LRDelay; - - float Damping; - float Feedback; - - float Spread; - } Echo; - - struct { - float LowCutoff; - float LowGain; - float Mid1Center; - float Mid1Gain; - float Mid1Width; - float Mid2Center; - float Mid2Gain; - float Mid2Width; - float HighCutoff; - float HighGain; - } Equalizer; - - struct { - float Frequency; - FShifterDirection LeftDirection; - FShifterDirection RightDirection; - } Fshifter; - - struct { - float Frequency; - float HighPassCutoff; - ModulatorWaveform Waveform; - } Modulator; - - struct { - int CoarseTune; - int FineTune; - } Pshifter; - - struct { - float Rate; - VMorpherPhenome PhonemeA; - VMorpherPhenome PhonemeB; - int PhonemeACoarseTuning; - int PhonemeBCoarseTuning; - VMorpherWaveform Waveform; - } Vmorpher; - - struct { - float Gain; - } Dedicated; +struct ReverbProps { + float Density; + float Diffusion; + float Gain; + float GainHF; + float GainLF; + float DecayTime; + float DecayHFRatio; + float DecayLFRatio; + float ReflectionsGain; + float ReflectionsDelay; + std::array ReflectionsPan; + float LateReverbGain; + float LateReverbDelay; + std::array LateReverbPan; + float EchoTime; + float EchoDepth; + float ModulationTime; + float ModulationDepth; + float AirAbsorptionGainHF; + float HFReference; + float LFReference; + float RoomRolloffFactor; + bool DecayHFLimit; }; +struct AutowahProps { + float AttackTime; + float ReleaseTime; + float Resonance; + float PeakGain; +}; + +struct ChorusProps { + ChorusWaveform Waveform; + int Phase; + float Rate; + float Depth; + float Feedback; + float Delay; +}; + +struct CompressorProps { + bool OnOff; +}; + +struct DistortionProps { + float Edge; + float Gain; + float LowpassCutoff; + float EQCenter; + float EQBandwidth; +}; + +struct EchoProps { + float Delay; + float LRDelay; + + float Damping; + float Feedback; + + float Spread; +}; + +struct EqualizerProps { + float LowCutoff; + float LowGain; + float Mid1Center; + float Mid1Gain; + float Mid1Width; + float Mid2Center; + float Mid2Gain; + float Mid2Width; + float HighCutoff; + float HighGain; +}; + +struct FshifterProps { + float Frequency; + FShifterDirection LeftDirection; + FShifterDirection RightDirection; +}; + +struct ModulatorProps { + float Frequency; + float HighPassCutoff; + ModulatorWaveform Waveform; +}; + +struct PshifterProps { + int CoarseTune; + int FineTune; +}; + +struct VmorpherProps { + float Rate; + VMorpherPhenome PhonemeA; + VMorpherPhenome PhonemeB; + int PhonemeACoarseTuning; + int PhonemeBCoarseTuning; + VMorpherWaveform Waveform; +}; + +struct DedicatedProps { + enum TargetType : bool { Dialog, Lfe }; + TargetType Target; + float Gain; +}; + +struct ConvolutionProps { + std::array OrientAt; + std::array OrientUp; +}; + +using EffectProps = std::variant; + struct EffectTarget { MixParams *Main; @@ -189,8 +208,14 @@ struct EffectState : public al::intrusive_ref { struct EffectStateFactory { + EffectStateFactory() = default; + EffectStateFactory(const EffectStateFactory&) = delete; + EffectStateFactory(EffectStateFactory&&) = delete; virtual ~EffectStateFactory() = default; + void operator=(const EffectStateFactory&) = delete; + void operator=(EffectStateFactory&&) = delete; + virtual al::intrusive_ptr create() = 0; }; diff --git a/Engine/lib/openal-soft/core/effectslot.cpp b/Engine/lib/openal-soft/core/effectslot.cpp index db8aa078c..d07c79a54 100644 --- a/Engine/lib/openal-soft/core/effectslot.cpp +++ b/Engine/lib/openal-soft/core/effectslot.cpp @@ -3,17 +3,13 @@ #include "effectslot.h" -#include +#include #include "almalloc.h" #include "context.h" -EffectSlotArray *EffectSlot::CreatePtrArray(size_t count) noexcept +std::unique_ptr EffectSlot::CreatePtrArray(size_t count) { - /* 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(EffectSlotArray), EffectSlotArray::Sizeof(count*2))}; - return al::construct_at(static_cast(ptr), count); + return std::unique_ptr{new(FamCount{count}) EffectSlotArray(count)}; } diff --git a/Engine/lib/openal-soft/core/effectslot.h b/Engine/lib/openal-soft/core/effectslot.h index 2624ae5fd..70dbbbad4 100644 --- a/Engine/lib/openal-soft/core/effectslot.h +++ b/Engine/lib/openal-soft/core/effectslot.h @@ -2,10 +2,11 @@ #define CORE_EFFECTSLOT_H #include +#include -#include "almalloc.h" #include "device.h" #include "effects/base.h" +#include "flexarray.h" #include "intrusive_ptr.h" struct EffectSlot; @@ -18,20 +19,18 @@ enum class EffectSlotType : unsigned char { None, Reverb, Chorus, - Distortion, - Echo, - Flanger, - FrequencyShifter, - VocalMorpher, - PitchShifter, - RingModulator, Autowah, Compressor, + Convolution, + Dedicated, + Distortion, + Echo, Equalizer, - EAXReverb, - DedicatedLFE, - DedicatedDialog, - Convolution + Flanger, + FrequencyShifter, + PitchShifter, + RingModulator, + VocalMorpher, }; struct EffectSlotProps { @@ -45,8 +44,6 @@ struct EffectSlotProps { al::intrusive_ptr State; std::atomic next; - - DEF_NEWDEL(EffectSlotProps) }; @@ -81,9 +78,7 @@ struct EffectSlot { al::vector mWetBuffer; - static EffectSlotArray *CreatePtrArray(size_t count) noexcept; - - DEF_NEWDEL(EffectSlot) + static std::unique_ptr CreatePtrArray(size_t count); }; #endif /* CORE_EFFECTSLOT_H */ diff --git a/Engine/lib/openal-soft/core/except.cpp b/Engine/lib/openal-soft/core/except.cpp index 45fd4eb5f..f338e9aeb 100644 --- a/Engine/lib/openal-soft/core/except.cpp +++ b/Engine/lib/openal-soft/core/except.cpp @@ -13,18 +13,20 @@ namespace al { base_exception::~base_exception() = default; -void base_exception::setMessage(const char* msg, std::va_list args) +void base_exception::setMessage(const char *msg, std::va_list args) { + /* NOLINTBEGIN(*-array-to-pointer-decay) */ std::va_list args2; va_copy(args2, args); int msglen{std::vsnprintf(nullptr, 0, msg, args)}; if(msglen > 0) LIKELY { mMessage.resize(static_cast(msglen)+1); - std::vsnprintf(const_cast(mMessage.data()), mMessage.length(), msg, args2); + std::vsnprintf(mMessage.data(), mMessage.length(), msg, args2); mMessage.pop_back(); } va_end(args2); + /* NOLINTEND(*-array-to-pointer-decay) */ } } // namespace al diff --git a/Engine/lib/openal-soft/core/except.h b/Engine/lib/openal-soft/core/except.h index 0e28e9dfe..1b3d634fc 100644 --- a/Engine/lib/openal-soft/core/except.h +++ b/Engine/lib/openal-soft/core/except.h @@ -13,19 +13,20 @@ class base_exception : public std::exception { std::string mMessage; protected: - base_exception() = default; - virtual ~base_exception(); - - void setMessage(const char *msg, std::va_list args); + auto setMessage(const char *msg, std::va_list args) -> void; public: - const char *what() const noexcept override { return mMessage.c_str(); } + base_exception() = default; + base_exception(const base_exception&) = default; + base_exception(base_exception&&) = default; + ~base_exception() override; + + auto operator=(const base_exception&) -> base_exception& = default; + auto operator=(base_exception&&) -> base_exception& = default; + + [[nodiscard]] auto what() const noexcept -> const char* override { return mMessage.c_str(); } }; } // namespace al -#define START_API_FUNC try - -#define END_API_FUNC catch(...) { std::terminate(); } - #endif /* CORE_EXCEPT_H */ diff --git a/Engine/lib/openal-soft/core/filters/biquad.cpp b/Engine/lib/openal-soft/core/filters/biquad.cpp index a0a62eb8b..c1faf5e9f 100644 --- a/Engine/lib/openal-soft/core/filters/biquad.cpp +++ b/Engine/lib/openal-soft/core/filters/biquad.cpp @@ -3,6 +3,7 @@ #include "biquad.h" +#include #include #include #include @@ -27,8 +28,8 @@ void BiquadFilterR::setParams(BiquadType type, Real f0norm, Real gain, Rea 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 }; + std::array a{{1.0f, 0.0f, 0.0f}}; + std::array b{{1.0f, 0.0f, 0.0f}}; /* Calculate filter coefficients depending on filter type */ switch(type) @@ -94,7 +95,7 @@ void BiquadFilterR::setParams(BiquadType type, Real f0norm, Real gain, Rea } template -void BiquadFilterR::process(const al::span src, Real *dst) +void BiquadFilterR::process(const al::span src, const al::span dst) { const Real b0{mB0}; const Real b1{mB1}; @@ -119,7 +120,7 @@ void BiquadFilterR::process(const al::span src, Real *dst) z2 = input*b2 - output*a2; return output; }; - std::transform(src.cbegin(), src.cend(), dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); mZ1 = z1; mZ2 = z2; @@ -127,7 +128,7 @@ void BiquadFilterR::process(const al::span src, Real *dst) template void BiquadFilterR::dualProcess(BiquadFilterR &other, const al::span src, - Real *dst) + const al::span dst) { const Real b00{mB0}; const Real b01{mB1}; @@ -156,7 +157,7 @@ void BiquadFilterR::dualProcess(BiquadFilterR &other, const al::span src, Real *dst); + void process(const al::span src, const al::span dst); /** Processes this filter and the other at the same time. */ - void dualProcess(BiquadFilterR &other, const al::span src, Real *dst); + void dualProcess(BiquadFilterR &other, const al::span src, + const al::span dst); /* Rather hacky. It's just here to support "manual" processing. */ - std::pair getComponents() const noexcept { return {mZ1, mZ2}; } + [[nodiscard]] auto getComponents() const noexcept -> std::pair { 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 + [[nodiscard]] auto processOne(const Real in, Real &z1, Real &z2) const noexcept -> Real { const Real out{in*mB0 + z1}; z1 = in*mB1 - out*mA1 + z2; @@ -134,7 +135,7 @@ template struct DualBiquadR { BiquadFilterR &f0, &f1; - void process(const al::span src, Real *dst) + void process(const al::span src, const al::span dst) { f0.dualProcess(f1, src, dst); } }; diff --git a/Engine/lib/openal-soft/core/filters/nfc.cpp b/Engine/lib/openal-soft/core/filters/nfc.cpp index aa64c6130..c3c313db4 100644 --- a/Engine/lib/openal-soft/core/filters/nfc.cpp +++ b/Engine/lib/openal-soft/core/filters/nfc.cpp @@ -48,24 +48,22 @@ 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 } +constexpr std::array B{ + std::array{ 0.0f, 0.0f, 0.0f, 0.0f}, + std::array{ 1.0f, 0.0f, 0.0f, 0.0f}, + std::array{ 3.0f, 3.0f, 0.0f, 0.0f}, + std::array{3.6778f, 6.4595f, 2.3222f, 0.0f}, + std::array{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; /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; - b_00 = B[1][0] * r; - g_0 = 1.0f + b_00; + float r{0.5f * w1}; + float b_00{B[1][0] * r}; + float g_0{1.0f + b_00}; nfc.base_gain = 1.0f / g_0; nfc.a1 = 2.0f * b_00 / g_0; @@ -95,14 +93,12 @@ void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept { NfcFilter2 nfc{}; - float b_10, b_11, g_1; - float r; /* 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; + float r{0.5f * w1}; + float b_10{B[2][0] * r}; + float b_11{B[2][1] * r * r}; + float g_1{1.0f + b_10 + b_11}; nfc.base_gain = 1.0f / g_1; nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -137,17 +133,14 @@ void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept 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; /* 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; + float r{0.5f * w1}; + float b_10{B[3][0] * r}; + float b_11{B[3][1] * r * r}; + float b_00{B[3][2] * r}; + float g_1{1.0f + b_10 + b_11}; + float g_0{1.0f + b_00}; nfc.base_gain = 1.0f / (g_1 * g_0); nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -189,18 +182,15 @@ void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept 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; /* 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; + float r{0.5f * w1}; + float b_10{B[4][0] * r}; + float b_11{B[4][1] * r * r}; + float b_00{B[4][2] * r}; + float b_01{B[4][3] * r * r}; + float g_1{1.0f + b_10 + b_11}; + float g_0{1.0f + b_00 + b_01}; nfc.base_gain = 1.0f / (g_1 * g_0); nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -262,7 +252,7 @@ void NfcFilter::adjust(const float w0) noexcept } -void NfcFilter::process1(const al::span src, float *RESTRICT dst) +void NfcFilter::process1(const al::span src, const al::span dst) { const float gain{first.gain}; const float b1{first.b1}; @@ -275,11 +265,11 @@ void NfcFilter::process1(const al::span src, float *RESTRICT dst) z1 += y; return out; }; - std::transform(src.cbegin(), src.cend(), dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); first.z[0] = z1; } -void NfcFilter::process2(const al::span src, float *RESTRICT dst) +void NfcFilter::process2(const al::span src, const al::span dst) { const float gain{second.gain}; const float b1{second.b1}; @@ -296,12 +286,12 @@ void NfcFilter::process2(const al::span src, float *RESTRICT dst) z1 += y; return out; }; - std::transform(src.cbegin(), src.cend(), dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); second.z[0] = z1; second.z[1] = z2; } -void NfcFilter::process3(const al::span src, float *RESTRICT dst) +void NfcFilter::process3(const al::span src, const al::span dst) { const float gain{third.gain}; const float b1{third.b1}; @@ -325,13 +315,13 @@ void NfcFilter::process3(const al::span src, float *RESTRICT dst) z3 += y; return out; }; - std::transform(src.cbegin(), src.cend(), dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); third.z[0] = z1; third.z[1] = z2; third.z[2] = z3; } -void NfcFilter::process4(const al::span src, float *RESTRICT dst) +void NfcFilter::process4(const al::span src, const al::span dst) { const float gain{fourth.gain}; const float b1{fourth.b1}; @@ -359,7 +349,7 @@ void NfcFilter::process4(const al::span src, float *RESTRICT dst) z3 += y; return out; }; - std::transform(src.cbegin(), src.cend(), dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); fourth.z[0] = z1; fourth.z[1] = z2; fourth.z[2] = z3; diff --git a/Engine/lib/openal-soft/core/filters/nfc.h b/Engine/lib/openal-soft/core/filters/nfc.h index 33f67a5f2..00ab4dd76 100644 --- a/Engine/lib/openal-soft/core/filters/nfc.h +++ b/Engine/lib/openal-soft/core/filters/nfc.h @@ -1,30 +1,31 @@ #ifndef CORE_FILTERS_NFC_H #define CORE_FILTERS_NFC_H +#include #include #include "alspan.h" struct NfcFilter1 { - float base_gain, gain; - float b1, a1; - float z[1]; + float base_gain{1.0f}, gain{1.0f}; + float b1{}, a1{}; + std::array z{}; }; struct NfcFilter2 { - float base_gain, gain; - float b1, b2, a1, a2; - float z[2]; + float base_gain{1.0f}, gain{1.0f}; + float b1{}, b2{}, a1{}, a2{}; + std::array z{}; }; struct NfcFilter3 { - float base_gain, gain; - float b1, b2, b3, a1, a2, a3; - float z[3]; + float base_gain{1.0f}, gain{1.0f}; + float b1{}, b2{}, b3{}, a1{}, a2{}, a3{}; + std::array z{}; }; struct NfcFilter4 { - float base_gain, gain; - float b1, b2, b3, b4, a1, a2, a3, a4; - float z[4]; + float base_gain{1.0f}, gain{1.0f}; + float b1{}, b2{}, b3{}, b4{}, a1{}, a2{}, a3{}, a4{}; + std::array z{}; }; class NfcFilter { @@ -39,7 +40,7 @@ public: * 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 + * average speaker distance, or based on the reference delay if outputting * NFC-HOA. It must not be negative, 0, or infinite. The source distance * should not be too small relative to the control distance. */ @@ -48,16 +49,16 @@ public: void adjust(const float w0) noexcept; /* Near-field control filter for first-order ambisonic channels (1-3). */ - void process1(const al::span src, float *RESTRICT dst); + void process1(const al::span src, const al::span dst); /* Near-field control filter for second-order ambisonic channels (4-8). */ - void process2(const al::span src, float *RESTRICT dst); + void process2(const al::span src, const al::span dst); /* Near-field control filter for third-order ambisonic channels (9-15). */ - void process3(const al::span src, float *RESTRICT dst); + void process3(const al::span src, const al::span dst); /* Near-field control filter for fourth-order ambisonic channels (16-24). */ - void process4(const al::span src, float *RESTRICT dst); + void process4(const al::span src, const al::span dst); }; #endif /* CORE_FILTERS_NFC_H */ diff --git a/Engine/lib/openal-soft/core/filters/splitter.cpp b/Engine/lib/openal-soft/core/filters/splitter.cpp index 983ba36f1..fbb6b2b7e 100644 --- a/Engine/lib/openal-soft/core/filters/splitter.cpp +++ b/Engine/lib/openal-soft/core/filters/splitter.cpp @@ -4,6 +4,7 @@ #include "splitter.h" #include +#include #include #include @@ -27,14 +28,17 @@ void BandSplitterR::init(Real f0norm) } template -void BandSplitterR::process(const al::span input, Real *hpout, Real *lpout) +void BandSplitterR::process(const al::span input, const al::span hpout, + const al::span lpout) { 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 + assert(lpout.size() <= input.size()); + auto lpiter = lpout.begin(); + auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpiter](const Real in) noexcept -> Real { /* Low-pass sample processing. */ Real d{(in - lp_z1) * lp_coeff}; @@ -45,7 +49,7 @@ void BandSplitterR::process(const al::span input, Real *hpout, lp_y = lp_z2 + d; lp_z2 = lp_y + d; - *(lpout++) = lp_y; + *(lpiter++) = lp_y; /* All-pass sample processing. */ Real ap_y{in*ap_coeff + ap_z1}; @@ -54,15 +58,15 @@ void BandSplitterR::process(const al::span input, Real *hpout, /* High-pass generated from removing low-passed output. */ return ap_y - lp_y; }; - std::transform(input.cbegin(), input.cend(), hpout, proc_sample); + std::transform(input.cbegin(), input.cend(), hpout.begin(), proc_sample); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; } template -void BandSplitterR::processHfScale(const al::span input, Real *RESTRICT output, - const Real hfscale) +void BandSplitterR::processHfScale(const al::span input, + const al::span output, const Real hfscale) { const Real ap_coeff{mCoeff}; const Real lp_coeff{mCoeff*0.5f + 0.5f}; @@ -89,7 +93,7 @@ void BandSplitterR::processHfScale(const al::span input, Real */ return (ap_y-lp_y)*hfscale + lp_y; }; - std::transform(input.begin(), input.end(), output, proc_sample); + std::transform(input.cbegin(), input.cend(), output.begin(), proc_sample); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; diff --git a/Engine/lib/openal-soft/core/filters/splitter.h b/Engine/lib/openal-soft/core/filters/splitter.h index e853eb385..e3658c19a 100644 --- a/Engine/lib/openal-soft/core/filters/splitter.h +++ b/Engine/lib/openal-soft/core/filters/splitter.h @@ -22,9 +22,11 @@ public: void init(Real f0norm); void clear() noexcept { mLpZ1 = mLpZ2 = mApZ1 = 0.0f; } - void process(const al::span input, Real *hpout, Real *lpout); + void process(const al::span input, const al::span hpout, + const al::span lpout); - void processHfScale(const al::span input, Real *output, const Real hfscale); + void processHfScale(const al::span input, const al::span output, + const Real hfscale); void processHfScale(const al::span samples, const Real hfscale); void processScale(const al::span samples, const Real hfscale, const Real lfscale); diff --git a/Engine/lib/openal-soft/core/fmt_traits.cpp b/Engine/lib/openal-soft/core/fmt_traits.cpp index 054d87669..9d79287db 100644 --- a/Engine/lib/openal-soft/core/fmt_traits.cpp +++ b/Engine/lib/openal-soft/core/fmt_traits.cpp @@ -6,7 +6,7 @@ namespace al { -const int16_t muLawDecompressionTable[256] = { +const std::array muLawDecompressionTable{{ -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, @@ -39,9 +39,9 @@ const int16_t muLawDecompressionTable[256] = { 244, 228, 212, 196, 180, 164, 148, 132, 120, 112, 104, 96, 88, 80, 72, 64, 56, 48, 40, 32, 24, 16, 8, 0 -}; +}}; -const int16_t aLawDecompressionTable[256] = { +const std::array aLawDecompressionTable{{ -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, @@ -74,6 +74,6 @@ const int16_t aLawDecompressionTable[256] = { 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, 688, 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848 -}; +}}; } // namespace al diff --git a/Engine/lib/openal-soft/core/fmt_traits.h b/Engine/lib/openal-soft/core/fmt_traits.h index f797f836f..e87ea57ce 100644 --- a/Engine/lib/openal-soft/core/fmt_traits.h +++ b/Engine/lib/openal-soft/core/fmt_traits.h @@ -1,17 +1,16 @@ #ifndef CORE_FMT_TRAITS_H #define CORE_FMT_TRAITS_H -#include -#include +#include +#include -#include "albyte.h" -#include "buffer_storage.h" +#include "storage_formats.h" namespace al { -extern const int16_t muLawDecompressionTable[256]; -extern const int16_t aLawDecompressionTable[256]; +extern const std::array muLawDecompressionTable; +extern const std::array aLawDecompressionTable; template @@ -19,63 +18,52 @@ struct FmtTypeTraits { }; template<> struct FmtTypeTraits { - using Type = uint8_t; + using Type = std::uint8_t; - template - static constexpr inline OutT to(const Type val) noexcept - { return val*OutT{1.0/128.0} - OutT{1.0}; } + constexpr float operator()(const Type val) const noexcept + { return float(val)*(1.0f/128.0f) - 1.0f; } }; template<> struct FmtTypeTraits { - using Type = int16_t; + using Type = std::int16_t; - template - static constexpr inline OutT to(const Type val) noexcept { return val*OutT{1.0/32768.0}; } + constexpr float operator()(const Type val) const noexcept + { return float(val) * (1.0f/32768.0f); } +}; +template<> +struct FmtTypeTraits { + using Type = std::int32_t; + + constexpr float operator()(const Type val) const noexcept + { return static_cast(val)*(1.0f/2147483648.0f); } }; template<> struct FmtTypeTraits { using Type = float; - template - static constexpr inline OutT to(const Type val) noexcept { return val; } + constexpr float operator()(const Type val) const noexcept { return val; } }; template<> struct FmtTypeTraits { using Type = double; - template - static constexpr inline OutT to(const Type val) noexcept { return static_cast(val); } + constexpr float operator()(const Type val) const noexcept { return static_cast(val); } }; template<> struct FmtTypeTraits { - using Type = uint8_t; + using Type = std::uint8_t; - template - static constexpr inline OutT to(const Type val) noexcept - { return muLawDecompressionTable[val] * OutT{1.0/32768.0}; } + constexpr float operator()(const Type val) const noexcept + { return float(muLawDecompressionTable[val]) * (1.0f/32768.0f); } }; template<> struct FmtTypeTraits { - using Type = uint8_t; + using Type = std::uint8_t; - template - static constexpr inline OutT to(const Type val) noexcept - { return aLawDecompressionTable[val] * OutT{1.0/32768.0}; } + constexpr float operator()(const Type val) const noexcept + { return float(aLawDecompressionTable[val]) * (1.0f/32768.0f); } }; - -template -inline void LoadSampleArray(DstT *RESTRICT dst, const al::byte *src, const size_t srcstep, - const size_t samples) noexcept -{ - using TypeTraits = FmtTypeTraits; - using SampleType = typename TypeTraits::Type; - - const SampleType *RESTRICT ssrc{reinterpret_cast(src)}; - for(size_t i{0u};i < samples;i++) - dst[i] = TypeTraits::template to(ssrc[i*srcstep]); -} - } // namespace al #endif /* CORE_FMT_TRAITS_H */ diff --git a/Engine/lib/openal-soft/core/fpu_ctrl.cpp b/Engine/lib/openal-soft/core/fpu_ctrl.cpp index 0cf0d6e72..28e60c042 100644 --- a/Engine/lib/openal-soft/core/fpu_ctrl.cpp +++ b/Engine/lib/openal-soft/core/fpu_ctrl.cpp @@ -8,54 +8,80 @@ #endif #ifdef HAVE_SSE_INTRINSICS #include -#ifndef _MM_DENORMALS_ZERO_MASK +#elif defined(HAVE_SSE) +#include +#endif + +#if defined(HAVE_SSE) && !defined(_MM_DENORMALS_ZERO_MASK) /* Some headers seem to be missing these? */ #define _MM_DENORMALS_ZERO_MASK 0x0040u #define _MM_DENORMALS_ZERO_ON 0x0040u #endif -#endif #include "cpu_caps.h" +namespace { -void FPUCtl::enter() noexcept +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +[[gnu::target("sse")]] +#endif +[[maybe_unused]] +void disable_denormals(unsigned int *state [[maybe_unused]]) { - if(this->in_mode) return; - #if defined(HAVE_SSE_INTRINSICS) - this->sse_state = _mm_getcsr(); - unsigned int sseState{this->sse_state}; + *state = _mm_getcsr(); + unsigned int sseState{*state}; sseState &= ~(_MM_FLUSH_ZERO_MASK | _MM_DENORMALS_ZERO_MASK); sseState |= _MM_FLUSH_ZERO_ON | _MM_DENORMALS_ZERO_ON; _mm_setcsr(sseState); -#elif defined(__GNUC__) && defined(HAVE_SSE) +#elif defined(HAVE_SSE) - if((CPUCapFlags&CPU_CAP_SSE)) + *state = _mm_getcsr(); + unsigned int sseState{*state}; + sseState &= ~_MM_FLUSH_ZERO_MASK; + sseState |= _MM_FLUSH_ZERO_ON; + if((CPUCapFlags&CPU_CAP_SSE2)) { - __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)); + sseState &= ~_MM_DENORMALS_ZERO_MASK; + sseState |= _MM_DENORMALS_ZERO_ON; } + _mm_setcsr(sseState); #endif - - this->in_mode = true; } -void FPUCtl::leave() noexcept +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +[[gnu::target("sse")]] +#endif +[[maybe_unused]] +void reset_fpu(unsigned int state [[maybe_unused]]) { - 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)); +#if defined(HAVE_SSE_INTRINSICS) || defined(HAVE_SSE) + _mm_setcsr(state); +#endif +} + +} // namespace + + +unsigned int FPUCtl::Set() noexcept +{ + unsigned int state{}; +#if defined(HAVE_SSE_INTRINSICS) + disable_denormals(&state); +#elif defined(HAVE_SSE) + if((CPUCapFlags&CPU_CAP_SSE)) + disable_denormals(&state); +#endif + return state; +} + +void FPUCtl::Reset(unsigned int state [[maybe_unused]]) noexcept +{ +#if defined(HAVE_SSE_INTRINSICS) + reset_fpu(state); +#elif defined(HAVE_SSE) + if((CPUCapFlags&CPU_CAP_SSE)) + reset_fpu(state); #endif - this->in_mode = false; } diff --git a/Engine/lib/openal-soft/core/fpu_ctrl.h b/Engine/lib/openal-soft/core/fpu_ctrl.h index 9554313ae..d4f75ec31 100644 --- a/Engine/lib/openal-soft/core/fpu_ctrl.h +++ b/Engine/lib/openal-soft/core/fpu_ctrl.h @@ -2,20 +2,31 @@ #define CORE_FPU_CTRL_H class FPUCtl { -#if defined(HAVE_SSE_INTRINSICS) || (defined(__GNUC__) && defined(HAVE_SSE)) unsigned int sse_state{}; -#endif bool in_mode{}; + static unsigned int Set() noexcept; + static void Reset(unsigned int state) noexcept; + public: - FPUCtl() noexcept { enter(); in_mode = true; } - ~FPUCtl() { if(in_mode) leave(); } + FPUCtl() noexcept : sse_state{Set()}, in_mode{true} { } + ~FPUCtl() { if(in_mode) Reset(sse_state); } FPUCtl(const FPUCtl&) = delete; FPUCtl& operator=(const FPUCtl&) = delete; - void enter() noexcept; - void leave() noexcept; + void enter() noexcept + { + if(!in_mode) + sse_state = Set(); + in_mode = true; + } + void leave() noexcept + { + if(in_mode) + Reset(sse_state); + in_mode = false; + } }; #endif /* CORE_FPU_CTRL_H */ diff --git a/Engine/lib/openal-soft/core/front_stablizer.h b/Engine/lib/openal-soft/core/front_stablizer.h index 6825111a7..8eeb6d747 100644 --- a/Engine/lib/openal-soft/core/front_stablizer.h +++ b/Engine/lib/openal-soft/core/front_stablizer.h @@ -7,6 +7,7 @@ #include "almalloc.h" #include "bufferline.h" #include "filters/splitter.h" +#include "flexarray.h" struct FrontStablizer { diff --git a/Engine/lib/openal-soft/core/helpers.cpp b/Engine/lib/openal-soft/core/helpers.cpp index 99cf009c8..5e973bf28 100644 --- a/Engine/lib/openal-soft/core/helpers.cpp +++ b/Engine/lib/openal-soft/core/helpers.cpp @@ -3,102 +3,64 @@ #include "helpers.h" +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include +#endif + #include -#include -#include #include -#include #include -#include +#include #include +#include +#include #include -#include +#include #include "almalloc.h" -#include "alfstream.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "logging.h" #include "strutils.h" -#include "vector.h" -/* Mixing thread piority level */ -int RTPrioLevel{1}; - -/* Allow reducing the process's RTTime limit for RTKit. */ -bool AllowRTTimeLimit{true}; - - -#ifdef _WIN32 - -#include - -const PathNamePair &GetProcBinary() -{ - static al::optional procbin; - if(procbin) return *procbin; - - auto fullpath = al::vector(256); - DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), static_cast(fullpath.size()))}; - while(len == fullpath.size()) - { - fullpath.resize(fullpath.size() << 1); - len = GetModuleFileNameW(nullptr, fullpath.data(), static_cast(fullpath.size())); - } - if(len == 0) - { - ERR("Failed to get process name: error %lu\n", GetLastError()); - procbin.emplace(); - return *procbin; - } - - fullpath.resize(len); - if(fullpath.back() != 0) - fullpath.push_back(0); - - std::replace(fullpath.begin(), fullpath.end(), '/', '\\'); - auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\'); - if(sep != fullpath.rend()) - { - *sep = 0; - procbin.emplace(wstr_to_utf8(fullpath.data()), wstr_to_utf8(al::to_address(sep.base()))); - } - else - procbin.emplace(std::string{}, wstr_to_utf8(fullpath.data())); - - TRACE("Got binary: %s, %s\n", procbin->path.c_str(), procbin->fname.c_str()); - return *procbin; -} - namespace { -void DirectorySearch(const char *path, const char *ext, al::vector *const results) -{ - std::string pathstr{path}; - pathstr += "\\*"; - pathstr += ext; - TRACE("Searching %s\n", pathstr.c_str()); +using namespace std::string_view_literals; - 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; +std::mutex gSearchLock; + +void DirectorySearch(const std::filesystem::path &path, const std::string_view ext, + std::vector *const results) +{ + namespace fs = std::filesystem; 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); + try { + auto fpath = path.lexically_normal(); + if(!fs::exists(fpath)) + return; - const al::span newlist{results->data()+base, results->size()-base}; + TRACE("Searching %s for *%.*s\n", fpath.u8string().c_str(), al::sizei(ext), ext.data()); + for(auto&& dirent : fs::directory_iterator{fpath}) + { + auto&& entrypath = dirent.path(); + if(!entrypath.has_extension()) + continue; + + if(fs::status(entrypath).type() == fs::file_type::regular + && al::case_compare(entrypath.extension().u8string(), ext) == 0) + results->emplace_back(entrypath.u8string()); + } + } + catch(std::exception& e) { + ERR("Exception enumerating files: %s\n", e.what()); + } + + const auto newlist = al::span{*results}.subspan(base); std::sort(newlist.begin(), newlist.end()); for(const auto &name : newlist) TRACE(" got %s\n", name.c_str()); @@ -106,83 +68,127 @@ void DirectorySearch(const char *path, const char *ext, al::vector } // namespace -al::vector SearchDataFiles(const char *ext, const char *subdir) -{ - auto is_slash = [](int c) noexcept -> int { return (c == '\\' || c == '/'); }; +#ifdef _WIN32 - static std::mutex search_lock; - std::lock_guard _{search_lock}; +#include +#include + +const PathNamePair &GetProcBinary() +{ + auto get_procbin = [] + { +#if !defined(ALSOFT_UWP) + DWORD pathlen{256}; + auto fullpath = std::wstring(pathlen, L'\0'); + DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), pathlen)}; + while(len == fullpath.size()) + { + pathlen <<= 1; + if(pathlen == 0) + { + /* pathlen overflow (more than 4 billion characters??) */ + len = 0; + break; + } + fullpath.resize(pathlen); + len = GetModuleFileNameW(nullptr, fullpath.data(), pathlen); + } + if(len == 0) + { + ERR("Failed to get process name: error %lu\n", GetLastError()); + return PathNamePair{}; + } + + fullpath.resize(len); +#else + const WCHAR *exePath{__wargv[0]}; + if(!exePath) + { + ERR("Failed to get process name: __wargv[0] == nullptr\n"); + return PathNamePair{}; + } + std::wstring fullpath{exePath}; +#endif + std::replace(fullpath.begin(), fullpath.end(), L'/', L'\\'); + + PathNamePair res{}; + if(auto seppos = fullpath.rfind(L'\\'); seppos < fullpath.size()) + { + res.path = wstr_to_utf8(std::wstring_view{fullpath}.substr(0, seppos)); + res.fname = wstr_to_utf8(std::wstring_view{fullpath}.substr(seppos+1)); + } + else + res.fname = wstr_to_utf8(fullpath); + + TRACE("Got binary: %s, %s\n", res.path.c_str(), res.fname.c_str()); + return res; + }; + static const PathNamePair procbin{get_procbin()}; + return procbin; +} + +namespace { + +#if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX) +struct CoTaskMemDeleter { + void operator()(void *mem) const { CoTaskMemFree(mem); } +}; +#endif + +} // namespace + +std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir) +{ + std::lock_guard srchlock{gSearchLock}; /* If the path is absolute, use it directly. */ - al::vector results; - if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2])) + std::vector results; + auto path = std::filesystem::u8path(subdir); + if(path.is_absolute()) { - std::string path{subdir}; - std::replace(path.begin(), path.end(), '/', '\\'); - DirectorySearch(path.c_str(), ext, &results); + DirectorySearch(path, 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); + DirectorySearch(*localpath, ext, &results); + else if(auto curpath = std::filesystem::current_path(); !curpath.empty()) + DirectorySearch(curpath, ext, &results); +#if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX) /* Search the local and global data dirs. */ - static const int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA }; - for(int id : ids) + for(const auto &folderid : std::array{FOLDERID_RoamingAppData, FOLDERID_ProgramData}) { - WCHAR buffer[MAX_PATH]; - if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE) + std::unique_ptr buffer; + const HRESULT hr{SHGetKnownFolderPath(folderid, KF_FLAG_DONT_UNEXPAND, nullptr, + al::out_ptr(buffer))}; + if(FAILED(hr) || !buffer || !*buffer) 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); + DirectorySearch(std::filesystem::path{buffer.get()}/path, ext, &results); } +#endif return results; } -void SetRTPriority(void) +void SetRTPriority() { +#if !defined(ALSOFT_UWP) if(RTPrioLevel > 0) { if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) ERR("Failed to set priority level for thread\n"); } +#endif } #else -#include -#include +#include #include +#include #ifdef __FreeBSD__ #include #endif @@ -197,7 +203,6 @@ void SetRTPriority(void) #include #endif #ifdef HAVE_RTKIT -#include #include #include "dbus_wrap.h" @@ -209,184 +214,112 @@ void SetRTPriority(void) const PathNamePair &GetProcBinary() { - static al::optional procbin; - if(procbin) return *procbin; - - al::vector 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 + auto get_procbin = [] { - pathname.resize(pathlen + 1); - sysctl(mib, 4, pathname.data(), &pathlen, nullptr, 0); - pathname.resize(pathlen); - } + std::string pathname; +#ifdef __FreeBSD__ + size_t pathlen{}; + std::array mib{{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}}; + if(sysctl(mib.data(), mib.size(), nullptr, &pathlen, nullptr, 0) == -1) + WARN("Failed to sysctl kern.proc.pathname: %s\n", + std::generic_category().message(errno).c_str()); + else + { + auto procpath = std::vector(pathlen+1, '\0'); + sysctl(mib.data(), mib.size(), procpath.data(), &pathlen, nullptr, 0); + pathname = procpath.data(); + } #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)); - } + if(pathname.empty()) + { + std::array procpath{}; + const pid_t pid{getpid()}; + if(proc_pidpath(pid, procpath.data(), procpath.size()) < 1) + ERR("proc_pidpath(%d, ...) failed: %s\n", pid, + std::generic_category().message(errno).c_str()); + else + pathname = procpath.data(); + } #endif #ifdef __HAIKU__ - if(pathname.empty()) - { - char procpath[PATH_MAX]; - if(find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, procpath, sizeof(procpath)) == B_OK) - pathname.insert(pathname.end(), procpath, procpath+strlen(procpath)); - } + if(pathname.empty()) + { + std::array procpath{}; + if(find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, procpath.data(), procpath.size()) == B_OK) + pathname = procpath.data(); + } #endif #ifndef __SWITCH__ - if(pathname.empty()) - { - static const char SelfLinkNames[][32]{ - "/proc/self/exe", - "/proc/self/file", - "/proc/curproc/exe", - "/proc/curproc/file" - }; - - pathname.resize(256); - - const char *selfname{}; - ssize_t len{}; - for(const char *name : SelfLinkNames) + if(pathname.empty()) { - selfname = name; - len = readlink(selfname, pathname.data(), pathname.size()); - if(len >= 0 || errno != ENOENT) break; - } + const std::array SelfLinkNames{ + "/proc/self/exe"sv, + "/proc/self/file"sv, + "/proc/curproc/exe"sv, + "/proc/curproc/file"sv, + }; - while(len > 0 && static_cast(len) == pathname.size()) - { - pathname.resize(pathname.size() << 1); - len = readlink(selfname, pathname.data(), pathname.size()); + for(const std::string_view name : SelfLinkNames) + { + try { + if(!std::filesystem::exists(name)) + continue; + if(auto path = std::filesystem::read_symlink(name); !path.empty()) + { + pathname = path.u8string(); + break; + } + } + catch(std::exception& e) { + WARN("Exception getting symlink %.*s: %s\n", al::sizei(name), name.data(), + e.what()); + } + } } - if(len <= 0) - { - WARN("Failed to readlink %s: %s\n", selfname, strerror(errno)); - len = 0; - } - - pathname.resize(static_cast(len)); - } #endif - while(!pathname.empty() && pathname.back() == 0) - pathname.pop_back(); - auto sep = std::find(pathname.crbegin(), pathname.crend(), '/'); - if(sep != pathname.crend()) - procbin.emplace(std::string(pathname.cbegin(), sep.base()-1), - std::string(sep.base(), pathname.cend())); - else - procbin.emplace(std::string{}, std::string(pathname.cbegin(), pathname.cend())); + PathNamePair res{}; + if(auto seppos = pathname.rfind('/'); seppos < pathname.size()) + { + res.path = std::string_view{pathname}.substr(0, seppos); + res.fname = std::string_view{pathname}.substr(seppos+1); + } + else + res.fname = pathname; - TRACE("Got binary: \"%s\", \"%s\"\n", procbin->path.c_str(), procbin->fname.c_str()); - return *procbin; + TRACE("Got binary: \"%s\", \"%s\"\n", res.path.c_str(), res.fname.c_str()); + return res; + }; + static const PathNamePair procbin{get_procbin()}; + return procbin; } -namespace { - -void DirectorySearch(const char *path, const char *ext, al::vector *const results) +std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir) { - TRACE("Searching %s for *%s\n", path, ext); - DIR *dir{opendir(path)}; - if(!dir) return; + std::lock_guard srchlock{gSearchLock}; - const auto base = results->size(); - const size_t extlen{strlen(ext)}; - - while(struct dirent *dirent{readdir(dir)}) + std::vector results; + auto path = std::filesystem::u8path(subdir); + if(path.is_absolute()) { - 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 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()); -} - -} // namespace - -al::vector SearchDataFiles(const char *ext, const char *subdir) -{ - static std::mutex search_lock; - std::lock_guard _{search_lock}; - - al::vector results; - if(subdir[0] == '/') - { - DirectorySearch(subdir, ext, &results); + DirectorySearch(path, 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 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(); - } - } + DirectorySearch(*localpath, ext, &results); + else if(auto curpath = std::filesystem::current_path(); !curpath.empty()) + DirectorySearch(curpath, ext, &results); - // Search local data dir + /* 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); - } + DirectorySearch(std::filesystem::path{*datapath}/path, 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); - } + DirectorySearch(std::filesystem::path{*homepath}/".local/share"/path, ext, &results); - // Search global data dirs + /* Search global data dirs */ std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")}; size_t curpos{0u}; @@ -394,30 +327,19 @@ al::vector SearchDataFiles(const char *ext, const char *subdir) { size_t nextpos{datadirs.find(':', curpos)}; - std::string path{(nextpos != std::string::npos) ? - datadirs.substr(curpos, nextpos++ - curpos) : datadirs.substr(curpos)}; + std::string_view pathname{(nextpos != std::string::npos) + ? std::string_view{datadirs}.substr(curpos, nextpos++ - curpos) + : std::string_view{datadirs}.substr(curpos)}; curpos = nextpos; - if(path.empty()) continue; - if(path.back() != '/') - path += '/'; - path += subdir; - - DirectorySearch(path.c_str(), ext, &results); + if(!pathname.empty()) + DirectorySearch(std::filesystem::path{pathname}/path, ext, &results); } #ifdef ALSOFT_INSTALL_DATADIR - // Search the installation data directory - { - std::string path{ALSOFT_INSTALL_DATADIR}; - if(!path.empty()) - { - if(path.back() != '/') - path += '/'; - path += subdir; - DirectorySearch(path.c_str(), ext, &results); - } - } + /* Search the installation data directory */ + if(auto instpath = std::filesystem::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty()) + DirectorySearch(instpath/path, ext, &results); #endif return results; @@ -425,7 +347,7 @@ al::vector SearchDataFiles(const char *ext, const char *subdir) namespace { -bool SetRTPriorityPthread(int prio) +bool SetRTPriorityPthread(int prio [[maybe_unused]]) { int err{ENOTSUP}; #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) @@ -438,23 +360,20 @@ bool SetRTPriorityPthread(int prio) rtmax = (rtmax-rtmin)/2 + rtmin; struct sched_param param{}; - param.sched_priority = clampi(prio, rtmin, rtmax); + param.sched_priority = std::clamp(prio, rtmin, rtmax); #ifdef SCHED_RESET_ON_FORK err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, ¶m); if(err == EINVAL) #endif err = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); if(err == 0) return true; - -#else - - std::ignore = prio; #endif - WARN("pthread_setschedparam failed: %s (%d)\n", std::strerror(err), err); + WARN("pthread_setschedparam failed: %s (%d)\n", std::generic_category().message(err).c_str(), + err); return false; } -bool SetRTPriorityRTKit(int prio) +bool SetRTPriorityRTKit(int prio [[maybe_unused]]) { #ifdef HAVE_RTKIT if(!HasDBus()) @@ -478,7 +397,7 @@ bool SetRTPriorityRTKit(int prio) if(err == -ENOENT) { err = std::abs(err); - ERR("Could not query RTKit: %s (%d)\n", std::strerror(err), err); + ERR("Could not query RTKit: %s (%d)\n", std::generic_category().message(err).c_str(), err); return false; } int rtmax{rtkit_get_max_realtime_priority(conn.get())}; @@ -514,19 +433,20 @@ bool SetRTPriorityRTKit(int prio) err = limit_rttime(conn.get()); if(err != 0) WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n", - std::strerror(err), err); + std::generic_category().message(err).c_str(), err); } /* Limit the maximum real-time priority to half. */ rtmax = (rtmax+1)/2; - prio = clampi(prio, 1, rtmax); + prio = std::clamp(prio, 1, rtmax); TRACE("Making real-time with priority %d (max: %d)\n", prio, rtmax); err = rtkit_make_realtime(conn.get(), 0, prio); if(err == 0) return true; err = std::abs(err); - WARN("Failed to set real-time priority: %s (%d)\n", std::strerror(err), err); + WARN("Failed to set real-time priority: %s (%d)\n", + std::generic_category().message(err).c_str(), err); } /* Don't try to set the niceness for non-Linux systems. Standard POSIX has * niceness as a per-process attribute, while the intent here is for the @@ -541,13 +461,13 @@ bool SetRTPriorityRTKit(int prio) if(err == 0) return true; err = std::abs(err); - WARN("Failed to set high priority: %s (%d)\n", std::strerror(err), err); + WARN("Failed to set high priority: %s (%d)\n", + std::generic_category().message(err).c_str(), err); } #endif /* __linux__ */ #else - std::ignore = prio; WARN("D-Bus not supported\n"); #endif return false; diff --git a/Engine/lib/openal-soft/core/helpers.h b/Engine/lib/openal-soft/core/helpers.h index f0bfcf1b5..3a987c9d1 100644 --- a/Engine/lib/openal-soft/core/helpers.h +++ b/Engine/lib/openal-soft/core/helpers.h @@ -2,17 +2,23 @@ #define CORE_HELPERS_H #include - -#include "vector.h" +#include +#include -struct PathNamePair { std::string path, fname; }; -const PathNamePair &GetProcBinary(void); +struct PathNamePair { + std::string path, fname; +}; +const PathNamePair &GetProcBinary(); -extern int RTPrioLevel; -extern bool AllowRTTimeLimit; -void SetRTPriority(void); +/* Mixing thread priority level */ +inline int RTPrioLevel{1}; -al::vector SearchDataFiles(const char *match, const char *subdir); +/* Allow reducing the process's RTTime limit for RTKit. */ +inline bool AllowRTTimeLimit{true}; + +void SetRTPriority(); + +std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir); #endif /* CORE_HELPERS_H */ diff --git a/Engine/lib/openal-soft/core/hrtf.cpp b/Engine/lib/openal-soft/core/hrtf.cpp index d5c7573a1..e2f0d893d 100644 --- a/Engine/lib/openal-soft/core/hrtf.cpp +++ b/Engine/lib/openal-soft/core/hrtf.cpp @@ -8,25 +8,28 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include #include +#include +#include #include #include +#include #include "albit.h" -#include "albyte.h" -#include "alfstream.h" #include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" +#include "alstring.h" #include "ambidefs.h" #include "filters/splitter.h" #include "helpers.h" @@ -34,15 +37,20 @@ #include "mixer/hrtfdefs.h" #include "opthelpers.h" #include "polyphase_resampler.h" -#include "vector.h" namespace { +using namespace std::string_view_literals; + struct HrtfEntry { std::string mDispName; std::string mFilename; + template + HrtfEntry(T&& dispname, U&& fname) + : mDispName{std::forward(dispname)}, mFilename{std::forward(fname)} + { } /* GCC warns when it tries to inline this. */ ~HrtfEntry(); }; @@ -50,11 +58,12 @@ HrtfEntry::~HrtfEntry() = default; struct LoadedHrtf { std::string mFilename; + uint mSampleRate{}; std::unique_ptr mEntry; template - LoadedHrtf(T&& name, U&& entry) - : mFilename{std::forward(name)}, mEntry{std::forward(entry)} + LoadedHrtf(T&& name, uint srate, U&& entry) + : mFilename{std::forward(name)}, mSampleRate{srate}, mEntry{std::forward(entry)} { } LoadedHrtf(LoadedHrtf&&) = default; /* GCC warns when it tries to inline this. */ @@ -86,24 +95,36 @@ constexpr uint HrirDelayFracBits{2}; constexpr uint HrirDelayFracOne{1 << HrirDelayFracBits}; constexpr uint HrirDelayFracHalf{HrirDelayFracOne >> 1}; +/* The sample rate is stored as a 24-bit integer, so 16MHz is the largest + * supported. + */ +constexpr uint MaxSampleRate{0xff'ff'ff}; + static_assert(MaxHrirDelay*HrirDelayFracOne < 256, "MAX_HRIR_DELAY or DELAY_FRAC too large"); -constexpr char magicMarker00[8]{'M','i','n','P','H','R','0','0'}; -constexpr char magicMarker01[8]{'M','i','n','P','H','R','0','1'}; -constexpr char magicMarker02[8]{'M','i','n','P','H','R','0','2'}; -constexpr char magicMarker03[8]{'M','i','n','P','H','R','0','3'}; + +[[nodiscard]] constexpr auto GetMarker00Name() noexcept { return "MinPHR00"sv; } +[[nodiscard]] constexpr auto GetMarker01Name() noexcept { return "MinPHR01"sv; } +[[nodiscard]] constexpr auto GetMarker02Name() noexcept { return "MinPHR02"sv; } +[[nodiscard]] constexpr auto GetMarker03Name() noexcept { return "MinPHR03"sv; } + /* First value for pass-through coefficients (remaining are 0), used for omni- * directional sounds. */ constexpr auto PassthruCoeff = static_cast(1.0/al::numbers::sqrt2); std::mutex LoadedHrtfLock; -al::vector LoadedHrtfs; +std::vector LoadedHrtfs; std::mutex EnumeratedHrtfLock; -al::vector EnumeratedHrtfs; +std::vector EnumeratedHrtfs; +/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + * To access a memory buffer through the std::istream interface, a custom + * std::streambuf implementation is needed that has to do pointer manipulation + * for seeking. With C++23, we may be able to use std::spanstream instead. + */ class databuf final : public std::streambuf { int_type underflow() override { return traits_type::eof(); } @@ -113,34 +134,32 @@ class databuf final : public std::streambuf { 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: + case std::ios_base::beg: + if(offset < 0 || offset > egptr()-eback()) return traits_type::eof(); + setg(eback(), eback()+offset, egptr()); + break; + + case std::ios_base::cur: + if((offset >= 0 && offset > egptr()-gptr()) || + (offset < 0 && -offset > gptr()-eback())) + return traits_type::eof(); + setg(eback(), gptr()+offset, egptr()); + break; + + case std::ios_base::end: + if(offset > 0 || -offset > egptr()-eback()) + return traits_type::eof(); + setg(eback(), egptr()+offset, egptr()); + break; + + default: + return traits_type::eof(); } - setg(eback(), cur, egptr()); - return cur - eback(); + return gptr() - eback(); } pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override @@ -152,24 +171,23 @@ class databuf final : public std::streambuf { if(pos < 0 || pos > egptr()-eback()) return traits_type::eof(); - setg(eback(), eback() + static_cast(pos), egptr()); + setg(eback(), eback()+static_cast(pos), egptr()); return pos; } public: - databuf(const char_type *start_, const char_type *end_) noexcept + databuf(const al::span data) noexcept { - setg(const_cast(start_), const_cast(start_), - const_cast(end_)); + setg(data.data(), data.data(), al::to_address(data.end())); } }; +/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ class idstream final : public std::istream { databuf mStreamBuf; public: - idstream(const char *start_, const char *end_) - : std::istream{nullptr}, mStreamBuf{start_, end_} + idstream(const al::span data) : std::istream{nullptr}, mStreamBuf{data} { init(&mStreamBuf); } }; @@ -184,7 +202,7 @@ IdxBlend CalcEvIndex(uint evcount, float ev) al::numbers::inv_pi_v; uint idx{float2uint(ev)}; - return IdxBlend{minu(idx, evcount-1), ev-static_cast(idx)}; + return IdxBlend{std::min(idx, evcount-1u), ev-static_cast(idx)}; } /* Calculate the azimuth index given the polar azimuth in radians. This will @@ -206,7 +224,7 @@ IdxBlend CalcAzIndex(uint azcount, float az) * and azimuth in radians. The coefficients are normalized. */ void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float spread, - HrirArray &coeffs, const al::span delays) + const HrirSpan coeffs, const al::span delays) const { const float dirfact{1.0f - (al::numbers::inv_pi_v/2.0f * spread)}; @@ -222,7 +240,7 @@ void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float /* Calculate the elevation indices. */ const auto elev0 = CalcEvIndex(field->evCount, elevation); - const size_t elev1_idx{minu(elev0.idx+1, field->evCount-1)}; + const size_t elev1_idx{std::min(elev0.idx+1u, field->evCount-1u)}; const size_t ir0offset{mElev[ebase + elev0.idx].irOffset}; const size_t ir1offset{mElev[ebase + elev1_idx].irOffset}; @@ -231,43 +249,43 @@ void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float const auto az1 = CalcAzIndex(mElev[ebase + elev1_idx].azCount, azimuth); /* Calculate the HRIR indices to blend. */ - const size_t idx[4]{ + const std::array idx{{ ir0offset + az0.idx, ir0offset + ((az0.idx+1) % mElev[ebase + elev0.idx].azCount), ir1offset + az1.idx, ir1offset + ((az1.idx+1) % mElev[ebase + elev1_idx].azCount) - }; + }}; /* Calculate bilinear blending weights, attenuated according to the * directional panning factor. */ - const float blend[4]{ + const std::array blend{{ (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. */ - float d{mDelays[idx[0]][0]*blend[0] + mDelays[idx[1]][0]*blend[1] + mDelays[idx[2]][0]*blend[2] - + mDelays[idx[3]][0]*blend[3]}; + float d{float(mDelays[idx[0]][0])*blend[0] + float(mDelays[idx[1]][0])*blend[1] + + float(mDelays[idx[2]][0])*blend[2] + float(mDelays[idx[3]][0])*blend[3]}; delays[0] = fastf2u(d * float{1.0f/HrirDelayFracOne}); - d = mDelays[idx[0]][1]*blend[0] + mDelays[idx[1]][1]*blend[1] + mDelays[idx[2]][1]*blend[2] - + mDelays[idx[3]][1]*blend[3]; + d = float(mDelays[idx[0]][1])*blend[0] + float(mDelays[idx[1]][1])*blend[1] + + float(mDelays[idx[2]][1])*blend[2] + float(mDelays[idx[3]][1])*blend[3]; delays[1] = fastf2u(d * float{1.0f/HrirDelayFracOne}); /* Calculate the blended HRIR coefficients. */ - float *coeffout{al::assume_aligned<16>(coeffs[0].data())}; - coeffout[0] = PassthruCoeff * (1.0f-dirfact); - coeffout[1] = PassthruCoeff * (1.0f-dirfact); - std::fill_n(coeffout+2, size_t{HrirLength-1}*2, 0.0f); + auto coeffout = coeffs.begin(); + coeffout[0][0] = PassthruCoeff * (1.0f-dirfact); + coeffout[0][1] = PassthruCoeff * (1.0f-dirfact); + std::fill_n(coeffout+1, size_t{HrirLength-1}, std::array{0.0f, 0.0f}); for(size_t c{0};c < 4;c++) { - const float *srccoeffs{al::assume_aligned<16>(mCoeffs[idx[c]][0].data())}; const float mult{blend[c]}; - auto blend_coeffs = [mult](const float src, const float coeff) noexcept -> float - { return src*mult + coeff; }; - std::transform(srccoeffs, srccoeffs + HrirLength*2, coeffout, coeffout, blend_coeffs); + auto blend_coeffs = [mult](const float2 &src, const float2 &coeff) noexcept -> float2 + { return float2{{src[0]*mult + coeff[0], src[1]*mult + coeff[1]}}; }; + std::transform(mCoeffs[idx[c]].cbegin(), mCoeffs[idx[c]].cend(), coeffout, coeffout, + blend_coeffs); } } @@ -276,7 +294,8 @@ std::unique_ptr DirectHrtfState::Create(size_t num_chans) { return std::unique_ptr{new(FamCount(num_chans)) DirectHrtfState{num_chans}}; } void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin, - const al::span AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels], + const al::span AmbiPoints, + const al::span> AmbiMatrix, const float XOverFreq, const al::span AmbiOrderHFGain) { using double2 = std::array; @@ -287,27 +306,28 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool const double xover_norm{double{XOverFreq} / Hrtf->mSampleRate}; mChannels[0].mSplitter.init(static_cast(xover_norm)); - for(size_t i{0};i < mChannels.size();++i) + mChannels[0].mHfScale = AmbiOrderHFGain[0]; + for(size_t i{1};i < mChannels.size();++i) { - const size_t order{AmbiIndex::OrderFromChannel()[i]}; + const size_t order{AmbiIndex::OrderFromChannel[i]}; mChannels[i].mSplitter = mChannels[0].mSplitter; mChannels[i].mHfScale = AmbiOrderHFGain[order]; } uint min_delay{HrtfHistoryLength*HrirDelayFracOne}, max_delay{0}; - al::vector impres; impres.reserve(AmbiPoints.size()); + std::vector impres; impres.reserve(AmbiPoints.size()); auto calc_res = [Hrtf,&max_delay,&min_delay](const AngularPoint &pt) -> ImpulseResponse { auto &field = Hrtf->mFields[0]; const auto elev0 = CalcEvIndex(field.evCount, pt.Elev.value); - const size_t elev1_idx{minu(elev0.idx+1, field.evCount-1)}; + const size_t elev1_idx{std::min(elev0.idx+1u, field.evCount-1u)}; const size_t ir0offset{Hrtf->mElev[elev0.idx].irOffset}; const size_t ir1offset{Hrtf->mElev[elev1_idx].irOffset}; const auto az0 = CalcAzIndex(Hrtf->mElev[elev0.idx].azCount, pt.Azim.value); const auto az1 = CalcAzIndex(Hrtf->mElev[elev1_idx].azCount, pt.Azim.value); - const size_t idx[4]{ + const std::array idx{ ir0offset + az0.idx, ir0offset + ((az0.idx+1) % Hrtf->mElev[elev0.idx].azCount), ir1offset + az1.idx, @@ -319,8 +339,8 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool ImpulseResponse res{Hrtf->mCoeffs[irOffset], Hrtf->mDelays[irOffset][0], Hrtf->mDelays[irOffset][1]}; - min_delay = minu(min_delay, minu(res.ldelay, res.rdelay)); - max_delay = maxu(max_delay, maxu(res.ldelay, res.rdelay)); + min_delay = std::min(min_delay, std::min(res.ldelay, res.rdelay)); + max_delay = std::max(max_delay, std::max(res.ldelay, res.rdelay)); return res; }; @@ -331,40 +351,44 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool TRACE("Min delay: %.2f, max delay: %.2f, FIR length: %u\n", min_delay/double{HrirDelayFracOne}, max_delay/double{HrirDelayFracOne}, irSize); - auto tmpres = al::vector>(mChannels.size()); + auto tmpres = std::vector>(mChannels.size()); max_delay = 0; - for(size_t c{0u};c < AmbiPoints.size();++c) + auto matrixline = AmbiMatrix.cbegin(); + for(auto &impulse : impres) { - const ConstHrirSpan hrir{impres[c].hrir}; - const uint base_delay{perHrirMin ? minu(impres[c].ldelay, impres[c].rdelay) : min_delay}; - const uint ldelay{hrir_delay_round(impres[c].ldelay - base_delay)}; - const uint rdelay{hrir_delay_round(impres[c].rdelay - base_delay)}; - max_delay = maxu(max_delay, maxu(impres[c].ldelay, impres[c].rdelay) - base_delay); + const ConstHrirSpan hrir{impulse.hrir}; + const uint base_delay{perHrirMin ? std::min(impulse.ldelay, impulse.rdelay) : min_delay}; + const uint ldelay{hrir_delay_round(impulse.ldelay - base_delay)}; + const uint rdelay{hrir_delay_round(impulse.rdelay - base_delay)}; + max_delay = std::max(max_delay, std::max(impulse.ldelay, impulse.rdelay) - base_delay); - for(size_t i{0u};i < mChannels.size();++i) + auto gains = matrixline->cbegin(); + ++matrixline; + for(auto &result : tmpres) { - const double mult{AmbiMatrix[c][i]}; - const size_t numirs{HrirLength - maxz(ldelay, rdelay)}; + const double mult{*(gains++)}; + const size_t numirs{HrirLength - std::max(ldelay, rdelay)}; size_t lidx{ldelay}, ridx{rdelay}; for(size_t j{0};j < numirs;++j) { - tmpres[i][lidx++][0] += hrir[j][0] * mult; - tmpres[i][ridx++][1] += hrir[j][1] * mult; + result[lidx++][0] += hrir[j][0] * mult; + result[ridx++][1] += hrir[j][1] * mult; } } } impres.clear(); - for(size_t i{0u};i < mChannels.size();++i) + auto output = mChannels.begin(); + for(auto &result : tmpres) { - auto copy_arr = [](const double2 &in) noexcept -> float2 + auto cast_array2 = [](const double2 &in) noexcept -> float2 { return float2{{static_cast(in[0]), static_cast(in[1])}}; }; - std::transform(tmpres[i].cbegin(), tmpres[i].cend(), mChannels[i].mCoeffs.begin(), - copy_arr); + std::transform(result.cbegin(), result.cend(), output->mCoeffs.begin(), cast_array2); + ++output; } tmpres.clear(); - const uint max_length{minu(hrir_delay_round(max_delay) + irSize, HrirLength)}; + const uint max_length{std::min(hrir_delay_round(max_delay) + irSize, HrirLength)}; TRACE("New max delay: %.2f, FIR length: %u\n", max_delay/double{HrirDelayFracOne}, max_length); mIrSize = max_length; @@ -376,8 +400,15 @@ namespace { std::unique_ptr CreateHrtfStore(uint rate, uint8_t irSize, const al::span fields, const al::span elevs, const HrirArray *coeffs, - const ubyte2 *delays, const char *filename) + const ubyte2 *delays) { + static_assert(alignof(HrtfStore::Field) <= alignof(HrtfStore)); + static_assert(alignof(HrtfStore::Elevation) <= alignof(HrtfStore)); + static_assert(16 <= alignof(HrtfStore)); + + if(rate > MaxSampleRate) + throw std::runtime_error{"Sample rate is too large (max: "+std::to_string(MaxSampleRate)+"hz)"}; + const size_t irCount{size_t{elevs.back().azCount} + elevs.back().irOffset}; size_t total{sizeof(HrtfStore)}; total = RoundUp(total, alignof(HrtfStore::Field)); /* Align for field infos */ @@ -388,56 +419,54 @@ std::unique_ptr CreateHrtfStore(uint rate, uint8_t irSize, total += sizeof(std::declval().mCoeffs[0])*irCount; total += sizeof(std::declval().mDelays[0])*irCount; - std::unique_ptr Hrtf{}; - if(void *ptr{al_calloc(16, total)}) - { - Hrtf.reset(al::construct_at(static_cast(ptr))); - InitRef(Hrtf->mRef, 1u); - Hrtf->mSampleRate = rate; - Hrtf->mIrSize = irSize; + static constexpr auto AlignVal = std::align_val_t{alignof(HrtfStore)}; + std::unique_ptr Hrtf{::new(::operator new[](total, AlignVal)) HrtfStore{}}; + Hrtf->mRef.store(1u, std::memory_order_relaxed); + Hrtf->mSampleRate = rate & 0xff'ff'ff; + Hrtf->mIrSize = irSize; - /* Set up pointers to storage following the main HRTF struct. */ - char *base = reinterpret_cast(Hrtf.get()); - size_t offset{sizeof(HrtfStore)}; + /* Set up pointers to storage following the main HRTF struct. */ + auto storage = al::span{reinterpret_cast(Hrtf.get()), total}; + auto base = storage.begin(); + ptrdiff_t offset{sizeof(HrtfStore)}; - offset = RoundUp(offset, alignof(HrtfStore::Field)); /* Align for field infos */ - auto field_ = reinterpret_cast(base + offset); - offset += sizeof(field_[0])*fields.size(); + offset = RoundUp(offset, alignof(HrtfStore::Field)); /* Align for field infos */ + auto field_ = al::span{reinterpret_cast(al::to_address(base + offset)), + fields.size()}; + offset += ptrdiff_t(sizeof(field_[0])*fields.size()); - offset = RoundUp(offset, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ - auto elev_ = reinterpret_cast(base + offset); - offset += sizeof(elev_[0])*elevs.size(); + offset = RoundUp(offset, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ + auto elev_ = al::span{reinterpret_cast(al::to_address(base + offset)), + elevs.size()}; + offset += ptrdiff_t(sizeof(elev_[0])*elevs.size()); - offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */ - auto coeffs_ = reinterpret_cast(base + offset); - offset += sizeof(coeffs_[0])*irCount; + offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */ + auto coeffs_ = al::span{reinterpret_cast(al::to_address(base + offset)), irCount}; + offset += ptrdiff_t(sizeof(coeffs_[0])*irCount); - auto delays_ = reinterpret_cast(base + offset); - offset += sizeof(delays_[0])*irCount; + auto delays_ = al::span{reinterpret_cast(al::to_address(base + offset)), irCount}; + offset += ptrdiff_t(sizeof(delays_[0])*irCount); - if(offset != total) - throw std::runtime_error{"HrtfStore allocation size mismatch"}; + if(size_t(offset) != total) + throw std::runtime_error{"HrtfStore allocation size mismatch"}; - /* Copy input data to storage. */ - std::uninitialized_copy(fields.cbegin(), fields.cend(), field_); - std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_); - std::uninitialized_copy_n(coeffs, irCount, coeffs_); - std::uninitialized_copy_n(delays, irCount, delays_); + /* Copy input data to storage. */ + std::uninitialized_copy(fields.cbegin(), fields.cend(), field_.begin()); + std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_.begin()); + std::uninitialized_copy_n(coeffs, irCount, coeffs_.begin()); + std::uninitialized_copy_n(delays, irCount, delays_.begin()); - /* Finally, assign the storage pointers. */ - Hrtf->mFields = al::as_span(field_, fields.size()); - Hrtf->mElev = elev_; - Hrtf->mCoeffs = coeffs_; - Hrtf->mDelays = delays_; - } - else - ERR("Out of memory allocating storage for %s.\n", filename); + /* Finally, assign the storage pointers. */ + Hrtf->mFields = field_; + Hrtf->mElev = elev_; + Hrtf->mCoeffs = coeffs_; + Hrtf->mDelays = delays_; return Hrtf; } -void MirrorLeftHrirs(const al::span elevs, HrirArray *coeffs, - ubyte2 *delays) +void MirrorLeftHrirs(const al::span elevs, al::span coeffs, + al::span delays) { for(const auto &elev : elevs) { @@ -477,11 +506,11 @@ T> readle(std::istream &data) static_assert((num_bits&7) == 0, "num_bits must be a multiple of 8"); static_assert(num_bits <= sizeof(T)*8, "num_bits is too large for the type"); - T ret{}; - if(!data.read(reinterpret_cast(&ret), num_bits/8)) + alignas(T) std::array ret{}; + if(!data.read(ret.data(), num_bits/8)) return static_cast(EOF); - return fixsign(ret); + return fixsign(al::bit_cast(ret)); } template @@ -491,13 +520,12 @@ T> readle(std::istream &data) static_assert((num_bits&7) == 0, "num_bits must be a multiple of 8"); static_assert(num_bits <= sizeof(T)*8, "num_bits is too large for the type"); - T ret{}; - al::byte b[sizeof(T)]{}; - if(!data.read(reinterpret_cast(b), num_bits/8)) + alignas(T) std::array ret{}; + if(!data.read(ret.data(), num_bits/8)) return static_cast(EOF); - std::reverse_copy(std::begin(b), std::end(b), reinterpret_cast(&ret)); + std::reverse(ret.begin(), ret.end()); - return fixsign(ret); + return fixsign(al::bit_cast(ret)); } template<> @@ -505,17 +533,14 @@ inline uint8_t readle(std::istream &data) { return static_cast(data.get()); } -std::unique_ptr LoadHrtf00(std::istream &data, const char *filename) +std::unique_ptr LoadHrtf00(std::istream &data) { uint rate{readle(data)}; ushort irCount{readle(data)}; ushort irSize{readle(data)}; ubyte evCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(irSize < MinIrLength || irSize > HrirLength) { @@ -529,14 +554,12 @@ std::unique_ptr LoadHrtf00(std::istream &data, const char *filename) return nullptr; } - auto elevs = al::vector(evCount); + auto elevs = std::vector(evCount); for(auto &elev : elevs) elev.irOffset = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{1};i < evCount;i++) { if(elevs[i].irOffset <= elevs[i-1].irOffset) @@ -571,20 +594,18 @@ std::unique_ptr LoadHrtf00(std::istream &data, const char *filename) return nullptr; } - auto coeffs = al::vector(irCount, HrirArray{}); - auto delays = al::vector(irCount); + auto coeffs = std::vector(irCount, HrirArray{}); + auto delays = std::vector(irCount); for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) - val[0] = readle(data) / 32768.0f; + for(auto &val : al::span{hrir}.first(irSize)) + val[0] = float(readle(data)) / 32768.0f; } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{0};i < irCount;i++) { if(delays[i][0] > MaxHrirDelay) @@ -596,23 +617,20 @@ std::unique_ptr LoadHrtf00(std::istream &data, const char *filename) } /* Mirror the left ear responses to the right ear. */ - MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + MirrorLeftHrirs(elevs, coeffs, delays); - const HrtfStore::Field field[1]{{0.0f, evCount}}; - return CreateHrtfStore(rate, static_cast(irSize), field, {elevs.data(), elevs.size()}, - coeffs.data(), delays.data(), filename); + const std::array field{HrtfStore::Field{0.0f, evCount}}; + return CreateHrtfStore(rate, static_cast(irSize), field, elevs, coeffs.data(), + delays.data()); } -std::unique_ptr LoadHrtf01(std::istream &data, const char *filename) +std::unique_ptr LoadHrtf01(std::istream &data) { uint rate{readle(data)}; uint8_t irSize{readle(data)}; ubyte evCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(irSize < MinIrLength || irSize > HrirLength) { @@ -626,14 +644,12 @@ std::unique_ptr LoadHrtf01(std::istream &data, const char *filename) return nullptr; } - auto elevs = al::vector(evCount); + auto elevs = std::vector(evCount); for(auto &elev : elevs) elev.azCount = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{0};i < evCount;++i) { if(elevs[i].azCount < MinAzCount || elevs[i].azCount > MaxAzCount) @@ -649,20 +665,18 @@ std::unique_ptr LoadHrtf01(std::istream &data, const char *filename) elevs[i].irOffset = static_cast(elevs[i-1].irOffset + elevs[i-1].azCount); const ushort irCount{static_cast(elevs.back().irOffset + elevs.back().azCount)}; - auto coeffs = al::vector(irCount, HrirArray{}); - auto delays = al::vector(irCount); + auto coeffs = std::vector(irCount, HrirArray{}); + auto delays = std::vector(irCount); for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) - val[0] = readle(data) / 32768.0f; + for(auto &val : al::span{hrir}.first(irSize)) + val[0] = float(readle(data)) / 32768.0f; } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{0};i < irCount;i++) { if(delays[i][0] > MaxHrirDelay) @@ -674,19 +688,18 @@ std::unique_ptr LoadHrtf01(std::istream &data, const char *filename) } /* Mirror the left ear responses to the right ear. */ - MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + MirrorLeftHrirs(elevs, coeffs, delays); - const HrtfStore::Field field[1]{{0.0f, evCount}}; - return CreateHrtfStore(rate, irSize, field, {elevs.data(), elevs.size()}, coeffs.data(), - delays.data(), filename); + const std::array field{HrtfStore::Field{0.0f, evCount}}; + return CreateHrtfStore(rate, irSize, field, elevs, coeffs.data(), delays.data()); } -std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) +std::unique_ptr LoadHrtf02(std::istream &data) { - constexpr ubyte SampleType_S16{0}; - constexpr ubyte SampleType_S24{1}; - constexpr ubyte ChanType_LeftOnly{0}; - constexpr ubyte ChanType_LeftRight{1}; + static constexpr ubyte SampleType_S16{0}; + static constexpr ubyte SampleType_S24{1}; + static constexpr ubyte ChanType_LeftOnly{0}; + static constexpr ubyte ChanType_LeftRight{1}; uint rate{readle(data)}; ubyte sampleType{readle(data)}; @@ -694,10 +707,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) uint8_t irSize{readle(data)}; ubyte fdCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(sampleType > SampleType_S24) { @@ -722,17 +732,14 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) return nullptr; } - auto fields = al::vector(fdCount); - auto elevs = al::vector{}; + auto fields = std::vector(fdCount); + auto elevs = std::vector{}; for(size_t f{0};f < fdCount;f++) { const ushort distance{readle(data)}; const ubyte evCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(distance < MinFdDistance || distance > MaxFdDistance) { @@ -747,7 +754,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) return nullptr; } - fields[f].distance = distance / 1000.0f; + fields[f].distance = float(distance) / 1000.0f; fields[f].evCount = evCount; if(f > 0 && fields[f].distance <= fields[f-1].distance) { @@ -758,13 +765,10 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) const size_t ebase{elevs.size()}; elevs.resize(ebase + evCount); - for(auto &elev : al::span(elevs.data()+ebase, evCount)) + for(auto &elev : al::span{elevs}.subspan(ebase, evCount)) elev.azCount = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; for(size_t e{0};e < evCount;e++) { @@ -787,33 +791,31 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) }); const auto irTotal = static_cast(elevs.back().azCount + elevs.back().irOffset); - auto coeffs = al::vector(irTotal, HrirArray{}); - auto delays = al::vector(irTotal); + auto coeffs = std::vector(irTotal, HrirArray{}); + auto delays = std::vector(irTotal); if(channelType == ChanType_LeftOnly) { if(sampleType == SampleType_S16) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) - val[0] = readle(data) / 32768.0f; + for(auto &val : al::span{hrir}.first(irSize)) + val[0] = float(readle(data)) / 32768.0f; } } else if(sampleType == SampleType_S24) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) val[0] = static_cast(readle(data)) / 8388608.0f; } } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{0};i < irTotal;++i) { if(delays[i][0] > MaxHrirDelay) @@ -825,7 +827,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) } /* Mirror the left ear responses to the right ear. */ - MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + MirrorLeftHrirs(elevs, coeffs, delays); } else if(channelType == ChanType_LeftRight) { @@ -833,10 +835,10 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) { - val[0] = readle(data) / 32768.0f; - val[1] = readle(data) / 32768.0f; + val[0] = float(readle(data)) / 32768.0f; + val[1] = float(readle(data)) / 32768.0f; } } } @@ -844,7 +846,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) { val[0] = static_cast(readle(data)) / 8388608.0f; val[1] = static_cast(readle(data)) / 8388608.0f; @@ -857,10 +859,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) val[1] = readle(data); } if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; for(size_t i{0};i < irTotal;++i) { @@ -881,10 +880,10 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) if(fdCount > 1) { - auto fields_ = al::vector(fields.size()); - auto elevs_ = al::vector(elevs.size()); - auto coeffs_ = al::vector(coeffs.size()); - auto delays_ = al::vector(delays.size()); + auto fields_ = std::vector(fields.size()); + auto elevs_ = std::vector(elevs.size()); + auto coeffs_ = std::vector(coeffs.size()); + auto delays_ = std::vector(delays.size()); /* Simple reverse for the per-field elements. */ std::reverse_copy(fields.cbegin(), fields.cend(), fields_.begin()); @@ -893,16 +892,16 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) * count. Reverse the order of the groups, keeping the relative order * of per-group azimuth counts. */ - auto elevs__end = elevs_.end(); - auto copy_azs = [&elevs,&elevs__end](const ptrdiff_t ebase, const HrtfStore::Field &field) + auto elevs_end = elevs_.end(); + auto copy_azs = [&elevs,&elevs_end](const ptrdiff_t ebase, const HrtfStore::Field &field) -> ptrdiff_t { auto elevs_src = elevs.begin()+ebase; - elevs__end = std::copy_backward(elevs_src, elevs_src+field.evCount, elevs__end); + elevs_end = std::copy_backward(elevs_src, elevs_src+field.evCount, elevs_end); return ebase + field.evCount; }; - (void)std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_azs); - assert(elevs_.begin() == elevs__end); + std::ignore = std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_azs); + assert(elevs_.begin() == elevs_end); /* Reestablish the IR offset for each elevation index, given the new * ordering of elevations. @@ -922,12 +921,13 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) auto copy_irs = [&elevs,&coeffs,&delays,&coeffs_end,&delays_end]( const ptrdiff_t ebase, const HrtfStore::Field &field) -> ptrdiff_t { - auto accum_az = [](int count, const HrtfStore::Elevation &elev) noexcept -> int + auto accum_az = [](const ptrdiff_t count, const HrtfStore::Elevation &elev) noexcept + -> ptrdiff_t { return count + elev.azCount; }; - const auto elevs_mid = elevs.cbegin() + ebase; - const auto elevs_end = elevs_mid + field.evCount; - const int abase{std::accumulate(elevs.cbegin(), elevs_mid, 0, accum_az)}; - const int num_azs{std::accumulate(elevs_mid, elevs_end, 0, accum_az)}; + const auto elev_mid = elevs.cbegin() + ebase; + const auto abase = std::accumulate(elevs.cbegin(), elev_mid, ptrdiff_t{0}, accum_az); + const auto num_azs = std::accumulate(elev_mid, elev_mid + field.evCount, ptrdiff_t{0}, + accum_az); coeffs_end = std::copy_backward(coeffs.cbegin() + abase, coeffs.cbegin() + (abase+num_azs), coeffs_end); @@ -936,7 +936,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) return ebase + field.evCount; }; - (void)std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_irs); + std::ignore = std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_irs); assert(coeffs_.begin() == coeffs_end); assert(delays_.begin() == delays_end); @@ -946,24 +946,20 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) delays = std::move(delays_); } - return CreateHrtfStore(rate, irSize, {fields.data(), fields.size()}, - {elevs.data(), elevs.size()}, coeffs.data(), delays.data(), filename); + return CreateHrtfStore(rate, irSize, fields, elevs, coeffs.data(), delays.data()); } -std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) +std::unique_ptr LoadHrtf03(std::istream &data) { - constexpr ubyte ChanType_LeftOnly{0}; - constexpr ubyte ChanType_LeftRight{1}; + static constexpr ubyte ChanType_LeftOnly{0}; + static constexpr ubyte ChanType_LeftRight{1}; uint rate{readle(data)}; ubyte channelType{readle(data)}; uint8_t irSize{readle(data)}; ubyte fdCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(channelType > ChanType_LeftRight) { @@ -983,17 +979,14 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) return nullptr; } - auto fields = al::vector(fdCount); - auto elevs = al::vector{}; + auto fields = std::vector(fdCount); + auto elevs = std::vector{}; for(size_t f{0};f < fdCount;f++) { const ushort distance{readle(data)}; const ubyte evCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(distance < MinFdDistance || distance > MaxFdDistance) { @@ -1008,7 +1001,7 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) return nullptr; } - fields[f].distance = distance / 1000.0f; + fields[f].distance = float(distance) / 1000.0f; fields[f].evCount = evCount; if(f > 0 && fields[f].distance > fields[f-1].distance) { @@ -1019,13 +1012,10 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) const size_t ebase{elevs.size()}; elevs.resize(ebase + evCount); - for(auto &elev : al::span(elevs.data()+ebase, evCount)) + for(auto &elev : al::span{elevs}.subspan(ebase, evCount)) elev.azCount = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; for(size_t e{0};e < evCount;e++) { @@ -1048,22 +1038,20 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) }); const auto irTotal = static_cast(elevs.back().azCount + elevs.back().irOffset); - auto coeffs = al::vector(irTotal, HrirArray{}); - auto delays = al::vector(irTotal); + auto coeffs = std::vector(irTotal, HrirArray{}); + auto delays = std::vector(irTotal); if(channelType == ChanType_LeftOnly) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) val[0] = static_cast(readle(data)) / 8388608.0f; } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{0};i < irTotal;++i) { if(delays[i][0] > MaxHrirDelay< LoadHrtf03(std::istream &data, const char *filename) } /* Mirror the left ear responses to the right ear. */ - MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + MirrorLeftHrirs(elevs, coeffs, delays); } else if(channelType == ChanType_LeftRight) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) { val[0] = static_cast(readle(data)) / 8388608.0f; val[1] = static_cast(readle(data)) / 8388608.0f; @@ -1093,10 +1081,7 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) val[1] = readle(data); } if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; for(size_t i{0};i < irTotal;++i) { @@ -1115,39 +1100,38 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) } } - return CreateHrtfStore(rate, irSize, {fields.data(), fields.size()}, - {elevs.data(), elevs.size()}, coeffs.data(), delays.data(), filename); + return CreateHrtfStore(rate, irSize, fields, elevs, coeffs.data(), delays.data()); } -bool checkName(const std::string &name) +bool checkName(const std::string_view name) { - auto match_name = [&name](const HrtfEntry &entry) -> bool { return name == entry.mDispName; }; + auto match_name = [name](const HrtfEntry &entry) -> bool { return name == entry.mDispName; }; auto &enum_names = EnumeratedHrtfs; return std::find_if(enum_names.cbegin(), enum_names.cend(), match_name) != enum_names.cend(); } -void AddFileEntry(const std::string &filename) +void AddFileEntry(const std::string_view filename) { /* Check if this file has already been enumerated. */ auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), - [&filename](const HrtfEntry &entry) -> bool + [filename](const HrtfEntry &entry) -> bool { return entry.mFilename == filename; }); if(enum_iter != EnumeratedHrtfs.cend()) { - TRACE("Skipping duplicate file entry %s\n", filename.c_str()); + TRACE("Skipping duplicate file entry %.*s\n", al::sizei(filename), filename.data()); return; } /* 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 namepos{filename.rfind('/')+1}; + if(!namepos) namepos = filename.rfind('\\')+1; - size_t extpos{filename.find_last_of('.')}; + size_t extpos{filename.rfind('.')}; if(extpos <= namepos) extpos = std::string::npos; - const std::string basename{(extpos == std::string::npos) ? + const std::string_view basename{(extpos == std::string::npos) ? filename.substr(namepos) : filename.substr(namepos, extpos-namepos)}; std::string newname{basename}; int count{1}; @@ -1157,8 +1141,7 @@ void AddFileEntry(const std::string &filename) newname += " #"; newname += std::to_string(++count); } - EnumeratedHrtfs.emplace_back(HrtfEntry{newname, filename}); - const HrtfEntry &entry = EnumeratedHrtfs.back(); + const HrtfEntry &entry = EnumeratedHrtfs.emplace_back(newname, filename); TRACE("Adding file entry \"%s\"\n", entry.mFilename.c_str()); } @@ -1166,9 +1149,10 @@ void AddFileEntry(const std::string &filename) /* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer * for input instead of opening the given filename. */ -void AddBuiltInEntry(const std::string &dispname, uint residx) +void AddBuiltInEntry(const std::string_view dispname, uint residx) { - const std::string filename{'!'+std::to_string(residx)+'_'+dispname}; + std::string filename{'!'+std::to_string(residx)+'_'}; + filename += dispname; auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), [&filename](const HrtfEntry &entry) -> bool @@ -1190,8 +1174,7 @@ void AddBuiltInEntry(const std::string &dispname, uint residx) newname += " #"; newname += std::to_string(++count); } - EnumeratedHrtfs.emplace_back(HrtfEntry{newname, filename}); - const HrtfEntry &entry = EnumeratedHrtfs.back(); + const HrtfEntry &entry = EnumeratedHrtfs.emplace_back(std::move(newname), std::move(filename)); TRACE("Adding built-in entry \"%s\"\n", entry.mFilename.c_str()); } @@ -1206,6 +1189,7 @@ al::span GetResource(int /*name*/) #else +/* NOLINTNEXTLINE(*-avoid-c-arrays) */ constexpr unsigned char hrtf_default[]{ #include "default_hrtf.txt" }; @@ -1221,56 +1205,52 @@ al::span GetResource(int name) } // namespace -al::vector EnumerateHrtf(al::optional pathopt) +std::vector EnumerateHrtf(std::optional pathopt) { - std::lock_guard _{EnumeratedHrtfLock}; + std::lock_guard enumlock{EnumeratedHrtfLock}; EnumeratedHrtfs.clear(); bool usedefaults{true}; if(pathopt) { - const char *pathlist{pathopt->c_str()}; - while(pathlist && *pathlist) + std::string_view pathlist{*pathopt}; + while(!pathlist.empty()) { - const char *next, *end; + while(!pathlist.empty() && (std::isspace(pathlist.front()) || pathlist.front() == ',')) + pathlist.remove_prefix(1); + if(pathlist.empty()) + break; - while(isspace(*pathlist) || *pathlist == ',') - pathlist++; - if(*pathlist == '\0') - continue; - - next = strchr(pathlist, ','); - if(next) - end = next++; + auto endpos = std::min(pathlist.find(','), pathlist.size()); + auto entry = pathlist.substr(0, endpos); + if(endpos < pathlist.size()) + pathlist.remove_prefix(++endpos); else { - end = pathlist + strlen(pathlist); + pathlist.remove_prefix(endpos); usedefaults = false; } - while(end != pathlist && isspace(*(end-1))) - --end; - if(end != pathlist) + while(!entry.empty() && std::isspace(entry.back())) + entry.remove_suffix(1); + if(!entry.empty()) { - const std::string pname{pathlist, end}; - for(const auto &fname : SearchDataFiles(".mhr", pname.c_str())) + for(const auto &fname : SearchDataFiles(".mhr"sv, entry)) AddFileEntry(fname); } - - pathlist = next; } } if(usedefaults) { - for(const auto &fname : SearchDataFiles(".mhr", "openal/hrtf")) + for(const auto &fname : SearchDataFiles(".mhr"sv, "openal/hrtf"sv)) AddFileEntry(fname); if(!GetResource(IDR_DEFAULT_HRTF_MHR).empty()) AddBuiltInEntry("Built-In HRTF", IDR_DEFAULT_HRTF_MHR); } - al::vector list; + std::vector list; list.reserve(EnumeratedHrtfs.size()); for(auto &entry : EnumeratedHrtfs) list.emplace_back(entry.mDispName); @@ -1278,28 +1258,35 @@ al::vector EnumerateHrtf(al::optional pathopt) return list; } -HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) -{ - std::lock_guard _{EnumeratedHrtfLock}; +HrtfStorePtr GetLoadedHrtf(const std::string_view name, const uint devrate) +try { + if(devrate > MaxSampleRate) + { + WARN("Device sample rate too large for HRTF (%uhz > %uhz)\n", devrate, MaxSampleRate); + return nullptr; + } + std::lock_guard enumlock{EnumeratedHrtfLock}; auto entry_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), - [&name](const HrtfEntry &entry) -> bool { return entry.mDispName == name; }); + [name](const HrtfEntry &entry) -> bool { return entry.mDispName == name; }); if(entry_iter == EnumeratedHrtfs.cend()) return nullptr; const std::string &fname = entry_iter->mFilename; - std::lock_guard __{LoadedHrtfLock}; - auto hrtf_lt_fname = [](LoadedHrtf &hrtf, const std::string &filename) -> bool - { return hrtf.mFilename < filename; }; - auto handle = std::lower_bound(LoadedHrtfs.begin(), LoadedHrtfs.end(), fname, hrtf_lt_fname); - while(handle != LoadedHrtfs.end() && handle->mFilename == fname) + std::lock_guard loadlock{LoadedHrtfLock}; + auto hrtf_lt_fname = [devrate](LoadedHrtf &hrtf, const std::string_view filename) -> bool { - HrtfStore *hrtf{handle->mEntry.get()}; - if(hrtf && hrtf->mSampleRate == devrate) + return hrtf.mSampleRate < devrate + || (hrtf.mSampleRate == devrate && hrtf.mFilename < filename); + }; + auto handle = std::lower_bound(LoadedHrtfs.begin(), LoadedHrtfs.end(), fname, hrtf_lt_fname); + if(handle != LoadedHrtfs.end() && handle->mSampleRate == devrate && handle->mFilename == fname) + { + if(HrtfStore *hrtf{handle->mEntry.get()}) { + assert(hrtf->mSampleRate == devrate); hrtf->add_ref(); return HrtfStorePtr{hrtf}; } - ++handle; } std::unique_ptr stream; @@ -1311,15 +1298,17 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) al::span res{GetResource(residx)}; if(res.empty()) { - ERR("Could not get resource %u, %s\n", residx, name.c_str()); + ERR("Could not get resource %u, %.*s\n", residx, al::sizei(name), name.data()); return nullptr; } - stream = std::make_unique(res.begin(), res.end()); + /* NOLINTNEXTLINE(*-const-cast) */ + stream = std::make_unique(al::span{const_cast(res.data()), res.size()}); } else { TRACE("Loading %s...\n", fname.c_str()); - auto fstr = std::make_unique(fname.c_str(), std::ios::binary); + auto fstr = std::make_unique(std::filesystem::u8path(fname), + std::ios::binary); if(!fstr->is_open()) { ERR("Could not open %s\n", fname.c_str()); @@ -1329,63 +1318,62 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) } std::unique_ptr hrtf; - char magic[sizeof(magicMarker03)]; - stream->read(magic, sizeof(magic)); - if(stream->gcount() < static_cast(sizeof(magicMarker03))) - ERR("%s data is too short (%zu bytes)\n", name.c_str(), stream->gcount()); - else if(memcmp(magic, magicMarker03, sizeof(magicMarker03)) == 0) + std::array magic{}; + stream->read(magic.data(), magic.size()); + if(stream->gcount() < static_cast(GetMarker03Name().size())) + ERR("%.*s data is too short (%zu bytes)\n", al::sizei(name),name.data(), stream->gcount()); + else if(GetMarker03Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v3\n"); - hrtf = LoadHrtf03(*stream, name.c_str()); + hrtf = LoadHrtf03(*stream); } - else if(memcmp(magic, magicMarker02, sizeof(magicMarker02)) == 0) + else if(GetMarker02Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v2\n"); - hrtf = LoadHrtf02(*stream, name.c_str()); + hrtf = LoadHrtf02(*stream); } - else if(memcmp(magic, magicMarker01, sizeof(magicMarker01)) == 0) + else if(GetMarker01Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v1\n"); - hrtf = LoadHrtf01(*stream, name.c_str()); + hrtf = LoadHrtf01(*stream); } - else if(memcmp(magic, magicMarker00, sizeof(magicMarker00)) == 0) + else if(GetMarker00Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v0\n"); - hrtf = LoadHrtf00(*stream, name.c_str()); + hrtf = LoadHrtf00(*stream); } else - ERR("Invalid header in %s: \"%.8s\"\n", name.c_str(), magic); + ERR("Invalid header in %.*s: \"%.8s\"\n", al::sizei(name), name.data(), magic.data()); stream.reset(); if(!hrtf) - { - ERR("Failed to load %s\n", name.c_str()); return nullptr; - } if(hrtf->mSampleRate != devrate) { - TRACE("Resampling HRTF %s (%uhz -> %uhz)\n", name.c_str(), hrtf->mSampleRate, devrate); + TRACE("Resampling HRTF %.*s (%uhz -> %uhz)\n", al::sizei(name), name.data(), + hrtf->mSampleRate, devrate); /* Calculate the last elevation's index and get the total IR count. */ - const size_t lastEv{std::accumulate(hrtf->mFields.begin(), hrtf->mFields.end(), size_t{0}, + const size_t lastEv{std::accumulate(hrtf->mFields.begin(), hrtf->mFields.end(), 0_uz, [](const size_t curval, const HrtfStore::Field &field) noexcept -> size_t { return curval + field.evCount; } ) - 1}; const size_t irCount{size_t{hrtf->mElev[lastEv].irOffset} + hrtf->mElev[lastEv].azCount}; /* Resample all the IRs. */ - std::array,2> inout; + std::array,2> inout{}; PPhaseResampler rs; rs.init(hrtf->mSampleRate, devrate); for(size_t i{0};i < irCount;++i) { - HrirArray &coeffs = const_cast(hrtf->mCoeffs[i]); + /* NOLINTNEXTLINE(*-const-cast) */ + auto coeffs = al::span{const_cast(hrtf->mCoeffs[i])}; for(size_t j{0};j < 2;++j) { std::transform(coeffs.cbegin(), coeffs.cend(), inout[0].begin(), [j](const float2 &in) noexcept -> double { return in[j]; }); - rs.process(HrirLength, inout[0].data(), HrirLength, inout[1].data()); + rs.process(inout[0], inout[1]); for(size_t k{0};k < HrirLength;++k) coeffs[k][j] = static_cast(inout[1][k]); } @@ -1394,15 +1382,15 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) /* Scale the delays for the new sample rate. */ float max_delay{0.0f}; - auto new_delays = al::vector(irCount); + auto new_delays = std::vector(irCount); const float rate_scale{static_cast(devrate)/static_cast(hrtf->mSampleRate)}; for(size_t i{0};i < irCount;++i) { for(size_t j{0};j < 2;++j) { - const float new_delay{std::round(hrtf->mDelays[i][j] * rate_scale) / + const float new_delay{std::round(float(hrtf->mDelays[i][j]) * rate_scale) / float{HrirDelayFracOne}}; - max_delay = maxf(max_delay, new_delay); + max_delay = std::max(max_delay, new_delay); new_delays[i][j] = new_delay; } } @@ -1420,25 +1408,31 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) for(size_t i{0};i < irCount;++i) { - ubyte2 &delays = const_cast(hrtf->mDelays[i]); - for(size_t j{0};j < 2;++j) - delays[j] = static_cast(float2int(new_delays[i][j]*delay_scale + 0.5f)); + /* NOLINTNEXTLINE(*-const-cast) */ + auto delays = al::span{const_cast(hrtf->mDelays[i])}; + std::transform(new_delays[i].cbegin(), new_delays[i].cend(), delays.begin(), + [delay_scale](const float delay) + { return static_cast(float2int(delay*delay_scale + 0.5f)); }); } /* Scale the IR size for the new sample rate and update the stored * sample rate. */ const float newIrSize{std::round(static_cast(hrtf->mIrSize) * rate_scale)}; - hrtf->mIrSize = static_cast(minf(HrirLength, newIrSize)); - hrtf->mSampleRate = devrate; + hrtf->mIrSize = static_cast(std::min(float{HrirLength}, newIrSize)); + hrtf->mSampleRate = devrate & 0xff'ff'ff; } - TRACE("Loaded HRTF %s for sample rate %uhz, %u-sample filter\n", name.c_str(), - hrtf->mSampleRate, hrtf->mIrSize); - handle = LoadedHrtfs.emplace(handle, fname, std::move(hrtf)); + handle = LoadedHrtfs.emplace(handle, fname, devrate, std::move(hrtf)); + TRACE("Loaded HRTF %.*s for sample rate %uhz, %u-sample filter\n", al::sizei(name),name.data(), + handle->mEntry->mSampleRate, handle->mEntry->mIrSize); return HrtfStorePtr{handle->mEntry.get()}; } +catch(std::exception& e) { + ERR("Failed to load %.*s: %s\n", al::sizei(name), name.data(), e.what()); + return nullptr; +} void HrtfStore::add_ref() @@ -1453,15 +1447,15 @@ void HrtfStore::dec_ref() TRACE("HrtfStore %p decreasing refcount to %u\n", decltype(std::declval()){this}, ref); if(ref == 0) { - std::lock_guard _{LoadedHrtfLock}; + std::lock_guard loadlock{LoadedHrtfLock}; /* Go through and remove all unused HRTFs. */ auto remove_unused = [](LoadedHrtf &hrtf) -> bool { HrtfStore *entry{hrtf.mEntry.get()}; - if(entry && ReadRef(entry->mRef) == 0) + if(entry && entry->mRef.load() == 0) { - TRACE("Unloading unused HRTF %s\n", hrtf.mFilename.data()); + TRACE("Unloading unused HRTF %s\n", hrtf.mFilename.c_str()); hrtf.mEntry = nullptr; return true; } diff --git a/Engine/lib/openal-soft/core/hrtf.h b/Engine/lib/openal-soft/core/hrtf.h index eb18682a5..e93fddaab 100644 --- a/Engine/lib/openal-soft/core/hrtf.h +++ b/Engine/lib/openal-soft/core/hrtf.h @@ -4,21 +4,23 @@ #include #include #include +#include #include +#include +#include #include "almalloc.h" -#include "aloptional.h" #include "alspan.h" #include "atomic.h" #include "ambidefs.h" #include "bufferline.h" -#include "mixer/hrtfdefs.h" +#include "flexarray.h" #include "intrusive_ptr.h" -#include "vector.h" +#include "mixer/hrtfdefs.h" -struct HrtfStore { - RefCount mRef; +struct alignas(16) HrtfStore { + std::atomic mRef; uint mSampleRate : 24; uint mIrSize : 8; @@ -36,17 +38,24 @@ struct HrtfStore { ushort azCount; ushort irOffset; }; - Elevation *mElev; - const HrirArray *mCoeffs; - const ubyte2 *mDelays; + al::span mElev; + al::span mCoeffs; + al::span mDelays; - void getCoeffs(float elevation, float azimuth, float distance, float spread, HrirArray &coeffs, - const al::span delays); + void getCoeffs(float elevation, float azimuth, float distance, float spread, + const HrirSpan coeffs, const al::span delays) const; void add_ref(); void dec_ref(); - DEF_PLACE_NEWDEL() + void *operator new(size_t) = delete; + void *operator new[](size_t) = delete; + void operator delete[](void*) noexcept = delete; + + void operator delete(gsl::owner block, void*) noexcept + { ::operator delete[](block, std::align_val_t{alignof(HrtfStore)}); } + void operator delete(gsl::owner block) noexcept + { ::operator delete[](block, std::align_val_t{alignof(HrtfStore)}); } }; using HrtfStorePtr = al::intrusive_ptr; @@ -60,7 +69,7 @@ struct AngularPoint { struct DirectHrtfState { - std::array mTemp; + std::array mTemp{}; /* HRTF filter state for dry buffer content */ uint mIrSize{0}; @@ -74,7 +83,8 @@ struct DirectHrtfState { * are ordered and scaled according to the matrix input. */ void build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin, - const al::span AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels], + const al::span AmbiPoints, + const al::span> AmbiMatrix, const float XOverFreq, const al::span AmbiOrderHFGain); static std::unique_ptr Create(size_t num_chans); @@ -83,7 +93,7 @@ struct DirectHrtfState { }; -al::vector EnumerateHrtf(al::optional pathopt); -HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate); +std::vector EnumerateHrtf(std::optional pathopt); +HrtfStorePtr GetLoadedHrtf(const std::string_view name, const uint devrate); #endif /* CORE_HRTF_H */ diff --git a/Engine/lib/openal-soft/core/logging.cpp b/Engine/lib/openal-soft/core/logging.cpp index 34a95e5ad..c0ff45c02 100644 --- a/Engine/lib/openal-soft/core/logging.cpp +++ b/Engine/lib/openal-soft/core/logging.cpp @@ -3,13 +3,19 @@ #include "logging.h" +#include +#include #include #include +#include +#include +#include #include +#include #include "alspan.h" +#include "opthelpers.h" #include "strutils.h" -#include "vector.h" #if defined(_WIN32) @@ -19,47 +25,108 @@ #include #endif -void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) + +FILE *gLogFile{stderr}; +#ifdef _DEBUG +LogLevel gLogLevel{LogLevel::Warning}; +#else +LogLevel gLogLevel{LogLevel::Error}; +#endif + + +namespace { + +enum class LogState : uint8_t { + FirstRun, + Ready, + Disable +}; + +std::mutex LogCallbackMutex; +LogState gLogState{LogState::FirstRun}; + +LogCallbackFunc gLogCallback{}; +void *gLogCallbackPtr{}; + +constexpr auto GetLevelCode(LogLevel level) noexcept -> std::optional { + switch(level) + { + case LogLevel::Disable: break; + case LogLevel::Error: return 'E'; + case LogLevel::Warning: return 'W'; + case LogLevel::Trace: return 'I'; + } + return std::nullopt; +} + +} // namespace + +void al_set_log_callback(LogCallbackFunc callback, void *userptr) +{ + auto cblock = std::lock_guard{LogCallbackMutex}; + gLogCallback = callback; + gLogCallbackPtr = callback ? userptr : nullptr; + if(gLogState == LogState::FirstRun) + { + auto extlogopt = al::getenv("ALSOFT_DISABLE_LOG_CALLBACK"); + if(!extlogopt || *extlogopt != "1") + gLogState = LogState::Ready; + else + gLogState = LogState::Disable; + } +} + +void al_print(LogLevel level, const char *fmt, ...) noexcept +try { /* Kind of ugly since string literals are const char arrays with a size * that includes the null terminator, which we want to exclude from the * span. */ - auto prefix = al::as_span("[ALSOFT] (--) ").first<14>(); + auto prefix = al::span{"[ALSOFT] (--) "}.first<14>(); switch(level) { case LogLevel::Disable: break; - case LogLevel::Error: prefix = al::as_span("[ALSOFT] (EE) ").first<14>(); break; - case LogLevel::Warning: prefix = al::as_span("[ALSOFT] (WW) ").first<14>(); break; - case LogLevel::Trace: prefix = al::as_span("[ALSOFT] (II) ").first<14>(); break; + case LogLevel::Error: prefix = al::span{"[ALSOFT] (EE) "}.first<14>(); break; + case LogLevel::Warning: prefix = al::span{"[ALSOFT] (WW) "}.first<14>(); break; + case LogLevel::Trace: prefix = al::span{"[ALSOFT] (II) "}.first<14>(); break; } - al::vector dynmsg; + std::vector dynmsg; std::array stcmsg{}; char *str{stcmsg.data()}; auto prefend1 = std::copy_n(prefix.begin(), prefix.size(), stcmsg.begin()); al::span msg{prefend1, stcmsg.end()}; + /* NOLINTBEGIN(*-array-to-pointer-decay) */ std::va_list args, args2; va_start(args, fmt); va_copy(args2, args); const int msglen{std::vsnprintf(msg.data(), msg.size(), fmt, args)}; - if(msglen >= 0 && static_cast(msglen) >= msg.size()) UNLIKELY + if(msglen >= 0) { - dynmsg.resize(static_cast(msglen)+prefix.size() + 1u); + if(static_cast(msglen) >= msg.size()) UNLIKELY + { + dynmsg.resize(static_cast(msglen)+prefix.size() + 1u); - str = dynmsg.data(); - auto prefend2 = std::copy_n(prefix.begin(), prefix.size(), dynmsg.begin()); - msg = {prefend2, dynmsg.end()}; + str = dynmsg.data(); + auto prefend2 = std::copy_n(prefix.begin(), prefix.size(), dynmsg.begin()); + msg = {prefend2, dynmsg.end()}; - std::vsnprintf(msg.data(), msg.size(), fmt, args2); + std::vsnprintf(msg.data(), msg.size(), fmt, args2); + } + msg = msg.first(static_cast(msglen)); } + else + msg = {msg.data(), std::strlen(msg.data())}; va_end(args2); va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ if(gLogLevel >= level) { + auto logfile = gLogFile; fputs(str, logfile); fflush(logfile); } @@ -86,4 +153,24 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) }; __android_log_print(android_severity(level), "openal", "%s", str); #endif + + auto cblock = std::lock_guard{LogCallbackMutex}; + if(gLogState != LogState::Disable) + { + while(!msg.empty() && std::isspace(msg.back())) + { + msg.back() = '\0'; + msg = msg.first(msg.size()-1); + } + if(auto logcode = GetLevelCode(level); logcode && !msg.empty()) + { + if(gLogCallback) + gLogCallback(gLogCallbackPtr, *logcode, msg.data(), static_cast(msg.size())); + else if(gLogState == LogState::FirstRun) + gLogState = LogState::Disable; + } + } +} +catch(...) { + /* Swallow any exceptions */ } diff --git a/Engine/lib/openal-soft/core/logging.h b/Engine/lib/openal-soft/core/logging.h index f4b6ab562..527e79540 100644 --- a/Engine/lib/openal-soft/core/logging.h +++ b/Engine/lib/openal-soft/core/logging.h @@ -1,9 +1,7 @@ #ifndef CORE_LOGGING_H #define CORE_LOGGING_H -#include - -#include "opthelpers.h" +#include enum class LogLevel { @@ -16,36 +14,23 @@ extern LogLevel gLogLevel; extern FILE *gLogFile; -#ifdef __USE_MINGW_ANSI_STDIO -[[gnu::format(gnu_printf,3,4)]] + +using LogCallbackFunc = void(*)(void *userptr, char level, const char *message, int length) noexcept; + +void al_set_log_callback(LogCallbackFunc callback, void *userptr); + + +#ifdef __MINGW32__ +[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] #else -[[gnu::format(printf,3,4)]] +[[gnu::format(printf,2,3)]] #endif -void al_print(LogLevel level, FILE *logfile, const char *fmt, ...); +void al_print(LogLevel level, const char *fmt, ...) noexcept; -#if (!defined(_WIN32) || defined(NDEBUG)) && !defined(__ANDROID__) -#define TRACE(...) do { \ - if(gLogLevel >= LogLevel::Trace) UNLIKELY \ - al_print(LogLevel::Trace, gLogFile, __VA_ARGS__); \ -} while(0) +#define TRACE(...) al_print(LogLevel::Trace, __VA_ARGS__) -#define WARN(...) do { \ - if(gLogLevel >= LogLevel::Warning) UNLIKELY \ - al_print(LogLevel::Warning, gLogFile, __VA_ARGS__); \ -} while(0) +#define WARN(...) al_print(LogLevel::Warning, __VA_ARGS__) -#define ERR(...) do { \ - if(gLogLevel >= LogLevel::Error) UNLIKELY \ - al_print(LogLevel::Error, gLogFile, __VA_ARGS__); \ -} while(0) - -#else - -#define TRACE(...) al_print(LogLevel::Trace, gLogFile, __VA_ARGS__) - -#define WARN(...) al_print(LogLevel::Warning, gLogFile, __VA_ARGS__) - -#define ERR(...) al_print(LogLevel::Error, gLogFile, __VA_ARGS__) -#endif +#define ERR(...) al_print(LogLevel::Error, __VA_ARGS__) #endif /* CORE_LOGGING_H */ diff --git a/Engine/lib/openal-soft/core/mastering.cpp b/Engine/lib/openal-soft/core/mastering.cpp index 97a4008e1..069a21cea 100644 --- a/Engine/lib/openal-soft/core/mastering.cpp +++ b/Engine/lib/openal-soft/core/mastering.cpp @@ -11,7 +11,6 @@ #include #include -#include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "opthelpers.h" @@ -21,8 +20,8 @@ static_assert((BufferLineSize & (BufferLineSize-1)) == 0, "BufferLineSize is not a power of 2"); struct SlidingHold { - alignas(16) float mValues[BufferLineSize]; - uint mExpiries[BufferLineSize]; + alignas(16) FloatBufferLine mValues; + std::array mExpiries; uint mLowerIndex; uint mUpperIndex; uint mLength; @@ -31,7 +30,9 @@ struct SlidingHold { namespace { -using namespace std::placeholders; +template +constexpr auto assume_aligned_span(const al::span s) noexcept -> al::span +{ return al::span{al::assume_aligned(s.data()), s.size()}; } /* 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. @@ -44,8 +45,8 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in) { static constexpr uint mask{BufferLineSize - 1}; const uint length{Hold->mLength}; - float (&values)[BufferLineSize] = Hold->mValues; - uint (&expiries)[BufferLineSize] = Hold->mExpiries; + const al::span values{Hold->mValues}; + const al::span expiries{Hold->mExpiries}; uint lowerIndex{Hold->mLowerIndex}; uint upperIndex{Hold->mUpperIndex}; @@ -60,14 +61,16 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in) } else { - do { + auto findLowerIndex = [&lowerIndex,in,values]() noexcept -> bool + { do { if(!(in >= values[lowerIndex])) - goto found_place; + return true; } while(lowerIndex--); + return false; + }; + while(!findLowerIndex()) lowerIndex = mask; - } while(true); - found_place: lowerIndex = (lowerIndex + 1) & mask; values[lowerIndex] = in; @@ -82,38 +85,41 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in) void ShiftSlidingHold(SlidingHold *Hold, const uint 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) + auto exp_upper = Hold->mExpiries.begin() + Hold->mUpperIndex; + if(Hold->mLowerIndex < Hold->mUpperIndex) { - std::transform(exp_begin, std::end(Hold->mExpiries), exp_begin, - [n](uint e){ return e - n; }); - exp_begin = std::begin(Hold->mExpiries); + std::transform(exp_upper, Hold->mExpiries.end(), exp_upper, + [n](const uint e) noexcept { return e - n; }); + exp_upper = Hold->mExpiries.begin(); } - std::transform(exp_begin, exp_last+1, exp_begin, [n](uint e){ return e - n; }); + const auto exp_lower = Hold->mExpiries.begin() + Hold->mLowerIndex; + std::transform(exp_upper, exp_lower+1, exp_upper, + [n](const uint e) noexcept { return e - n; }); } +} // namespace /* Multichannel compression is linked via the absolute maximum of all * channels. */ -void LinkChannels(Compressor *Comp, const uint SamplesToDo, const FloatBufferLine *OutBuffer) +void Compressor::linkChannels(const uint SamplesToDo, + const al::span OutBuffer) { - const size_t numChans{Comp->mNumChans}; - ASSUME(SamplesToDo > 0); - ASSUME(numChans > 0); + ASSUME(SamplesToDo <= BufferLineSize); - auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; - std::fill(side_begin, side_begin+SamplesToDo, 0.0f); + const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::fill_n(sideChain.begin(), sideChain.size(), 0.0f); - auto fill_max = [SamplesToDo,side_begin](const FloatBufferLine &input) -> void + auto fill_max = [sideChain](const FloatBufferLine &input) -> void { - const float *RESTRICT buffer{al::assume_aligned<16>(input.data())}; - auto max_abs = std::bind(maxf, _1, std::bind(static_cast(std::fabs), _2)); - std::transform(side_begin, side_begin+SamplesToDo, buffer, side_begin, max_abs); + const auto buffer = assume_aligned_span<16>(al::span{input}); + auto max_abs = [](const float s0, const float s1) noexcept -> float + { return std::max(s0, std::fabs(s1)); }; + std::transform(sideChain.begin(), sideChain.end(), buffer.begin(), sideChain.begin(), + max_abs); }; - std::for_each(OutBuffer, OutBuffer+numChans, fill_max); + std::for_each(OutBuffer.begin(), OutBuffer.end(), fill_max); } /* This calculates the squared crest factor of the control signal for the @@ -121,60 +127,63 @@ void LinkChannels(Compressor *Comp, const uint SamplesToDo, const FloatBufferLin * it uses an instantaneous squared peak detector and a squared RMS detector * both with 200ms release times. */ -void CrestDetector(Compressor *Comp, const uint SamplesToDo) +void Compressor::crestDetector(const uint SamplesToDo) { - const float a_crest{Comp->mCrestCoeff}; - float y2_peak{Comp->mLastPeakSq}; - float y2_rms{Comp->mLastRmsSq}; + const float a_crest{mCrestCoeff}; + float y2_peak{mLastPeakSq}; + float y2_rms{mLastRmsSq}; ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); auto calc_crest = [&y2_rms,&y2_peak,a_crest](const float x_abs) noexcept -> float { - const float x2{clampf(x_abs * x_abs, 0.000001f, 1000000.0f)}; + const float x2{std::clamp(x_abs*x_abs, 0.000001f, 1000000.0f)}; - y2_peak = maxf(x2, lerpf(x2, y2_peak, a_crest)); + y2_peak = std::max(x2, lerpf(x2, y2_peak, a_crest)); y2_rms = lerpf(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); + const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::transform(sideChain.cbegin(), sideChain.cend(), mCrestFactor.begin(), calc_crest); - Comp->mLastPeakSq = y2_peak; - Comp->mLastRmsSq = y2_rms; + mLastPeakSq = y2_peak; + 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 uint SamplesToDo) +void Compressor::peakDetector(const uint SamplesToDo) { ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); - /* 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, - [](float s) { return std::log(maxf(0.000001f, s)); }); + /* Clamp the minimum amplitude to near-zero and convert to logarithmic. */ + const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(), + [](float s) { return std::log(std::max(0.000001f, s)); }); } /* 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 uint SamplesToDo) +void Compressor::peakHoldDetector(const uint SamplesToDo) { ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); - SlidingHold *hold{Comp->mHold}; + SlidingHold *hold{mHold.get()}; uint i{0}; auto detect_peak = [&i,hold](const float x_abs) -> float { - const float x_G{std::log(maxf(0.000001f, x_abs))}; + const float x_G{std::log(std::max(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); + auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(), detect_peak); ShiftSlidingHold(hold, SamplesToDo); } @@ -184,46 +193,46 @@ void PeakHoldDetector(Compressor *Comp, const uint SamplesToDo) * to knee width, attack/release times, make-up/post gain, and clipping * reduction. */ -void GainCompressor(Compressor *Comp, const uint SamplesToDo) +void Compressor::gainCompressor(const uint 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 uint lookAhead{Comp->mLookAhead}; - const float threshold{Comp->mThreshold}; - const float slope{Comp->mSlope}; - const float attack{Comp->mAttack}; - const float release{Comp->mRelease}; - const float c_est{Comp->mGainEstimate}; - const float a_adp{Comp->mAdaptCoeff}; - const float *crestFactor{Comp->mCrestFactor}; - float postGain{Comp->mPostGain}; - float knee{Comp->mKnee}; + const bool autoKnee{mAuto.Knee}; + const bool autoAttack{mAuto.Attack}; + const bool autoRelease{mAuto.Release}; + const bool autoPostGain{mAuto.PostGain}; + const bool autoDeclip{mAuto.Declip}; + const float threshold{mThreshold}; + const float slope{mSlope}; + const float attack{mAttack}; + const float release{mRelease}; + const float c_est{mGainEstimate}; + const float a_adp{mAdaptCoeff}; + auto lookAhead = mSideChain.cbegin() + mLookAhead; + auto crestFactor = mCrestFactor.cbegin(); + float postGain{mPostGain}; + float knee{mKnee}; float t_att{attack}; float t_rel{release - attack}; float a_att{std::exp(-1.0f / t_att)}; float a_rel{std::exp(-1.0f / t_rel)}; - float y_1{Comp->mLastRelease}; - float y_L{Comp->mLastAttack}; - float c_dev{Comp->mLastGainDev}; + float y_1{mLastRelease}; + float y_L{mLastAttack}; + float c_dev{mLastGainDev}; ASSUME(SamplesToDo > 0); - for(float &sideChain : al::span{Comp->mSideChain, SamplesToDo}) + auto process = [&](const float input) -> float { if(autoKnee) - knee = maxf(0.0f, 2.5f * (c_dev + c_est)); + knee = std::max(0.0f, 2.5f * (c_dev + c_est)); const float knee_h{0.5f * knee}; /* This is the gain computer. It applies a static compression curve * to the control signal. */ - const float x_over{std::addressof(sideChain)[lookAhead] - threshold}; + const float x_over{*(lookAhead++) - threshold}; const float 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) : + (std::fabs(x_over) < knee_h) ? (x_over+knee_h) * (x_over+knee_h) / (2.0f * knee) : x_over}; const float y2_crest{*(crestFactor++)}; @@ -243,7 +252,7 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo) * above to compensate for the chained operating mode. */ const float x_L{-slope * y_G}; - y_1 = maxf(x_L, lerpf(x_L, y_1, a_rel)); + y_1 = std::max(x_L, lerpf(x_L, y_1, a_rel)); y_L = lerpf(y_1, y_L, a_att); /* Knee width and make-up gain automation make use of a smoothed @@ -262,17 +271,19 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo) * same output level. */ if(autoDeclip) - c_dev = maxf(c_dev, sideChain - y_L - threshold - c_est); + c_dev = std::max(c_dev, input - y_L - threshold - c_est); postGain = -(c_dev + c_est); } - sideChain = std::exp(postGain - y_L); - } + return std::exp(postGain - y_L); + }; + auto sideChain = al::span{mSideChain}.first(SamplesToDo); + std::transform(sideChain.begin(), sideChain.end(), sideChain.begin(), process); - Comp->mLastRelease = y_1; - Comp->mLastAttack = y_L; - Comp->mLastGainDev = c_dev; + mLastRelease = y_1; + mLastAttack = y_L; + mLastGainDev = c_dev; } /* Combined with the hold time, a look-ahead delay can improve handling of @@ -280,36 +291,35 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo) * reaching the offending impulse. This is best used when operating as a * limiter. */ -void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutBuffer) +void Compressor::signalDelay(const uint SamplesToDo, const al::span OutBuffer) { - const size_t numChans{Comp->mNumChans}; - const uint lookAhead{Comp->mLookAhead}; + const auto lookAhead = mLookAhead; ASSUME(SamplesToDo > 0); - ASSUME(numChans > 0); + ASSUME(SamplesToDo <= BufferLineSize); ASSUME(lookAhead > 0); + ASSUME(lookAhead < BufferLineSize); - for(size_t c{0};c < numChans;c++) + auto delays = mDelay.begin(); + for(auto &buffer : OutBuffer) { - float *inout{al::assume_aligned<16>(OutBuffer[c].data())}; - float *delaybuf{al::assume_aligned<16>(Comp->mDelay[c].data())}; + const auto inout = al::span{buffer}.first(SamplesToDo); + const auto delaybuf = al::span{*(delays++)}.first(lookAhead); - auto inout_end = inout + SamplesToDo; - if(SamplesToDo >= lookAhead) LIKELY + if(SamplesToDo >= delaybuf.size()) LIKELY { - auto delay_end = std::rotate(inout, inout_end - lookAhead, inout_end); - std::swap_ranges(inout, delay_end, delaybuf); + const auto inout_start = inout.end() - ptrdiff_t(delaybuf.size()); + const auto delay_end = std::rotate(inout.begin(), inout_start, inout.end()); + std::swap_ranges(inout.begin(), delay_end, delaybuf.begin()); } else { - auto delay_start = std::swap_ranges(inout, inout_end, delaybuf); - std::rotate(delaybuf, delay_start, delaybuf + lookAhead); + auto delay_start = std::swap_ranges(inout.begin(), inout.end(), delaybuf.begin()); + std::rotate(delaybuf.begin(), delay_start, delaybuf.end()); } } } -} // namespace - std::unique_ptr Compressor::Create(const size_t NumChans, const float SampleRate, const bool AutoKnee, const bool AutoAttack, const bool AutoRelease, const bool AutoPostGain, @@ -317,24 +327,12 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa const float PostGainDb, const float ThresholdDb, const float Ratio, const float KneeDb, const float AttackTime, const float ReleaseTime) { - const auto lookAhead = static_cast( - clampf(std::round(LookAheadTime*SampleRate), 0.0f, BufferLineSize-1)); - const auto hold = static_cast( - clampf(std::round(HoldTime*SampleRate), 0.0f, BufferLineSize-1)); + const auto lookAhead = static_cast(std::clamp(std::round(LookAheadTime*SampleRate), 0.0f, + BufferLineSize-1.0f)); + const auto hold = static_cast(std::clamp(std::round(HoldTime*SampleRate), 0.0f, + BufferLineSize-1.0f)); - 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 = CompressorPtr{al::construct_at(static_cast(al_calloc(16, size)))}; + auto Comp = CompressorPtr{new Compressor{}}; Comp->mNumChans = NumChans; Comp->mAuto.Knee = AutoKnee; Comp->mAuto.Attack = AutoAttack; @@ -343,12 +341,12 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa 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); + Comp->mPostGain = std::log(10.0f)/20.0f * PostGainDb; + Comp->mThreshold = std::log(10.0f)/20.0f * ThresholdDb; + Comp->mSlope = 1.0f / std::max(1.0f, Ratio) - 1.0f; + Comp->mKnee = std::max(0.0f, std::log(10.0f)/20.0f * KneeDb); + Comp->mAttack = std::max(1.0f, AttackTime * SampleRate); + Comp->mRelease = std::max(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 @@ -359,17 +357,18 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa if(lookAhead > 0) { + /* 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) { - Comp->mHold = al::construct_at(reinterpret_cast(Comp.get() + 1)); + Comp->mHold = std::make_unique(); Comp->mHold->mValues[0] = -std::numeric_limits::infinity(); Comp->mHold->mExpiries[0] = hold; Comp->mHold->mLength = hold; - Comp->mDelay = reinterpret_cast(Comp->mHold + 1); } - else - Comp->mDelay = reinterpret_cast(Comp.get() + 1); - std::uninitialized_fill_n(Comp->mDelay, NumChans, FloatBufferLine{}); + Comp->mDelay.resize(NumChans, FloatBufferLine{}); } Comp->mCrestCoeff = std::exp(-1.0f / (0.200f * SampleRate)); // 200ms @@ -379,15 +378,7 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa return Comp; } -Compressor::~Compressor() -{ - if(mHold) - al::destroy_at(mHold); - mHold = nullptr; - if(mDelay) - al::destroy_n(mDelay, mNumChans); - mDelay = nullptr; -} +Compressor::~Compressor() = default; void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) @@ -395,45 +386,46 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) const size_t numChans{mNumChans}; ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); ASSUME(numChans > 0); + const auto output = al::span{OutBuffer, numChans}; const float preGain{mPreGain}; if(preGain != 1.0f) { auto apply_gain = [SamplesToDo,preGain](FloatBufferLine &input) noexcept -> void { - float *buffer{al::assume_aligned<16>(input.data())}; - std::transform(buffer, buffer+SamplesToDo, buffer, - [preGain](float s) { return s * preGain; }); + const auto buffer = assume_aligned_span<16>(al::span{input}.first(SamplesToDo)); + std::transform(buffer.cbegin(), buffer.cend(), buffer.begin(), + [preGain](const float s) noexcept { return s * preGain; }); }; - std::for_each(OutBuffer, OutBuffer+numChans, apply_gain); + std::for_each(output.begin(), output.end(), apply_gain); } - LinkChannels(this, SamplesToDo, OutBuffer); + linkChannels(SamplesToDo, output); if(mAuto.Attack || mAuto.Release) - CrestDetector(this, SamplesToDo); + crestDetector(SamplesToDo); if(mHold) - PeakHoldDetector(this, SamplesToDo); + peakHoldDetector(SamplesToDo); else - PeakDetector(this, SamplesToDo); + peakDetector(SamplesToDo); - GainCompressor(this, SamplesToDo); + gainCompressor(SamplesToDo); - if(mDelay) - SignalDelay(this, SamplesToDo, OutBuffer); + if(!mDelay.empty()) + signalDelay(SamplesToDo, output); - const float (&sideChain)[BufferLineSize*2] = mSideChain; - auto apply_comp = [SamplesToDo,&sideChain](FloatBufferLine &input) noexcept -> void + const auto gains = assume_aligned_span<16>(al::span{mSideChain}.first(SamplesToDo)); + auto apply_comp = [gains](const FloatBufferSpan input) noexcept -> void { - float *buffer{al::assume_aligned<16>(input.data())}; - const float *gains{al::assume_aligned<16>(&sideChain[0])}; - std::transform(gains, gains+SamplesToDo, buffer, buffer, - [](float g, float s) { return g * s; }); + const auto buffer = assume_aligned_span<16>(input); + std::transform(gains.cbegin(), gains.cend(), buffer.cbegin(), buffer.begin(), + std::multiplies{}); }; - std::for_each(OutBuffer, OutBuffer+numChans, apply_comp); + std::for_each(output.begin(), output.end(), apply_comp); - auto side_begin = std::begin(mSideChain) + SamplesToDo; - std::copy(side_begin, side_begin+mLookAhead, std::begin(mSideChain)); + const auto delayedGains = al::span{mSideChain}.subspan(SamplesToDo, mLookAhead); + std::copy(delayedGains.begin(), delayedGains.end(), mSideChain.begin()); } diff --git a/Engine/lib/openal-soft/core/mastering.h b/Engine/lib/openal-soft/core/mastering.h index 1a36937ca..032eae082 100644 --- a/Engine/lib/openal-soft/core/mastering.h +++ b/Engine/lib/openal-soft/core/mastering.h @@ -1,10 +1,14 @@ #ifndef CORE_MASTERING_H #define CORE_MASTERING_H +#include #include #include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" #include "bufferline.h" +#include "vector.h" struct SlidingHold; @@ -21,16 +25,17 @@ using uint = unsigned int; * * http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/ */ -struct Compressor { +class Compressor { size_t mNumChans{0u}; - struct { + struct AutoFlags { bool Knee : 1; bool Attack : 1; bool Release : 1; bool PostGain : 1; bool Declip : 1; - } mAuto{}; + }; + AutoFlags mAuto{}; uint mLookAhead{0}; @@ -44,11 +49,11 @@ struct Compressor { float mAttack{0.0f}; float mRelease{0.0f}; - alignas(16) float mSideChain[2*BufferLineSize]{}; - alignas(16) float mCrestFactor[BufferLineSize]{}; + alignas(16) std::array mSideChain{}; + alignas(16) std::array mCrestFactor{}; - SlidingHold *mHold{nullptr}; - FloatBufferLine *mDelay{nullptr}; + std::unique_ptr mHold; + al::vector mDelay; float mCrestCoeff{0.0f}; float mGainEstimate{0.0f}; @@ -60,12 +65,19 @@ struct Compressor { float mLastAttack{0.0f}; float mLastGainDev{0.0f}; + Compressor() = default; + void linkChannels(const uint SamplesToDo, const al::span OutBuffer); + void crestDetector(const uint SamplesToDo); + void peakDetector(const uint SamplesToDo); + void peakHoldDetector(const uint SamplesToDo); + void gainCompressor(const uint SamplesToDo); + void signalDelay(const uint SamplesToDo, const al::span OutBuffer); + +public: ~Compressor(); void process(const uint SamplesToDo, FloatBufferLine *OutBuffer); - int getLookAhead() const noexcept { return static_cast(mLookAhead); } - - DEF_PLACE_NEWDEL() + [[nodiscard]] auto getLookAhead() const noexcept -> uint { return mLookAhead; } /** * The compressor is initialized with the following settings: diff --git a/Engine/lib/openal-soft/core/mixer.cpp b/Engine/lib/openal-soft/core/mixer.cpp index 066c57bd7..bba7ae206 100644 --- a/Engine/lib/openal-soft/core/mixer.cpp +++ b/Engine/lib/openal-soft/core/mixer.cpp @@ -3,10 +3,12 @@ #include "mixer.h" +#include #include +#include #include "alnumbers.h" -#include "devformat.h" +#include "core/ambidefs.h" #include "device.h" #include "mixer/defs.h" @@ -82,14 +84,13 @@ std::array CalcAmbiCoeffs(const float y, const float z, c return coeffs; } -void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain, - const al::span gains) +void ComputePanGains(const MixParams *mix, const al::span coeffs, + const float ingain, const al::span gains) { - auto ambimap = mix->AmbiMap.cbegin(); + auto ambimap = al::span{std::as_const(mix->AmbiMap)}.first(mix->Buffer.size()); - auto iter = std::transform(ambimap, ambimap+mix->Buffer.size(), gains.begin(), + auto iter = std::transform(ambimap.begin(), ambimap.end(), gains.begin(), [coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> float - { return chanmap.Scale * coeffs[chanmap.Index] * ingain; } - ); + { return chanmap.Scale * coeffs[chanmap.Index] * ingain; }); std::fill(iter, gains.end(), 0.0f); } diff --git a/Engine/lib/openal-soft/core/mixer.h b/Engine/lib/openal-soft/core/mixer.h index aa7597bba..b5f1b9aa1 100644 --- a/Engine/lib/openal-soft/core/mixer.h +++ b/Engine/lib/openal-soft/core/mixer.h @@ -3,34 +3,32 @@ #include #include -#include -#include +#include #include "alspan.h" #include "ambidefs.h" #include "bufferline.h" -#include "devformat.h" struct MixParams; /* Mixer functions that handle one input and multiple output channels. */ using MixerOutFunc = void(*)(const al::span InSamples, - const al::span OutBuffer, float *CurrentGains, const float *TargetGains, - const size_t Counter, const size_t OutPos); + const al::span OutBuffer, const al::span CurrentGains, + const al::span TargetGains, const std::size_t Counter, const std::size_t OutPos); extern MixerOutFunc MixSamplesOut; inline void MixSamples(const al::span InSamples, - const al::span OutBuffer, float *CurrentGains, const float *TargetGains, - const size_t Counter, const size_t OutPos) + const al::span OutBuffer, const al::span CurrentGains, + const al::span TargetGains, const std::size_t Counter, const std::size_t OutPos) { MixSamplesOut(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); } /* Mixer functions that handle one input and one output channel. */ -using MixerOneFunc = void(*)(const al::span InSamples, float *OutBuffer, - float &CurrentGain, const float TargetGain, const size_t Counter); +using MixerOneFunc = void(*)(const al::span InSamples,const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const std::size_t Counter); extern MixerOneFunc MixSamplesOne; -inline void MixSamples(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +inline void MixSamples(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const std::size_t Counter) { MixSamplesOne(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); } @@ -58,7 +56,7 @@ std::array CalcAmbiCoeffs(const float y, const float z, c * vector must be normalized (unit length), and the spread is the angular width * of the sound (0...tau). */ -inline std::array CalcDirectionCoeffs(const float (&dir)[3], +inline std::array CalcDirectionCoeffs(const al::span dir, const float spread) { /* Convert from OpenAL coords to Ambisonics. */ @@ -71,7 +69,7 @@ inline std::array CalcDirectionCoeffs(const float (&dir)[ * Calculates ambisonic coefficients based on an OpenAL direction vector. The * vector must be normalized (unit length). */ -constexpr std::array CalcDirectionCoeffs(const float (&dir)[3]) +constexpr std::array CalcDirectionCoeffs(const al::span dir) { /* Convert from OpenAL coords to Ambisonics. */ return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2]); @@ -103,7 +101,7 @@ inline std::array CalcAngleCoeffs(const float azimuth, * 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 gains); +void ComputePanGains(const MixParams *mix, const al::span coeffs, + const float ingain, const al::span gains); #endif /* CORE_MIXER_H */ diff --git a/Engine/lib/openal-soft/core/mixer/defs.h b/Engine/lib/openal-soft/core/mixer/defs.h index 48daca9b9..f19217c80 100644 --- a/Engine/lib/openal-soft/core/mixer/defs.h +++ b/Engine/lib/openal-soft/core/mixer/defs.h @@ -2,13 +2,15 @@ #define CORE_MIXER_DEFS_H #include -#include +#include +#include +#include +#include #include "alspan.h" #include "core/bufferline.h" -#include "core/resampler_limits.h" +#include "core/cubic_defs.h" -struct CubicCoefficients; struct HrtfChannelState; struct HrtfFilter; struct MixHrtfFilter; @@ -17,18 +19,19 @@ using uint = unsigned int; using float2 = std::array; -constexpr int MixerFracBits{16}; -constexpr int MixerFracOne{1 << MixerFracBits}; -constexpr int MixerFracMask{MixerFracOne - 1}; -constexpr int MixerFracHalf{MixerFracOne >> 1}; +inline constexpr int MixerFracBits{16}; +inline constexpr int MixerFracOne{1 << MixerFracBits}; +inline constexpr int MixerFracMask{MixerFracOne - 1}; +inline constexpr int MixerFracHalf{MixerFracOne >> 1}; -constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */ +inline constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */ -enum class Resampler : uint8_t { +enum class Resampler : std::uint8_t { Point, Linear, - Cubic, + Spline, + Gaussian, FastBSinc12, BSinc12, FastBSinc24, @@ -49,56 +52,59 @@ struct BsincState { * delta coefficients. Starting at phase index 0, each subsequent phase * index follows contiguously. */ - const float *filter; + al::span filter; }; struct CubicState { /* Filter coefficients, and coefficient deltas. Starting at phase index 0, * each subsequent phase index follows contiguously. */ - const CubicCoefficients *filter; + al::span filter; + CubicState(al::span f) : filter{f} { } }; -union InterpState { - CubicState cubic; - BsincState bsinc; -}; +using InterpState = std::variant; -using ResamplerFunc = void(*)(const InterpState *state, const float *RESTRICT src, uint frac, +using ResamplerFunc = void(*)(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst); ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state); template -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, +void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst); template void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos); + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos); template -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter); +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter); template -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize); +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo); template -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize); +void MixHrtfBlend_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, + const size_t SamplesToDo); template void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize); + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo); /* Vectorized resampler helpers */ template -inline void InitPosArrays(uint frac, uint increment, uint (&frac_arr)[N], uint (&pos_arr)[N]) +constexpr void InitPosArrays(uint pos, uint frac, const uint increment, + const al::span frac_arr, const al::span pos_arr) { - pos_arr[0] = 0; + static_assert(pos_arr.size() == frac_arr.size()); + pos_arr[0] = pos; frac_arr[0] = frac; - for(size_t i{1};i < N;i++) + for(size_t i{1};i < pos_arr.size();++i) { const uint frac_tmp{frac_arr[i-1] + increment}; pos_arr[i] = pos_arr[i-1] + (frac_tmp>>MixerFracBits); diff --git a/Engine/lib/openal-soft/core/mixer/hrtfbase.h b/Engine/lib/openal-soft/core/mixer/hrtfbase.h index 36f88e496..703bfab9d 100644 --- a/Engine/lib/openal-soft/core/mixer/hrtfbase.h +++ b/Engine/lib/openal-soft/core/mixer/hrtfbase.h @@ -4,21 +4,23 @@ #include #include -#include "almalloc.h" +#include "defs.h" #include "hrtfdefs.h" #include "opthelpers.h" using uint = unsigned int; -using ApplyCoeffsT = void(&)(float2 *RESTRICT Values, const size_t irSize, +using ApplyCoeffsT = void(const al::span Values, const size_t irSize, const ConstHrirSpan Coeffs, const float left, const float right); template -inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, const size_t IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) +inline void MixHrtfBase(const al::span InSamples, const al::span AccumSamples, + const size_t IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) { - ASSUME(BufferSize > 0); + ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); + ASSUME(IrSize <= HrirLength); const ConstHrirSpan Coeffs{hrtfparams->Coeffs}; const float gainstep{hrtfparams->GainStep}; @@ -27,26 +29,28 @@ inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, c size_t ldelay{HrtfHistoryLength - hrtfparams->Delay[0]}; size_t rdelay{HrtfHistoryLength - hrtfparams->Delay[1]}; float stepcount{0.0f}; - for(size_t i{0u};i < BufferSize;++i) + for(size_t i{0u};i < SamplesToDo;++i) { const float g{gain + gainstep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, left, right); + ApplyCoeffs(AccumSamples.subspan(i), IrSize, Coeffs, left, right); stepcount += 1.0f; } } template -inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSamples, - const size_t IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, - const size_t BufferSize) +inline void MixHrtfBlendBase(const al::span InSamples, + const al::span AccumSamples, const size_t IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo) { - ASSUME(BufferSize > 0); + ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); + ASSUME(IrSize <= HrirLength); const ConstHrirSpan OldCoeffs{oldparams->Coeffs}; - const float oldGainStep{oldparams->Gain / static_cast(BufferSize)}; + const float oldGainStep{oldparams->Gain / static_cast(SamplesToDo)}; const ConstHrirSpan NewCoeffs{newparams->Coeffs}; const float newGainStep{newparams->GainStep}; @@ -54,29 +58,29 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl { size_t ldelay{HrtfHistoryLength - oldparams->Delay[0]}; size_t rdelay{HrtfHistoryLength - oldparams->Delay[1]}; - auto stepcount = static_cast(BufferSize); - for(size_t i{0u};i < BufferSize;++i) + auto stepcount = static_cast(SamplesToDo); + for(size_t i{0u};i < SamplesToDo;++i) { const float g{oldGainStep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, OldCoeffs, left, right); + ApplyCoeffs(AccumSamples.subspan(i), IrSize, OldCoeffs, left, right); stepcount -= 1.0f; } } - if(newGainStep*static_cast(BufferSize) > GainSilenceThreshold) LIKELY + if(newGainStep*static_cast(SamplesToDo) > GainSilenceThreshold) LIKELY { size_t ldelay{HrtfHistoryLength+1 - newparams->Delay[0]}; size_t rdelay{HrtfHistoryLength+1 - newparams->Delay[1]}; float stepcount{1.0f}; - for(size_t i{1u};i < BufferSize;++i) + for(size_t i{1u};i < SamplesToDo;++i) { const float g{newGainStep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, NewCoeffs, left, right); + ApplyCoeffs(AccumSamples.subspan(i), IrSize, NewCoeffs, left, right); stepcount += 1.0f; } @@ -85,45 +89,52 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl template inline void MixDirectHrtfBase(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *RESTRICT AccumSamples, - float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChannelState, + const size_t IrSize, const size_t SamplesToDo) { - ASSUME(BufferSize > 0); + ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); + ASSUME(IrSize <= HrirLength); + assert(ChannelState.size() == InSamples.size()); + auto ChanState = ChannelState.begin(); for(const FloatBufferLine &input : InSamples) { /* For dual-band processing, the signal needs extra scaling applied to * the high frequency response. The band-splitter applies this scaling * with a consistent phase shift regardless of the scale amount. */ - ChanState->mSplitter.processHfScale({input.data(), BufferSize}, TempBuf, + ChanState->mSplitter.processHfScale(al::span{input}.first(SamplesToDo), TempBuf, ChanState->mHfScale); /* Now apply the HRIR coefficients to this channel. */ - const float *RESTRICT tempbuf{al::assume_aligned<16>(TempBuf)}; const ConstHrirSpan Coeffs{ChanState->mCoeffs}; - for(size_t i{0u};i < BufferSize;++i) + for(size_t i{0u};i < SamplesToDo;++i) { - const float insample{tempbuf[i]}; - ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, insample, insample); + const float insample{TempBuf[i]}; + ApplyCoeffs(AccumSamples.subspan(i), IrSize, Coeffs, insample, insample); } ++ChanState; } /* Add the HRTF signal to the existing "direct" signal. */ - float *RESTRICT left{al::assume_aligned<16>(LeftOut.data())}; - float *RESTRICT right{al::assume_aligned<16>(RightOut.data())}; - for(size_t i{0u};i < BufferSize;++i) - left[i] += AccumSamples[i][0]; - for(size_t i{0u};i < BufferSize;++i) - right[i] += AccumSamples[i][1]; + const auto left = al::span{al::assume_aligned<16>(LeftOut.data()), SamplesToDo}; + std::transform(left.cbegin(), left.cend(), AccumSamples.cbegin(), left.begin(), + [](const float sample, const float2 &accum) noexcept -> float + { return sample + accum[0]; }); + const auto right = al::span{al::assume_aligned<16>(RightOut.data()), SamplesToDo}; + std::transform(right.cbegin(), right.cend(), AccumSamples.cbegin(), right.begin(), + [](const float sample, const float2 &accum) noexcept -> float + { return sample + accum[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, HrirLength, AccumSamples); - std::fill_n(accum_iter, BufferSize, float2{}); + const auto accum_inprog = AccumSamples.subspan(SamplesToDo, HrirLength); + auto accum_iter = std::copy(accum_inprog.cbegin(), accum_inprog.cend(), AccumSamples.begin()); + std::fill_n(accum_iter, SamplesToDo, float2{}); } #endif /* CORE_MIXER_HRTFBASE_H */ diff --git a/Engine/lib/openal-soft/core/mixer/mixer_c.cpp b/Engine/lib/openal-soft/core/mixer/mixer_c.cpp index 28a92ef7e..e14454d26 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_c.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_c.cpp @@ -1,14 +1,21 @@ #include "config.h" -#include -#include +#include +#include +#include #include +#include #include "alnumeric.h" +#include "alspan.h" #include "core/bsinc_defs.h" +#include "core/bufferline.h" #include "core/cubic_defs.h" +#include "core/mixer/hrtfdefs.h" +#include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" +#include "opthelpers.h" struct CTag; struct PointTag; @@ -28,191 +35,246 @@ constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; -inline float do_point(const InterpState&, const float *RESTRICT vals, const uint) -{ return vals[0]; } -inline float do_lerp(const InterpState&, const float *RESTRICT vals, const uint frac) -{ return lerpf(vals[0], vals[1], static_cast(frac)*(1.0f/MixerFracOne)); } -inline float do_cubic(const InterpState &istate, const float *RESTRICT vals, const uint frac) +using SamplerNST = float(const al::span, const size_t, const uint) noexcept; + +template +using SamplerT = float(const T&,const al::span,const size_t,const uint) noexcept; + +[[nodiscard]] constexpr +auto do_point(const al::span vals, const size_t pos, const uint) noexcept -> float +{ return vals[pos]; } +[[nodiscard]] constexpr +auto do_lerp(const al::span vals, const size_t pos, const uint frac) noexcept -> float +{ return lerpf(vals[pos+0], vals[pos+1], static_cast(frac)*(1.0f/MixerFracOne)); } +[[nodiscard]] constexpr +auto do_cubic(const CubicState &istate, const al::span vals, const size_t pos, + const uint frac) noexcept -> float { /* Calculate the phase index and factor. */ - const uint pi{frac >> CubicPhaseDiffBits}; + const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; - const float *RESTRICT fil{al::assume_aligned<16>(istate.cubic.filter[pi].mCoeffs)}; - const float *RESTRICT phd{al::assume_aligned<16>(istate.cubic.filter[pi].mDeltas)}; + const auto fil = al::span{istate.filter[pi].mCoeffs}; + const auto phd = al::span{istate.filter[pi].mDeltas}; /* Apply the phase interpolated filter. */ - return (fil[0] + pf*phd[0])*vals[0] + (fil[1] + pf*phd[1])*vals[1] - + (fil[2] + pf*phd[2])*vals[2] + (fil[3] + pf*phd[3])*vals[3]; + return (fil[0] + pf*phd[0])*vals[pos+0] + (fil[1] + pf*phd[1])*vals[pos+1] + + (fil[2] + pf*phd[2])*vals[pos+2] + (fil[3] + pf*phd[3])*vals[pos+3]; } -inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac) +[[nodiscard]] constexpr +auto do_fastbsinc(const BsincState &bsinc, const al::span vals, const size_t pos, + const uint frac) noexcept -> float { - const size_t m{istate.bsinc.m}; + const size_t m{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); /* Calculate the phase index and factor. */ - const uint pi{frac >> BsincPhaseDiffBits}; + const uint pi{frac >> BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)}; - const float *RESTRICT fil{istate.bsinc.filter + m*pi*2}; - const float *RESTRICT phd{fil + m}; - const float *RESTRICT scd{fil + BSincPhaseCount*2*m}; - const float *RESTRICT spd{scd + m}; + const auto fil = bsinc.filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(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[pos+j_f]; + return r; +} +[[nodiscard]] constexpr +auto do_bsinc(const BsincState &bsinc, const al::span vals, const size_t pos, + const uint frac) noexcept -> float +{ + const size_t m{bsinc.m}; + ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); + + /* Calculate the phase index and factor. */ + const uint pi{frac >> BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); + const float pf{static_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)}; + + const auto fil = bsinc.filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); + const auto scd = fil.subspan(BSincPhaseCount*2_uz*m); + const auto spd = scd.subspan(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 uint frac) -{ - const size_t m{istate.bsinc.m}; - ASSUME(m > 0); - - /* Calculate the phase index and factor. */ - const uint pi{frac >> BsincPhaseDiffBits}; - const float pf{static_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)}; - - const float *RESTRICT fil{istate.bsinc.filter + m*pi*2}; - const float *RESTRICT 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]; + for(size_t j_f{0};j_f < m;++j_f) + r += (fil[j_f] + bsinc.sf*scd[j_f] + pf*(phd[j_f] + bsinc.sf*spd[j_f])) * vals[pos+j_f]; return r; } -using SamplerT = float(&)(const InterpState&, const float*RESTRICT, const uint); -template -void DoResample(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +template +void DoResample(const al::span src, uint frac, const uint increment, + const al::span dst) { - const InterpState istate{*state}; ASSUME(frac < MixerFracOne); - for(float &out : dst) + size_t pos{0}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment]() -> float { - out = Sampler(istate, src, frac); - + const float output{Sampler(src, pos, frac)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } -inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, - const float left, const float right) +template Sampler> +void DoResample(const U istate, const al::span src, uint frac, const uint increment, + const al::span dst) +{ + ASSUME(frac < MixerFracOne); + size_t pos{0}; + std::generate(dst.begin(), dst.end(), [istate,src,&pos,&frac,increment]() -> float + { + const float output{Sampler(istate, src, pos, frac)}; + frac += increment; + pos += frac>>MixerFracBits; + frac &= MixerFracMask; + return output; + }); +} + +inline void ApplyCoeffs(const al::span Values, const size_t IrSize, + const ConstHrirSpan Coeffs, const float left, const float right) noexcept { ASSUME(IrSize >= MinIrLength); - for(size_t c{0};c < IrSize;++c) - { - Values[c][0] += Coeffs[c][0] * left; - Values[c][1] += Coeffs[c][1] * right; - } + ASSUME(IrSize <= HrirLength); + + auto mix_impulse = [left,right](const float2 &value, const float2 &coeff) noexcept -> float2 + { return float2{{value[0] + coeff[0]*left, value[1] + coeff[1]*right}}; }; + std::transform(Values.cbegin(), Values.cbegin()+ptrdiff_t(IrSize), Coeffs.cbegin(), + Values.begin(), mix_impulse); } -force_inline void MixLine(const al::span InSamples, float *RESTRICT dst, - float &CurrentGain, const float TargetGain, const float delta, const size_t min_len, +force_inline void MixLine(al::span InSamples, const al::span dst, + float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, size_t Counter) { - float gain{CurrentGain}; - const float step{(TargetGain-gain) * delta}; + const float step{(TargetGain-CurrentGain) * delta}; - size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = TargetGain; - else + auto output = dst.begin(); + if(std::abs(step) > std::numeric_limits::epsilon()) { - float step_count{0.0f}; - for(;pos != min_len;++pos) - { - dst[pos] += InSamples[pos] * (gain + step*step_count); - step_count += 1.0f; - } - if(pos == Counter) - gain = TargetGain; - else - gain += step*step_count; - } - CurrentGain = gain; + auto input = InSamples.first(fade_len); + InSamples = InSamples.subspan(fade_len); - if(!(std::abs(gain) > GainSilenceThreshold)) + const float gain{CurrentGain}; + float step_count{0.0f}; + output = std::transform(input.begin(), input.end(), output, output, + [gain,step,&step_count](const float in, float out) noexcept -> float + { + out += in * (gain + step*step_count); + step_count += 1.0f; + return out; + }); + + if(fade_len < Counter) + { + CurrentGain = gain + step*step_count; + return; + } + } + CurrentGain = TargetGain; + + if(!(std::abs(TargetGain) > GainSilenceThreshold)) return; - for(;pos != InSamples.size();++pos) - dst[pos] += InSamples[pos] * gain; + + std::transform(InSamples.begin(), InSamples.end(), output, output, + [TargetGain](const float in, const float out) noexcept -> float + { return out + in*TargetGain; }); } } // namespace template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) -{ DoResample(state, src, frac, increment, dst); } +{ DoResample(src.subspan(MaxResamplerEdge), frac, increment, dst); } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) -{ DoResample(state, src, frac, increment, dst); } +{ DoResample(src.subspan(MaxResamplerEdge), frac, increment, dst); } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, +void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) -{ DoResample(state, src-1, frac, increment, dst); } +{ + DoResample(std::get(*state), src.subspan(MaxResamplerEdge-1), + frac, increment, dst); +} template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) +{ + const auto istate = std::get(*state); + ASSUME(istate.l <= MaxResamplerEdge); + DoResample(istate, src.subspan(MaxResamplerEdge-istate.l), frac, + increment, dst); +} + +template<> +void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) -{ DoResample(state, src-state->bsinc.l, frac, increment, dst); } - -template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) -{ DoResample(state, src-state->bsinc.l, frac, increment, dst); } +{ + const auto istate = std::get(*state); + ASSUME(istate.l <= MaxResamplerEdge); + DoResample(istate, src.subspan(MaxResamplerEdge-istate.l), frac, + increment, dst); +} template<> -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +void MixHrtfBlend_(const al::span InSamples,const al::span AccumSamples, + const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, + const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); + SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, - IrSize, BufferSize); + IrSize, SamplesToDo); } template<> void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); + const auto fade_len = std::min(Counter, InSamples.size()); + auto curgains = CurrentGains.begin(); + auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) - MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, - *TargetGains++, delta, min_len, Counter); + MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, + fade_len, Counter); } template<> -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); + const auto fade_len = std::min(Counter, InSamples.size()); - MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, - TargetGain, delta, min_len, Counter); + MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, Counter); } diff --git a/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp b/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp index ef2936b32..bbbe44708 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp @@ -2,14 +2,22 @@ #include -#include +#include +#include +#include #include +#include #include "alnumeric.h" +#include "alspan.h" #include "core/bsinc_defs.h" +#include "core/bufferline.h" #include "core/cubic_defs.h" +#include "core/mixer/hrtfdefs.h" +#include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" +#include "opthelpers.h" struct NEONTag; struct LerpTag; @@ -22,6 +30,8 @@ struct FastBSincTag; #pragma GCC target("fpu=neon") #endif +using uint = unsigned int; + namespace { constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits}; @@ -32,6 +42,19 @@ constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; +force_inline +void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3) noexcept +{ + float32x4x2_t t0_{vzipq_f32(x0, x2)}; + float32x4x2_t t1_{vzipq_f32(x1, x3)}; + float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])}; + float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])}; + x0 = u0_.val[0]; + x1 = u0_.val[1]; + x2 = u1_.val[0]; + x3 = u1_.val[1]; +} + inline float32x4_t set_f4(float l0, float l1, float l2, float l3) { float32x4_t ret{vmovq_n_f32(l0)}; @@ -41,60 +64,59 @@ inline float32x4_t set_f4(float l0, float l1, float l2, float l3) return ret; } -inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, - const float left, const float right) +inline void ApplyCoeffs(const al::span Values, const size_t IrSize, + const ConstHrirSpan Coeffs, const float left, const float right) { - float32x4_t leftright4; - { - float32x2_t leftright2{vmov_n_f32(left)}; - leftright2 = vset_lane_f32(right, leftright2, 1); - leftright4 = vcombine_f32(leftright2, leftright2); - } - ASSUME(IrSize >= MinIrLength); - for(size_t c{0};c < IrSize;c += 2) + ASSUME(IrSize <= HrirLength); + + auto dup_samples = [left,right]() -> float32x4_t { - float32x4_t vals = vld1q_f32(&Values[c][0]); - float32x4_t coefs = vld1q_f32(&Coeffs[c][0]); + float32x2_t leftright2{vset_lane_f32(right, vmov_n_f32(left), 1)}; + return vcombine_f32(leftright2, leftright2); + }; + const auto leftright4 = dup_samples(); + const auto count4 = size_t{(IrSize+1) >> 1}; - vals = vmlaq_f32(vals, coefs, leftright4); - - vst1q_f32(&Values[c][0], vals); - } + const auto vals4 = al::span{reinterpret_cast(Values[0].data()), count4}; + const auto coeffs4 = al::span{reinterpret_cast(Coeffs[0].data()), count4}; + std::transform(vals4.cbegin(), vals4.cend(), coeffs4.cbegin(), vals4.begin(), + [leftright4](const float32x4_t &val, const float32x4_t &coeff) -> float32x4_t + { return vmlaq_f32(val, coeff, leftright4); }); } -force_inline void MixLine(const al::span InSamples, float *RESTRICT dst, - float &CurrentGain, const float TargetGain, const float delta, const size_t min_len, - const size_t aligned_len, size_t Counter) +force_inline void MixLine(const al::span InSamples, const al::span dst, + float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, + const size_t realign_len, size_t Counter) { - float gain{CurrentGain}; - const float step{(TargetGain-gain) * delta}; + const auto step = float{(TargetGain-CurrentGain) * delta}; - size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = TargetGain; - else + auto pos = size_t{0}; + if(std::abs(step) > std::numeric_limits::epsilon()) { - float step_count{0.0f}; + const auto gain = float{CurrentGain}; + auto step_count = float{0.0f}; /* Mix with applying gain steps in aligned multiples of 4. */ - if(size_t todo{min_len >> 2}) + if(const size_t todo{fade_len >> 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{vdupq_n_f32(0.0f)}; - step_count4 = vsetq_lane_f32(1.0f, step_count4, 1); - step_count4 = vsetq_lane_f32(2.0f, step_count4, 2); - step_count4 = vsetq_lane_f32(3.0f, step_count4, 3); + const auto four4 = vdupq_n_f32(4.0f); + const auto step4 = vdupq_n_f32(step); + const auto gain4 = vdupq_n_f32(gain); + auto step_count4 = set_f4(0.0f, 1.0f, 2.0f, 3.0f); + + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.first(todo); + const auto out4 = al::span{reinterpret_cast(dst.data()), dst.size()/4}; + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4,step4,four4,&step_count4](const float32x4_t val4, float32x4_t dry4) + { + /* dry += val * (gain + step*step_count) */ + dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4)); + step_count4 = vaddq_f32(step_count4, four4); + return dry4; + }); + pos += in4.size()*4; - do { - const float32x4_t val4 = vld1q_f32(&InSamples[pos]); - float32x4_t dry4 = vld1q_f32(&dst[pos]); - dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4)); - step_count4 = vaddq_f32(step_count4, four4); - vst1q_f32(&dst[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. @@ -102,152 +124,242 @@ force_inline void MixLine(const al::span InSamples, float *RESTRICT step_count = vgetq_lane_f32(step_count4, 0); } /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - for(size_t leftover{min_len&3};leftover;++pos,--leftover) + if(const size_t leftover{fade_len&3}) { - dst[pos] += InSamples[pos] * (gain + step*step_count); - step_count += 1.0f; + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [gain,step,&step_count](const float val, float dry) noexcept -> float + { + dry += val * (gain + step*step_count); + step_count += 1.0f; + return dry; + }); + pos += leftover; + } + if(pos < Counter) + { + CurrentGain = gain + step*step_count; + return; } - if(pos == Counter) - gain = TargetGain; - else - gain += step*step_count; /* Mix until pos is aligned with 4 or the mix is done. */ - for(size_t leftover{aligned_len&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; - } - CurrentGain = gain; + if(const size_t leftover{realign_len&3}) + { + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); - if(!(std::abs(gain) > GainSilenceThreshold)) - return; - if(size_t todo{(InSamples.size()-pos) >> 2}) - { - const float32x4_t gain4 = vdupq_n_f32(gain); - do { - const float32x4_t val4 = vld1q_f32(&InSamples[pos]); - float32x4_t dry4 = vld1q_f32(&dst[pos]); - dry4 = vmlaq_f32(dry4, val4, gain4); - vst1q_f32(&dst[pos], dry4); - pos += 4; - } while(--todo); + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); + pos += leftover; + } + } + CurrentGain = TargetGain; + + if(!(std::abs(TargetGain) > GainSilenceThreshold)) + return; + if(const size_t todo{(InSamples.size()-pos) >> 2}) + { + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.last(todo); + const auto out = dst.subspan(pos); + const auto out4 = al::span{reinterpret_cast(out.data()), out.size()/4}; + + const auto gain4 = vdupq_n_f32(TargetGain); + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4](const float32x4_t val4, const float32x4_t dry4) -> float32x4_t + { return vmlaq_f32(dry4, val4, gain4); }); + pos += in4.size()*4; + } + if(const size_t leftover{(InSamples.size()-pos)&3}) + { + const auto in = InSamples.last(leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); } - for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; } } // namespace template<> -void Resample_(const InterpState*, const float *RESTRICT src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); - const int32x4_t increment4 = vdupq_n_s32(static_cast(increment*4)); + const uint32x4_t increment4 = vdupq_n_u32(increment*4u); const float32x4_t fracOne4 = vdupq_n_f32(1.0f/MixerFracOne); - const int32x4_t fracMask4 = vdupq_n_s32(MixerFracMask); - alignas(16) uint pos_[4], frac_[4]; - int32x4_t pos4, frac4; + const uint32x4_t fracMask4 = vdupq_n_u32(MixerFracMask); - InitPosArrays(frac, increment, frac_, pos_); - frac4 = vld1q_s32(reinterpret_cast(frac_)); - pos4 = vld1q_s32(reinterpret_cast(pos_)); + alignas(16) std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); + uint32x4_t frac4 = vld1q_u32(frac_.data()); + uint32x4_t pos4 = vld1q_u32(pos_.data()); - auto dst_iter = dst.begin(); - for(size_t todo{dst.size()>>2};todo;--todo) + auto vecout = al::span{reinterpret_cast(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> float32x4_t { - 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 uint pos0{vgetq_lane_u32(pos4, 0)}; + const uint pos1{vgetq_lane_u32(pos4, 1)}; + const uint pos2{vgetq_lane_u32(pos4, 2)}; + const uint pos3{vgetq_lane_u32(pos4, 3)}; + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); const float32x4_t val1{set_f4(src[pos0], src[pos1], src[pos2], src[pos3])}; - const float32x4_t val2{set_f4(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])}; + const float32x4_t val2{set_f4(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; /* 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 mu{vmulq_f32(vcvtq_f32_u32(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, MixerFracBits)); - frac4 = vandq_s32(frac4, fracMask4); - } + frac4 = vaddq_u32(frac4, increment4); + pos4 = vaddq_u32(pos4, vshrq_n_u32(frac4, MixerFracBits)); + frac4 = vandq_u32(frac4, fracMask4); + return out; + }); if(size_t todo{dst.size()&3}) { - src += static_cast(vgetq_lane_s32(pos4, 0)); - frac = static_cast(vgetq_lane_s32(frac4, 0)); + auto pos = size_t{vgetq_lane_u32(pos4, 0)}; + frac = vgetq_lane_u32(frac4, 0); - do { - *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); + const auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment] + { + const float output{lerpf(src[pos+0], src[pos+1], + static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } while(--todo); + return output; + }); } } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); - const CubicCoefficients *RESTRICT filter = al::assume_aligned<16>(state->cubic.filter); + const auto filter = std::get(*state).filter; - src -= 1; - for(float &out_sample : dst) + const uint32x4_t increment4{vdupq_n_u32(increment*4u)}; + const uint32x4_t fracMask4{vdupq_n_u32(MixerFracMask)}; + const float32x4_t fracDiffOne4{vdupq_n_f32(1.0f/CubicPhaseDiffOne)}; + const uint32x4_t fracDiffMask4{vdupq_n_u32(CubicPhaseDiffMask)}; + + alignas(16) std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); + uint32x4_t frac4{vld1q_u32(frac_.data())}; + uint32x4_t pos4{vld1q_u32(pos_.data())}; + + auto vecout = al::span{reinterpret_cast(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { - const uint pi{frac >> CubicPhaseDiffBits}; - const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; - const float32x4_t pf4{vdupq_n_f32(pf)}; + const uint pos0{vgetq_lane_u32(pos4, 0)}; + const uint pos1{vgetq_lane_u32(pos4, 1)}; + const uint pos2{vgetq_lane_u32(pos4, 2)}; + const uint pos3{vgetq_lane_u32(pos4, 3)}; + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const float32x4_t val0{vld1q_f32(&src[pos0])}; + const float32x4_t val1{vld1q_f32(&src[pos1])}; + const float32x4_t val2{vld1q_f32(&src[pos2])}; + const float32x4_t val3{vld1q_f32(&src[pos3])}; - /* Apply the phase interpolated filter. */ + const uint32x4_t pi4{vshrq_n_u32(frac4, CubicPhaseDiffBits)}; + const uint pi0{vgetq_lane_u32(pi4, 0)}; ASSUME(pi0 < CubicPhaseCount); + const uint pi1{vgetq_lane_u32(pi4, 1)}; ASSUME(pi1 < CubicPhaseCount); + const uint pi2{vgetq_lane_u32(pi4, 2)}; ASSUME(pi2 < CubicPhaseCount); + const uint pi3{vgetq_lane_u32(pi4, 3)}; ASSUME(pi3 < CubicPhaseCount); - /* f = fil + pf*phd */ - const float32x4_t f4 = vmlaq_f32(vld1q_f32(filter[pi].mCoeffs), pf4, - vld1q_f32(filter[pi].mDeltas)); - /* r = f*src */ - float32x4_t r4{vmulq_f32(f4, vld1q_f32(src))}; + const float32x4_t pf4{vmulq_f32(vcvtq_f32_u32(vandq_u32(frac4, fracDiffMask4)), + fracDiffOne4)}; - r4 = vaddq_f32(r4, vrev64q_f32(r4)); - out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + float32x4_t r0{vmulq_f32(val0, + vmlaq_f32(vld1q_f32(filter[pi0].mCoeffs.data()), vdupq_lane_f32(vget_low_f32(pf4), 0), + vld1q_f32(filter[pi0].mDeltas.data())))}; + float32x4_t r1{vmulq_f32(val1, + vmlaq_f32(vld1q_f32(filter[pi1].mCoeffs.data()), vdupq_lane_f32(vget_low_f32(pf4), 1), + vld1q_f32(filter[pi1].mDeltas.data())))}; + float32x4_t r2{vmulq_f32(val2, + vmlaq_f32(vld1q_f32(filter[pi2].mCoeffs.data()), vdupq_lane_f32(vget_high_f32(pf4), 0), + vld1q_f32(filter[pi2].mDeltas.data())))}; + float32x4_t r3{vmulq_f32(val3, + vmlaq_f32(vld1q_f32(filter[pi3].mCoeffs.data()), vdupq_lane_f32(vget_high_f32(pf4), 1), + vld1q_f32(filter[pi3].mDeltas.data())))}; - frac += increment; - src += frac>>MixerFracBits; - frac &= MixerFracMask; + vtranspose4(r0, r1, r2, r3); + r0 = vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3)); + + frac4 = vaddq_u32(frac4, increment4); + pos4 = vaddq_u32(pos4, vshrq_n_u32(frac4, MixerFracBits)); + frac4 = vandq_u32(frac4, fracMask4); + return r0; + }); + + if(const size_t todo{dst.size()&3}) + { + auto pos = size_t{vgetq_lane_u32(pos4, 0)}; + frac = vgetq_lane_u32(frac4, 0); + + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] + { + const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); + const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; + const float32x4_t pf4{vdupq_n_f32(pf)}; + + const float32x4_t f4{vmlaq_f32(vld1q_f32(filter[pi].mCoeffs.data()), pf4, + vld1q_f32(filter[pi].mDeltas.data()))}; + float32x4_t r4{vmulq_f32(f4, vld1q_f32(&src[pos]))}; + + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; + + frac += increment; + pos += frac>>MixerFracBits; + frac &= MixerFracMask; + return output; + }); } } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span 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}; + const auto &bsinc = std::get(*state); + const auto sf4 = vdupq_n_f32(bsinc.sf); + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + const auto filter = bsinc.filter.first(4_uz*BSincPhaseCount*m); + + ASSUME(bsinc.l <= MaxResamplerEdge); + auto pos = size_t{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,sf4,m,filter]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const uint pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // 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 *RESTRICT fil{filter + m*pi*2}; - const float *RESTRICT phd{fil + m}; - const float *RESTRICT scd{fil + BSincPhaseCount*2*m}; - const float *RESTRICT spd{scd + m}; + const auto fil = filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); + const auto scd = fil.subspan(2_uz*BSincPhaseCount*m); + const auto spd = scd.subspan(m); size_t td{m >> 2}; size_t j{0u}; @@ -257,41 +369,46 @@ void Resample_(const InterpState *state, const float *RESTRICT vmlaq_f32(vld1q_f32(&fil[j]), sf4, vld1q_f32(&scd[j])), pf4, vmlaq_f32(vld1q_f32(&phd[j]), sf4, vld1q_f32(&spd[j]))); /* r += f*src */ - r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j])); + r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[pos+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); + const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { - const float *const filter{state->bsinc.filter}; - const size_t m{state->bsinc.m}; + const auto &bsinc = std::get(*state); + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + const auto filter = bsinc.filter.first(2_uz*BSincPhaseCount*m); + + ASSUME(bsinc.l <= MaxResamplerEdge); + auto pos = size_t{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,m,filter]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const uint pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the phase interpolated filter. float32x4_t r4{vdupq_n_f32(0.0f)}; { const float32x4_t pf4{vdupq_n_f32(pf)}; - const float *RESTRICT fil{filter + m*pi*2}; - const float *RESTRICT phd{fil + m}; + const auto fil = filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); size_t td{m >> 2}; size_t j{0u}; @@ -299,64 +416,69 @@ void Resample_(const InterpState *state, const float *REST /* f = fil + pf*phd */ const float32x4_t f4 = vmlaq_f32(vld1q_f32(&fil[j]), pf4, vld1q_f32(&phd[j])); /* r += f*src */ - r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j])); + r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[pos+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); + const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } template<> -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +void MixHrtfBlend_(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); + SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, - IrSize, BufferSize); + IrSize, SamplesToDo); } template<> -void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) +void Mix_(const al::span InSamples,const al::span OutBuffer, + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); - const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; + auto curgains = CurrentGains.begin(); + auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) - MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, - *TargetGains++, delta, min_len, aligned_len, Counter); + MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, + fade_len, realign_len, Counter); } template<> -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); - const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; - MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len, - aligned_len, Counter); + MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter); } diff --git a/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp b/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp index 0aa5d5fba..df42823a7 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp @@ -1,15 +1,25 @@ #include "config.h" +#include #include -#include +#include +#include +#include +#include #include +#include #include "alnumeric.h" +#include "alspan.h" #include "core/bsinc_defs.h" +#include "core/bufferline.h" #include "core/cubic_defs.h" +#include "core/mixer/hrtfdefs.h" +#include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" +#include "opthelpers.h" struct SSETag; struct CubicTag; @@ -31,42 +41,48 @@ constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; -#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z)) +force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept +{ return _mm_add_ps(x, _mm_mul_ps(y, z)); } -inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, - const float left, const float right) +inline void ApplyCoeffs(const al::span Values, const size_t IrSize, + const ConstHrirSpan Coeffs, const float left, const float right) { - const __m128 lrlr{_mm_setr_ps(left, right, left, right)}; - ASSUME(IrSize >= MinIrLength); + ASSUME(IrSize <= HrirLength); + const auto lrlr = _mm_setr_ps(left, right, left, right); + /* Round up the IR size to a multiple of 2 for SIMD (2 IRs for 2 channels + * is 4 floats), to avoid cutting the last sample for odd IR counts. The + * underlying HRIR is a fixed-size multiple of 2, any extra samples are + * either 0 (silence) or more IR samples that get applied for "free". + */ + const auto count4 = size_t{(IrSize+1) >> 1}; + /* 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(Values)&15)) + if(!(reinterpret_cast(Values.data())&15)) { - for(size_t i{0};i < IrSize;i += 2) - { - const __m128 coeffs{_mm_load_ps(Coeffs[i].data())}; - __m128 vals{_mm_load_ps(Values[i].data())}; - vals = MLA4(vals, lrlr, coeffs); - _mm_store_ps(Values[i].data(), vals); - } + const auto vals4 = al::span{reinterpret_cast<__m128*>(Values[0].data()), count4}; + const auto coeffs4 = al::span{reinterpret_cast(Coeffs[0].data()), count4}; + + std::transform(vals4.cbegin(), vals4.cend(), coeffs4.cbegin(), vals4.begin(), + [lrlr](const __m128 &val, const __m128 &coeff) -> __m128 + { return vmadd(val, coeff, lrlr); }); } else { - __m128 imp0, imp1; - __m128 coeffs{_mm_load_ps(Coeffs[0].data())}; - __m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(Values[0].data()))}; - imp0 = _mm_mul_ps(lrlr, coeffs); + auto coeffs = _mm_load_ps(Coeffs[0].data()); + auto vals = _mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(Values[0].data())); + auto imp0 = _mm_mul_ps(lrlr, coeffs); vals = _mm_add_ps(imp0, vals); _mm_storel_pi(reinterpret_cast<__m64*>(Values[0].data()), vals); - size_t td{((IrSize+1)>>1) - 1}; + size_t td{count4 - 1}; size_t i{1}; do { coeffs = _mm_load_ps(Coeffs[i+1].data()); vals = _mm_load_ps(Values[i].data()); - imp1 = _mm_mul_ps(lrlr, coeffs); + const auto 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].data(), vals); @@ -80,37 +96,38 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Cons } } -force_inline void MixLine(const al::span InSamples, float *RESTRICT dst, - float &CurrentGain, const float TargetGain, const float delta, const size_t min_len, - const size_t aligned_len, size_t Counter) +force_inline void MixLine(const al::span InSamples, const al::span dst, + float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, + const size_t realign_len, size_t Counter) { - float gain{CurrentGain}; - const float step{(TargetGain-gain) * delta}; + const auto step = float{(TargetGain-CurrentGain) * delta}; size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = TargetGain; - else + if(std::abs(step) > std::numeric_limits::epsilon()) { - float step_count{0.0f}; + const auto gain = float{CurrentGain}; + auto step_count = float{0.0f}; /* Mix with applying gain steps in aligned multiples of 4. */ - if(size_t todo{min_len >> 2}) + if(const size_t todo{fade_len >> 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(&InSamples[pos])}; - __m128 dry4{_mm_load_ps(&dst[pos])}; + const auto four4 = _mm_set1_ps(4.0f); + const auto step4 = _mm_set1_ps(step); + const auto gain4 = _mm_set1_ps(gain); + auto step_count4 = _mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f); - /* dry += val * (gain + step*step_count) */ - dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4)); + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.first(todo); + const auto out4 = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4,step4,four4,&step_count4](const __m128 val4, __m128 dry4) -> __m128 + { + /* dry += val * (gain + step*step_count) */ + dry4 = vmadd(dry4, val4, vmadd(gain4, step4, step_count4)); + step_count4 = _mm_add_ps(step_count4, four4); + return dry4; + }); + pos += in4.size()*4; - _mm_store_ps(&dst[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. @@ -118,210 +135,252 @@ force_inline void MixLine(const al::span InSamples, float *RESTRICT step_count = _mm_cvtss_f32(step_count4); } /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - for(size_t leftover{min_len&3};leftover;++pos,--leftover) + if(const size_t leftover{fade_len&3}) { - dst[pos] += InSamples[pos] * (gain + step*step_count); - step_count += 1.0f; + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [gain,step,&step_count](const float val, float dry) noexcept -> float + { + dry += val * (gain + step*step_count); + step_count += 1.0f; + return dry; + }); + pos += leftover; + } + if(pos < Counter) + { + CurrentGain = gain + step*step_count; + return; } - if(pos == Counter) - gain = TargetGain; - else - gain += step*step_count; /* Mix until pos is aligned with 4 or the mix is done. */ - for(size_t leftover{aligned_len&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; - } - CurrentGain = gain; + if(const size_t leftover{realign_len&3}) + { + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); - if(!(std::abs(gain) > GainSilenceThreshold)) + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); + pos += leftover; + } + } + CurrentGain = TargetGain; + + if(!(std::abs(TargetGain) > GainSilenceThreshold)) return; if(size_t todo{(InSamples.size()-pos) >> 2}) { - const __m128 gain4{_mm_set1_ps(gain)}; - do { - const __m128 val4{_mm_load_ps(&InSamples[pos])}; - __m128 dry4{_mm_load_ps(&dst[pos])}; - dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4)); - _mm_store_ps(&dst[pos], dry4); - pos += 4; - } while(--todo); + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.last(todo); + const auto out = dst.subspan(pos); + const auto out4 = al::span{reinterpret_cast<__m128*>(out.data()), out.size()/4}; + + const auto gain4 = _mm_set1_ps(TargetGain); + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4](const __m128 val4, const __m128 dry4) -> __m128 + { return vmadd(dry4, val4, gain4); }); + pos += in4.size()*4; + } + if(const size_t leftover{(InSamples.size()-pos)&3}) + { + const auto in = InSamples.last(leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); } - for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; } } // namespace template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); - const CubicCoefficients *RESTRICT filter = al::assume_aligned<16>(state->cubic.filter); + const auto filter = std::get(*state).filter; - src -= 1; - for(float &out_sample : dst) + size_t pos{MaxResamplerEdge-1}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,filter]() -> float { - const uint pi{frac >> CubicPhaseDiffBits}; + const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; const __m128 pf4{_mm_set1_ps(pf)}; /* Apply the phase interpolated filter. */ /* f = fil + pf*phd */ - const __m128 f4 = MLA4(_mm_load_ps(filter[pi].mCoeffs), pf4, - _mm_load_ps(filter[pi].mDeltas)); + const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, + _mm_load_ps(filter[pi].mDeltas.data())); /* r = f*src */ - __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(src))}; + __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; 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); + const float output{_mm_cvtss_f32(r4)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { - const float *const filter{state->bsinc.filter}; - const __m128 sf4{_mm_set1_ps(state->bsinc.sf)}; - const size_t m{state->bsinc.m}; + const auto &bsinc = std::get(*state); + const auto sf4 = _mm_set1_ps(bsinc.sf); + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + const auto filter = bsinc.filter.first(4_uz*BSincPhaseCount*m); + + ASSUME(bsinc.l <= MaxResamplerEdge); + auto pos = size_t{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,sf4,m,filter]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const size_t pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the scale and phase interpolated filter. - __m128 r4{_mm_setzero_ps()}; + auto r4 = _mm_setzero_ps(); { - const __m128 pf4{_mm_set1_ps(pf)}; - const float *RESTRICT fil{filter + m*pi*2}; - const float *RESTRICT phd{fil + m}; - const float *RESTRICT scd{fil + BSincPhaseCount*2*m}; - const float *RESTRICT spd{scd + m}; - size_t td{m >> 2}; - size_t j{0u}; + const auto pf4 = _mm_set1_ps(pf); + const auto fil = filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); + const auto scd = fil.subspan(2_uz*BSincPhaseCount*m); + const auto spd = scd.subspan(m); + auto td = size_t{m >> 2}; + auto j = size_t{0}; do { /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ - const __m128 f4 = MLA4( - MLA4(_mm_load_ps(&fil[j]), sf4, _mm_load_ps(&scd[j])), - pf4, MLA4(_mm_load_ps(&phd[j]), sf4, _mm_load_ps(&spd[j]))); + const __m128 f4 = vmadd( + vmadd(_mm_load_ps(&fil[j]), sf4, _mm_load_ps(&scd[j])), + pf4, vmadd(_mm_load_ps(&phd[j]), sf4, _mm_load_ps(&spd[j]))); /* r += f*src */ - r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j])); + r4 = vmadd(r4, f4, _mm_loadu_ps(&src[pos+j])); j += 4; } while(--td); } 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); + const auto output = _mm_cvtss_f32(r4); frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { - const float *const filter{state->bsinc.filter}; - const size_t m{state->bsinc.m}; + const auto &bsinc = std::get(*state); + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + const auto filter = bsinc.filter.first(2_uz*m*BSincPhaseCount); + + ASSUME(bsinc.l <= MaxResamplerEdge); + size_t pos{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,filter,m]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const size_t pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the phase interpolated filter. - __m128 r4{_mm_setzero_ps()}; + auto r4 = _mm_setzero_ps(); { - const __m128 pf4{_mm_set1_ps(pf)}; - const float *RESTRICT fil{filter + m*pi*2}; - const float *RESTRICT phd{fil + m}; - size_t td{m >> 2}; - size_t j{0u}; + const auto pf4 = _mm_set1_ps(pf); + const auto fil = filter.subspan(2_uz*m*pi); + const auto phd = fil.subspan(m); + auto td = size_t{m >> 2}; + auto j = size_t{0}; do { /* f = fil + pf*phd */ - const __m128 f4 = MLA4(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j])); + const auto f4 = vmadd(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j])); /* r += f*src */ - r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j])); + r4 = vmadd(r4, f4, _mm_loadu_ps(&src[pos+j])); j += 4; } while(--td); } 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); + const auto output = _mm_cvtss_f32(r4); frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } template<> -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +void MixHrtfBlend_(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); + SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, - IrSize, BufferSize); + IrSize, SamplesToDo); } template<> void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); - const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; + auto curgains = CurrentGains.begin(); + auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) - MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, - *TargetGains++, delta, min_len, aligned_len, Counter); + MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, + fade_len, realign_len, Counter); } template<> -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); - const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; - MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len, - aligned_len, Counter); + MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter); } diff --git a/Engine/lib/openal-soft/core/mixer/mixer_sse2.cpp b/Engine/lib/openal-soft/core/mixer/mixer_sse2.cpp index edaaf7a1a..c79d50cab 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_sse2.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_sse2.cpp @@ -23,19 +23,42 @@ #include #include +#include +#include +#include +#include + #include "alnumeric.h" +#include "alspan.h" +#include "core/cubic_defs.h" +#include "core/resampler_limits.h" #include "defs.h" +#include "opthelpers.h" struct SSE2Tag; struct LerpTag; +struct CubicTag; #if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE2__) #pragma GCC target("sse2") #endif +using uint = unsigned int; + +namespace { + +constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; +constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; +constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; + +force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept +{ return _mm_add_ps(x, _mm_mul_ps(y, z)); } + +} // namespace + template<> -void Resample_(const InterpState*, const float *RESTRICT src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); @@ -44,47 +67,148 @@ void Resample_(const InterpState*, const float *RESTRICT src, u const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)}; const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; - alignas(16) uint pos_[4], frac_[4]; - InitPosArrays(frac, increment, frac_, pos_); + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; - auto dst_iter = dst.begin(); - for(size_t todo{dst.size()>>2};todo;--todo) + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128 { - const int pos0{_mm_cvtsi128_si32(pos4)}; - const int pos1{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))}; - const int pos2{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))}; - const int pos3{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))}; - 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])}; + const auto pos0 = static_cast(_mm_cvtsi128_si32(pos4)); + const auto pos1 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))); + const auto pos2 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))); + const auto pos3 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))); + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const __m128 val1{_mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3])}; + const __m128 val2{_mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; /* 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, MixerFracBits)); + frac4 = _mm_and_si128(frac4, fracMask4); + return out; + }); + + if(size_t todo{dst.size()&3}) + { + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; + frac = static_cast(_mm_cvtsi128_si32(frac4)); + + const auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment]() + { + const float smp{lerpf(src[pos+0], src[pos+1], + static_cast(frac) * (1.0f/MixerFracOne))}; + + frac += increment; + pos += frac>>MixerFracBits; + frac &= MixerFracMask; + return smp; + }); + } +} + +template<> +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) +{ + ASSUME(frac < MixerFracOne); + + const auto filter = std::get(*state).filter; + + const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; + const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; + const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)}; + const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)}; + + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); + __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), + static_cast(frac_[2]), static_cast(frac_[3]))}; + __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), + static_cast(pos_[2]), static_cast(pos_[3]))}; + + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] + { + const auto pos0 = static_cast(_mm_cvtsi128_si32(pos4)); + const auto pos1 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))); + const auto pos2 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))); + const auto pos3 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))); + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const __m128 val0{_mm_loadu_ps(&src[pos0])}; + const __m128 val1{_mm_loadu_ps(&src[pos1])}; + const __m128 val2{_mm_loadu_ps(&src[pos2])}; + const __m128 val3{_mm_loadu_ps(&src[pos3])}; + + const __m128i pi4{_mm_srli_epi32(frac4, CubicPhaseDiffBits)}; + const auto pi0 = static_cast(_mm_cvtsi128_si32(pi4)); + const auto pi1 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 4))); + const auto pi2 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 8))); + const auto pi3 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 12))); + ASSUME(pi0 < CubicPhaseCount); ASSUME(pi1 < CubicPhaseCount); + ASSUME(pi2 < CubicPhaseCount); ASSUME(pi3 < CubicPhaseCount); + + const __m128 pf4{_mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(frac4, fracDiffMask4)), + fracDiffOne4)}; + + __m128 r0{_mm_mul_ps(val0, + vmadd(_mm_load_ps(filter[pi0].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(0, 0, 0, 0)), + _mm_load_ps(filter[pi0].mDeltas.data())))}; + __m128 r1{_mm_mul_ps(val1, + vmadd(_mm_load_ps(filter[pi1].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(1, 1, 1, 1)), + _mm_load_ps(filter[pi1].mDeltas.data())))}; + __m128 r2{_mm_mul_ps(val2, + vmadd(_mm_load_ps(filter[pi2].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(2, 2, 2, 2)), + _mm_load_ps(filter[pi2].mDeltas.data())))}; + __m128 r3{_mm_mul_ps(val3, + vmadd(_mm_load_ps(filter[pi3].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(3, 3, 3, 3)), + _mm_load_ps(filter[pi3].mDeltas.data())))}; + + _MM_TRANSPOSE4_PS(r0, r1, r2, r3); + r0 = _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3)); frac4 = _mm_add_epi32(frac4, increment4); pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); frac4 = _mm_and_si128(frac4, fracMask4); - } + return r0; + }); - if(size_t todo{dst.size()&3}) + if(const size_t todo{dst.size()&3}) { - src += static_cast(_mm_cvtsi128_si32(pos4)); + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); - do { - *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] + { + const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); + const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; + const __m128 pf4{_mm_set1_ps(pf)}; + + const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, + _mm_load_ps(filter[pi].mDeltas.data())); + __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; + + 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)); + const float output{_mm_cvtss_f32(r4)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } while(--todo); + return output; + }); } } diff --git a/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp b/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp index 8ccd9fd3a..345330454 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp @@ -24,19 +24,42 @@ #include #include +#include +#include +#include +#include + #include "alnumeric.h" +#include "alspan.h" +#include "core/cubic_defs.h" +#include "core/resampler_limits.h" #include "defs.h" +#include "opthelpers.h" struct SSE4Tag; struct LerpTag; +struct CubicTag; #if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE4_1__) #pragma GCC target("sse4.1") #endif +using uint = unsigned int; + +namespace { + +constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; +constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; +constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; + +force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept +{ return _mm_add_ps(x, _mm_mul_ps(y, z)); } + +} // namespace + template<> -void Resample_(const InterpState*, const float *RESTRICT src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); @@ -45,35 +68,34 @@ void Resample_(const InterpState*, const float *RESTRICT src, u const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)}; const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; - alignas(16) uint pos_[4], frac_[4]; - InitPosArrays(frac, increment, frac_, pos_); + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; - auto dst_iter = dst.begin(); - for(size_t todo{dst.size()>>2};todo;--todo) + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { - 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])}; + const auto pos0 = static_cast(_mm_extract_epi32(pos4, 0)); + const auto pos1 = static_cast(_mm_extract_epi32(pos4, 1)); + const auto pos2 = static_cast(_mm_extract_epi32(pos4, 2)); + const auto pos3 = static_cast(_mm_extract_epi32(pos4, 3)); + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const __m128 val1{_mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3])}; + const __m128 val2{_mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; /* 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, MixerFracBits)); frac4 = _mm_and_si128(frac4, fracMask4); - } + return out; + }); if(size_t todo{dst.size()&3}) { @@ -81,15 +103,117 @@ void Resample_(const InterpState*, const float *RESTRICT src, u * four samples, so the lowest element is the next position to * resample. */ - src += static_cast(_mm_cvtsi128_si32(pos4)); + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); - do { - *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment] + { + const float smp{lerpf(src[pos+0], src[pos+1], + static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } while(--todo); + return smp; + }); + } +} + +template<> +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) +{ + ASSUME(frac < MixerFracOne); + + const auto filter = std::get(*state).filter; + + const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; + const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; + const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)}; + const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)}; + + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); + __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), + static_cast(frac_[2]), static_cast(frac_[3]))}; + __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), + static_cast(pos_[2]), static_cast(pos_[3]))}; + + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] + { + const auto pos0 = static_cast(_mm_extract_epi32(pos4, 0)); + const auto pos1 = static_cast(_mm_extract_epi32(pos4, 1)); + const auto pos2 = static_cast(_mm_extract_epi32(pos4, 2)); + const auto pos3 = static_cast(_mm_extract_epi32(pos4, 3)); + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const __m128 val0{_mm_loadu_ps(&src[pos0])}; + const __m128 val1{_mm_loadu_ps(&src[pos1])}; + const __m128 val2{_mm_loadu_ps(&src[pos2])}; + const __m128 val3{_mm_loadu_ps(&src[pos3])}; + + const __m128i pi4{_mm_srli_epi32(frac4, CubicPhaseDiffBits)}; + const auto pi0 = static_cast(_mm_extract_epi32(pi4, 0)); + const auto pi1 = static_cast(_mm_extract_epi32(pi4, 1)); + const auto pi2 = static_cast(_mm_extract_epi32(pi4, 2)); + const auto pi3 = static_cast(_mm_extract_epi32(pi4, 3)); + ASSUME(pi0 < CubicPhaseCount); ASSUME(pi1 < CubicPhaseCount); + ASSUME(pi2 < CubicPhaseCount); ASSUME(pi3 < CubicPhaseCount); + + const __m128 pf4{_mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(frac4, fracDiffMask4)), + fracDiffOne4)}; + + __m128 r0{_mm_mul_ps(val0, + vmadd(_mm_load_ps(filter[pi0].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(0, 0, 0, 0)), + _mm_load_ps(filter[pi0].mDeltas.data())))}; + __m128 r1{_mm_mul_ps(val1, + vmadd(_mm_load_ps(filter[pi1].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(1, 1, 1, 1)), + _mm_load_ps(filter[pi1].mDeltas.data())))}; + __m128 r2{_mm_mul_ps(val2, + vmadd(_mm_load_ps(filter[pi2].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(2, 2, 2, 2)), + _mm_load_ps(filter[pi2].mDeltas.data())))}; + __m128 r3{_mm_mul_ps(val3, + vmadd(_mm_load_ps(filter[pi3].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(3, 3, 3, 3)), + _mm_load_ps(filter[pi3].mDeltas.data())))}; + + _MM_TRANSPOSE4_PS(r0, r1, r2, r3); + r0 = _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3)); + + frac4 = _mm_add_epi32(frac4, increment4); + pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); + frac4 = _mm_and_si128(frac4, fracMask4); + return r0; + }); + + if(const size_t todo{dst.size()&3}) + { + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; + frac = static_cast(_mm_cvtsi128_si32(frac4)); + + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] + { + const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); + const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; + const __m128 pf4{_mm_set1_ps(pf)}; + + const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, + _mm_load_ps(filter[pi].mDeltas.data())); + __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; + + 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)); + const float output{_mm_cvtss_f32(r4)}; + + frac += increment; + pos += frac>>MixerFracBits; + frac &= MixerFracMask; + return output; + }); } } diff --git a/Engine/lib/openal-soft/core/resampler_limits.h b/Engine/lib/openal-soft/core/resampler_limits.h index 9d4cefdae..a32807e8e 100644 --- a/Engine/lib/openal-soft/core/resampler_limits.h +++ b/Engine/lib/openal-soft/core/resampler_limits.h @@ -5,8 +5,8 @@ * Note that the padding is symmetric (half at the beginning and half at the * end)! */ -constexpr int MaxResamplerPadding{48}; +constexpr unsigned int MaxResamplerPadding{48}; -constexpr int MaxResamplerEdge{MaxResamplerPadding >> 1}; +constexpr unsigned int MaxResamplerEdge{MaxResamplerPadding >> 1}; #endif /* CORE_RESAMPLER_LIMITS_H */ diff --git a/Engine/lib/openal-soft/core/rtkit.cpp b/Engine/lib/openal-soft/core/rtkit.cpp index ff944ebf2..70b5e12bb 100644 --- a/Engine/lib/openal-soft/core/rtkit.cpp +++ b/Engine/lib/openal-soft/core/rtkit.cpp @@ -30,14 +30,14 @@ #include "rtkit.h" -#include +#include #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include -#include +#include #include #include #ifdef __linux__ @@ -153,29 +153,29 @@ int rtkit_get_int_property(DBusConnection *connection, const char *propname, lon } // namespace -int rtkit_get_max_realtime_priority(DBusConnection *connection) +int rtkit_get_max_realtime_priority(DBusConnection *system_bus) { long long retval{}; - int err{rtkit_get_int_property(connection, "MaxRealtimePriority", &retval)}; + int err{rtkit_get_int_property(system_bus, "MaxRealtimePriority", &retval)}; return err < 0 ? err : static_cast(retval); } -int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level) +int rtkit_get_min_nice_level(DBusConnection *system_bus, int *min_nice_level) { long long retval{}; - int err{rtkit_get_int_property(connection, "MinNiceLevel", &retval)}; + int err{rtkit_get_int_property(system_bus, "MinNiceLevel", &retval)}; if(err >= 0) *min_nice_level = static_cast(retval); return err; } -long long rtkit_get_rttime_usec_max(DBusConnection *connection) +long long rtkit_get_rttime_usec_max(DBusConnection *system_bus) { long long retval{}; - int err{rtkit_get_int_property(connection, "RTTimeUSecMax", &retval)}; + int err{rtkit_get_int_property(system_bus, "RTTimeUSecMax", &retval)}; return err < 0 ? err : retval; } -int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) +int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority) { if(thread == 0) thread = _gettid(); @@ -195,7 +195,7 @@ int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) if(!ready) return -ENOMEM; dbus::Error error; - dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1, + dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(system_bus, m.get(), -1, &error.get())}; if(!r) return translate_error(error->name); @@ -205,7 +205,7 @@ int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) return 0; } -int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) +int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level) { if(thread == 0) thread = _gettid(); @@ -225,7 +225,7 @@ int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_ if(!ready) return -ENOMEM; dbus::Error error; - dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1, + dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(system_bus, m.get(), -1, &error.get())}; if(!r) return translate_error(error->name); diff --git a/Engine/lib/openal-soft/core/storage_formats.cpp b/Engine/lib/openal-soft/core/storage_formats.cpp new file mode 100644 index 000000000..51f64644c --- /dev/null +++ b/Engine/lib/openal-soft/core/storage_formats.cpp @@ -0,0 +1,85 @@ + +#include "config.h" + +#include "storage_formats.h" + +#include + + +const char *NameFromFormat(FmtType type) noexcept +{ + switch(type) + { + case FmtUByte: return "UInt8"; + case FmtShort: return "Int16"; + case FmtInt: return "Int32"; + case FmtFloat: return "Float"; + case FmtDouble: return "Double"; + case FmtMulaw: return "muLaw"; + case FmtAlaw: return "aLaw"; + case FmtIMA4: return "IMA4 ADPCM"; + case FmtMSADPCM: return "MS ADPCM"; + } + return ""; +} + +const char *NameFromFormat(FmtChannels channels) noexcept +{ + switch(channels) + { + case FmtMono: return "Mono"; + case FmtStereo: return "Stereo"; + case FmtRear: return "Rear"; + case FmtQuad: return "Quadraphonic"; + case FmtX51: return "Surround 5.1"; + case FmtX61: return "Surround 6.1"; + case FmtX71: return "Surround 7.1"; + case FmtBFormat2D: return "B-Format 2D"; + case FmtBFormat3D: return "B-Format 3D"; + case FmtUHJ2: return "UHJ2"; + case FmtUHJ3: return "UHJ3"; + case FmtUHJ4: return "UHJ4"; + case FmtSuperStereo: return "Super Stereo"; + case FmtMonoDup: return "Mono (dup)"; + } + return ""; +} + +uint BytesFromFmt(FmtType type) noexcept +{ + switch(type) + { + case FmtUByte: return sizeof(std::uint8_t); + case FmtShort: return sizeof(std::int16_t); + case FmtInt: return sizeof(std::int32_t); + case FmtFloat: return sizeof(float); + case FmtDouble: return sizeof(double); + case FmtMulaw: return sizeof(std::uint8_t); + case FmtAlaw: return sizeof(std::uint8_t); + case FmtIMA4: break; + case FmtMSADPCM: break; + } + return 0; +} + +uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept +{ + switch(chans) + { + case FmtMono: return 1; + case FmtStereo: return 2; + case FmtRear: return 2; + case FmtQuad: return 4; + case FmtX51: return 6; + case FmtX61: return 7; + case FmtX71: return 8; + case FmtBFormat2D: return (ambiorder*2) + 1; + case FmtBFormat3D: return (ambiorder+1) * (ambiorder+1); + case FmtUHJ2: return 2; + case FmtUHJ3: return 3; + case FmtUHJ4: return 4; + case FmtSuperStereo: return 2; + case FmtMonoDup: return 1; + } + return 0; +} diff --git a/Engine/lib/openal-soft/core/storage_formats.h b/Engine/lib/openal-soft/core/storage_formats.h new file mode 100644 index 000000000..acced258a --- /dev/null +++ b/Engine/lib/openal-soft/core/storage_formats.h @@ -0,0 +1,54 @@ +#ifndef CORE_STORAGE_FORMATS_H +#define CORE_STORAGE_FORMATS_H + +using uint = unsigned int; + +/* Storable formats */ +enum FmtType : unsigned char { + FmtUByte, + FmtShort, + FmtInt, + FmtFloat, + FmtDouble, + FmtMulaw, + FmtAlaw, + FmtIMA4, + FmtMSADPCM, +}; +enum FmtChannels : unsigned char { + FmtMono, + FmtStereo, + FmtRear, + FmtQuad, + FmtX51, /* (WFX order) */ + FmtX61, /* (WFX order) */ + FmtX71, /* (WFX order) */ + FmtBFormat2D, + FmtBFormat3D, + FmtUHJ2, /* 2-channel UHJ, aka "BHJ", stereo-compatible */ + FmtUHJ3, /* 3-channel UHJ, aka "THJ" */ + FmtUHJ4, /* 4-channel UHJ, aka "PHJ" */ + FmtSuperStereo, /* Stereo processed with Super Stereo. */ + FmtMonoDup, /* Mono duplicated for left/right separation */ +}; + +enum class AmbiLayout : unsigned char { + FuMa, + ACN, +}; +enum class AmbiScaling : unsigned char { + FuMa, + SN3D, + N3D, + UHJ, +}; + +const char *NameFromFormat(FmtType type) noexcept; +const char *NameFromFormat(FmtChannels channels) noexcept; + +uint BytesFromFmt(FmtType type) noexcept; +uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept; +inline uint FrameSizeFromFmt(FmtChannels chans, FmtType type, uint ambiorder) noexcept +{ return ChannelsFromFmt(chans, ambiorder) * BytesFromFmt(type); } + +#endif /* CORE_STORAGE_FORMATS_H */ diff --git a/Engine/lib/openal-soft/core/uhjfilter.cpp b/Engine/lib/openal-soft/core/uhjfilter.cpp index df50956a3..a2c3cd459 100644 --- a/Engine/lib/openal-soft/core/uhjfilter.cpp +++ b/Engine/lib/openal-soft/core/uhjfilter.cpp @@ -4,54 +4,150 @@ #include "uhjfilter.h" #include -#include +#include +#include +#include +#include #include "alcomplex.h" -#include "alnumeric.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "core/bufferline.h" #include "opthelpers.h" +#include "pffft.h" #include "phase_shifter.h" - - -UhjQualityType UhjDecodeQuality{UhjQualityType::Default}; -UhjQualityType UhjEncodeQuality{UhjQualityType::Default}; +#include "vector.h" namespace { -const PhaseShifterT PShiftLq{}; -const PhaseShifterT PShiftHq{}; +template +constexpr auto assume_aligned_span(const al::span s) noexcept -> al::span +{ return al::span{al::assume_aligned(s.data()), s.size()}; } + +/* Convolution is implemented using a segmented overlap-add method. The filter + * response is broken up into multiple segments of 128 samples, and each + * segment has an FFT applied with a 256-sample buffer (the latter half left + * silent) to get its frequency-domain response. + * + * Input samples are similarly broken up into 128-sample segments, with a 256- + * sample FFT applied to each new incoming segment to get its frequency-domain + * response. A history of FFT'd input segments is maintained, equal to the + * number of filter response segments. + * + * To apply the convolution, each filter response segment is convolved with its + * paired input segment (using complex multiplies, far cheaper than time-domain + * FIRs), accumulating into an FFT buffer. The input history is then shifted to + * align with later filter response segments for the next input segment. + * + * An inverse FFT is then applied to the accumulated FFT buffer to get a 256- + * sample time-domain response for output, which is split in two halves. The + * first half is the 128-sample output, and the second half is a 128-sample + * (really, 127) delayed extension, which gets added to the output next time. + * Convolving two time-domain responses of length N results in a time-domain + * signal of length N*2 - 1, and this holds true regardless of the convolution + * being applied in the frequency domain, so these "overflow" samples need to + * be accounted for. + */ +template +struct SegmentedFilter { + static constexpr size_t sFftLength{256}; + static constexpr size_t sSampleLength{sFftLength / 2}; + static constexpr size_t sNumSegments{N/sSampleLength}; + static_assert(N >= sFftLength); + static_assert((N % sSampleLength) == 0); + + PFFFTSetup mFft; + alignas(16) std::array mFilterData; + + SegmentedFilter() : mFft{sFftLength, PFFFT_REAL} + { + static constexpr size_t fft_size{N}; + + /* To set up the filter, we first need to generate the desired + * response (not reversed). + */ + auto tmpBuffer = std::vector(fft_size, 0.0); + for(std::size_t i{0};i < fft_size/2;++i) + { + const int k{int{fft_size/2} - static_cast(i*2 + 1)}; + + const double w{2.0*al::numbers::pi * static_cast(i*2 + 1) + / double{fft_size}}; + const double window{0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) + - 0.0106411*std::cos(3.0*w)}; + + const double pk{al::numbers::pi * static_cast(k)}; + tmpBuffer[i*2 + 1] = window * (1.0-std::cos(pk)) / pk; + } + + /* The segments of the filter are converted back to the frequency + * domain, each on their own (0 stuffed). + */ + using complex_d = std::complex; + auto fftBuffer = std::vector(sFftLength); + auto fftTmp = al::vector(sFftLength); + auto filter = mFilterData.begin(); + for(size_t s{0};s < sNumSegments;++s) + { + const auto tmpspan = al::span{tmpBuffer}.subspan(sSampleLength*s, sSampleLength); + auto iter = std::copy_n(tmpspan.cbegin(), tmpspan.size(), fftBuffer.begin()); + std::fill(iter, fftBuffer.end(), complex_d{}); + forward_fft(fftBuffer); + + /* Convert to zdomain data for PFFFT, scaled by the FFT length so + * the iFFT result will be normalized. + */ + for(size_t i{0};i < sSampleLength;++i) + { + fftTmp[i*2 + 0] = static_cast(fftBuffer[i].real()) / float{sFftLength}; + fftTmp[i*2 + 1] = static_cast((i == 0) ? fftBuffer[sSampleLength].real() + : fftBuffer[i].imag()) / float{sFftLength}; + } + mFft.zreorder(fftTmp.data(), al::to_address(filter), PFFFT_BACKWARD); + filter += sFftLength; + } + } +}; template -struct GetPhaseShifter; -template<> -struct GetPhaseShifter { static auto& Get() noexcept { return PShiftLq; } }; -template<> -struct GetPhaseShifter { static auto& Get() noexcept { return PShiftHq; } }; +const SegmentedFilter gSegmentedFilter; +template +const PhaseShifterT PShifter; -constexpr float square(float x) noexcept -{ return x*x; } /* Filter coefficients for the 'base' all-pass IIR, which applies a frequency- * dependent phase-shift of N degrees. The output of the filter requires a 1- * sample delay. */ constexpr std::array Filter1Coeff{{ - square(0.6923878f), square(0.9360654322959f), square(0.9882295226860f), - square(0.9987488452737f) + 0.479400865589f, 0.876218493539f, 0.976597589508f, 0.997499255936f }}; /* Filter coefficients for the offset all-pass IIR, which applies a frequency- * dependent phase-shift of N+90 degrees. */ constexpr std::array Filter2Coeff{{ - square(0.4021921162426f), square(0.8561710882420f), square(0.9722909545651f), - square(0.9952884791278f) + 0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156684f }}; } // namespace +void UhjAllPassFilter::processOne(const al::span coeffs, float x) +{ + auto state = mState; + for(size_t i{0};i < 4;++i) + { + const float y{x*coeffs[i] + state[i].z[0]}; + state[i].z[0] = state[i].z[1]; + state[i].z[1] = y*coeffs[i] - x; + x = y; + } + mState = state; +} + void UhjAllPassFilter::process(const al::span coeffs, - const al::span src, const bool updateState, float *RESTRICT dst) + const al::span src, const bool updateState, const al::span dst) { auto state = mState; @@ -66,7 +162,7 @@ void UhjAllPassFilter::process(const al::span coeffs, } return x; }; - std::transform(src.begin(), src.end(), dst, proc_sample); + std::transform(src.begin(), src.end(), dst.begin(), proc_sample); if(updateState) LIKELY mState = state; } @@ -92,68 +188,138 @@ template void UhjEncoder::encode(float *LeftOut, float *RightOut, const al::span InSamples, const size_t SamplesToDo) { - const auto &PShift = GetPhaseShifter::Get(); + static constexpr auto &Filter = gSegmentedFilter; + static_assert(sFftLength == Filter.sFftLength); + static_assert(sSegmentSize == Filter.sSampleLength); + static_assert(sNumSegments == Filter.sNumSegments); ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); - const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])}; - const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])}; - const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])}; + const auto winput = al::span{al::assume_aligned<16>(InSamples[0]), SamplesToDo}; + const auto xinput = al::span{al::assume_aligned<16>(InSamples[1]), SamplesToDo}; + const auto yinput = al::span{al::assume_aligned<16>(InSamples[2]), SamplesToDo}; - std::copy_n(winput, SamplesToDo, mW.begin()+sFilterDelay); - std::copy_n(xinput, SamplesToDo, mX.begin()+sFilterDelay); - std::copy_n(yinput, SamplesToDo, mY.begin()+sFilterDelay); + std::copy_n(winput.begin(), SamplesToDo, mW.begin()+sFilterDelay); + std::copy_n(xinput.begin(), SamplesToDo, mX.begin()+sFilterDelay); + std::copy_n(yinput.begin(), SamplesToDo, mY.begin()+sFilterDelay); /* S = 0.9396926*W + 0.1855740*X */ - for(size_t i{0};i < SamplesToDo;++i) - mS[i] = 0.9396926f*mW[i] + 0.1855740f*mX[i]; + std::transform(mW.begin(), mW.begin()+SamplesToDo, mX.begin(), mS.begin(), + [](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; }); /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */ - std::transform(winput, winput+SamplesToDo, xinput, mWX.begin() + sWXInOffset, - [](const float w, const float x) noexcept -> float - { return -0.3420201f*w + 0.5098604f*x; }); - PShift.process({mD.data(), SamplesToDo}, mWX.data()); + auto dstore = mD.begin(); + size_t curseg{mCurrentSegment}; + for(size_t base{0};base < SamplesToDo;) + { + const size_t todo{std::min(sSegmentSize-mFifoPos, SamplesToDo-base)}; + auto wseg = winput.subspan(base, todo); + auto xseg = xinput.subspan(base, todo); + auto wxio = al::span{mWXInOut}.subspan(mFifoPos, todo); + + /* Copy out the samples that were previously processed by the FFT. */ + dstore = std::copy_n(wxio.begin(), todo, dstore); + + /* Transform the non-delayed input and store in the front half of the + * filter input. + */ + std::transform(wseg.begin(), wseg.end(), xseg.begin(), wxio.begin(), + [](const float w, const float x) noexcept -> float + { return -0.3420201f*w + 0.5098604f*x; }); + + mFifoPos += todo; + base += todo; + + /* Check whether the input buffer is filled with new samples. */ + if(mFifoPos < sSegmentSize) break; + mFifoPos = 0; + + /* Copy the new input to the next history segment, clearing the back + * half of the segment, and convert to the frequency domain. + */ + auto input = mWXHistory.begin() + curseg*sFftLength; + std::copy_n(mWXInOut.begin(), sSegmentSize, input); + std::fill_n(input+sSegmentSize, sSegmentSize, 0.0f); + + Filter.mFft.transform(al::to_address(input), al::to_address(input), mWorkData.data(), + PFFFT_FORWARD); + + /* Convolve each input segment with its IR filter counterpart (aligned + * in time, from newest to oldest). + */ + mFftBuffer.fill(0.0f); + auto filter = Filter.mFilterData.begin(); + for(size_t s{curseg};s < sNumSegments;++s) + { + Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), + mFftBuffer.data()); + input += sFftLength; + filter += sFftLength; + } + input = mWXHistory.begin(); + for(size_t s{0};s < curseg;++s) + { + Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), + mFftBuffer.data()); + input += sFftLength; + filter += sFftLength; + } + + /* Convert back to samples, writing to the output and storing the extra + * for next time. + */ + Filter.mFft.transform(mFftBuffer.data(), mFftBuffer.data(), mWorkData.data(), + PFFFT_BACKWARD); + + std::transform(mFftBuffer.begin(), mFftBuffer.begin()+sSegmentSize, + mWXInOut.begin()+sSegmentSize, mWXInOut.begin(), std::plus{}); + std::copy_n(mFftBuffer.begin()+sSegmentSize, sSegmentSize, mWXInOut.begin()+sSegmentSize); + + /* Shift the input history. */ + curseg = curseg ? (curseg-1) : (sNumSegments-1); + } + mCurrentSegment = curseg; /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */ - for(size_t i{0};i < SamplesToDo;++i) - mD[i] = mD[i] + 0.6554516f*mY[i]; + std::transform(mD.begin(), mD.begin()+SamplesToDo, mY.begin(), mD.begin(), + [](const float jwx, const float y) noexcept { return jwx + 0.6554516f*y; }); /* Copy the future samples to the front for next time. */ std::copy(mW.cbegin()+SamplesToDo, mW.cbegin()+SamplesToDo+sFilterDelay, mW.begin()); std::copy(mX.cbegin()+SamplesToDo, mX.cbegin()+SamplesToDo+sFilterDelay, mX.begin()); std::copy(mY.cbegin()+SamplesToDo, mY.cbegin()+SamplesToDo+sFilterDelay, mY.begin()); - std::copy(mWX.cbegin()+SamplesToDo, mWX.cbegin()+SamplesToDo+sWXInOffset, mWX.begin()); /* Apply a delay to the existing output to align with the input delay. */ - auto *delayBuffer = mDirectDelay.data(); + auto delayBuffer = mDirectDelay.begin(); for(float *buffer : {LeftOut, RightOut}) { - float *distbuf{al::assume_aligned<16>(delayBuffer->data())}; + const auto distbuf = assume_aligned_span<16>(al::span{*delayBuffer}); ++delayBuffer; - float *inout{al::assume_aligned<16>(buffer)}; - auto inout_end = inout + SamplesToDo; - if(SamplesToDo >= sFilterDelay) LIKELY + const auto inout = al::span{al::assume_aligned<16>(buffer), SamplesToDo}; + if(SamplesToDo >= sFilterDelay) { - auto delay_end = std::rotate(inout, inout_end - sFilterDelay, inout_end); - std::swap_ranges(inout, delay_end, distbuf); + auto delay_end = std::rotate(inout.begin(), inout.end() - sFilterDelay, inout.end()); + std::swap_ranges(inout.begin(), delay_end, distbuf.begin()); } else { - auto delay_start = std::swap_ranges(inout, inout_end, distbuf); - std::rotate(distbuf, delay_start, distbuf + sFilterDelay); + auto delay_start = std::swap_ranges(inout.begin(), inout.end(), distbuf.begin()); + std::rotate(distbuf.begin(), delay_start, distbuf.begin() + sFilterDelay); } } /* Combine the direct signal with the produced output. */ /* Left = (S + D)/2.0 */ - float *RESTRICT left{al::assume_aligned<16>(LeftOut)}; - for(size_t i{0};i < SamplesToDo;i++) + const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo}; + for(size_t i{0};i < SamplesToDo;++i) left[i] += (mS[i] + mD[i]) * 0.5f; + /* Right = (S - D)/2.0 */ - float *RESTRICT right{al::assume_aligned<16>(RightOut)}; - for(size_t i{0};i < SamplesToDo;i++) + const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo}; + for(size_t i{0};i < SamplesToDo;++i) right[i] += (mS[i] - mD[i]) * 0.5f; } @@ -176,47 +342,51 @@ void UhjEncoderIIR::encode(float *LeftOut, float *RightOut, const al::span InSamples, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); - const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])}; - const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])}; - const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])}; + const auto winput = al::span{al::assume_aligned<16>(InSamples[0]), SamplesToDo}; + const auto xinput = al::span{al::assume_aligned<16>(InSamples[1]), SamplesToDo}; + const auto yinput = al::span{al::assume_aligned<16>(InSamples[2]), SamplesToDo}; /* S = 0.9396926*W + 0.1855740*X */ - std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(), + std::transform(winput.begin(), winput.end(), xinput.begin(), mTemp.begin(), [](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; }); - mFilter1WX.process(Filter1Coeff, {mTemp.data(), SamplesToDo}, true, mS.data()+1); + mFilter1WX.process(Filter1Coeff, al::span{mTemp}.first(SamplesToDo), true, + al::span{mS}.subspan(1)); mS[0] = mDelayWX; mDelayWX = mS[SamplesToDo]; /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mWX. */ - std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(), + std::transform(winput.begin(), winput.end(), xinput.begin(), mTemp.begin(), [](const float w, const float x) noexcept { return -0.3420201f*w + 0.5098604f*x; }); - mFilter2WX.process(Filter2Coeff, {mTemp.data(), SamplesToDo}, true, mWX.data()); + mFilter2WX.process(Filter2Coeff, al::span{mTemp}.first(SamplesToDo), true, mWX); /* Apply filter1 to Y and store in mD. */ - mFilter1Y.process(Filter1Coeff, {yinput, SamplesToDo}, SamplesToDo, mD.data()+1); + mFilter1Y.process(Filter1Coeff, yinput, true, al::span{mD}.subspan(1)); mD[0] = mDelayY; mDelayY = mD[SamplesToDo]; /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */ - for(size_t i{0};i < SamplesToDo;++i) - mD[i] = mWX[i] + 0.6554516f*mD[i]; + std::transform(mWX.begin(), mWX.begin()+SamplesToDo, mD.begin(), mD.begin(), + [](const float jwx, const float y) noexcept { return jwx + 0.6554516f*y; }); /* Apply the base filter to the existing output to align with the processed * signal. */ - mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true, mTemp.data()+1); + mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true, + al::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[0]; mDirectDelay[0] = mTemp[SamplesToDo]; /* Left = (S + D)/2.0 */ - float *RESTRICT left{al::assume_aligned<16>(LeftOut)}; - for(size_t i{0};i < SamplesToDo;i++) + const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo}; + for(size_t i{0};i < SamplesToDo;++i) left[i] = (mS[i] + mD[i])*0.5f + mTemp[i]; - mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, true, mTemp.data()+1); + mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, true, + al::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[1]; mDirectDelay[1] = mTemp[SamplesToDo]; /* Right = (S - D)/2.0 */ - float *RESTRICT right{al::assume_aligned<16>(RightOut)}; - for(size_t i{0};i < SamplesToDo;i++) + const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo}; + for(size_t i{0};i < SamplesToDo;++i) right[i] = (mS[i] - mD[i])*0.5f + mTemp[i]; } @@ -240,31 +410,29 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesT { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); - const auto &PShift = GetPhaseShifter::Get(); + constexpr auto &PShift = PShifter; ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; - const float *RESTRICT t{al::assume_aligned<16>(samples[2])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; + const auto t = al::span{al::assume_aligned<16>(samples[2]), samplesToDo+sInputPadding}; /* S = Left + Right */ - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* D = Left - Right */ - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mD[i] = left[i] - right[i]; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), std::minus{}); /* T */ - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mT[i] = t[i]; + std::copy(t.begin(), t.end(), mT.begin()); } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); @@ -272,21 +440,22 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesT [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin()); - PShift.process({xoutput, samplesToDo}, mTemp.data()); + PShift.process(xoutput, mTemp); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.981532f*mS[i] + 0.197484f*xoutput[i]; + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jdt) noexcept { return 0.981532f*s + 0.197484f*jdt; }); + /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.418496f*mS[i] - xoutput[i]; + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jdt) noexcept { return 0.418496f*s - jdt; }); /* Precompute j*S and store in youtput. */ tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin()); - PShift.process({youtput, samplesToDo}, mTemp.data()); + PShift.process(youtput, mTemp); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ for(size_t i{0};i < samplesToDo;++i) @@ -294,10 +463,10 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesT if(samples.size() > 3) { - float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])}; + const auto zoutput = al::span{al::assume_aligned<16>(samples[3]), samplesToDo}; /* Z = 1.023332*Q */ - for(size_t i{0};i < samplesToDo;++i) - zoutput[i] = 1.023332f*zoutput[i]; + std::transform(zoutput.begin(), zoutput.end(), zoutput.begin(), + [](const float q) noexcept { return 1.023332f*q; }); } } @@ -307,70 +476,67 @@ void UhjDecoderIIR::decode(const al::span samples, const size_t samplesT static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; /* S = Left + Right */ - for(size_t i{0};i < samplesToDo;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* D = Left - Right */ - for(size_t i{0};i < samplesToDo;++i) - mD[i] = left[i] - right[i]; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), std::minus{}); } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo+sInputPadding}; /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ - std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, mTemp.begin(), + std::transform(mD.cbegin(), mD.cbegin()+sInputPadding+samplesToDo, youtput.begin(), + mTemp.begin(), [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); - mFilter2DT.process(Filter2Coeff, {mTemp.data(), samplesToDo}, updateState, xoutput); + if(mFirstRun) mFilter2DT.processOne(Filter2Coeff, mTemp[0]); + mFilter2DT.process(Filter2Coeff, al::span{mTemp}.subspan(1,samplesToDo), updateState, xoutput); /* Apply filter1 to S and store in mTemp. */ - mTemp[0] = mDelayS; - mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1); - if(updateState) LIKELY mDelayS = mTemp[samplesToDo]; + mFilter1S.process(Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.981532f*mTemp[i] + 0.197484f*xoutput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jdt) noexcept { return 0.981532f*s + 0.197484f*jdt; }); /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.418496f*mTemp[i] - xoutput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jdt) noexcept { return 0.418496f*s - jdt; }); /* Apply filter1 to (0.795968*D - 0.676392*T) and store in mTemp. */ - std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, youtput, + std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput.begin(), youtput.begin(), [](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; }); - mTemp[0] = mDelayDT; - mFilter1DT.process(Filter1Coeff, {youtput, samplesToDo}, updateState, mTemp.data()+1); - if(updateState) LIKELY mDelayDT = mTemp[samplesToDo]; + mFilter1DT.process(Filter1Coeff, youtput.first(samplesToDo), updateState, mTemp); /* Precompute j*S and store in youtput. */ - mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput); + if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]); + mFilter2S.process(Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ - for(size_t i{0};i < samplesToDo;++i) - youtput[i] = mTemp[i] + 0.186633f*youtput[i]; - + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(), + [](const float dt, const float js) noexcept { return dt + 0.186633f*js; }); if(samples.size() > 3) { - float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])}; + const auto zoutput = al::span{al::assume_aligned<16>(samples[3]), samplesToDo}; /* Apply filter1 to Q and store in mTemp. */ - mTemp[0] = mDelayQ; - mFilter1Q.process(Filter1Coeff, {zoutput, samplesToDo}, updateState, mTemp.data()+1); - if(updateState) LIKELY mDelayQ = mTemp[samplesToDo]; + mFilter1Q.process(Filter1Coeff, zoutput, updateState, mTemp); /* Z = 1.023332*Q */ - for(size_t i{0};i < samplesToDo;++i) - zoutput[i] = 1.023332f*mTemp[i]; + std::transform(mTemp.begin(), mTemp.end(), zoutput.begin(), + [](const float q) noexcept { return 1.023332f*q; }); } + + mFirstRun = false; } @@ -392,16 +558,16 @@ void UhjStereoDecoder::decode(const al::span samples, const size_t sa { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); - const auto &PShift = GetPhaseShifter::Get(); + constexpr auto &PShift = PShifter; ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* Pre-apply the width factor to the difference signal D. Smoothly * interpolate when it changes. @@ -410,53 +576,60 @@ void UhjStereoDecoder::decode(const al::span samples, const size_t sa const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; if(wtarget == wcurrent || !updateState) { - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mD[i] = (left[i] - right[i]) * wcurrent; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), + [wcurrent](const float l, const float r) noexcept { return (l-r) * wcurrent; }); mCurrentWidth = wcurrent; } else { const float wstep{(wtarget - wcurrent) / static_cast(samplesToDo)}; float fi{0.0f}; - for(size_t i{0};i < samplesToDo;++i) - { - mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi); - fi += 1.0f; - } - for(size_t i{samplesToDo};i < samplesToDo+sInputPadding;++i) - mD[i] = (left[i] - right[i]) * wtarget; + + const auto lfade = left.first(samplesToDo); + auto dstore = std::transform(lfade.begin(), lfade.begin(), right.begin(), mD.begin(), + [wcurrent,wstep,&fi](const float l, const float r) noexcept + { + const float ret{(l-r) * (wcurrent + wstep*fi)}; + fi += 1.0f; + return ret; + }); + + const auto lend = left.subspan(samplesToDo); + const auto rend = right.subspan(samplesToDo); + std::transform(lend.begin(), lend.end(), rend.begin(), dstore, + [wtarget](const float l, const float r) noexcept { return (l-r) * wtarget; }); mCurrentWidth = wtarget; } } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Precompute j*D and store in xoutput. */ auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); std::copy_n(mD.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin()); - PShift.process({xoutput, samplesToDo}, mTemp.data()); + PShift.process(xoutput, mTemp); /* W = 0.6098637*S - 0.6896511*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.6098637f*mS[i] - 0.6896511f*xoutput[i]; + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jd) noexcept { return 0.6098637f*s - 0.6896511f*jd; }); /* X = 0.8624776*S + 0.7626955*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.8624776f*mS[i] + 0.7626955f*xoutput[i]; + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jd) noexcept { return 0.8624776f*s + 0.7626955f*jd; }); /* Precompute j*S and store in youtput. */ tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin()); - PShift.process({youtput, samplesToDo}, mTemp.data()); + PShift.process(youtput, mTemp); /* Y = 1.6822415*w*D - 0.2156194*j*S */ - for(size_t i{0};i < samplesToDo;++i) - youtput[i] = 1.6822415f*mD[i] - 0.2156194f*youtput[i]; + std::transform(mD.begin(), mD.begin()+samplesToDo, youtput.begin(), youtput.begin(), + [](const float d, const float js) noexcept { return 1.6822415f*d - 0.2156194f*js; }); } void UhjStereoDecoderIIR::decode(const al::span samples, const size_t samplesToDo, @@ -465,13 +638,13 @@ void UhjStereoDecoderIIR::decode(const al::span samples, const size_t sa static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; - for(size_t i{0};i < samplesToDo;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* Pre-apply the width factor to the difference signal D. Smoothly * interpolate when it changes. @@ -480,53 +653,63 @@ void UhjStereoDecoderIIR::decode(const al::span samples, const size_t sa const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; if(wtarget == wcurrent || !updateState) { - for(size_t i{0};i < samplesToDo;++i) - mD[i] = (left[i] - right[i]) * wcurrent; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), + [wcurrent](const float l, const float r) noexcept + { return (l-r) * wcurrent; }); mCurrentWidth = wcurrent; } else { const float wstep{(wtarget - wcurrent) / static_cast(samplesToDo)}; float fi{0.0f}; - for(size_t i{0};i < samplesToDo;++i) - { - mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi); - fi += 1.0f; - } + + const auto lfade = left.first(samplesToDo); + auto dstore = std::transform(lfade.begin(), lfade.begin(), right.begin(), mD.begin(), + [wcurrent,wstep,&fi](const float l, const float r) noexcept + { + const float ret{(l-r) * (wcurrent + wstep*fi)}; + fi += 1.0f; + return ret; + }); + + const auto lend = left.subspan(samplesToDo); + const auto rend = right.subspan(samplesToDo); + std::transform(lend.begin(), lend.end(), rend.begin(), dstore, + [wtarget](const float l, const float r) noexcept { return (l-r) * wtarget; }); mCurrentWidth = wtarget; } } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Apply filter1 to S and store in mTemp. */ - mTemp[0] = mDelayS; - mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1); - if(updateState) LIKELY mDelayS = mTemp[samplesToDo]; + mFilter1S.process(Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); /* Precompute j*D and store in xoutput. */ - mFilter2D.process(Filter2Coeff, {mD.data(), samplesToDo}, updateState, xoutput); + if(mFirstRun) mFilter2D.processOne(Filter2Coeff, mD[0]); + mFilter2D.process(Filter2Coeff, al::span{mD}.subspan(1, samplesToDo), updateState, xoutput); /* W = 0.6098637*S - 0.6896511*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.6098637f*mTemp[i] - 0.6896511f*xoutput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jd) noexcept { return 0.6098637f*s - 0.6896511f*jd; }); /* X = 0.8624776*S + 0.7626955*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.8624776f*mTemp[i] + 0.7626955f*xoutput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jd) noexcept { return 0.8624776f*s + 0.7626955f*jd; }); /* Precompute j*S and store in youtput. */ - mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput); + if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]); + mFilter2S.process(Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Apply filter1 to D and store in mTemp. */ - mTemp[0] = mDelayD; - mFilter1D.process(Filter1Coeff, {mD.data(), samplesToDo}, updateState, mTemp.data()+1); - if(updateState) LIKELY mDelayD = mTemp[samplesToDo]; + mFilter1D.process(Filter1Coeff, al::span{mD}.first(samplesToDo), updateState, mTemp); /* Y = 1.6822415*w*D - 0.2156194*j*S */ - for(size_t i{0};i < samplesToDo;++i) - youtput[i] = 1.6822415f*mTemp[i] - 0.2156194f*youtput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(), + [](const float d, const float js) noexcept { return 1.6822415f*d - 0.2156194f*js; }); + + mFirstRun = false; } diff --git a/Engine/lib/openal-soft/core/uhjfilter.h b/Engine/lib/openal-soft/core/uhjfilter.h index df3080944..55be31b22 100644 --- a/Engine/lib/openal-soft/core/uhjfilter.h +++ b/Engine/lib/openal-soft/core/uhjfilter.h @@ -2,42 +2,50 @@ #define CORE_UHJFILTER_H #include +#include +#include -#include "almalloc.h" #include "alspan.h" #include "bufferline.h" -static constexpr size_t UhjLength256{256}; -static constexpr size_t UhjLength512{512}; +inline constexpr std::size_t UhjLength256{256}; +inline constexpr std::size_t UhjLength512{512}; -enum class UhjQualityType : uint8_t { +enum class UhjQualityType : std::uint8_t { IIR = 0, FIR256, FIR512, Default = IIR }; -extern UhjQualityType UhjDecodeQuality; -extern UhjQualityType UhjEncodeQuality; +inline UhjQualityType UhjDecodeQuality{UhjQualityType::Default}; +inline UhjQualityType UhjEncodeQuality{UhjQualityType::Default}; struct UhjAllPassFilter { struct AllPassState { /* Last two delayed components for direct form II. */ - float z[2]; + std::array z{}; }; std::array mState; + void processOne(const al::span coeffs, float x); void process(const al::span coeffs, const al::span src, - const bool update, float *RESTRICT dst); + const bool update, const al::span dst); }; struct UhjEncoderBase { + UhjEncoderBase() = default; + UhjEncoderBase(const UhjEncoderBase&) = delete; + UhjEncoderBase(UhjEncoderBase&&) = delete; virtual ~UhjEncoderBase() = default; - virtual size_t getDelay() noexcept = 0; + void operator=(const UhjEncoderBase&) = delete; + void operator=(UhjEncoderBase&&) = delete; + + virtual std::size_t getDelay() noexcept = 0; /** * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input @@ -45,12 +53,15 @@ struct UhjEncoderBase { * with an additional +3dB boost). */ virtual void encode(float *LeftOut, float *RightOut, - const al::span InSamples, const size_t SamplesToDo) = 0; + const al::span InSamples, const std::size_t SamplesToDo) = 0; }; -template +template struct UhjEncoder final : public UhjEncoderBase { - static constexpr size_t sFilterDelay{N/2}; + static constexpr std::size_t sFftLength{256}; + static constexpr std::size_t sSegmentSize{sFftLength/2}; + static constexpr std::size_t sNumSegments{N/sSegmentSize}; + static constexpr std::size_t sFilterDelay{N/2 + sSegmentSize}; /* Delays and processing storage for the input signal. */ alignas(16) std::array mW{}; @@ -60,15 +71,16 @@ struct UhjEncoder final : public UhjEncoderBase { alignas(16) std::array mS{}; alignas(16) std::array mD{}; - /* History and temp storage for the FIR filter. New samples should be - * written to index sFilterDelay*2 - 1. - */ - static constexpr size_t sWXInOffset{sFilterDelay*2 - 1}; - alignas(16) std::array mWX{}; + /* History and temp storage for the convolution filter. */ + std::size_t mFifoPos{}, mCurrentSegment{}; + alignas(16) std::array mWXInOut{}; + alignas(16) std::array mFftBuffer{}; + alignas(16) std::array mWorkData{}; + alignas(16) std::array mWXHistory{}; alignas(16) std::array,2> mDirectDelay{}; - size_t getDelay() noexcept override { return sFilterDelay; } + std::size_t getDelay() noexcept override { return sFilterDelay; } /** * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input @@ -76,13 +88,11 @@ struct UhjEncoder final : public UhjEncoderBase { * with an additional +3dB boost). */ void encode(float *LeftOut, float *RightOut, const al::span InSamples, - const size_t SamplesToDo) override; - - DEF_NEWDEL(UhjEncoder) + const std::size_t SamplesToDo) final; }; struct UhjEncoderIIR final : public UhjEncoderBase { - static constexpr size_t sFilterDelay{1}; + static constexpr std::size_t sFilterDelay{1}; /* Processing storage for the input signal. */ alignas(16) std::array mS{}; @@ -98,7 +108,7 @@ struct UhjEncoderIIR final : public UhjEncoderBase { std::array mFilter1Direct; std::array mDirectDelay{}; - size_t getDelay() noexcept override { return sFilterDelay; } + std::size_t getDelay() noexcept override { return sFilterDelay; } /** * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input @@ -106,22 +116,26 @@ struct UhjEncoderIIR final : public UhjEncoderBase { * with an additional +3dB boost). */ void encode(float *LeftOut, float *RightOut, const al::span InSamples, - const size_t SamplesToDo) override; - - DEF_NEWDEL(UhjEncoderIIR) + const std::size_t SamplesToDo) final; }; struct DecoderBase { - static constexpr size_t sMaxPadding{256}; + static constexpr std::size_t sMaxPadding{256}; /* For 2-channel UHJ, shelf filters should use these LF responses. */ static constexpr float sWLFScale{0.661f}; static constexpr float sXYLFScale{1.293f}; + DecoderBase() = default; + DecoderBase(const DecoderBase&) = delete; + DecoderBase(DecoderBase&&) = delete; virtual ~DecoderBase() = default; - virtual void decode(const al::span samples, const size_t samplesToDo, + void operator=(const DecoderBase&) = delete; + void operator=(DecoderBase&&) = delete; + + virtual void decode(const al::span samples, const std::size_t samplesToDo, const bool updateState) = 0; /** @@ -131,10 +145,10 @@ struct DecoderBase { float mWidthControl{0.593f}; }; -template +template struct UhjDecoder final : public DecoderBase { /* The number of extra sample frames needed for input. */ - static constexpr size_t sInputPadding{N/2}; + static constexpr std::size_t sInputPadding{N/2}; alignas(16) std::array mS{}; alignas(16) std::array mD{}; @@ -153,24 +167,23 @@ struct UhjDecoder final : public DecoderBase { * reconstructed from 2-channel UHJ should not be run through a normal * B-Format decoder, as it needs different shelf filters. */ - void decode(const al::span samples, const size_t samplesToDo, - const bool updateState) override; - - DEF_NEWDEL(UhjDecoder) + void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) final; }; struct UhjDecoderIIR final : public DecoderBase { - /* FIXME: These IIR decoder filters actually have a 1-sample delay on the - * non-filtered components, which is not reflected in the source latency - * value. sInputPadding is 0, however, because it doesn't need any extra - * input samples. + /* These IIR decoder filters normally have a 1-sample delay on the non- + * filtered components. However, the filtered components are made to skip + * the first output sample and take one future sample, which puts it ahead + * by one sample. The first filtered output sample is cut to align it with + * the first non-filtered sample, similar to the FIR filters. */ - static constexpr size_t sInputPadding{0}; + static constexpr std::size_t sInputPadding{1}; - alignas(16) std::array mS{}; - alignas(16) std::array mD{}; - alignas(16) std::array mTemp{}; - float mDelayS{}, mDelayDT{}, mDelayQ{}; + bool mFirstRun{true}; + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mTemp{}; UhjAllPassFilter mFilter1S; UhjAllPassFilter mFilter2DT; @@ -178,15 +191,13 @@ struct UhjDecoderIIR final : public DecoderBase { UhjAllPassFilter mFilter2S; UhjAllPassFilter mFilter1Q; - void decode(const al::span samples, const size_t samplesToDo, - const bool updateState) override; - - DEF_NEWDEL(UhjDecoderIIR) + void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) final; }; -template +template struct UhjStereoDecoder final : public DecoderBase { - static constexpr size_t sInputPadding{N/2}; + static constexpr std::size_t sInputPadding{N/2}; float mCurrentWidth{-1.0f}; @@ -204,31 +215,27 @@ struct UhjStereoDecoder final : public DecoderBase { * should contain 3 channels, the first two being the left and right stereo * channels, and the third left empty. */ - void decode(const al::span samples, const size_t samplesToDo, - const bool updateState) override; - - DEF_NEWDEL(UhjStereoDecoder) + void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) final; }; struct UhjStereoDecoderIIR final : public DecoderBase { - static constexpr size_t sInputPadding{0}; + static constexpr std::size_t sInputPadding{1}; + bool mFirstRun{true}; float mCurrentWidth{-1.0f}; - alignas(16) std::array mS{}; - alignas(16) std::array mD{}; - alignas(16) std::array mTemp{}; - float mDelayS{}, mDelayD{}; + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mTemp{}; UhjAllPassFilter mFilter1S; UhjAllPassFilter mFilter2D; UhjAllPassFilter mFilter1D; UhjAllPassFilter mFilter2S; - void decode(const al::span samples, const size_t samplesToDo, - const bool updateState) override; - - DEF_NEWDEL(UhjStereoDecoderIIR) + void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) final; }; #endif /* CORE_UHJFILTER_H */ diff --git a/Engine/lib/openal-soft/core/uiddefs.cpp b/Engine/lib/openal-soft/core/uiddefs.cpp index 244c01a5a..e93376763 100644 --- a/Engine/lib/openal-soft/core/uiddefs.cpp +++ b/Engine/lib/openal-soft/core/uiddefs.cpp @@ -4,14 +4,10 @@ #ifndef AL_NO_UID_DEFS -#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H) +#if defined(HAVE_GUIDDEF_H) #define INITGUID #include -#ifdef HAVE_GUIDDEF_H #include -#else -#include -#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); @@ -19,12 +15,8 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80,0x 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 +#if defined(HAVE_WASAPI) && !defined(ALSOFT_UWP) #include #include #include diff --git a/Engine/lib/openal-soft/core/voice.cpp b/Engine/lib/openal-soft/core/voice.cpp index e8fbcccd3..9d2b34a07 100644 --- a/Engine/lib/openal-soft/core/voice.cpp +++ b/Engine/lib/openal-soft/core/voice.cpp @@ -9,16 +9,15 @@ #include #include #include +#include #include #include #include -#include +#include #include #include -#include "albyte.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "ambidefs.h" @@ -51,26 +50,25 @@ struct NEONTag; #endif -static_assert(!(sizeof(DeviceBase::MixerBufferLine)&15), - "DeviceBase::MixerBufferLine must be a multiple of 16 bytes"); +static_assert(!(DeviceBase::MixerLineSize&3), "MixerLineSize must be a multiple of 4"); static_assert(!(MaxResamplerEdge&3), "MaxResamplerEdge is not a multiple of 4"); static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!"); static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize, "MaxPitch and/or BufferLineSize are too large for MixerFracBits!"); -Resampler ResamplerDefault{Resampler::Cubic}; - namespace { using uint = unsigned int; using namespace std::chrono; +using namespace std::string_view_literals; -using HrtfMixerFunc = void(*)(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize); -using HrtfMixerBlendFunc = void(*)(const float *InSamples, float2 *AccumSamples, - const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, - const size_t BufferSize); +using HrtfMixerFunc = void(*)(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const MixHrtfFilter *hrtfparams, + const size_t SamplesToDo); +using HrtfMixerBlendFunc = void(*)(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo); HrtfMixerFunc MixHrtfSamples{MixHrtf_}; HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_}; @@ -129,42 +127,45 @@ inline HrtfMixerBlendFunc SelectHrtfBlendMixer() } // namespace -void Voice::InitMixer(al::optional resampler) +void Voice::InitMixer(std::optional resopt) { - if(resampler) + if(resopt) { struct ResamplerEntry { - const char name[16]; + const std::string_view name; const Resampler resampler; }; - constexpr ResamplerEntry ResamplerList[]{ - { "none", Resampler::Point }, - { "point", Resampler::Point }, - { "linear", Resampler::Linear }, - { "cubic", Resampler::Cubic }, - { "bsinc12", Resampler::BSinc12 }, - { "fast_bsinc12", Resampler::FastBSinc12 }, - { "bsinc24", Resampler::BSinc24 }, - { "fast_bsinc24", Resampler::FastBSinc24 }, + constexpr std::array ResamplerList{ + ResamplerEntry{"none"sv, Resampler::Point}, + ResamplerEntry{"point"sv, Resampler::Point}, + ResamplerEntry{"linear"sv, Resampler::Linear}, + ResamplerEntry{"spline"sv, Resampler::Spline}, + ResamplerEntry{"gaussian"sv, Resampler::Gaussian}, + ResamplerEntry{"bsinc12"sv, Resampler::BSinc12}, + ResamplerEntry{"fast_bsinc12"sv, Resampler::FastBSinc12}, + ResamplerEntry{"bsinc24"sv, Resampler::BSinc24}, + ResamplerEntry{"fast_bsinc24"sv, Resampler::FastBSinc24}, }; - const char *str{resampler->c_str()}; - if(al::strcasecmp(str, "bsinc") == 0) + std::string_view resampler{*resopt}; + if(al::case_compare(resampler, "cubic"sv) == 0 + || al::case_compare(resampler, "sinc4"sv) == 0 + || al::case_compare(resampler, "sinc8"sv) == 0) { - WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str); - str = "bsinc12"; + WARN("Resampler option \"%s\" is deprecated, using gaussian\n", resopt->c_str()); + resampler = "gaussian"sv; } - else if(al::strcasecmp(str, "sinc4") == 0 || al::strcasecmp(str, "sinc8") == 0) + else if(al::case_compare(resampler, "bsinc"sv) == 0) { - WARN("Resampler option \"%s\" is deprecated, using cubic\n", str); - str = "cubic"; + WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", resopt->c_str()); + resampler = "bsinc12"sv; } - 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); + auto iter = std::find_if(ResamplerList.begin(), ResamplerList.end(), + [resampler](const ResamplerEntry &entry) -> bool + { return al::case_compare(resampler, entry.name) == 0; }); + if(iter == ResamplerList.end()) + ERR("Invalid resampler: %s\n", resopt->c_str()); else ResamplerDefault = iter->resampler; } @@ -179,7 +180,7 @@ void Voice::InitMixer(al::optional resampler) namespace { /* IMA ADPCM Stepsize table */ -constexpr int IMAStep_size[89] = { +constexpr std::array IMAStep_size{{ 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, @@ -189,35 +190,35 @@ constexpr int IMAStep_size[89] = { 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] = { +constexpr std::array IMA4Codeword{{ 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] = { +constexpr std::arrayIMA4Index_adjust{{ -1,-1,-1,-1, 2, 4, 6, 8, -1,-1,-1,-1, 2, 4, 6, 8 -}; +}}; /* MSADPCM Adaption table */ -constexpr int MSADPCMAdaption[16] = { +constexpr std::array MSADPCMAdaption{{ 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 } +constexpr std::array MSADPCMAdaptionCoeff{ + std::array{256, 0}, + std::array{512, -256}, + std::array{ 0, 0}, + std::array{192, 64}, + std::array{240, 0}, + std::array{460, -208}, + std::array{392, -232} }; @@ -227,17 +228,16 @@ void SendSourceStoppedEvent(ContextBase *context, uint id) auto evt_vec = ring->getWriteVector(); if(evt_vec.first.len < 1) return; - AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), - AsyncEvent::SourceStateChange)}; - evt->u.srcstate.id = id; - evt->u.srcstate.state = AsyncEvent::SrcState::Stop; + auto &evt = InitAsyncEvent(evt_vec.first.buf); + evt.mId = id; + evt.mState = AsyncSrcState::Stop; ring->writeAdvance(1); } -const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *dst, - const al::span src, int type) +al::span DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, + const al::span dst, const al::span src, int type) { switch(type) { @@ -249,70 +249,91 @@ const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *ds case AF_LowPass: lpfilter.process(src, dst); hpfilter.clear(); - return dst; + return dst.first(src.size()); case AF_HighPass: lpfilter.clear(); hpfilter.process(src, dst); - return dst; + return dst.first(src.size()); case AF_BandPass: DualBiquad{lpfilter, hpfilter}.process(src, dst); - return dst; + return dst.first(src.size()); } - return src.data(); + return src; } template -inline void LoadSamples(float *RESTRICT dstSamples, const al::byte *src, const size_t srcChan, - const size_t srcOffset, const size_t srcStep, const size_t /*samplesPerBlock*/, - const size_t samplesToLoad) noexcept +inline void LoadSamples(const al::span dstSamples, const al::span srcData, + const size_t srcChan, const size_t srcOffset, const size_t srcStep, + const size_t samplesPerBlock [[maybe_unused]]) noexcept { - constexpr size_t sampleSize{sizeof(typename al::FmtTypeTraits::Type)}; - auto s = src + (srcOffset*srcStep + srcChan)*sampleSize; + using TypeTraits = al::FmtTypeTraits; + using SampleType = typename TypeTraits::Type; + static constexpr size_t sampleSize{sizeof(SampleType)}; + assert(srcChan < srcStep); + auto converter = TypeTraits{}; - al::LoadSampleArray(dstSamples, s, srcStep, samplesToLoad); + al::span src{reinterpret_cast(srcData.data()), + srcData.size()/sampleSize}; + auto ssrc = src.cbegin() + ptrdiff_t(srcOffset*srcStep); + std::generate(dstSamples.begin(), dstSamples.end(), [&ssrc,srcChan,srcStep,converter] + { + auto ret = converter(ssrc[srcChan]); + ssrc += ptrdiff_t(srcStep); + return ret; + }); } template<> -inline void LoadSamples(float *RESTRICT dstSamples, const al::byte *src, +inline void LoadSamples(al::span dstSamples, al::span src, const size_t srcChan, const size_t srcOffset, const size_t srcStep, - const size_t samplesPerBlock, const size_t samplesToLoad) noexcept + const size_t samplesPerBlock) noexcept { + static constexpr int MaxStepIndex{static_cast(IMAStep_size.size()) - 1}; + + assert(srcStep > 0 || srcStep <= 2); + assert(srcChan < srcStep); + assert(samplesPerBlock > 1); const size_t blockBytes{((samplesPerBlock-1)/2 + 4)*srcStep}; /* Skip to the ADPCM block containing the srcOffset sample. */ - src += srcOffset/samplesPerBlock*blockBytes; + src = src.subspan(srcOffset/samplesPerBlock*blockBytes); /* Calculate how many samples need to be skipped in the block. */ size_t skip{srcOffset % samplesPerBlock}; /* NOTE: This could probably be optimized better. */ - size_t wrote{0}; - do { + while(!dstSamples.empty()) + { + auto nibbleData = src.cbegin(); + src = src.subspan(blockBytes); + /* Each IMA4 block starts with a signed 16-bit sample, and a signed * 16-bit table index. The table index needs to be clamped. */ - int sample{src[srcChan*4] | (src[srcChan*4 + 1] << 8)}; - int index{src[srcChan*4 + 2] | (src[srcChan*4 + 3] << 8)}; + int sample{int(nibbleData[srcChan*4]) | (int(nibbleData[srcChan*4 + 1]) << 8)}; + int index{int(nibbleData[srcChan*4 + 2]) | (int(nibbleData[srcChan*4 + 3]) << 8)}; + nibbleData += ptrdiff_t((srcStep+srcChan)*4); sample = (sample^0x8000) - 32768; - index = clampi((index^0x8000) - 32768, 0, al::size(IMAStep_size)-1); + index = std::clamp((index^0x8000) - 32768, 0, MaxStepIndex); if(skip == 0) { - dstSamples[wrote++] = static_cast(sample) / 32768.0f; - if(wrote == samplesToLoad) return; + dstSamples[0] = static_cast(sample) / 32768.0f; + dstSamples = dstSamples.subspan<1>(); + if(dstSamples.empty()) return; } else --skip; auto decode_sample = [&sample,&index](const uint nibble) { - sample += IMA4Codeword[nibble] * IMAStep_size[index] / 8; - sample = clampi(sample, -32768, 32767); + sample += IMA4Codeword[nibble] * IMAStep_size[static_cast(index)] / 8; + sample = std::clamp(sample, -32768, 32767); index += IMA4Index_adjust[nibble]; - index = clampi(index, 0, al::size(IMAStep_size)-1); + index = std::clamp(index, 0, MaxStepIndex); return sample; }; @@ -325,71 +346,72 @@ inline void LoadSamples(float *RESTRICT dstSamples, const al::byte *src * always be less than the block size). They need to be decoded despite * being ignored for proper state on the remaining samples. */ - const al::byte *nibbleData{src + (srcStep+srcChan)*4}; size_t nibbleOffset{0}; const size_t startOffset{skip + 1}; for(;skip;--skip) { const size_t byteShift{(nibbleOffset&1) * 4}; - const size_t wordOffset{(nibbleOffset>>1) & ~size_t{3}}; + const size_t wordOffset{(nibbleOffset>>1) & ~3_uz}; const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; ++nibbleOffset; - std::ignore = decode_sample((nibbleData[byteOffset]>>byteShift) & 15u); + std::ignore = decode_sample(uint(nibbleData[byteOffset]>>byteShift) & 15u); } /* Second, decode the rest of the block and write to the output, until * the end of the block or the end of output. */ - const size_t todo{minz(samplesPerBlock-startOffset, samplesToLoad-wrote)}; - for(size_t i{0};i < todo;++i) + const size_t todo{std::min(samplesPerBlock-startOffset, dstSamples.size())}; + std::generate_n(dstSamples.begin(), todo, [&] { const size_t byteShift{(nibbleOffset&1) * 4}; - const size_t wordOffset{(nibbleOffset>>1) & ~size_t{3}}; + const size_t wordOffset{(nibbleOffset>>1) & ~3_uz}; const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; ++nibbleOffset; - const int result{decode_sample((nibbleData[byteOffset]>>byteShift) & 15u)}; - dstSamples[wrote++] = static_cast(result) / 32768.0f; - } - if(wrote == samplesToLoad) - return; - - src += blockBytes; - } while(true); + const int result{decode_sample(uint(nibbleData[byteOffset]>>byteShift) & 15u)}; + return static_cast(result) / 32768.0f; + }); + dstSamples = dstSamples.subspan(todo); + } } template<> -inline void LoadSamples(float *RESTRICT dstSamples, const al::byte *src, +inline void LoadSamples(al::span dstSamples, al::span src, const size_t srcChan, const size_t srcOffset, const size_t srcStep, - const size_t samplesPerBlock, const size_t samplesToLoad) noexcept + const size_t samplesPerBlock) noexcept { + assert(srcStep > 0 || srcStep <= 2); + assert(srcChan < srcStep); + assert(samplesPerBlock > 2); const size_t blockBytes{((samplesPerBlock-2)/2 + 7)*srcStep}; - src += srcOffset/samplesPerBlock*blockBytes; + src = src.subspan(srcOffset/samplesPerBlock*blockBytes); size_t skip{srcOffset % samplesPerBlock}; - size_t wrote{0}; - do { + while(!dstSamples.empty()) + { + auto input = src.cbegin(); + src = src.subspan(blockBytes); + /* Each MS ADPCM block starts with an 8-bit block predictor, used to * dictate how the two sample history values are mixed with the decoded * sample, and an initial signed 16-bit delta value which scales the * nibble sample value. This is followed by the two initial 16-bit * sample history values. */ - const al::byte *input{src}; - const uint8_t blockpred{std::min(input[srcChan], uint8_t{6})}; - input += srcStep; - int delta{input[2*srcChan + 0] | (input[2*srcChan + 1] << 8)}; - input += srcStep*2; + const uint8_t blockpred{std::min(uint8_t(input[srcChan]), uint8_t{6})}; + input += ptrdiff_t(srcStep); + int delta{int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1]) << 8)}; + input += ptrdiff_t(srcStep*2); - int sampleHistory[2]{}; - sampleHistory[0] = input[2*srcChan + 0] | (input[2*srcChan + 1]<<8); - input += srcStep*2; - sampleHistory[1] = input[2*srcChan + 0] | (input[2*srcChan + 1]<<8); - input += srcStep*2; + std::array sampleHistory{}; + sampleHistory[0] = int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1])<<8); + input += ptrdiff_t(srcStep*2); + sampleHistory[1] = int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1])<<8); + input += ptrdiff_t(srcStep*2); - const auto coeffs = al::as_span(MSADPCMAdaptionCoeff[blockpred]); + const al::span coeffs{MSADPCMAdaptionCoeff[blockpred]}; delta = (delta^0x8000) - 32768; sampleHistory[0] = (sampleHistory[0]^0x8000) - 32768; sampleHistory[1] = (sampleHistory[1]^0x8000) - 32768; @@ -399,16 +421,19 @@ inline void LoadSamples(float *RESTRICT dstSamples, const al::byte * */ if(skip == 0) { - dstSamples[wrote++] = static_cast(sampleHistory[1]) / 32768.0f; - if(wrote == samplesToLoad) return; - dstSamples[wrote++] = static_cast(sampleHistory[0]) / 32768.0f; - if(wrote == samplesToLoad) return; + dstSamples[0] = static_cast(sampleHistory[1]) / 32768.0f; + dstSamples = dstSamples.subspan<1>(); + if(dstSamples.empty()) return; + dstSamples[0] = static_cast(sampleHistory[0]) / 32768.0f; + dstSamples = dstSamples.subspan<1>(); + if(dstSamples.empty()) return; } else if(skip == 1) { --skip; - dstSamples[wrote++] = static_cast(sampleHistory[0]) / 32768.0f; - if(wrote == samplesToLoad) return; + dstSamples[0] = static_cast(sampleHistory[0]) / 32768.0f; + dstSamples = dstSamples.subspan<1>(); + if(dstSamples.empty()) return; } else skip -= 2; @@ -417,13 +442,13 @@ inline void LoadSamples(float *RESTRICT dstSamples, const al::byte * { int pred{(sampleHistory[0]*coeffs[0] + sampleHistory[1]*coeffs[1]) / 256}; pred += ((nibble^0x08) - 0x08) * delta; - pred = clampi(pred, -32768, 32767); + pred = std::clamp(pred, -32768, 32767); sampleHistory[1] = sampleHistory[0]; sampleHistory[0] = pred; - delta = (MSADPCMAdaption[nibble] * delta) / 256; - delta = maxi(16, delta); + delta = (MSADPCMAdaption[static_cast(nibble)] * delta) / 256; + delta = std::max(16, delta); return pred; }; @@ -439,42 +464,40 @@ inline void LoadSamples(float *RESTRICT dstSamples, const al::byte * const size_t byteShift{((nibbleOffset&1)^1) * 4}; nibbleOffset += srcStep; - std::ignore = decode_sample((input[byteOffset]>>byteShift) & 15); + std::ignore = decode_sample(int(input[byteOffset]>>byteShift) & 15); } /* Now decode the rest of the block, until the end of the block or the * dst buffer is filled. */ - const size_t todo{minz(samplesPerBlock-startOffset, samplesToLoad-wrote)}; - for(size_t j{0};j < todo;++j) + const size_t todo{std::min(samplesPerBlock-startOffset, dstSamples.size())}; + std::generate_n(dstSamples.begin(), todo, [&] { const size_t byteOffset{nibbleOffset>>1}; const size_t byteShift{((nibbleOffset&1)^1) * 4}; nibbleOffset += srcStep; - const int sample{decode_sample((input[byteOffset]>>byteShift) & 15)}; - dstSamples[wrote++] = static_cast(sample) / 32768.0f; - } - if(wrote == samplesToLoad) - return; - - src += blockBytes; - } while(true); + const int sample{decode_sample(int(input[byteOffset]>>byteShift) & 15)}; + return static_cast(sample) / 32768.0f; + }); + dstSamples = dstSamples.subspan(todo); + } } -void LoadSamples(float *dstSamples, const al::byte *src, const size_t srcChan, - const size_t srcOffset, const FmtType srcType, const size_t srcStep, - const size_t samplesPerBlock, const size_t samplesToLoad) noexcept +void LoadSamples(const al::span dstSamples, const al::span src, + const size_t srcChan, const size_t srcOffset, const FmtType srcType, const size_t srcStep, + const size_t samplesPerBlock) noexcept { #define HANDLE_FMT(T) case T: \ LoadSamples(dstSamples, src, srcChan, srcOffset, srcStep, \ - samplesPerBlock, samplesToLoad); \ + samplesPerBlock); \ break switch(srcType) { HANDLE_FMT(FmtUByte); HANDLE_FMT(FmtShort); + HANDLE_FMT(FmtInt); HANDLE_FMT(FmtFloat); HANDLE_FMT(FmtDouble); HANDLE_FMT(FmtMulaw); @@ -487,26 +510,24 @@ void LoadSamples(float *dstSamples, const al::byte *src, const size_t srcChan, void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, const size_t dataPosInt, const FmtType sampleType, const size_t srcChannel, - const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad, - float *voiceSamples) + const size_t srcStep, al::span voiceSamples) { if(!bufferLoopItem) { + float lastSample{0.0f}; /* Load what's left to play from the buffer */ if(buffer->mSampleLen > dataPosInt) LIKELY { const size_t buffer_remaining{buffer->mSampleLen - dataPosInt}; - const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer_remaining)}; - LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, dataPosInt, - sampleType, srcStep, buffer->mBlockAlign, remaining); - samplesLoaded += remaining; + const size_t remaining{std::min(voiceSamples.size(), buffer_remaining)}; + LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, + sampleType, srcStep, buffer->mBlockAlign); + lastSample = voiceSamples[remaining-1]; + voiceSamples = voiceSamples.subspan(remaining); } - if(const size_t toFill{samplesToLoad - samplesLoaded}) - { - auto srcsamples = voiceSamples + samplesLoaded; - std::fill_n(srcsamples, toFill, *(srcsamples-1)); - } + if(const size_t toFill{voiceSamples.size()}) + std::fill_n(voiceSamples.begin(), toFill, lastSample); } else { @@ -518,49 +539,47 @@ void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, : (((dataPosInt-loopStart)%(loopEnd-loopStart)) + loopStart)}; /* Load what's left of this loop iteration */ - const size_t remaining{minz(samplesToLoad-samplesLoaded, loopEnd-dataPosInt)}; - LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, intPos, sampleType, - srcStep, buffer->mBlockAlign, remaining); - samplesLoaded += remaining; + const size_t remaining{std::min(voiceSamples.size(), loopEnd-dataPosInt)}; + LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, intPos, + sampleType, srcStep, buffer->mBlockAlign); + voiceSamples = voiceSamples.subspan(remaining); /* Load repeats of the loop to fill the buffer. */ const size_t loopSize{loopEnd - loopStart}; - while(const size_t toFill{minz(samplesToLoad - samplesLoaded, loopSize)}) + while(const size_t toFill{std::min(voiceSamples.size(), loopSize)}) { - LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, loopStart, - sampleType, srcStep, buffer->mBlockAlign, toFill); - samplesLoaded += toFill; + LoadSamples(voiceSamples.first(toFill), buffer->mSamples, srcChannel, loopStart, + sampleType, srcStep, buffer->mBlockAlign); + voiceSamples = voiceSamples.subspan(toFill); } } } void LoadBufferCallback(VoiceBufferItem *buffer, const size_t dataPosInt, const size_t numCallbackSamples, const FmtType sampleType, const size_t srcChannel, - const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad, float *voiceSamples) + const size_t srcStep, al::span voiceSamples) { - /* Load what's left to play from the buffer */ + float lastSample{0.0f}; if(numCallbackSamples > dataPosInt) LIKELY { - const size_t remaining{minz(samplesToLoad-samplesLoaded, numCallbackSamples-dataPosInt)}; - LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, dataPosInt, - sampleType, srcStep, buffer->mBlockAlign, remaining); - samplesLoaded += remaining; + const size_t remaining{std::min(voiceSamples.size(), numCallbackSamples-dataPosInt)}; + LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, + sampleType, srcStep, buffer->mBlockAlign); + lastSample = voiceSamples[remaining-1]; + voiceSamples = voiceSamples.subspan(remaining); } - if(const size_t toFill{samplesToLoad - samplesLoaded}) - { - auto srcsamples = voiceSamples + samplesLoaded; - std::fill_n(srcsamples, toFill, *(srcsamples-1)); - } + if(const size_t toFill{voiceSamples.size()}) + std::fill_n(voiceSamples.begin(), toFill, lastSample); } void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, size_t dataPosInt, const FmtType sampleType, const size_t srcChannel, - const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad, - float *voiceSamples) + const size_t srcStep, al::span voiceSamples) { + float lastSample{0.0f}; /* Crawl the buffer queue to fill in the temp buffer */ - while(buffer && samplesLoaded != samplesToLoad) + while(buffer && !voiceSamples.empty()) { if(dataPosInt >= buffer->mSampleLen) { @@ -570,48 +589,47 @@ void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, continue; } - const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer->mSampleLen-dataPosInt)}; - LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, dataPosInt, - sampleType, srcStep, buffer->mBlockAlign, remaining); + const size_t remaining{std::min(voiceSamples.size(), buffer->mSampleLen-dataPosInt)}; + LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, + sampleType, srcStep, buffer->mBlockAlign); - samplesLoaded += remaining; - if(samplesLoaded == samplesToLoad) + lastSample = voiceSamples[remaining-1]; + voiceSamples = voiceSamples.subspan(remaining); + if(voiceSamples.empty()) break; dataPosInt = 0; buffer = buffer->mNext.load(std::memory_order_acquire); if(!buffer) buffer = bufferLoopItem; } - if(const size_t toFill{samplesToLoad - samplesLoaded}) - { - auto srcsamples = voiceSamples + samplesLoaded; - std::fill_n(srcsamples, toFill, *(srcsamples-1)); - } + if(const size_t toFill{voiceSamples.size()}) + std::fill_n(voiceSamples.begin(), toFill, lastSample); } -void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &parms, - const float TargetGain, const uint Counter, uint OutPos, const bool IsPlaying, - DeviceBase *Device) +void DoHrtfMix(const al::span samples, DirectParams &parms, const float TargetGain, + const size_t Counter, size_t OutPos, const bool IsPlaying, DeviceBase *Device) { const uint IrSize{Device->mIrSize}; - auto &HrtfSamples = Device->HrtfSourceData; - auto &AccumSamples = Device->HrtfAccumData; + const auto HrtfSamples = al::span{Device->ExtraSampleData}; + const auto AccumSamples = al::span{Device->HrtfAccumData}; /* Copy the HRTF history and new input samples into a temp buffer. */ auto src_iter = std::copy(parms.Hrtf.History.begin(), parms.Hrtf.History.end(), - std::begin(HrtfSamples)); - std::copy_n(samples, DstBufferSize, src_iter); + HrtfSamples.begin()); + std::copy_n(samples.begin(), samples.size(), src_iter); /* Copy the last used samples back into the history buffer for later. */ if(IsPlaying) LIKELY - std::copy_n(std::begin(HrtfSamples) + DstBufferSize, parms.Hrtf.History.size(), - parms.Hrtf.History.begin()); + { + const auto endsamples = HrtfSamples.subspan(samples.size(), parms.Hrtf.History.size()); + std::copy_n(endsamples.cbegin(), endsamples.size(), parms.Hrtf.History.begin()); + } /* If fading and this is the first mixing pass, fade between the IRs. */ - uint fademix{0u}; + size_t fademix{0}; if(Counter && OutPos == 0) { - fademix = minu(DstBufferSize, Counter); + fademix = std::min(samples.size(), Counter); float gain{TargetGain}; @@ -630,8 +648,8 @@ void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &par parms.Hrtf.Target.Coeffs, parms.Hrtf.Target.Delay, 0.0f, gain / static_cast(fademix)}; - MixHrtfBlendSamples(HrtfSamples, AccumSamples+OutPos, IrSize, &parms.Hrtf.Old, &hrtfparams, - fademix); + MixHrtfBlendSamples(HrtfSamples, AccumSamples.subspan(OutPos), IrSize, &parms.Hrtf.Old, + &hrtfparams, fademix); /* Update the old parameters with the result. */ parms.Hrtf.Old = parms.Hrtf.Target; @@ -639,15 +657,15 @@ void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &par OutPos += fademix; } - if(fademix < DstBufferSize) + if(fademix < samples.size()) { - const uint todo{DstBufferSize - fademix}; + const size_t todo{samples.size() - fademix}; float gain{TargetGain}; /* Interpolate the target gain if the gain fading lasts longer than * this mix. */ - if(Counter > DstBufferSize) + if(Counter > samples.size()) { const float a{static_cast(todo) / static_cast(Counter-fademix)}; gain = lerpf(parms.Hrtf.Old.Gain, TargetGain, a); @@ -658,35 +676,39 @@ void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &par parms.Hrtf.Target.Delay, parms.Hrtf.Old.Gain, (gain - parms.Hrtf.Old.Gain) / static_cast(todo)}; - MixHrtfSamples(HrtfSamples+fademix, AccumSamples+OutPos, IrSize, &hrtfparams, todo); + MixHrtfSamples(HrtfSamples.subspan(fademix), AccumSamples.subspan(OutPos), IrSize, + &hrtfparams, todo); /* Store the now-current gain for next time. */ parms.Hrtf.Old.Gain = gain; } } -void DoNfcMix(const al::span samples, FloatBufferLine *OutBuffer, DirectParams &parms, - const float *TargetGains, const uint Counter, const uint OutPos, DeviceBase *Device) +void DoNfcMix(const al::span samples, al::span OutBuffer, + DirectParams &parms, const al::span OutGains, + const uint Counter, const uint OutPos, DeviceBase *Device) { - using FilterProc = void (NfcFilter::*)(const al::span, float*); - static constexpr FilterProc NfcProcess[MaxAmbiOrder+1]{ - nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3}; + using FilterProc = void (NfcFilter::*)(const al::span, const al::span); + static constexpr std::array NfcProcess{{ + nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3}}; - float *CurrentGains{parms.Gains.Current.data()}; - MixSamples(samples, {OutBuffer, 1u}, CurrentGains, TargetGains, Counter, OutPos); - ++OutBuffer; - ++CurrentGains; - ++TargetGains; + auto CurrentGains = al::span{parms.Gains.Current}.subspan(0); + auto TargetGains = OutGains.subspan(0); + MixSamples(samples, OutBuffer.first(1), CurrentGains, TargetGains, Counter, OutPos); + OutBuffer = OutBuffer.subspan(1); + CurrentGains = CurrentGains.subspan(1); + TargetGains = TargetGains.subspan(1); - const al::span nfcsamples{Device->NfcSampleData, samples.size()}; + const auto nfcsamples = al::span{Device->ExtraSampleData}.subspan(samples.size()); size_t order{1}; while(const size_t chancount{Device->NumChannelsPerOrder[order]}) { - (parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples.data()); - MixSamples(nfcsamples, {OutBuffer, chancount}, CurrentGains, TargetGains, Counter, OutPos); - OutBuffer += chancount; - CurrentGains += chancount; - TargetGains += chancount; + (parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples); + MixSamples(nfcsamples, OutBuffer.first(chancount), CurrentGains, TargetGains, Counter, + OutPos); + OutBuffer = OutBuffer.subspan(chancount); + CurrentGains = CurrentGains.subspan(chancount); + TargetGains = TargetGains.subspan(chancount); if(++order == MaxAmbiOrder+1) break; } @@ -697,7 +719,7 @@ void DoNfcMix(const al::span samples, FloatBufferLine *OutBuffer, D void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds deviceTime, const uint SamplesToDo) { - static constexpr std::array SilentTarget{}; + static constexpr std::array SilentTarget{}; ASSUME(SamplesToDo > 0); @@ -773,34 +795,34 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi /* Get a span of pointers to hold the floating point, deinterlaced, * resampled buffer data to be mixed. */ - std::array SamplePointers; - const al::span MixingSamples{SamplePointers.data(), mChans.size()}; - auto get_bufferline = [](DeviceBase::MixerBufferLine &bufline) noexcept -> float* - { return bufline.data(); }; - std::transform(Device->mSampleData.end() - mChans.size(), Device->mSampleData.end(), - MixingSamples.begin(), get_bufferline); - - /* If there's a matching sample step and no phase offset, use a simple copy - * for resampling. - */ - const ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0) - ? ResamplerFunc{[](const InterpState*, const float *RESTRICT src, uint, const uint, - const al::span dst) { std::copy_n(src, dst.size(), dst.begin()); }} - : mResampler}; + auto SamplePointers = std::array{}; + const auto MixingSamples = al::span{SamplePointers}.first(mChans.size()); + { + const uint channelStep{(samplesToLoad+3u)&~3u}; + auto base = Device->mSampleData.end() - MixingSamples.size()*channelStep; + std::generate(MixingSamples.begin(), MixingSamples.end(), [&base,channelStep] + { + const auto ret = base; + base += channelStep; + return al::to_address(ret); + }); + } /* UHJ2 and SuperStereo only have 2 buffer channels, but 3 mixing channels - * (3rd channel is generated from decoding). + * (3rd channel is generated from decoding). MonoDup only has 1 buffer + * channel, but 2 mixing channels (2nd channel is just duplicated). */ - const size_t realChannels{(mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 2u + const size_t realChannels{(mFmtChannels == FmtMonoDup) ? 1u + : (mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 2u : MixingSamples.size()}; for(size_t chan{0};chan < realChannels;++chan) { - using ResBufType = decltype(DeviceBase::mResampleData); - static constexpr uint srcSizeMax{static_cast(ResBufType{}.size()-MaxResamplerEdge)}; + static constexpr uint ResBufSize{std::tuple_size_v}; + static constexpr uint srcSizeMax{ResBufSize - MaxResamplerEdge}; - const auto prevSamples = al::as_span(mPrevSamples[chan]); - const auto resampleBuffer = std::copy(prevSamples.cbegin(), prevSamples.cend(), - Device->mResampleData.begin()) - MaxResamplerEdge; + const al::span prevSamples{mPrevSamples[chan]}; + std::copy(prevSamples.cbegin(), prevSamples.cend(), Device->mResampleData.begin()); + const auto resampleBuffer = al::span{Device->mResampleData}.subspan(); int intPos{DataPosInt}; uint fracPos{DataPosFrac}; @@ -849,86 +871,101 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi } return std::make_pair(dstBufferSize, srcSizeMax); }; - const auto bufferSizes = calc_buffer_sizes(samplesToLoad - samplesLoaded); - const auto dstBufferSize = bufferSizes.first; - const auto srcBufferSize = bufferSizes.second; + const auto [dstBufferSize, srcBufferSize] = calc_buffer_sizes( + samplesToLoad - samplesLoaded); + + size_t srcSampleDelay{0}; + if(intPos < 0) UNLIKELY + { + /* If the current position is negative, there's that many + * silent samples to load before using the buffer. + */ + srcSampleDelay = static_cast(-intPos); + if(srcSampleDelay >= srcBufferSize) + { + /* If the number of silent source samples exceeds the + * number to load, the output will be silent. + */ + std::fill_n(MixingSamples[chan]+samplesLoaded, dstBufferSize, 0.0f); + std::fill_n(resampleBuffer.begin(), srcBufferSize, 0.0f); + goto skip_resample; + } + + std::fill_n(resampleBuffer.begin(), srcSampleDelay, 0.0f); + } /* Load the necessary samples from the given buffer(s). */ - if(!BufferListItem) + if(!BufferListItem) UNLIKELY { - const uint avail{minu(srcBufferSize, MaxResamplerEdge)}; - const uint tofill{maxu(srcBufferSize, MaxResamplerEdge)}; + const uint avail{std::min(srcBufferSize, MaxResamplerEdge)}; + const uint tofill{std::max(srcBufferSize, MaxResamplerEdge)}; + const auto srcbuf = resampleBuffer.first(tofill); /* When loading from a voice that ended prematurely, only take * the samples that get closest to 0 amplitude. This helps * certain sounds fade out better. */ - auto abs_lt = [](const float lhs, const float rhs) noexcept -> bool - { return std::abs(lhs) < std::abs(rhs); }; - auto srciter = std::min_element(resampleBuffer, resampleBuffer+avail, abs_lt); + auto srciter = std::min_element(srcbuf.begin(), srcbuf.begin()+ptrdiff_t(avail), + [](const float l, const float r) { return std::abs(l) < std::abs(r); }); - std::fill(srciter+1, resampleBuffer+tofill, *srciter); + std::fill(srciter+1, srcbuf.end(), *srciter); + } + else if(mFlags.test(VoiceIsStatic)) + { + const auto uintPos = static_cast(std::max(intPos, 0)); + const auto bufferSamples = resampleBuffer.subspan(srcSampleDelay, + srcBufferSize-srcSampleDelay); + LoadBufferStatic(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan, + mFrameStep, bufferSamples); + } + else if(mFlags.test(VoiceIsCallback)) + { + const auto uintPos = static_cast(std::max(intPos, 0)); + const uint callbackBase{mCallbackBlockBase * mSamplesPerBlock}; + const size_t bufferOffset{uintPos - callbackBase}; + const size_t needSamples{bufferOffset + srcBufferSize - srcSampleDelay}; + const size_t needBlocks{(needSamples + mSamplesPerBlock-1) / mSamplesPerBlock}; + if(!mFlags.test(VoiceCallbackStopped) && needBlocks > mNumCallbackBlocks) + { + const size_t byteOffset{mNumCallbackBlocks*size_t{mBytesPerBlock}}; + const size_t needBytes{(needBlocks-mNumCallbackBlocks)*size_t{mBytesPerBlock}}; + + const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData, + &BufferListItem->mSamples[byteOffset], static_cast(needBytes))}; + if(gotBytes < 0) + mFlags.set(VoiceCallbackStopped); + else if(static_cast(gotBytes) < needBytes) + { + mFlags.set(VoiceCallbackStopped); + mNumCallbackBlocks += static_cast(gotBytes) / mBytesPerBlock; + } + else + mNumCallbackBlocks = static_cast(needBlocks); + } + const size_t numSamples{size_t{mNumCallbackBlocks} * mSamplesPerBlock}; + const auto bufferSamples = resampleBuffer.subspan(srcSampleDelay, + srcBufferSize-srcSampleDelay); + LoadBufferCallback(BufferListItem, bufferOffset, numSamples, mFmtType, chan, + mFrameStep, bufferSamples); } else { - size_t srcSampleDelay{0}; - if(intPos < 0) UNLIKELY - { - /* If the current position is negative, there's that many - * silent samples to load before using the buffer. - */ - srcSampleDelay = static_cast(-intPos); - if(srcSampleDelay >= srcBufferSize) - { - /* If the number of silent source samples exceeds the - * number to load, the output will be silent. - */ - std::fill_n(MixingSamples[chan]+samplesLoaded, dstBufferSize, 0.0f); - std::fill_n(resampleBuffer, srcBufferSize, 0.0f); - goto skip_resample; - } - - std::fill_n(resampleBuffer, srcSampleDelay, 0.0f); - } - const uint uintPos{static_cast(maxi(intPos, 0))}; - - if(mFlags.test(VoiceIsStatic)) - LoadBufferStatic(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan, - mFrameStep, srcSampleDelay, srcBufferSize, al::to_address(resampleBuffer)); - else if(mFlags.test(VoiceIsCallback)) - { - const uint callbackBase{mCallbackBlockBase * mSamplesPerBlock}; - const size_t bufferOffset{uintPos - callbackBase}; - const size_t needSamples{bufferOffset + srcBufferSize - srcSampleDelay}; - const size_t needBlocks{(needSamples + mSamplesPerBlock-1) / mSamplesPerBlock}; - if(!mFlags.test(VoiceCallbackStopped) && needBlocks > mNumCallbackBlocks) - { - const size_t byteOffset{mNumCallbackBlocks*mBytesPerBlock}; - const size_t needBytes{(needBlocks-mNumCallbackBlocks)*mBytesPerBlock}; - - const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData, - &BufferListItem->mSamples[byteOffset], static_cast(needBytes))}; - if(gotBytes < 0) - mFlags.set(VoiceCallbackStopped); - else if(static_cast(gotBytes) < needBytes) - { - mFlags.set(VoiceCallbackStopped); - mNumCallbackBlocks += static_cast(gotBytes) / mBytesPerBlock; - } - else - mNumCallbackBlocks = static_cast(needBlocks); - } - const size_t numSamples{uint{mNumCallbackBlocks} * mSamplesPerBlock}; - LoadBufferCallback(BufferListItem, bufferOffset, numSamples, mFmtType, chan, - mFrameStep, srcSampleDelay, srcBufferSize, al::to_address(resampleBuffer)); - } - else - LoadBufferQueue(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan, - mFrameStep, srcSampleDelay, srcBufferSize, al::to_address(resampleBuffer)); + const auto uintPos = static_cast(std::max(intPos, 0)); + const auto bufferSamples = resampleBuffer.subspan(srcSampleDelay, + srcBufferSize-srcSampleDelay); + LoadBufferQueue(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan, + mFrameStep, bufferSamples); } - Resample(&mResampleState, al::to_address(resampleBuffer), fracPos, increment, - {MixingSamples[chan]+samplesLoaded, dstBufferSize}); + /* If there's a matching sample step and no phase offset, use a + * simple copy for resampling. + */ + if(increment == MixerFracOne && fracPos == 0) + std::copy_n(resampleBuffer.cbegin(), dstBufferSize, + MixingSamples[chan]+samplesLoaded); + else + mResampler(&mResampleState, Device->mResampleData, fracPos, increment, + {MixingSamples[chan]+samplesLoaded, dstBufferSize}); /* Store the last source samples used for next time. */ if(vstate == Playing) LIKELY @@ -941,7 +978,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi { const size_t dstOffset{samplesToMix - samplesLoaded}; const size_t srcOffset{(dstOffset*increment + fracPos) >> MixerFracBits}; - std::copy_n(resampleBuffer-MaxResamplerEdge+srcOffset, prevSamples.size(), + std::copy_n(Device->mResampleData.cbegin()+srcOffset, prevSamples.size(), prevSamples.begin()); } } @@ -953,18 +990,26 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi fracPos += dstBufferSize*increment; const uint srcOffset{fracPos >> MixerFracBits}; fracPos &= MixerFracMask; - intPos += srcOffset; + intPos += static_cast(srcOffset); /* If more samples need to be loaded, copy the back of the * resampleBuffer to the front to reuse it. prevSamples isn't * reliable since it's only updated for the end of the mix. */ - std::copy(resampleBuffer-MaxResamplerEdge+srcOffset, - resampleBuffer+MaxResamplerEdge+srcOffset, resampleBuffer-MaxResamplerEdge); + std::copy_n(Device->mResampleData.cbegin()+srcOffset, MaxResamplerPadding, + Device->mResampleData.begin()); } } } - for(auto &samples : MixingSamples.subspan(realChannels)) + if(mFmtChannels == FmtMonoDup) + { + /* NOTE: a mono source shouldn't have a decoder or the VoiceIsAmbisonic + * flag, so aliasing instead of copying to the second channel shouldn't + * be a problem. + */ + MixingSamples[1] = MixingSamples[0]; + } + else for(auto &samples : MixingSamples.subspan(realChannels)) std::fill_n(samples, samplesToLoad, 0.0f); if(mDecoder) @@ -981,7 +1026,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi } } - const uint Counter{mFlags.test(VoiceIsFading) ? minu(samplesToMix, 64u) : 0u}; + const uint Counter{mFlags.test(VoiceIsFading) ? std::min(samplesToMix, 64u) : 0u}; if(!Counter) { /* No fading, just overwrite the old/current params. */ @@ -1012,25 +1057,24 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi const al::span FilterBuf{Device->FilteredData}; { DirectParams &parms = chandata.mDryParams; - const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(), - {*voiceSamples, samplesToMix}, mDirect.FilterType)}; + const auto samples = DoFilters(parms.LowPass, parms.HighPass, FilterBuf, + {*voiceSamples, samplesToMix}, mDirect.FilterType); if(mFlags.test(VoiceHasHrtf)) { - const float TargetGain{parms.Hrtf.Target.Gain * (vstate == Playing)}; - DoHrtfMix(samples, samplesToMix, parms, TargetGain, Counter, OutPos, - (vstate == Playing), Device); + const float TargetGain{parms.Hrtf.Target.Gain * float(vstate == Playing)}; + DoHrtfMix(samples, parms, TargetGain, Counter, OutPos, (vstate == Playing), + Device); } else { - const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data() - : SilentTarget.data()}; + const auto TargetGains = (vstate == Playing) ? al::span{parms.Gains.Target} + : al::span{SilentTarget}; if(mFlags.test(VoiceHasNfc)) - DoNfcMix({samples, samplesToMix}, mDirect.Buffer.data(), parms, - TargetGains, Counter, OutPos, Device); + DoNfcMix(samples, mDirect.Buffer, parms, TargetGains, Counter, OutPos, Device); else - MixSamples({samples, samplesToMix}, mDirect.Buffer, - parms.Gains.Current.data(), TargetGains, Counter, OutPos); + MixSamples(samples, mDirect.Buffer, parms.Gains.Current, TargetGains, Counter, + OutPos); } } @@ -1040,13 +1084,13 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi continue; SendParams &parms = chandata.mWetParams[send]; - const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(), - {*voiceSamples, samplesToMix}, mSend[send].FilterType)}; + const auto samples = DoFilters(parms.LowPass, parms.HighPass, FilterBuf, + {*voiceSamples, samplesToMix}, mSend[send].FilterType); - const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data() - : SilentTarget.data()}; - MixSamples({samples, samplesToMix}, mSend[send].Buffer, - parms.Gains.Current.data(), TargetGains, Counter, OutPos); + const auto TargetGains = (vstate == Playing) ? al::span{parms.Gains.Target} + : al::span{SilentTarget}; + MixSamples(samples, mSend[send].Buffer, parms.Gains.Current, TargetGains, Counter, + OutPos); } ++voiceSamples; @@ -1063,12 +1107,11 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi /* Update voice positions and buffers as needed. */ DataPosFrac += increment*samplesToMix; - const uint SrcSamplesDone{DataPosFrac>>MixerFracBits}; - DataPosInt += SrcSamplesDone; + DataPosInt += static_cast(DataPosFrac>>MixerFracBits); DataPosFrac &= MixerFracMask; uint buffers_done{0u}; - if(BufferListItem && DataPosInt >= 0) LIKELY + if(BufferListItem && DataPosInt > 0) LIKELY { if(mFlags.test(VoiceIsStatic)) { @@ -1099,10 +1142,11 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi const uint blocksDone{currentBlock - mCallbackBlockBase}; if(blocksDone < mNumCallbackBlocks) { - const size_t byteOffset{blocksDone*mBytesPerBlock}; - const size_t byteEnd{mNumCallbackBlocks*mBytesPerBlock}; - al::byte *data{BufferListItem->mSamples}; - std::copy(data+byteOffset, data+byteEnd, data); + const size_t byteOffset{blocksDone*size_t{mBytesPerBlock}}; + const size_t byteEnd{mNumCallbackBlocks*size_t{mBytesPerBlock}}; + const al::span data{BufferListItem->mSamples}; + std::copy(data.cbegin()+ptrdiff_t(byteOffset), data.cbegin()+ptrdiff_t(byteEnd), + data.begin()); mNumCallbackBlocks -= blocksDone; mCallbackBlockBase += blocksDone; } @@ -1120,7 +1164,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi if(BufferListItem->mSampleLen > static_cast(DataPosInt)) break; - DataPosInt -= BufferListItem->mSampleLen; + DataPosInt -= static_cast(BufferListItem->mSampleLen); ++buffers_done; BufferListItem = BufferListItem->mNext.load(std::memory_order_relaxed); @@ -1145,16 +1189,15 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi /* Send any events now, after the position/buffer info was updated. */ const auto enabledevt = Context->mEnabledEvts.load(std::memory_order_acquire); - if(buffers_done > 0 && enabledevt.test(AsyncEvent::BufferCompleted)) + if(buffers_done > 0 && enabledevt.test(al::to_underlying(AsyncEnableBits::BufferCompleted))) { RingBuffer *ring{Context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); if(evt_vec.first.len > 0) { - AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), - AsyncEvent::BufferCompleted)}; - evt->u.bufcomp.id = SourceID; - evt->u.bufcomp.count = buffers_done; + auto &evt = InitAsyncEvent(evt_vec.first.buf); + evt.mId = SourceID; + evt.mCount = buffers_done; ring->writeAdvance(1); } } @@ -1165,7 +1208,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi * ensures any residual noise fades to 0 amplitude. */ mPlayState.store(Stopping, std::memory_order_release); - if(enabledevt.test(AsyncEvent::SourceStateChange)) + if(enabledevt.test(al::to_underlying(AsyncEnableBits::SourceState))) SendSourceStoppedEvent(Context, SourceID); } } @@ -1175,22 +1218,23 @@ void Voice::prepare(DeviceBase *device) /* Even if storing really high order ambisonics, we only mix channels for * orders up to the device order. The rest are simply dropped. */ - uint num_channels{(mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 3 : - ChannelsFromFmt(mFmtChannels, minu(mAmbiOrder, device->mAmbiOrder))}; - if(num_channels > device->mSampleData.size()) UNLIKELY + uint num_channels{(mFmtChannels == FmtMonoDup) ? 2 + : (mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 3 + : ChannelsFromFmt(mFmtChannels, std::min(mAmbiOrder, device->mAmbiOrder))}; + if(num_channels > device->MixerChannelsMax) UNLIKELY { - ERR("Unexpected channel count: %u (limit: %zu, %d:%d)\n", num_channels, - device->mSampleData.size(), mFmtChannels, mAmbiOrder); - num_channels = static_cast(device->mSampleData.size()); + ERR("Unexpected channel count: %u (limit: %zu, %s : %d)\n", num_channels, + device->MixerChannelsMax, NameFromFormat(mFmtChannels), mAmbiOrder); + num_channels = device->MixerChannelsMax; } if(mChans.capacity() > 2 && num_channels < mChans.capacity()) { decltype(mChans){}.swap(mChans); decltype(mPrevSamples){}.swap(mPrevSamples); } - mChans.reserve(maxu(2, num_channels)); + mChans.reserve(std::max(2u, num_channels)); mChans.resize(num_channels); - mPrevSamples.reserve(maxu(2, num_channels)); + mPrevSamples.reserve(std::max(2u, num_channels)); mPrevSamples.resize(num_channels); mDecoder = nullptr; @@ -1274,8 +1318,10 @@ void Voice::prepare(DeviceBase *device) */ else if(mAmbiOrder && device->mAmbiOrder > mAmbiOrder) { - const uint8_t *OrderFromChan{Is2DAmbisonic(mFmtChannels) ? - AmbiIndex::OrderFrom2DChannel().data() : AmbiIndex::OrderFromChannel().data()}; + auto OrdersSpan = Is2DAmbisonic(mFmtChannels) + ? al::span{AmbiIndex::OrderFrom2DChannel} + : al::span{AmbiIndex::OrderFromChannel}; + auto OrderFromChan = OrdersSpan.cbegin(); const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder, device->m2DMixing); diff --git a/Engine/lib/openal-soft/core/voice.h b/Engine/lib/openal-soft/core/voice.h index 57ee7b019..c983c3e9f 100644 --- a/Engine/lib/openal-soft/core/voice.h +++ b/Engine/lib/openal-soft/core/voice.h @@ -5,13 +5,12 @@ #include #include #include +#include #include -#include +#include #include -#include "albyte.h" #include "almalloc.h" -#include "aloptional.h" #include "alspan.h" #include "bufferline.h" #include "buffer_storage.h" @@ -33,7 +32,7 @@ enum class DistanceModel : unsigned char; using uint = unsigned int; -#define MAX_SENDS 6 +inline constexpr size_t MaxSendCount{6}; enum class SpatializeMode : unsigned char { @@ -49,7 +48,7 @@ enum class DirectMode : unsigned char { }; -constexpr uint MaxPitch{10}; +inline constexpr uint MaxPitch{10}; enum { @@ -66,26 +65,29 @@ struct DirectParams { NfcFilter NFCtrlFilter; - struct { - HrtfFilter Old; - HrtfFilter Target; - alignas(16) std::array History; - } Hrtf; + struct HrtfParams { + HrtfFilter Old{}; + HrtfFilter Target{}; + alignas(16) std::array History{}; + }; + HrtfParams Hrtf; - struct { - std::array Current; - std::array Target; - } Gains; + struct GainParams { + std::array Current{}; + std::array Target{}; + }; + GainParams Gains; }; struct SendParams { BiquadFilter LowPass; BiquadFilter HighPass; - struct { - std::array Current; - std::array Target; - } Gains; + struct GainParams { + std::array Current{}; + std::array Target{}; + }; + GainParams Gains; }; @@ -100,7 +102,7 @@ struct VoiceBufferItem { uint mLoopStart{0u}; uint mLoopEnd{0u}; - al::byte *mSamples{nullptr}; + al::span mSamples{}; }; @@ -139,15 +141,18 @@ struct VoiceProps { float Radius; float EnhWidth; + float Panning; /** Direct filter and auxiliary send info. */ - struct { + struct DirectData { float Gain; float GainHF; float HFReference; float GainLF; float LFReference; - } Direct; + }; + DirectData Direct; + struct SendData { EffectSlot *Slot; float Gain; @@ -155,13 +160,12 @@ struct VoiceProps { float HFReference; float GainLF; float LFReference; - } Send[MAX_SENDS]; + }; + std::array Send; }; struct VoicePropsItem : public VoiceProps { std::atomic next{nullptr}; - - DEF_NEWDEL(VoicePropsItem) }; enum : uint { @@ -186,7 +190,7 @@ struct Voice { std::atomic mUpdate{nullptr}; - VoiceProps mProps; + VoiceProps mProps{}; std::atomic mSourceID{0u}; std::atomic mPlayState{Stopped}; @@ -196,30 +200,30 @@ struct Voice { * Source offset in samples, relative to the currently playing buffer, NOT * the whole queue. */ - std::atomic mPosition; + std::atomic mPosition{}; /** Fractional (fixed-point) offset to the next sample. */ - std::atomic mPositionFrac; + std::atomic mPositionFrac{}; /* Current buffer queue item being played. */ - std::atomic mCurrentBuffer; + std::atomic mCurrentBuffer{}; /* Buffer queue item to loop to at end of queue (will be NULL for non- * looping voices). */ - std::atomic mLoopBuffer; + std::atomic mLoopBuffer{}; std::chrono::nanoseconds mStartTime{}; /* Properties for the attached buffer(s). */ - FmtChannels mFmtChannels; - FmtType mFmtType; - uint mFrequency; - uint mFrameStep; /**< In steps of the sample type size. */ - uint mBytesPerBlock; /**< Or for PCM formats, BytesPerFrame. */ - uint mSamplesPerBlock; /**< Always 1 for PCM formats. */ - AmbiLayout mAmbiLayout; - AmbiScaling mAmbiScaling; - uint mAmbiOrder; + FmtChannels mFmtChannels{}; + FmtType mFmtType{}; + uint mFrequency{}; + uint mFrameStep{}; /**< In steps of the sample type size. */ + uint mBytesPerBlock{}; /**< Or for PCM formats, BytesPerFrame. */ + uint mSamplesPerBlock{}; /**< Always 1 for PCM formats. */ + AmbiLayout mAmbiLayout{}; + AmbiScaling mAmbiScaling{}; + uint mAmbiOrder{}; std::unique_ptr mDecoder; uint mDecoderPadding{}; @@ -227,20 +231,20 @@ struct Voice { /** Current target parameters used for mixing. */ uint mStep{0}; - ResamplerFunc mResampler; + ResamplerFunc mResampler{}; - InterpState mResampleState; + InterpState mResampleState{}; std::bitset mFlags{}; uint mNumCallbackBlocks{0}; uint mCallbackBlockBase{0}; struct TargetData { - int FilterType; + int FilterType{}; al::span Buffer; }; TargetData mDirect; - std::array mSend; + std::array mSend; /* The first MaxResamplerPadding/2 elements are the sample history from the * previous mix, with an additional MaxResamplerPadding/2 elements that are @@ -251,11 +255,11 @@ struct Voice { al::vector mPrevSamples{2}; struct ChannelData { - float mAmbiHFScale, mAmbiLFScale; + float mAmbiHFScale{}, mAmbiLFScale{}; BandSplitter mAmbiSplitter; DirectParams mDryParams; - std::array mWetParams; + std::array mWetParams; }; al::vector mChans{2}; @@ -270,11 +274,9 @@ struct Voice { void prepare(DeviceBase *device); - static void InitMixer(al::optional resampler); - - DEF_NEWDEL(Voice) + static void InitMixer(std::optional resopt); }; -extern Resampler ResamplerDefault; +inline Resampler ResamplerDefault{Resampler::Gaussian}; #endif /* CORE_VOICE_H */ diff --git a/Engine/lib/openal-soft/core/voice_change.h b/Engine/lib/openal-soft/core/voice_change.h index ddc6186f5..e97c48f33 100644 --- a/Engine/lib/openal-soft/core/voice_change.h +++ b/Engine/lib/openal-soft/core/voice_change.h @@ -3,8 +3,6 @@ #include -#include "almalloc.h" - struct Voice; using uint = unsigned int; @@ -24,8 +22,6 @@ struct VoiceChange { VChangeState mState{}; std::atomic mNext{nullptr}; - - DEF_NEWDEL(VoiceChange) }; #endif /* VOICE_CHANGE_H */ diff --git a/Engine/lib/openal-soft/docs/ambisonics.txt b/Engine/lib/openal-soft/docs/ambisonics.txt index b1b111d6d..7798c8f90 100644 --- a/Engine/lib/openal-soft/docs/ambisonics.txt +++ b/Engine/lib/openal-soft/docs/ambisonics.txt @@ -12,7 +12,7 @@ What Is It? Originally developed in the 1970s by Michael Gerzon and a team others, Ambisonics was created as a means of recording and playing back 3D sound. -Taking advantage of the way sound waves propogate, it is possible to record a +Taking advantage of the way sound waves propagate, it is possible to record a fully 3D soundfield using as few as 4 channels (or even just 3, if you don't mind dropping down to 2 dimensions like many surround sound systems are). This representation is called B-Format. It was designed to handle audio independent @@ -63,7 +63,7 @@ remain correct over a larger area around the center of the speakers. In addition, Ambisonics can encode the near-field effect of sounds, effectively capturing the sound distance. The near-field effect is a subtle low-frequency boost as a result of wave-front curvature, and properly compensating for this -occuring with the output speakers (as well as emulating it with a synthesized +occurring with the output speakers (as well as emulating it with a synthesized soundfield) can create an improved sense of distance for sounds that move near or far. diff --git a/Engine/lib/openal-soft/docs/env-vars.txt b/Engine/lib/openal-soft/docs/env-vars.txt index 815a30980..0c15cbe99 100644 --- a/Engine/lib/openal-soft/docs/env-vars.txt +++ b/Engine/lib/openal-soft/docs/env-vars.txt @@ -78,6 +78,15 @@ Same as for __ALSOFT_REVERSE_Z, but for Y (up/down) panning. __ALSOFT_REVERSE_X Same as for __ALSOFT_REVERSE_Z, but for X (left/right) panning. +__ALSOFT_DEFAULT_ERROR +Applications that erroneously call alGetError prior to setting a context as +current may not like that OpenAL Soft returns 0xA004 (AL_INVALID_OPERATION), +indicating that the call could not be executed as there's no context to get the +error value from. This can be set to 0 (AL_NO_ERROR) to let such apps pass the +check despite the problem. Other applications, however, may see AL_NO_ERROR +returned and assume any previous AL calls succeeded when they actually failed, +so this should only be set when necessary. + __ALSOFT_SUSPEND_CONTEXT Due to the OpenAL spec not being very clear about them, behavior of the alcSuspendContext and alcProcessContext methods has varied, and because of diff --git a/Engine/lib/openal-soft/examples/alconvolve.c b/Engine/lib/openal-soft/examples/alconvolve.c index 93fd2eb4a..597d6ea2c 100644 --- a/Engine/lib/openal-soft/examples/alconvolve.c +++ b/Engine/lib/openal-soft/examples/alconvolve.c @@ -22,7 +22,7 @@ * THE SOFTWARE. */ -/* This file contains an example for applying convolution reverb to a source. */ +/* This file contains an example for applying convolution to a source. */ #include #include @@ -38,10 +38,12 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" -#ifndef AL_SOFT_convolution_reverb -#define AL_SOFT_convolution_reverb -#define AL_EFFECT_CONVOLUTION_REVERB_SOFT 0xA000 + +#ifndef AL_SOFT_convolution_effect +#define AL_SOFT_convolution_effect +#define AL_EFFECT_CONVOLUTION_SOFT 0xA000 #endif @@ -88,11 +90,11 @@ static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; /* This stuff defines a simple streaming player object, the same as alstream.c. * Comments are removed for brevity, see alstream.c for more details. */ -#define NUM_BUFFERS 4 -#define BUFFER_SAMPLES 8192 +enum { NumBuffers = 4 }; +enum { BufferSamples = 8192 }; typedef struct StreamPlayer { - ALuint buffers[NUM_BUFFERS]; + ALuint buffers[NumBuffers]; ALuint source; SNDFILE *sndfile; @@ -109,7 +111,7 @@ static StreamPlayer *NewPlayer(void) player = calloc(1, sizeof(*player)); assert(player != NULL); - alGenBuffers(NUM_BUFFERS, player->buffers); + alGenBuffers(NumBuffers, player->buffers); assert(alGetError() == AL_NO_ERROR && "Could not create buffers"); alGenSources(1, &player->source); @@ -138,11 +140,11 @@ static void DeletePlayer(StreamPlayer *player) ClosePlayerFile(player); alDeleteSources(1, &player->source); - alDeleteBuffers(NUM_BUFFERS, player->buffers); + alDeleteBuffers(NumBuffers, player->buffers); if(alGetError() != AL_NO_ERROR) fprintf(stderr, "Failed to delete object IDs\n"); - memset(player, 0, sizeof(*player)); + memset(player, 0, sizeof(*player)); /* NOLINT(clang-analyzer-security.insecureAPI.*) */ free(player); } @@ -184,7 +186,7 @@ static int OpenPlayerFile(StreamPlayer *player, const char *filename) return 0; } - frame_size = (size_t)(BUFFER_SAMPLES * player->sfinfo.channels) * sizeof(float); + frame_size = (size_t)(BufferSamples * player->sfinfo.channels) * sizeof(float); player->membuf = malloc(frame_size); return 1; @@ -197,9 +199,9 @@ static int StartPlayer(StreamPlayer *player) alSourceRewind(player->source); alSourcei(player->source, AL_BUFFER, 0); - for(i = 0;i < NUM_BUFFERS;i++) + for(i = 0;i < NumBuffers;i++) { - sf_count_t slen = sf_readf_float(player->sndfile, player->membuf, BUFFER_SAMPLES); + sf_count_t slen = sf_readf_float(player->sndfile, player->membuf, BufferSamples); if(slen < 1) break; slen *= player->sfinfo.channels * (sf_count_t)sizeof(float); @@ -243,7 +245,7 @@ static int UpdatePlayer(StreamPlayer *player) alSourceUnqueueBuffers(player->source, 1, &bufid); processed--; - slen = sf_readf_float(player->sndfile, player->membuf, BUFFER_SAMPLES); + slen = sf_readf_float(player->sndfile, player->membuf, BufferSamples); if(slen > 0) { slen *= player->sfinfo.channels * (sf_count_t)sizeof(float); @@ -278,21 +280,21 @@ static int UpdatePlayer(StreamPlayer *player) } -/* CreateEffect creates a new OpenAL effect object with a convolution reverb - * type, and returns the new effect ID. +/* CreateEffect creates a new OpenAL effect object with a convolution type, and + * returns the new effect ID. */ static ALuint CreateEffect(void) { ALuint effect = 0; ALenum err; - printf("Using Convolution Reverb\n"); + printf("Using Convolution\n"); - /* Create the effect object and set the convolution reverb effect type. */ + /* Create the effect object and set the convolution effect type. */ alGenEffects(1, &effect); - alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_CONVOLUTION_REVERB_SOFT); + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_CONVOLUTION_SOFT); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { @@ -359,10 +361,10 @@ static ALuint LoadSound(const char *filename) } namepart = strrchr(filename, '/'); - if(namepart || (namepart=strrchr(filename, '\\'))) - namepart++; - else - namepart = filename; + if(!namepart) namepart = strrchr(filename, '\\'); + if(!namepart) namepart = filename; + else namepart++; + printf("Loading: %s (%s, %dhz, %" PRId64 " samples / %.2f seconds)\n", namepart, FormatName(format), sfinfo.samplerate, sfinfo.frames, (double)sfinfo.frames / sfinfo.samplerate); @@ -391,7 +393,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { @@ -423,10 +425,10 @@ int main(int argc, char **argv) if(InitAL(&argv, &argc) != 0) return 1; - if(!alIsExtensionPresent("AL_SOFTX_convolution_reverb")) + if(!alIsExtensionPresent("AL_SOFTX_convolution_effect")) { CloseAL(); - fprintf(stderr, "Error: Convolution revern not supported\n"); + fprintf(stderr, "Error: Convolution effect not supported\n"); return 1; } @@ -500,11 +502,11 @@ int main(int argc, char **argv) alGenAuxiliaryEffectSlots(1, &slot); /* Set the impulse response sound buffer on the effect slot. This allows - * effects to access it as needed. In this case, convolution reverb uses it - * as the filter source. NOTE: Unlike the effect object, the buffer *is* - * kept referenced and may not be changed or deleted as long as it's set, - * just like with a source. When another buffer is set, or the effect slot - * is deleted, the buffer reference is released. + * effects to access it as needed. In this case, convolution uses it as the + * filter source. NOTE: Unlike the effect object, the buffer *is* kept + * referenced and may not be changed or deleted as long as it's set, just + * like with a source. When another buffer is set, or the effect slot is + * deleted, the buffer reference is released. * * The effect slot's gain is reduced because the impulse responses I've * tested with result in excessively loud reverb. Is that normal? Even with @@ -555,10 +557,9 @@ int main(int argc, char **argv) continue; namepart = strrchr(argv[i], '/'); - if(namepart || (namepart=strrchr(argv[i], '\\'))) - namepart++; - else - namepart = argv[i]; + if(!namepart) namepart = strrchr(argv[i], '\\'); + if(!namepart) namepart = argv[i]; + else namepart++; printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format), player->sfinfo.samplerate); diff --git a/Engine/lib/openal-soft/examples/aldirect.cpp b/Engine/lib/openal-soft/examples/aldirect.cpp new file mode 100644 index 000000000..d7964adda --- /dev/null +++ b/Engine/lib/openal-soft/examples/aldirect.cpp @@ -0,0 +1,472 @@ +/* + * OpenAL Direct Context Example + * + * Copyright (c) 2024 by Chris Robinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file contains an example for playing a sound buffer with the Direct API + * extension. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sndfile.h" + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alspan.h" +#include "common/alhelpers.h" + +#include "win_main_utf8.h" + +namespace { + +/* On Windows when using Creative's router, we need to override the ALC + * functions and access the driver functions directly. This isn't needed when + * not using the router, or on other OSs. + */ +LPALCOPENDEVICE p_alcOpenDevice{alcOpenDevice}; +LPALCCLOSEDEVICE p_alcCloseDevice{alcCloseDevice}; +LPALCISEXTENSIONPRESENT p_alcIsExtensionPresent{alcIsExtensionPresent}; +LPALCCREATECONTEXT p_alcCreateContext{alcCreateContext}; +LPALCDESTROYCONTEXT p_alcDestroyContext{alcDestroyContext}; +LPALCGETPROCADDRESS p_alcGetProcAddress{alcGetProcAddress}; + + +LPALGETSTRINGDIRECT alGetStringDirect{}; +LPALGETERRORDIRECT alGetErrorDirect{}; +LPALISEXTENSIONPRESENTDIRECT alIsExtensionPresentDirect{}; + +LPALGENBUFFERSDIRECT alGenBuffersDirect{}; +LPALDELETEBUFFERSDIRECT alDeleteBuffersDirect{}; +LPALISBUFFERDIRECT alIsBufferDirect{}; +LPALBUFFERIDIRECT alBufferiDirect{}; +LPALBUFFERDATADIRECT alBufferDataDirect{}; + +LPALGENSOURCESDIRECT alGenSourcesDirect{}; +LPALDELETESOURCESDIRECT alDeleteSourcesDirect{}; +LPALSOURCEIDIRECT alSourceiDirect{}; +LPALGETSOURCEIDIRECT alGetSourceiDirect{}; +LPALGETSOURCEFDIRECT alGetSourcefDirect{}; +LPALSOURCEPLAYDIRECT alSourcePlayDirect{}; + + +struct SndFileDeleter { + void operator()(SNDFILE *sndfile) { sf_close(sndfile); } +}; +using SndFilePtr = std::unique_ptr; + +enum class FormatType { + Int16, + Float, + IMA4, + MSADPCM +}; + +/* LoadBuffer loads the named audio file into an OpenAL buffer object, and + * returns the new buffer ID. + */ +ALuint LoadSound(ALCcontext *context, const std::string_view filename) +{ + /* Open the audio file and check that it's usable. */ + SF_INFO sfinfo{}; + SndFilePtr sndfile{sf_open(std::string{filename}.c_str(), SFM_READ, &sfinfo)}; + if(!sndfile) + { + std::cerr<< "Could not open audio in "<(inf.datalen, ALubyte{0}); + inf.data = fmtbuf.data(); + if(sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR) + sample_format = FormatType::Int16; + else + { + /* Read the nBlockAlign field, and convert from bytes- to + * samples-per-block (verifying it's valid by converting back + * and comparing to the original value). + */ + byteblockalign = fmtbuf[12] | (fmtbuf[13]<<8); + if(sample_format == FormatType::IMA4) + { + splblockalign = (byteblockalign/sfinfo.channels - 4)/4*8 + 1; + if(splblockalign < 1 + || ((splblockalign-1)/2 + 4)*sfinfo.channels != byteblockalign) + sample_format = FormatType::Int16; + } + else if(sample_format == FormatType::MSADPCM) + { + splblockalign = (byteblockalign/sfinfo.channels - 7)*2 + 2; + if(splblockalign < 2 + || ((splblockalign-2)/2 + 7)*sfinfo.channels != byteblockalign) + sample_format = FormatType::Int16; + } + else + sample_format = FormatType::Int16; + } + } + } + + if(sample_format == FormatType::Int16) + { + splblockalign = 1; + byteblockalign = sfinfo.channels * 2; + } + else if(sample_format == FormatType::Float) + { + splblockalign = 1; + byteblockalign = sfinfo.channels * 4; + } + + /* Figure out the OpenAL format from the file and desired sample type. */ + ALenum format{AL_NONE}; + if(sfinfo.channels == 1) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_MONO16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_MONO_FLOAT32; + else if(sample_format == FormatType::IMA4) + format = AL_FORMAT_MONO_IMA4; + else if(sample_format == FormatType::MSADPCM) + format = AL_FORMAT_MONO_MSADPCM_SOFT; + } + else if(sfinfo.channels == 2) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_STEREO16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_STEREO_FLOAT32; + else if(sample_format == FormatType::IMA4) + format = AL_FORMAT_STEREO_IMA4; + else if(sample_format == FormatType::MSADPCM) + format = AL_FORMAT_STEREO_MSADPCM_SOFT; + } + else if(sfinfo.channels == 3) + { + if(sf_command(sndfile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_BFORMAT2D_16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_BFORMAT2D_FLOAT32; + } + } + else if(sfinfo.channels == 4) + { + if(sf_command(sndfile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_BFORMAT3D_16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_BFORMAT3D_FLOAT32; + } + } + if(!format) + { + std::cerr<< "Unsupported channel count: "< sf_count_t{std::numeric_limits::max()}/byteblockalign) + { + std::cerr<< "Too many sample frames in "<(static_cast(sfinfo.frames / splblockalign + * byteblockalign)); + + sf_count_t num_frames{}; + if(sample_format == FormatType::Int16) + num_frames = sf_readf_short(sndfile.get(), reinterpret_cast(membuf.data()), + sfinfo.frames); + else if(sample_format == FormatType::Float) + num_frames = sf_readf_float(sndfile.get(), reinterpret_cast(membuf.data()), + sfinfo.frames); + else + { + const sf_count_t count{sfinfo.frames / splblockalign * byteblockalign}; + num_frames = sf_read_raw(sndfile.get(), membuf.data(), count); + if(num_frames > 0) + num_frames = num_frames / byteblockalign * splblockalign; + } + if(num_frames < 1) + { + std::cerr<< "Failed to read samples in "<(num_frames / splblockalign * byteblockalign); + + std::cout<< "Loading: "< 1) + alBufferiDirect(context, buffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, splblockalign); + alBufferDataDirect(context, buffer, format, membuf.data(), num_bytes, sfinfo.samplerate); + + /* Check if an error occurred, and clean up if so. */ + if(ALenum err{alGetErrorDirect(context)}; err != AL_NO_ERROR) + { + std::cerr<< "OpenAL Error: "< args) +{ + /* Print out usage if no arguments were specified */ + if(args.size() < 2) + { + std::cerr<< "Usage: "<] \n"; + return 1; + } + + /* Initialize OpenAL. */ + args = args.subspan(1); + + ALCdevice *device{}; + if(args.size() > 1 && args[0] == "-device") + { + device = p_alcOpenDevice(std::string{args[1]}.c_str()); + if(!device) + std::cerr<< "Failed to open \""<( + p_alcGetProcAddress(device, "alcGetProcAddress2")); + p_alcCloseDevice(device); + + /* Load the driver-specific ALC functions we'll be using. */ +#define LOAD_PROC(N) p_##N = reinterpret_cast(p_alcGetProcAddress2(nullptr, #N)) + LOAD_PROC(alcOpenDevice); + LOAD_PROC(alcCloseDevice); + LOAD_PROC(alcIsExtensionPresent); + LOAD_PROC(alcGetProcAddress); + LOAD_PROC(alcCreateContext); + LOAD_PROC(alcDestroyContext); + LOAD_PROC(alcGetProcAddress); +#undef LOAD_PROC + device = p_alcOpenDevice(devname.c_str()); + assert(device != nullptr); + } + + /* Load the Direct API functions we're using. */ +#define LOAD_PROC(N) N = reinterpret_cast(p_alcGetProcAddress(device, #N)) + LOAD_PROC(alGetStringDirect); + LOAD_PROC(alGetErrorDirect); + LOAD_PROC(alIsExtensionPresentDirect); + + LOAD_PROC(alGenBuffersDirect); + LOAD_PROC(alDeleteBuffersDirect); + LOAD_PROC(alIsBufferDirect); + LOAD_PROC(alBufferiDirect); + LOAD_PROC(alBufferDataDirect); + + LOAD_PROC(alGenSourcesDirect); + LOAD_PROC(alDeleteSourcesDirect); + LOAD_PROC(alSourceiDirect); + LOAD_PROC(alGetSourceiDirect); + LOAD_PROC(alGetSourcefDirect); + LOAD_PROC(alSourcePlayDirect); +#undef LOAD_PROC + + /* Create the context. It doesn't need to be set as current to use with the + * Direct API functions. + */ + ALCcontext *context{p_alcCreateContext(device, nullptr)}; + if(!context) + { + p_alcCloseDevice(device); + std::cerr<< "Could not create a context!\n"; + return 1; + } + + /* Load the sound into a buffer. */ + const ALuint buffer{LoadSound(context, args[0])}; + if(!buffer) + { + p_alcDestroyContext(context); + p_alcCloseDevice(device); + return 1; + } + + /* Create the source to play the sound with. */ + ALuint source{0}; + alGenSourcesDirect(context, 1, &source); + alSourceiDirect(context, source, AL_BUFFER, static_cast(buffer)); + assert(alGetErrorDirect(context)==AL_NO_ERROR && "Failed to setup sound source"); + + /* Play the sound until it finishes. */ + alSourcePlayDirect(context, source); + ALenum state{}; + do { + al_nssleep(10000000); + alGetSourceiDirect(context, source, AL_SOURCE_STATE, &state); + + /* Get the source offset. */ + ALfloat offset{}; + alGetSourcefDirect(context, source, AL_SEC_OFFSET, &offset); + printf("\rOffset: %f ", offset); + fflush(stdout); + } while(alGetErrorDirect(context) == AL_NO_ERROR && state == AL_PLAYING); + printf("\n"); + + /* All done. Delete resources, and close down OpenAL. */ + alDeleteSourcesDirect(context, 1, &source); + alDeleteBuffersDirect(context, 1, &buffer); + + p_alcDestroyContext(context); + p_alcCloseDevice(device); + + return 0; +} + +} // namespace + +int main(int argc, char **argv) +{ + assert(argc >= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/Engine/lib/openal-soft/examples/alffplay.cpp b/Engine/lib/openal-soft/examples/alffplay.cpp index ae40a51aa..e51515e7e 100644 --- a/Engine/lib/openal-soft/examples/alffplay.cpp +++ b/Engine/lib/openal-soft/examples/alffplay.cpp @@ -4,29 +4,31 @@ * Requires C++14. */ -#include -#include #include -#include -#include -#include -#include -#include -#include +#include #include +#include #include #include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include #ifdef __GNUC__ _Pragma("GCC diagnostic push") @@ -67,17 +69,14 @@ _Pragma("GCC diagnostic pop") #include "AL/al.h" #include "AL/alext.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" #include "common/alhelpers.h" namespace { -inline constexpr int64_t operator "" _i64(unsigned long long int n) noexcept { return static_cast(n); } - -#ifndef M_PI -#define M_PI (3.14159265358979323846) -#endif - using fixed32 = std::chrono::duration>; using nanoseconds = std::chrono::nanoseconds; using microseconds = std::chrono::microseconds; @@ -170,12 +169,17 @@ using SwsContextPtr = std::unique_ptr; struct ChannelLayout : public AVChannelLayout { ChannelLayout() : AVChannelLayout{} { } + ChannelLayout(const ChannelLayout &rhs) : AVChannelLayout{} + { av_channel_layout_copy(this, &rhs); } ~ChannelLayout() { av_channel_layout_uninit(this); } + + auto operator=(const ChannelLayout &rhs) -> ChannelLayout& + { av_channel_layout_copy(this, &rhs); return *this; } }; -template class DataQueue { + const size_t mSizeLimit; std::mutex mPacketMutex, mFrameMutex; std::condition_variable mPacketCond; std::condition_variable mInFrameCond, mOutFrameCond; @@ -199,6 +203,8 @@ class DataQueue { } public: + DataQueue(size_t size_limit) : mSizeLimit{size_limit} { } + int sendPacket(AVCodecContext *codecctx) { AVPacketPtr packet{getPacket()}; @@ -237,7 +243,7 @@ public: void setFinished() { { - std::lock_guard _{mPacketMutex}; + std::lock_guard packetlock{mPacketMutex}; mFinished = true; } mPacketCond.notify_one(); @@ -246,7 +252,7 @@ public: void flush() { { - std::lock_guard _{mPacketMutex}; + std::lock_guard packetlock{mPacketMutex}; mFinished = true; mPackets.clear(); @@ -258,8 +264,8 @@ public: bool put(const AVPacket *pkt) { { - std::unique_lock lock{mPacketMutex}; - if(mTotalSize >= SizeLimit || mFinished) + std::lock_guard packet_lock{mPacketMutex}; + if(mTotalSize >= mSizeLimit || mFinished) return false; mPackets.push_back(AVPacketPtr{av_packet_alloc()}); @@ -285,7 +291,7 @@ struct AudioState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - DataQueue<2*1024*1024> mQueue; + DataQueue mQueue{2_uz*1024_uz*1024_uz}; /* Used for clock difference average computation */ seconds_d64 mClockDiffAvg{0}; @@ -305,13 +311,13 @@ struct AudioState { AVSampleFormat mDstSampleFmt{AV_SAMPLE_FMT_NONE}; /* Storage of converted samples */ - uint8_t *mSamples{nullptr}; + std::array mSamples{}; + al::span mSamplesSpan{}; int mSamplesLen{0}; /* In samples */ int mSamplesPos{0}; int mSamplesMax{0}; - std::unique_ptr mBufferData; - size_t mBufferDataSize{0}; + std::vector mBufferData; std::atomic mReadPos{0}; std::atomic mWritePos{0}; @@ -321,7 +327,7 @@ struct AudioState { std::mutex mSrcMutex; std::condition_variable mSrcCond; - std::atomic_flag mConnected; + std::atomic_flag mConnected{}; ALuint mSource{0}; std::array mBuffers{}; ALuint mBufferIdx{0}; @@ -335,18 +341,18 @@ struct AudioState { if(mBuffers[0]) alDeleteBuffers(static_cast(mBuffers.size()), mBuffers.data()); - av_freep(&mSamples); + av_freep(mSamples.data()); } static void AL_APIENTRY eventCallbackC(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, void *userParam) + ALsizei length, const ALchar *message, void *userParam) noexcept { static_cast(userParam)->eventCallback(eventType, object, param, length, message); } void eventCallback(ALenum eventType, ALuint object, ALuint param, ALsizei length, - const ALchar *message); + const ALchar *message) noexcept; - static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) + static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) noexcept { return static_cast(userptr)->bufferCallback(data, size); } - ALsizei bufferCallback(void *data, ALsizei size); + ALsizei bufferCallback(void *data, ALsizei size) noexcept; nanoseconds getClockNoLock(); nanoseconds getClock() @@ -359,7 +365,7 @@ struct AudioState { int getSync(); int decodeFrame(); - bool readAudio(uint8_t *samples, unsigned int length, int &sample_skip); + bool readAudio(al::span samples, unsigned int length, int &sample_skip); bool readAudio(int sample_skip); int handler(); @@ -371,7 +377,7 @@ struct VideoState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - DataQueue<14*1024*1024> mQueue; + DataQueue mQueue{14_uz*1024_uz*1024_uz}; /* The pts of the currently displayed frame, and the time (av_gettime) it * was last updated - used to have running video pts @@ -409,7 +415,7 @@ struct VideoState { nanoseconds getClock(); - void display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame); + void display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame) const; void updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw); int handler(); }; @@ -437,8 +443,7 @@ struct MovieState { std::string mFilename; - MovieState(std::string fname) - : mAudio(*this), mVideo(*this), mFilename(std::move(fname)) + MovieState(std::string_view fname) : mAudio{*this}, mVideo{*this}, mFilename{fname} { } ~MovieState() { @@ -449,16 +454,14 @@ struct MovieState { static int decode_interrupt_cb(void *ctx); bool prepare(); - void setTitle(SDL_Window *window); + void setTitle(SDL_Window *window) const; void stop(); - nanoseconds getClock(); + [[nodiscard]] nanoseconds getClock() const; + [[nodiscard]] nanoseconds getMasterClock(); + [[nodiscard]] nanoseconds getDuration() const; - nanoseconds getMasterClock(); - - nanoseconds getDuration(); - - int streamComponentOpen(unsigned int stream_index); + bool streamComponentOpen(AVStream *stream); int parse_handler(); }; @@ -474,8 +477,8 @@ nanoseconds AudioState::getClockNoLock() // Get the current device clock time and latency. auto device = alcGetContextsDevice(alcGetCurrentContext()); - ALCint64SOFT devtimes[2]{0,0}; - alcGetInteger64vSOFT(device, ALC_DEVICE_CLOCK_LATENCY_SOFT, 2, devtimes); + std::array devtimes{}; + alcGetInteger64vSOFT(device, ALC_DEVICE_CLOCK_LATENCY_SOFT, 2, devtimes.data()); auto latency = nanoseconds{devtimes[1]}; auto device_time = nanoseconds{devtimes[0]}; @@ -485,7 +488,7 @@ nanoseconds AudioState::getClockNoLock() return device_time - mDeviceStartTime - latency; } - if(mBufferDataSize > 0) + if(!mBufferData.empty()) { if(mDeviceStartTime == nanoseconds::min()) return nanoseconds::zero(); @@ -494,15 +497,14 @@ nanoseconds AudioState::getClockNoLock() * actually the timestamp of the first sample frame played. The audio * clock, then, is that plus the current source offset. */ - ALint64SOFT offset[2]; + std::array offset{}; if(alGetSourcei64vSOFT) - alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset); + alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset.data()); else { ALint ioffset; alGetSourcei(mSource, AL_SAMPLE_OFFSET, &ioffset); offset[0] = ALint64SOFT{ioffset} << 32; - offset[1] = 0; } /* NOTE: The source state must be checked last, in case an underrun * occurs and the source stops between getting the state and retrieving @@ -523,7 +525,7 @@ nanoseconds AudioState::getClockNoLock() */ const size_t woffset{mWritePos.load(std::memory_order_acquire)}; const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - + const size_t readable{((woffset>=roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; pts = mCurrentPts - nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate; @@ -550,15 +552,14 @@ nanoseconds AudioState::getClockNoLock() nanoseconds pts{mCurrentPts}; if(mSource) { - ALint64SOFT offset[2]; + std::array offset{}; if(alGetSourcei64vSOFT) - alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset); + alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset.data()); else { ALint ioffset; alGetSourcei(mSource, AL_SAMPLE_OFFSET, &ioffset); offset[0] = ALint64SOFT{ioffset} << 32; - offset[1] = 0; } ALint queued, status; alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); @@ -586,10 +587,10 @@ bool AudioState::startPlayback() { const size_t woffset{mWritePos.load(std::memory_order_acquire)}; const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - + const size_t readable{((woffset >= roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; - if(mBufferDataSize > 0) + if(!mBufferData.empty()) { if(readable == 0) return false; @@ -610,8 +611,8 @@ bool AudioState::startPlayback() /* Subtract the total buffer queue time from the current pts to get the * pts of the start of the queue. */ - int64_t srctimes[2]{0,0}; - alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_CLOCK_SOFT, srctimes); + std::array srctimes{}; + alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_CLOCK_SOFT, srctimes.data()); auto device_time = nanoseconds{srctimes[1]}; auto src_offset = duration_cast(fixed32{srctimes[0]}) / mCodecCtx->sample_rate; @@ -622,7 +623,7 @@ bool AudioState::startPlayback() * the device time the stream would have started at to reach where it * is now. */ - if(mBufferDataSize > 0) + if(!mBufferData.empty()) { nanoseconds startpts{mCurrentPts - nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate}; @@ -680,14 +681,19 @@ int AudioState::decodeFrame() if(mDecodedFrame->nb_samples > mSamplesMax) { - av_freep(&mSamples); - av_samples_alloc(&mSamples, nullptr, mCodecCtx->ch_layout.nb_channels, + av_freep(mSamples.data()); + av_samples_alloc(mSamples.data(), nullptr, mCodecCtx->ch_layout.nb_channels, mDecodedFrame->nb_samples, mDstSampleFmt, 0); mSamplesMax = mDecodedFrame->nb_samples; + mSamplesSpan = {mSamples[0], static_cast(mSamplesMax)*mFrameSize}; } + /* Copy to a local to mark const. Don't know why this can't be implicit. */ + using data_t = decltype(decltype(mDecodedFrame)::element_type::data); + std::array> cdata{}; + std::copy(std::begin(mDecodedFrame->data), std::end(mDecodedFrame->data), cdata.begin()); /* Return the amount of sample frames converted */ - int data_size{swr_convert(mSwresCtx.get(), &mSamples, mDecodedFrame->nb_samples, - const_cast(mDecodedFrame->data), mDecodedFrame->nb_samples)}; + const int data_size{swr_convert(mSwresCtx.get(), mSamples.data(), mDecodedFrame->nb_samples, + cdata.data(), mDecodedFrame->nb_samples)}; av_frame_unref(mDecodedFrame.get()); return data_size; @@ -697,15 +703,15 @@ int AudioState::decodeFrame() * multiple of the template type size. */ template -static void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t frame_size) +void sample_dup(al::span out, al::span in, size_t count, size_t frame_size) { - auto *sample = reinterpret_cast(in); - auto *dst = reinterpret_cast(out); + auto sample = al::span{reinterpret_cast(in.data()), in.size()/sizeof(T)}; + auto dst = al::span{reinterpret_cast(out.data()), out.size()/sizeof(T)}; /* NOTE: frame_size is a multiple of sizeof(T). */ - size_t type_mult{frame_size / sizeof(T)}; + const size_t type_mult{frame_size / sizeof(T)}; if(type_mult == 1) - std::fill_n(dst, count, *sample); + std::fill_n(dst.begin(), count, sample.front()); else for(size_t i{0};i < count;++i) { for(size_t j{0};j < type_mult;++j) @@ -713,7 +719,7 @@ static void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t fra } } -static void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t frame_size) +void sample_dup(al::span out, al::span in, size_t count, size_t frame_size) { if((frame_size&7) == 0) sample_dup(out, in, count, frame_size); @@ -725,7 +731,7 @@ static void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t fra sample_dup(out, in, count, frame_size); } -bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_skip) +bool AudioState::readAudio(al::span samples, unsigned int length, int &sample_skip) { unsigned int audio_size{0}; @@ -739,20 +745,21 @@ bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_sk { const auto len = static_cast(mSamplesLen - mSamplesPos); if(rem > len) rem = len; - std::copy_n(mSamples + static_cast(mSamplesPos)*mFrameSize, - rem*mFrameSize, samples); + const size_t boffset{static_cast(mSamplesPos) * size_t{mFrameSize}}; + std::copy_n(mSamplesSpan.cbegin()+ptrdiff_t(boffset), rem*size_t{mFrameSize}, + samples.begin()); } else { rem = std::min(rem, static_cast(-mSamplesPos)); /* Add samples by copying the first sample */ - sample_dup(samples, mSamples, rem, mFrameSize); + sample_dup(samples, mSamplesSpan, rem, mFrameSize); } - mSamplesPos += rem; + mSamplesPos += static_cast(rem); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; - samples += rem*mFrameSize; + samples = samples.subspan(rem*size_t{mFrameSize}); audio_size += rem; while(mSamplesPos >= mSamplesLen) @@ -777,7 +784,7 @@ bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_sk if(audio_size < length) { const unsigned int rem{length - audio_size}; - std::fill_n(samples, rem*mFrameSize, + std::fill_n(samples.begin(), rem*mFrameSize, (mDstSampleFmt == AV_SAMPLE_FMT_U8) ? 0x80 : 0x00); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; } @@ -791,17 +798,17 @@ bool AudioState::readAudio(int sample_skip) while(mSamplesLen > 0) { const size_t nsamples{((roffset > woffset) ? roffset-woffset-1 - : (roffset == 0) ? (mBufferDataSize-woffset-1) - : (mBufferDataSize-woffset)) / mFrameSize}; + : (roffset == 0) ? (mBufferData.size()-woffset-1) + : (mBufferData.size()-woffset)) / mFrameSize}; if(!nsamples) break; if(mSamplesPos < 0) { const size_t rem{std::min(nsamples, static_cast(-mSamplesPos))}; - sample_dup(&mBufferData[woffset], mSamples, rem, mFrameSize); + sample_dup(al::span{mBufferData}.subspan(woffset), mSamplesSpan, rem, mFrameSize); woffset += rem * mFrameSize; - if(woffset == mBufferDataSize) woffset = 0; + if(woffset == mBufferData.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; @@ -813,9 +820,10 @@ bool AudioState::readAudio(int sample_skip) const size_t boffset{static_cast(mSamplesPos) * size_t{mFrameSize}}; const size_t nbytes{rem * mFrameSize}; - memcpy(&mBufferData[woffset], mSamples + boffset, nbytes); + std::copy_n(mSamplesSpan.cbegin()+ptrdiff_t(boffset), nbytes, + mBufferData.begin()+ptrdiff_t(woffset)); woffset += nbytes; - if(woffset == mBufferDataSize) woffset = 0; + if(woffset == mBufferData.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; @@ -840,7 +848,7 @@ bool AudioState::readAudio(int sample_skip) void AL_APIENTRY AudioState::eventCallback(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message) + ALsizei length, const ALchar *message) noexcept { if(eventType == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) { @@ -878,25 +886,26 @@ void AL_APIENTRY AudioState::eventCallback(ALenum eventType, ALuint object, ALui } } -ALsizei AudioState::bufferCallback(void *data, ALsizei size) +ALsizei AudioState::bufferCallback(void *data, ALsizei size) noexcept { + auto dst = al::span{static_cast(data), static_cast(size)}; ALsizei got{0}; size_t roffset{mReadPos.load(std::memory_order_acquire)}; - while(got < size) + while(!dst.empty()) { const size_t woffset{mWritePos.load(std::memory_order_relaxed)}; if(woffset == roffset) break; - size_t todo{((woffset < roffset) ? mBufferDataSize : woffset) - roffset}; - todo = std::min(todo, static_cast(size-got)); + size_t todo{((woffset < roffset) ? mBufferData.size() : woffset) - roffset}; + todo = std::min(todo, dst.size()); - memcpy(data, &mBufferData[roffset], todo); - data = static_cast(data) + todo; + std::copy_n(mBufferData.cbegin()+ptrdiff_t(roffset), todo, dst.begin()); + dst = dst.subspan(todo); got += static_cast(todo); roffset += todo; - if(roffset == mBufferDataSize) + if(roffset == mBufferData.size()) roffset = 0; } mReadPos.store(roffset, std::memory_order_release); @@ -936,10 +945,11 @@ int AudioState::handler() }; EventControlManager event_controller{sleep_time}; - std::unique_ptr samples; + std::vector samples; ALsizei buffer_len{0}; /* Find a suitable format for OpenAL. */ + const auto layoutmask = mCodecCtx->ch_layout.u.mask; /* NOLINT(*-union-access) */ mDstChanLayout = 0; mFormat = AL_NONE; if((mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP @@ -957,29 +967,28 @@ int AudioState::handler() { if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_7POINT1) + if(layoutmask == AV_CH_LAYOUT_7POINT1) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 8; mFormat = alGetEnumValue("AL_FORMAT_71CHN32"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1 - || mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1_BACK) + if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 6; mFormat = alGetEnumValue("AL_FORMAT_51CHN32"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_QUAD) + if(layoutmask == AV_CH_LAYOUT_QUAD) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 4; mFormat = alGetEnumValue("AL_FORMAT_QUAD32"); } } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_MONO) + if(layoutmask == AV_CH_LAYOUT_MONO) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 1; mFormat = AL_FORMAT_MONO_FLOAT32; } @@ -1019,29 +1028,28 @@ int AudioState::handler() { if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_7POINT1) + if(layoutmask == AV_CH_LAYOUT_7POINT1) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 8; mFormat = alGetEnumValue("AL_FORMAT_71CHN8"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1 - || mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1_BACK) + if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 6; mFormat = alGetEnumValue("AL_FORMAT_51CHN8"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_QUAD) + if(layoutmask == AV_CH_LAYOUT_QUAD) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 4; mFormat = alGetEnumValue("AL_FORMAT_QUAD8"); } } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_MONO) + if(layoutmask == AV_CH_LAYOUT_MONO) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 1; mFormat = AL_FORMAT_MONO8; } @@ -1073,29 +1081,28 @@ int AudioState::handler() { if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_7POINT1) + if(layoutmask == AV_CH_LAYOUT_7POINT1) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 8; mFormat = alGetEnumValue("AL_FORMAT_71CHN16"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1 - || mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1_BACK) + if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 6; mFormat = alGetEnumValue("AL_FORMAT_51CHN16"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_QUAD) + if(layoutmask == AV_CH_LAYOUT_QUAD) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 4; mFormat = alGetEnumValue("AL_FORMAT_QUAD16"); } } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_MONO) + if(layoutmask == AV_CH_LAYOUT_MONO) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 1; mFormat = AL_FORMAT_MONO16; } @@ -1120,7 +1127,8 @@ int AudioState::handler() } } - mSamples = nullptr; + mSamples.fill(nullptr); + mSamplesSpan = {}; mSamplesMax = 0; mSamplesPos = 0; mSamplesLen = 0; @@ -1151,9 +1159,9 @@ int AudioState::handler() mSwresCtx.reset(ps); if(err != 0) { - char errstr[AV_ERROR_MAX_STRING_SIZE]{}; + std::array errstr{}; std::cerr<< "Failed to allocate SwrContext: " - < mtx(64*64, 0.0); + std::vector mtx(64_uz*64_uz, 0.0); mtx[0 + 0*64] = std::sqrt(0.5); mtx[3 + 1*64] = 1.0; mtx[1 + 2*64] = 1.0; @@ -1185,17 +1193,17 @@ int AudioState::handler() mSwresCtx.reset(ps); if(err != 0) { - char errstr[AV_ERROR_MAX_STRING_SIZE]{}; + std::array errstr{}; std::cerr<< "Failed to allocate SwrContext: " - < errstr{}; std::cerr<< "Failed to initialize audio converter: " - <(M_PI / 3.0), static_cast(-M_PI / 3.0)}; - alSourcefv(mSource, AL_STEREO_ANGLES, angles); + static constexpr std::array angles{static_cast(al::numbers::pi / 3.0), + static_cast(-al::numbers::pi / 3.0)}; + alSourcefv(mSource, AL_STEREO_ANGLES, angles.data()); } if(has_bfmt_ex) { @@ -1237,13 +1246,12 @@ int AudioState::handler() } else { - mBufferDataSize = static_cast(duration_cast(mCodecCtx->sample_rate * - AudioBufferTotalTime).count()) * mFrameSize; - mBufferData = std::make_unique(mBufferDataSize); - std::fill_n(mBufferData.get(), mBufferDataSize, uint8_t{}); + mBufferData.resize(static_cast(duration_cast(mCodecCtx->sample_rate * + AudioBufferTotalTime).count()) * mFrameSize); + std::fill(mBufferData.begin(), mBufferData.end(), uint8_t{}); mReadPos.store(0, std::memory_order_relaxed); - mWritePos.store(mBufferDataSize/mFrameSize/2*mFrameSize, std::memory_order_relaxed); + mWritePos.store(mBufferData.size()/mFrameSize/2*mFrameSize, std::memory_order_relaxed); ALCint refresh{}; alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); @@ -1255,12 +1263,12 @@ int AudioState::handler() buffer_len = static_cast(duration_cast(mCodecCtx->sample_rate * AudioBufferTime).count() * mFrameSize); if(buffer_len > 0) - samples = std::make_unique(static_cast(buffer_len)); + samples.resize(static_cast(buffer_len)); /* Prefill the codec buffer. */ auto packet_sender = [this]() { - while(1) + while(true) { const int ret{mQueue.sendPacket(mCodecCtx.get())}; if(ret == AVErrorEOF) break; @@ -1287,7 +1295,7 @@ int AudioState::handler() mCurrentPts += skip; } - while(1) + while(true) { if(mMovie.mQuit.load(std::memory_order_relaxed)) { @@ -1299,11 +1307,11 @@ int AudioState::handler() mSamplesLen = decodeFrame(); mSamplesPos = mSamplesLen; } while(mSamplesLen > 0); - goto finish; + break; } ALenum state; - if(mBufferDataSize > 0) + if(!mBufferData.empty()) { alGetSourcei(mSource, AL_SOURCE_STATE, &state); @@ -1333,13 +1341,13 @@ int AudioState::handler() /* Read the next chunk of data, filling the buffer, and queue * it on the source. */ - if(!readAudio(samples.get(), static_cast(buffer_len), sync_skip)) + if(!readAudio(samples, static_cast(buffer_len), sync_skip)) break; const ALuint bufid{mBuffers[mBufferIdx]}; mBufferIdx = static_cast((mBufferIdx+1) % mBuffers.size()); - alBufferData(bufid, mFormat, samples.get(), buffer_len, mCodecCtx->sample_rate); + alBufferData(bufid, mFormat, samples.data(), buffer_len, mCodecCtx->sample_rate); alSourceQueueBuffers(mSource, 1, &bufid); ++queued; } @@ -1380,7 +1388,6 @@ int AudioState::handler() mSrcCond.wait_for(srclock, sleep_time); } -finish: alSourceRewind(mSource); alSourcei(mSource, AL_BUFFER, 0); @@ -1393,7 +1400,7 @@ finish: nanoseconds VideoState::getClock() { /* NOTE: This returns incorrect times while not playing. */ - std::lock_guard _{mDispPtsMutex}; + std::lock_guard displock{mDispPtsMutex}; if(mDisplayPtsTime == microseconds::min()) return nanoseconds::zero(); auto delta = get_avtime() - mDisplayPtsTime; @@ -1401,7 +1408,7 @@ nanoseconds VideoState::getClock() } /* Called by VideoState::updateVideo to display the next video frame. */ -void VideoState::display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame) +void VideoState::display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame) const { if(!mImage) return; @@ -1451,7 +1458,7 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re auto clocktime = mMovie.getMasterClock(); bool updated{false}; - while(1) + while(true) { size_t next_idx{(read_idx+1)%mPictQ.size()}; if(next_idx == mPictQWrite.load(std::memory_order_acquire)) @@ -1511,9 +1518,9 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re { double aspect_ratio = av_q2d(frame->sample_aspect_ratio); if(aspect_ratio >= 1.0) - frame_width = static_cast(frame_width*aspect_ratio + 0.5); + frame_width = static_cast(std::lround(frame_width * aspect_ratio)); else if(aspect_ratio > 0.0) - frame_height = static_cast(frame_height/aspect_ratio + 0.5); + frame_height = static_cast(std::lround(frame_height / aspect_ratio)); } SDL_SetWindowSize(screen, frame_width, frame_height); } @@ -1546,18 +1553,17 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re } /* point pict at the queue */ - uint8_t *pict_data[3]; - pict_data[0] = static_cast(pixels); - pict_data[1] = pict_data[0] + w*h; - pict_data[2] = pict_data[1] + w*h/4; + const auto framesize = static_cast(w)*static_cast(h); + const auto pixelspan = al::span{static_cast(pixels), framesize*3/2}; + const std::array pict_data{ + al::to_address(pixelspan.begin()), + al::to_address(pixelspan.begin() + ptrdiff_t{w}*h), + al::to_address(pixelspan.begin() + ptrdiff_t{w}*h + ptrdiff_t{w}*h/4) + }; + const std::array pict_linesize{pitch, pitch/2, pitch/2}; - int pict_linesize[3]; - pict_linesize[0] = pitch; - pict_linesize[1] = pitch / 2; - pict_linesize[2] = pitch / 2; - - sws_scale(mSwscaleCtx.get(), reinterpret_cast(frame->data), frame->linesize, - 0, h, pict_data, pict_linesize); + sws_scale(mSwscaleCtx.get(), std::data(frame->data), std::data(frame->linesize), + 0, h, pict_data.data(), pict_linesize.data()); SDL_UnlockTexture(mImage); } @@ -1575,7 +1581,7 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re { auto disp_time = get_avtime(); - std::lock_guard _{mDispPtsMutex}; + std::lock_guard displock{mDispPtsMutex}; mDisplayPts = vp->mPts; mDisplayPtsTime = disp_time; } @@ -1599,7 +1605,7 @@ int VideoState::handler() /* Prefill the codec buffer. */ auto packet_sender = [this]() { - while(1) + while(true) { const int ret{mQueue.sendPacket(mCodecCtx.get())}; if(ret == AVErrorEOF) break; @@ -1608,12 +1614,12 @@ int VideoState::handler() auto sender = std::async(std::launch::async, packet_sender); { - std::lock_guard _{mDispPtsMutex}; + std::lock_guard displock{mDispPtsMutex}; mDisplayPtsTime = get_avtime(); } auto current_pts = nanoseconds::zero(); - while(1) + while(true) { size_t write_idx{mPictQWrite.load(std::memory_order_relaxed)}; Picture *vp{&mPictQ[write_idx]}; @@ -1707,7 +1713,7 @@ bool MovieState::prepare() return true; } -void MovieState::setTitle(SDL_Window *window) +void MovieState::setTitle(SDL_Window *window) const { auto pos1 = mFilename.rfind('/'); auto pos2 = mFilename.rfind('\\'); @@ -1717,7 +1723,7 @@ void MovieState::setTitle(SDL_Window *window) SDL_SetWindowTitle(window, (mFilename.substr(fpos)+" - "+AppName).c_str()); } -nanoseconds MovieState::getClock() +nanoseconds MovieState::getClock() const { if(mClockBase == microseconds::min()) return nanoseconds::zero(); @@ -1733,49 +1739,46 @@ nanoseconds MovieState::getMasterClock() return getClock(); } -nanoseconds MovieState::getDuration() +nanoseconds MovieState::getDuration() const { return std::chrono::duration>(mFormatCtx->duration); } -int MovieState::streamComponentOpen(unsigned int stream_index) +bool MovieState::streamComponentOpen(AVStream *stream) { - 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)}; - if(!avctx) return -1; + if(!avctx) return false; - if(avcodec_parameters_to_context(avctx.get(), mFormatCtx->streams[stream_index]->codecpar)) - return -1; + if(avcodec_parameters_to_context(avctx.get(), stream->codecpar)) + return false; const AVCodec *codec{avcodec_find_decoder(avctx->codec_id)}; if(!codec || avcodec_open2(avctx.get(), codec, nullptr) < 0) { std::cerr<< "Unsupported codec: "<codec_id) << " (0x"<codec_id<codec_type) { case AVMEDIA_TYPE_AUDIO: - mAudio.mStream = mFormatCtx->streams[stream_index]; + mAudio.mStream = stream; mAudio.mCodecCtx = std::move(avctx); - break; + return true; case AVMEDIA_TYPE_VIDEO: - mVideo.mStream = mFormatCtx->streams[stream_index]; + mVideo.mStream = stream; mVideo.mCodecCtx = std::move(avctx); - break; + return true; default: - return -1; + break; } - return static_cast(stream_index); + return false; } int MovieState::parse_handler() @@ -1787,13 +1790,16 @@ int MovieState::parse_handler() int audio_index{-1}; /* Find the first video and audio streams */ - for(unsigned int i{0u};i < mFormatCtx->nb_streams;i++) + const auto ctxstreams = al::span{mFormatCtx->streams, mFormatCtx->nb_streams}; + for(size_t i{0};i < ctxstreams.size();++i) { - auto codecpar = mFormatCtx->streams[i]->codecpar; - 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); + auto codecpar = ctxstreams[i]->codecpar; + if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !DisableVideo && video_index < 0 + && streamComponentOpen(ctxstreams[i])) + video_index = static_cast(i); + else if(codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0 + && streamComponentOpen(ctxstreams[i])) + audio_index = static_cast(i); } { @@ -1895,18 +1901,16 @@ std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) return os; } -} // namespace - -int main(int argc, char *argv[]) +int main(al::span args) { SDL_SetMainReady(); std::unique_ptr movState; - if(argc < 2) + if(args.size() < 2) { - std::cerr<< "Usage: "<] [-direct] " <] [-direct] " <( - alcGetProcAddress(device, "alcGetInteger64vSOFT") - ); + alcGetProcAddress(device, "alcGetInteger64vSOFT")); } } @@ -1988,8 +1988,7 @@ int main(int argc, char *argv[]) { std::cout<< "Found AL_SOFT_source_latency" <( - alGetProcAddress("alGetSourcei64vSOFT") - ); + alGetProcAddress("alGetSourcei64vSOFT")); } if(alIsExtensionPresent("AL_SOFT_events")) { @@ -2006,10 +2005,10 @@ int main(int argc, char *argv[]) alGetProcAddress("alBufferCallbackSOFT")); } - int fileidx{0}; - for(;fileidx < argc;++fileidx) + size_t fileidx{0}; + for(;fileidx < args.size();++fileidx) { - if(strcmp(argv[fileidx], "-direct") == 0) + if(args[fileidx] == "-direct") { if(alIsExtensionPresent("AL_SOFT_direct_channels_remix")) { @@ -2024,7 +2023,7 @@ int main(int argc, char *argv[]) else std::cerr<< "AL_SOFT_direct_channels not supported for direct output" <{new MovieState{argv[fileidx++]}}; + movState = std::make_unique(args[fileidx++]); if(!movState->prepare()) movState = nullptr; } if(!movState) @@ -2077,7 +2076,7 @@ int main(int argc, char *argv[]) Next, Quit } eom_action{EomAction::Next}; seconds last_time{seconds::min()}; - while(1) + while(true) { /* SDL_WaitEventTimeout is broken, just force a 10ms sleep. */ std::this_thread::sleep_for(milliseconds{10}); @@ -2143,9 +2142,9 @@ int main(int argc, char *argv[]) if(eom_action != EomAction::Quit) { movState = nullptr; - while(fileidx < argc && !movState) + while(fileidx < args.size() && !movState) { - movState = std::unique_ptr{new MovieState{argv[fileidx++]}}; + movState = std::make_unique(args[fileidx++]); if(!movState->prepare()) movState = nullptr; } if(movState) @@ -2179,3 +2178,13 @@ int main(int argc, char *argv[]) std::cerr<< "SDL_WaitEvent error - "<= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/Engine/lib/openal-soft/examples/alhrtf.c b/Engine/lib/openal-soft/examples/alhrtf.c index d878870e1..a2f1824a1 100644 --- a/Engine/lib/openal-soft/examples/alhrtf.c +++ b/Engine/lib/openal-soft/examples/alhrtf.c @@ -40,6 +40,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + #ifndef M_PI #define M_PI (3.14159265358979323846) @@ -121,7 +123,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { diff --git a/Engine/lib/openal-soft/examples/allatency.c b/Engine/lib/openal-soft/examples/allatency.c index ab4a4ebc1..9a5354423 100644 --- a/Engine/lib/openal-soft/examples/allatency.c +++ b/Engine/lib/openal-soft/examples/allatency.c @@ -37,6 +37,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + static LPALSOURCEDSOFT alSourcedSOFT; static LPALSOURCE3DSOFT alSource3dSOFT; @@ -124,7 +126,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { diff --git a/Engine/lib/openal-soft/examples/alloopback.c b/Engine/lib/openal-soft/examples/alloopback.c index 56cd420f3..106d0d461 100644 --- a/Engine/lib/openal-soft/examples/alloopback.c +++ b/Engine/lib/openal-soft/examples/alloopback.c @@ -29,6 +29,7 @@ #include #include #include +#include #define SDL_MAIN_HANDLED #include "SDL.h" @@ -118,7 +119,7 @@ static ALuint CreateSineWave(void) alGenBuffers(1, &buffer); alBufferData(buffer, AL_FORMAT_MONO16, data, sizeof(data), 44100); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { diff --git a/Engine/lib/openal-soft/examples/almultireverb.c b/Engine/lib/openal-soft/examples/almultireverb.c index a77cc59e5..f9a46c4f0 100644 --- a/Engine/lib/openal-soft/examples/almultireverb.c +++ b/Engine/lib/openal-soft/examples/almultireverb.c @@ -47,6 +47,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + #ifndef M_PI #define M_PI 3.14159265358979323846 @@ -106,7 +108,8 @@ static int LoadEffect(ALuint effect, const EFXEAXREVERBPROPERTIES *reverb) * the needed panning vectors). */ alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); - if((err=alGetError()) != AL_NO_ERROR) + err = alGetError(); + if(err != AL_NO_ERROR) { fprintf(stderr, "Failed to set EAX Reverb: %s (0x%04x)\n", alGetString(err), err); return 0; @@ -137,8 +140,9 @@ static int LoadEffect(ALuint effect, const EFXEAXREVERBPROPERTIES *reverb) alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor); alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); - /* Check if an error occured, and return failure if so. */ - if((err=alGetError()) != AL_NO_ERROR) + /* Check if an error occurred, and return failure if so. */ + err = alGetError(); + if(err != AL_NO_ERROR) { fprintf(stderr, "Error setting up reverb: %s\n", alGetString(err)); return 0; @@ -210,7 +214,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { @@ -493,7 +497,7 @@ int main(int argc, char **argv) } if(argc < 1) { - fprintf(stderr, "No filename spacified.\n"); + fprintf(stderr, "No filename specified.\n"); CloseAL(); return 1; } diff --git a/Engine/lib/openal-soft/examples/alplay.c b/Engine/lib/openal-soft/examples/alplay.c index 4291cb476..9af7ca40e 100644 --- a/Engine/lib/openal-soft/examples/alplay.c +++ b/Engine/lib/openal-soft/examples/alplay.c @@ -37,6 +37,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + enum FormatType { Int16, @@ -266,7 +268,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { diff --git a/Engine/lib/openal-soft/examples/alrecord.c b/Engine/lib/openal-soft/examples/alrecord.c index 03894493b..2a1fbd2f4 100644 --- a/Engine/lib/openal-soft/examples/alrecord.c +++ b/Engine/lib/openal-soft/examples/alrecord.c @@ -139,7 +139,7 @@ int main(int argc, char **argv) char *end; if(strcmp(argv[0], "--") == 0) break; - else if(strcmp(argv[0], "--channels") == 0 || strcmp(argv[0], "-c") == 0) + if(strcmp(argv[0], "--channels") == 0 || strcmp(argv[0], "-c") == 0) { if(argc < 2) { diff --git a/Engine/lib/openal-soft/examples/alreverb.c b/Engine/lib/openal-soft/examples/alreverb.c index 11a3ac6b2..5f68fe3a1 100644 --- a/Engine/lib/openal-soft/examples/alreverb.c +++ b/Engine/lib/openal-soft/examples/alreverb.c @@ -40,6 +40,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + /* Effect object functions */ static LPALGENEFFECTS alGenEffects; @@ -75,16 +77,17 @@ static ALuint LoadEffect(const EFXEAXREVERBPROPERTIES *reverb) ALuint effect = 0; ALenum err; + /* Clear error state. */ + alGetError(); + /* Create the effect object and check if we can do EAX reverb. */ alGenEffects(1, &effect); - if(alGetEnumValue("AL_EFFECT_EAXREVERB") != 0) + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + err = alGetError(); + if(err == AL_NO_ERROR) { printf("Using EAX Reverb\n"); - /* EAX Reverb is available. Set the EAX effect type then load the - * reverb properties. */ - alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); - alEffectf(effect, AL_EAXREVERB_DENSITY, reverb->flDensity); alEffectf(effect, AL_EAXREVERB_DIFFUSION, reverb->flDiffusion); alEffectf(effect, AL_EAXREVERB_GAIN, reverb->flGain); @@ -114,7 +117,8 @@ static ALuint LoadEffect(const EFXEAXREVERBPROPERTIES *reverb) printf("Using Standard Reverb\n"); /* No EAX Reverb. Set the standard reverb effect type then load the - * available reverb properties. */ + * available reverb properties. + */ alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); alEffectf(effect, AL_REVERB_DENSITY, reverb->flDensity); @@ -132,7 +136,7 @@ static ALuint LoadEffect(const EFXEAXREVERBPROPERTIES *reverb) alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); } - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { @@ -219,7 +223,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { diff --git a/Engine/lib/openal-soft/examples/alstream.c b/Engine/lib/openal-soft/examples/alstream.c index a61680d25..028290f55 100644 --- a/Engine/lib/openal-soft/examples/alstream.c +++ b/Engine/lib/openal-soft/examples/alstream.c @@ -37,13 +37,15 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + /* Define the number of buffers and buffer size (in milliseconds) to use. 4 * buffers at 200ms each gives a nice per-chunk size, and lets the queue last * for almost one second. */ -#define NUM_BUFFERS 4 -#define BUFFER_MILLISEC 200 +enum { NumBuffers = 4 }; +enum { BufferMillisec = 200 }; typedef enum SampleType { Int16, Float, IMA4, MSADPCM @@ -51,7 +53,7 @@ typedef enum SampleType { typedef struct StreamPlayer { /* These are the buffers and source to play out through OpenAL with. */ - ALuint buffers[NUM_BUFFERS]; + ALuint buffers[NumBuffers]; ALuint source; /* Handle for the audio file */ @@ -88,7 +90,7 @@ static StreamPlayer *NewPlayer(void) assert(player != NULL); /* Generate the buffers and source */ - alGenBuffers(NUM_BUFFERS, player->buffers); + alGenBuffers(NumBuffers, player->buffers); assert(alGetError() == AL_NO_ERROR && "Could not create buffers"); alGenSources(1, &player->source); @@ -111,11 +113,11 @@ static void DeletePlayer(StreamPlayer *player) ClosePlayerFile(player); alDeleteSources(1, &player->source); - alDeleteBuffers(NUM_BUFFERS, player->buffers); + alDeleteBuffers(NumBuffers, player->buffers); if(alGetError() != AL_NO_ERROR) fprintf(stderr, "Failed to delete object IDs\n"); - memset(player, 0, sizeof(*player)); + memset(player, 0, sizeof(*player)); /* NOLINT(clang-analyzer-security.insecureAPI.*) */ free(player); } @@ -291,8 +293,8 @@ static int OpenPlayerFile(StreamPlayer *player, const char *filename) } player->block_count = player->sfinfo.samplerate / player->sampleblockalign; - player->block_count = player->block_count * BUFFER_MILLISEC / 1000; - player->membuf = malloc((size_t)(player->block_count * player->byteblockalign)); + player->block_count = player->block_count * BufferMillisec / 1000; + player->membuf = malloc((size_t)player->block_count * (size_t)player->byteblockalign); return 1; } @@ -310,7 +312,7 @@ static void ClosePlayerFile(StreamPlayer *player) if(player->sampleblockalign > 1) { ALsizei i; - for(i = 0;i < NUM_BUFFERS;i++) + for(i = 0;i < NumBuffers;i++) alBufferi(player->buffers[i], AL_UNPACK_BLOCK_ALIGNMENT_SOFT, 0); player->sampleblockalign = 0; player->byteblockalign = 0; @@ -328,7 +330,7 @@ static int StartPlayer(StreamPlayer *player) alSourcei(player->source, AL_BUFFER, 0); /* Fill the buffer queue */ - for(i = 0;i < NUM_BUFFERS;i++) + for(i = 0;i < NumBuffers;i++) { sf_count_t slen; @@ -336,21 +338,21 @@ static int StartPlayer(StreamPlayer *player) if(player->sample_type == Int16) { slen = sf_readf_short(player->sndfile, player->membuf, - player->block_count * player->sampleblockalign); + (sf_count_t)player->block_count * player->sampleblockalign); if(slen < 1) break; slen *= player->byteblockalign; } else if(player->sample_type == Float) { slen = sf_readf_float(player->sndfile, player->membuf, - player->block_count * player->sampleblockalign); + (sf_count_t)player->block_count * player->sampleblockalign); if(slen < 1) break; slen *= player->byteblockalign; } else { slen = sf_read_raw(player->sndfile, player->membuf, - player->block_count * player->byteblockalign); + (sf_count_t)player->block_count * player->byteblockalign); if(slen > 0) slen -= slen%player->byteblockalign; if(slen < 1) break; } @@ -407,19 +409,19 @@ static int UpdatePlayer(StreamPlayer *player) if(player->sample_type == Int16) { slen = sf_readf_short(player->sndfile, player->membuf, - player->block_count * player->sampleblockalign); + (sf_count_t)player->block_count * player->sampleblockalign); if(slen > 0) slen *= player->byteblockalign; } else if(player->sample_type == Float) { slen = sf_readf_float(player->sndfile, player->membuf, - player->block_count * player->sampleblockalign); + (sf_count_t)player->block_count * player->sampleblockalign); if(slen > 0) slen *= player->byteblockalign; } else { slen = sf_read_raw(player->sndfile, player->membuf, - player->block_count * player->byteblockalign); + (sf_count_t)player->block_count * player->byteblockalign); if(slen > 0) slen -= slen%player->byteblockalign; } @@ -486,10 +488,9 @@ int main(int argc, char **argv) /* Get the name portion, without the path, for display. */ namepart = strrchr(argv[i], '/'); - if(namepart || (namepart=strrchr(argv[i], '\\'))) - namepart++; - else - namepart = argv[i]; + if(!namepart) namepart = strrchr(argv[i], '\\'); + if(!namepart) namepart = argv[i]; + else namepart++; printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format), player->sfinfo.samplerate); diff --git a/Engine/lib/openal-soft/examples/alstreamcb.cpp b/Engine/lib/openal-soft/examples/alstreamcb.cpp index a2e7b6592..dae340482 100644 --- a/Engine/lib/openal-soft/examples/alstreamcb.cpp +++ b/Engine/lib/openal-soft/examples/alstreamcb.cpp @@ -24,15 +24,19 @@ /* This file contains a streaming audio player using a callback buffer. */ -#include -#include -#include +#include #include +#include #include +#include +#include +#include +#include #include #include #include +#include #include #include @@ -42,8 +46,12 @@ #include "AL/alc.h" #include "AL/alext.h" +#include "alspan.h" +#include "alstring.h" #include "common/alhelpers.h" +#include "win_main_utf8.h" + namespace { @@ -56,8 +64,7 @@ struct StreamPlayer { /* A lockless ring-buffer (supports single-provider, single-consumer * operation). */ - std::unique_ptr mBufferData; - size_t mBufferDataSize{0}; + std::vector mBufferData; std::atomic mReadPos{0}; std::atomic mWritePos{0}; size_t mSamplesPerBlock{1}; @@ -78,7 +85,7 @@ struct StreamPlayer { size_t mDecoderOffset{0}; /* The format of the callback samples. */ - ALenum mFormat; + ALenum mFormat{}; StreamPlayer() { @@ -114,15 +121,16 @@ struct StreamPlayer { } } - bool open(const char *filename) + bool open(const std::string &filename) { close(); /* Open the file and figure out the OpenAL format. */ - mSndfile = sf_open(filename, SFM_READ, &mSfInfo); + mSndfile = sf_open(filename.c_str(), SFM_READ, &mSfInfo); if(!mSndfile) { - fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(mSndfile)); + fprintf(stderr, "Could not open audio in %s: %s\n", filename.c_str(), + sf_strerror(mSndfile)); return false; } @@ -166,8 +174,8 @@ struct StreamPlayer { mSampleFormat = SampleType::Int16; else { - auto fmtbuf = std::make_unique(inf.datalen); - inf.data = fmtbuf.get(); + auto fmtbuf = std::vector(inf.datalen); + inf.data = fmtbuf.data(); if(sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR) mSampleFormat = SampleType::Int16; else @@ -194,12 +202,12 @@ struct StreamPlayer { if(mSampleFormat == SampleType::Int16) { mSamplesPerBlock = 1; - mBytesPerBlock = static_cast(mSfInfo.channels * 2); + mBytesPerBlock = static_cast(mSfInfo.channels) * 2; } else if(mSampleFormat == SampleType::Float) { mSamplesPerBlock = 1; - mBytesPerBlock = static_cast(mSfInfo.channels * 4); + mBytesPerBlock = static_cast(mSfInfo.channels) * 4; } else { @@ -232,7 +240,7 @@ struct StreamPlayer { } else if(mSfInfo.channels == 3) { - if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) { if(mSampleFormat == SampleType::Int16) mFormat = AL_FORMAT_BFORMAT2D_16; @@ -242,7 +250,7 @@ struct StreamPlayer { } else if(mSfInfo.channels == 4) { - if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) { if(mSampleFormat == SampleType::Int16) mFormat = AL_FORMAT_BFORMAT3D_16; @@ -262,8 +270,7 @@ struct StreamPlayer { /* Set a 1s ring buffer size. */ size_t numblocks{(static_cast(mSfInfo.samplerate) + mSamplesPerBlock-1) / mSamplesPerBlock}; - mBufferDataSize = static_cast(numblocks * mBytesPerBlock); - mBufferData.reset(new ALbyte[mBufferDataSize]); + mBufferData.resize(static_cast(numblocks * mBytesPerBlock)); mReadPos.store(0, std::memory_order_relaxed); mWritePos.store(0, std::memory_order_relaxed); mDecoderOffset = 0; @@ -276,10 +283,11 @@ struct StreamPlayer { * but it allows the callback implementation to have a nice 'this' pointer * with normal member access. */ - static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) + static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) noexcept { return static_cast(userptr)->bufferCallback(data, size); } - ALsizei bufferCallback(void *data, ALsizei size) + ALsizei bufferCallback(void *data, ALsizei size) noexcept { + auto dst = al::span{static_cast(data), static_cast(size)}; /* NOTE: The callback *MUST* be real-time safe! That means no blocking, * no allocations or deallocations, no I/O, no page faults, or calls to * functions that could do these things (this includes calling to @@ -290,7 +298,7 @@ struct StreamPlayer { ALsizei got{0}; size_t roffset{mReadPos.load(std::memory_order_acquire)}; - while(got < size) + while(!dst.empty()) { /* If the write offset == read offset, there's nothing left in the * ring-buffer. Break from the loop and give what has been written. @@ -303,19 +311,19 @@ struct StreamPlayer { * that case, otherwise read up to the write offset. Also limit the * amount to copy given how much is remaining to write. */ - size_t todo{((woffset < roffset) ? mBufferDataSize : woffset) - roffset}; - todo = std::min(todo, static_cast(size-got)); + size_t todo{((woffset < roffset) ? mBufferData.size() : woffset) - roffset}; + todo = std::min(todo, dst.size()); /* Copy from the ring buffer to the provided output buffer. Wrap * the resulting read offset if it reached the end of the ring- * buffer. */ - memcpy(data, &mBufferData[roffset], todo); - data = static_cast(data) + todo; + std::copy_n(mBufferData.cbegin()+ptrdiff_t(roffset), todo, dst.begin()); + dst = dst.subspan(todo); got += static_cast(todo); roffset += todo; - if(roffset == mBufferDataSize) + if(roffset == mBufferData.size()) roffset = 0; } /* Finally, store the updated read offset, and return how many bytes @@ -351,7 +359,7 @@ struct StreamPlayer { if(state != AL_INITIAL) { const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - + const size_t readable{((woffset >= roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; /* For a stopped (underrun) source, the current playback offset is * the current decoder offset excluding the readable buffered data. @@ -362,7 +370,7 @@ struct StreamPlayer { ? (mDecoderOffset-readable) / mBytesPerBlock * mSamplesPerBlock : (static_cast(pos) + mStartOffset/mBytesPerBlock*mSamplesPerBlock)) / static_cast(mSfInfo.samplerate)}; - printf("\r%3zus (%3zu%% full)", curtime, readable * 100 / mBufferDataSize); + printf("\r%3zus (%3zu%% full)", curtime, readable * 100 / mBufferData.size()); } else fputs("Starting...", stdout); @@ -415,8 +423,8 @@ struct StreamPlayer { * data can fit, and calculate how much can go in front before * wrapping. */ - const size_t writable{(!roffset ? mBufferDataSize-woffset-1 : - (mBufferDataSize-woffset)) / mBytesPerBlock}; + const size_t writable{(!roffset ? mBufferData.size()-woffset-1 : + (mBufferData.size()-woffset)) / mBytesPerBlock}; if(!writable) break; if(mSampleFormat == SampleType::Int16) @@ -444,7 +452,7 @@ struct StreamPlayer { } woffset += read_bytes; - if(woffset == mBufferDataSize) + if(woffset == mBufferData.size()) woffset = 0; } mWritePos.store(woffset, std::memory_order_release); @@ -459,7 +467,7 @@ struct StreamPlayer { * what's available. */ const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - + const size_t readable{((woffset >= roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; if(readable == 0) return false; @@ -476,29 +484,28 @@ struct StreamPlayer { } }; -} // namespace - -int main(int argc, char **argv) +int main(al::span args) { /* A simple RAII container for OpenAL startup and shutdown. */ struct AudioManager { - AudioManager(char ***argv_, int *argc_) + AudioManager(al::span &args_) { - if(InitAL(argv_, argc_) != 0) + if(InitAL(args_) != 0) throw std::runtime_error{"Failed to initialize OpenAL"}; } ~AudioManager() { CloseAL(); } }; /* Print out usage if no arguments were specified */ - if(argc < 2) + if(args.size() < 2) { - fprintf(stderr, "Usage: %s [-device ] \n", argv[0]); + fprintf(stderr, "Usage: %.*s [-device ] \n", al::sizei(args[0]), + args[0].data()); return 1; } - argv++; argc--; - AudioManager almgr{&argv, &argc}; + args = args.subspan(1); + AudioManager almgr{args}; if(!alIsExtensionPresent("AL_SOFT_callback_buffer")) { @@ -512,23 +519,23 @@ int main(int argc, char **argv) ALCint refresh{25}; alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); - std::unique_ptr player{new StreamPlayer{}}; + auto player = std::make_unique(); /* Play each file listed on the command line */ - for(int i{0};i < argc;++i) + for(size_t i{0};i < args.size();++i) { - if(!player->open(argv[i])) + if(!player->open(std::string{args[i]})) continue; /* Get the name portion, without the path, for display. */ - const char *namepart{strrchr(argv[i], '/')}; - if(namepart || (namepart=strrchr(argv[i], '\\'))) - ++namepart; - else - namepart = argv[i]; + auto namepart = args[i]; + if(auto sep = namepart.rfind('/'); sep < namepart.size()) + namepart = namepart.substr(sep+1); + else if(sep = namepart.rfind('\\'); sep < namepart.size()) + namepart = namepart.substr(sep+1); - printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->mFormat), - player->mSfInfo.samplerate); + printf("Playing: %.*s (%s, %dhz)\n", al::sizei(namepart), namepart.data(), + FormatName(player->mFormat), player->mSfInfo.samplerate); fflush(stdout); if(!player->prepare()) @@ -549,3 +556,13 @@ int main(int argc, char **argv) return 0; } + +} // namespace + +int main(int argc, char **argv) +{ + assert(argc >= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/Engine/lib/openal-soft/examples/altonegen.c b/Engine/lib/openal-soft/examples/altonegen.c index 75db2d6be..52a57875a 100644 --- a/Engine/lib/openal-soft/examples/altonegen.c +++ b/Engine/lib/openal-soft/examples/altonegen.c @@ -79,63 +79,72 @@ static inline ALuint dither_rng(ALuint *seed) return *seed; } -static void ApplySin(ALfloat *data, ALdouble g, ALuint srate, ALuint freq) +static void ApplySin(ALfloat *data, ALuint length, ALdouble g, ALuint srate, ALuint freq) { - ALdouble smps_per_cycle = (ALdouble)srate / freq; + ALdouble cycles_per_sample = (ALdouble)freq / srate; ALuint i; - for(i = 0;i < srate;i++) + for(i = 0;i < length;i++) { ALdouble ival; - data[i] += (ALfloat)(sin(modf(i/smps_per_cycle, &ival) * 2.0*M_PI) * g); + data[i] += (ALfloat)(sin(modf(i*cycles_per_sample, &ival) * 2.0*M_PI) * g); } } /* Generates waveforms using additive synthesis. Each waveform is constructed * by summing one or more sine waves, up to (and excluding) nyquist. */ -static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate, ALfloat gain) +static ALuint CreateWave(enum WaveType type, ALuint seconds, ALuint freq, ALuint srate, + ALfloat gain) { ALuint seed = 22222; + ALuint num_samples; ALuint data_size; ALfloat *data; ALuint buffer; ALenum err; ALuint i; - data_size = (ALuint)(srate * sizeof(ALfloat)); + if(seconds > INT_MAX / srate / sizeof(ALfloat)) + { + fprintf(stderr, "Too many seconds: %u * %u * %zu > %d\n", seconds, srate, sizeof(ALfloat), + INT_MAX); + return 0; + } + + num_samples = seconds * srate; + + data_size = (ALuint)(num_samples * sizeof(ALfloat)); data = calloc(1, data_size); switch(type) { case WT_Sine: - ApplySin(data, 1.0, srate, freq); + ApplySin(data, num_samples, 1.0, srate, freq); break; case WT_Square: for(i = 1;freq*i < srate/2;i+=2) - ApplySin(data, 4.0/M_PI * 1.0/i, srate, freq*i); + ApplySin(data, num_samples, 4.0/M_PI * 1.0/i, srate, freq*i); break; case WT_Sawtooth: for(i = 1;freq*i < srate/2;i++) - ApplySin(data, 2.0/M_PI * ((i&1)*2 - 1.0) / i, srate, freq*i); + ApplySin(data, num_samples, 2.0/M_PI * ((i&1)*2 - 1.0) / i, srate, freq*i); break; case WT_Triangle: for(i = 1;freq*i < srate/2;i+=2) - ApplySin(data, 8.0/(M_PI*M_PI) * (1.0 - (i&2)) / (i*i), srate, freq*i); + ApplySin(data, num_samples, 8.0/(M_PI*M_PI) * (1.0 - (i&2)) / (i*i), srate, freq*i); break; case WT_Impulse: /* NOTE: Impulse isn't handled using additive synthesis, and is - * instead just a non-0 sample at a given rate. This can still be - * useful to test (other than resampling, the ALSOFT_DEFAULT_REVERB - * environment variable can prove useful here to test the reverb - * response). + * instead just a non-0 sample. This can be useful to test (other + * than resampling, the ALSOFT_DEFAULT_REVERB environment variable + * can test the reverb response). */ - for(i = 0;i < srate;i++) - data[i] = (i%(srate/freq)) ? 0.0f : 1.0f; + data[0] = 1.0f; break; case WT_WhiteNoise: /* NOTE: WhiteNoise is just uniform set of uncorrelated values, and * is not influenced by the waveform frequency. */ - for(i = 0;i < srate;i++) + for(i = 0;i < num_samples;i++) { ALuint rng0 = dither_rng(&seed); ALuint rng1 = dither_rng(&seed); @@ -146,7 +155,7 @@ static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate, ALfloat if(gain != 1.0f) { - for(i = 0;i < srate;i++) + for(i = 0;i < num_samples;i++) data[i] *= gain; } @@ -156,7 +165,7 @@ static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate, ALfloat alBufferData(buffer, AL_FORMAT_MONO_FLOAT32, data, (ALsizei)data_size, (ALsizei)srate); free(data); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { @@ -175,8 +184,8 @@ int main(int argc, char *argv[]) enum WaveType wavetype = WT_Sine; const char *appname = argv[0]; ALuint source, buffer; - ALint last_pos, num_loops; - ALint max_loops = 4; + ALint last_pos; + ALint seconds = 4; ALint srate = -1; ALint tone_freq = 1000; ALCint dev_rate; @@ -217,10 +226,13 @@ int main(int argc, char *argv[]) CloseAL(); return 1; } - else if(i+1 < argc && strcmp(argv[i], "-t") == 0) + + if(i+1 < argc && strcmp(argv[i], "-t") == 0) { i++; - max_loops = atoi(argv[i]) - 1; + seconds = atoi(argv[i]); + if(seconds <= 0) + seconds = 4; } else if(i+1 < argc && (strcmp(argv[i], "--waveform") == 0 || strcmp(argv[i], "-w") == 0)) { @@ -281,7 +293,7 @@ int main(int argc, char *argv[]) srate = dev_rate; /* Load the sound into a buffer. */ - buffer = CreateWave(wavetype, (ALuint)tone_freq, (ALuint)srate, gain); + buffer = CreateWave(wavetype, (ALuint)seconds, (ALuint)tone_freq, (ALuint)srate, gain); if(!buffer) { CloseAL(); @@ -289,7 +301,7 @@ int main(int argc, char *argv[]) } printf("Playing %dhz %s-wave tone with %dhz sample rate and %dhz output, for %d second%s...\n", - tone_freq, GetWaveTypeName(wavetype), srate, dev_rate, max_loops+1, max_loops?"s":""); + tone_freq, GetWaveTypeName(wavetype), srate, dev_rate, seconds, (seconds!=1)?"s":""); fflush(stdout); /* Create the source to play the sound with. */ @@ -299,21 +311,18 @@ int main(int argc, char *argv[]) assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound for a while. */ - num_loops = 0; - last_pos = 0; - alSourcei(source, AL_LOOPING, (max_loops > 0) ? AL_TRUE : AL_FALSE); + last_pos = -1; alSourcePlay(source); do { ALint pos; al_nssleep(10000000); - alGetSourcei(source, AL_SAMPLE_OFFSET, &pos); alGetSourcei(source, AL_SOURCE_STATE, &state); - if(pos < last_pos && state == AL_PLAYING) + alGetSourcei(source, AL_SAMPLE_OFFSET, &pos); + pos /= srate; + + if(pos > last_pos) { - ++num_loops; - if(num_loops >= max_loops) - alSourcei(source, AL_LOOPING, AL_FALSE); - printf("%d...\n", max_loops - num_loops + 1); + printf("%d...\n", seconds - pos); fflush(stdout); } last_pos = pos; diff --git a/Engine/lib/openal-soft/examples/common/alhelpers.h b/Engine/lib/openal-soft/examples/common/alhelpers.h index 34f738647..97f553657 100644 --- a/Engine/lib/openal-soft/examples/common/alhelpers.h +++ b/Engine/lib/openal-soft/examples/common/alhelpers.h @@ -2,13 +2,14 @@ #define ALHELPERS_H #include "AL/al.h" +#include "AL/alc.h" #ifdef __cplusplus extern "C" { #endif /* Some helper functions to get the name from the format enums. */ -const char *FormatName(ALenum type); +const char *FormatName(ALenum format); /* Easy device init/deinit functions. InitAL returns 0 on success. */ int InitAL(char ***argv, int *argc); @@ -33,6 +34,54 @@ void al_nssleep(unsigned long nsec); #ifdef __cplusplus } // extern "C" + +#include +#include +#include +#include + +#include "alspan.h" + +int InitAL(al::span &args) +{ + ALCdevice *device{}; + + /* Open and initialize a device */ + if(args.size() > 1 && args[0] == "-device") + { + device = alcOpenDevice(std::string{args[1]}.c_str()); + if(!device) + std::cerr<< "Failed to open \""<= 201703L +#define AL_API_NOEXCEPT17 noexcept +#else +#define AL_API_NOEXCEPT17 +#endif + +#else /* AL_DISABLE_NOEXCEPT */ + +#define AL_API_NOEXCEPT +#define AL_API_NOEXCEPT17 +#endif + +#else /* __cplusplus */ + +#define AL_API_NOEXCEPT +#define AL_API_NOEXCEPT17 #endif #ifndef AL_API @@ -455,220 +475,221 @@ typedef void ALvoid; #ifndef AL_NO_PROTOTYPES /* Renderer State management. */ -AL_API void AL_APIENTRY alEnable(ALenum capability); -AL_API void AL_APIENTRY alDisable(ALenum capability); -AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability); +AL_API void AL_APIENTRY alEnable(ALenum capability) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDisable(ALenum capability) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability) AL_API_NOEXCEPT; /* Context state setting. */ -AL_API void AL_APIENTRY alDopplerFactor(ALfloat value); -AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value); -AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value); -AL_API void AL_APIENTRY alDistanceModel(ALenum distanceModel); +AL_API void AL_APIENTRY alDopplerFactor(ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDistanceModel(ALenum distanceModel) AL_API_NOEXCEPT; /* Context state retrieval. */ -AL_API const ALchar* AL_APIENTRY alGetString(ALenum param); -AL_API void AL_APIENTRY alGetBooleanv(ALenum param, ALboolean *values); -AL_API void AL_APIENTRY alGetIntegerv(ALenum param, ALint *values); -AL_API void AL_APIENTRY alGetFloatv(ALenum param, ALfloat *values); -AL_API void AL_APIENTRY alGetDoublev(ALenum param, ALdouble *values); -AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum param); -AL_API ALint AL_APIENTRY alGetInteger(ALenum param); -AL_API ALfloat AL_APIENTRY alGetFloat(ALenum param); -AL_API ALdouble AL_APIENTRY alGetDouble(ALenum param); +AL_API const ALchar* AL_APIENTRY alGetString(ALenum param) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBooleanv(ALenum param, ALboolean *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetIntegerv(ALenum param, ALint *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetFloatv(ALenum param, ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetDoublev(ALenum param, ALdouble *values) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum param) AL_API_NOEXCEPT; +AL_API ALint AL_APIENTRY alGetInteger(ALenum param) AL_API_NOEXCEPT; +AL_API ALfloat AL_APIENTRY alGetFloat(ALenum param) AL_API_NOEXCEPT; +AL_API ALdouble AL_APIENTRY alGetDouble(ALenum param) AL_API_NOEXCEPT; /** * Obtain the first error generated in the AL context since the last call to * this function. */ -AL_API ALenum AL_APIENTRY alGetError(void); +AL_API ALenum AL_APIENTRY alGetError(void) AL_API_NOEXCEPT; /** Query for the presence of an extension on the AL context. */ -AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extname); +AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extname) AL_API_NOEXCEPT; /** * Retrieve the address of a function. The returned function may be context- * specific. */ -AL_API void* AL_APIENTRY alGetProcAddress(const ALchar *fname); +AL_API void* AL_APIENTRY alGetProcAddress(const ALchar *fname) AL_API_NOEXCEPT; /** * Retrieve the value of an enum. The returned value may be context-specific. */ -AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *ename); +AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *ename) AL_API_NOEXCEPT; /* Set listener parameters. */ -AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value); -AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values); -AL_API void AL_APIENTRY alListeneri(ALenum param, ALint value); -AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3); -AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values); +AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alListeneri(ALenum param, ALint value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values) AL_API_NOEXCEPT; /* Get listener parameters. */ -AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value); -AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -AL_API void AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values); -AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value); -AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3); -AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint *values); +AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint *values) AL_API_NOEXCEPT; /** Create source objects. */ -AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources); +AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources) AL_API_NOEXCEPT; /** Delete source objects. */ -AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources); +AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Verify an ID is for a valid source. */ -AL_API ALboolean AL_APIENTRY alIsSource(ALuint source); +AL_API ALboolean AL_APIENTRY alIsSource(ALuint source) AL_API_NOEXCEPT; /* Set source parameters. */ -AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value); -AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values); -AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value); -AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3); -AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values); +AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT; /* Get source parameters. */ -AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value); -AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values); -AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value); -AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3); -AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values); +AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT; /** Play, restart, or resume a source, setting its state to AL_PLAYING. */ -AL_API void AL_APIENTRY alSourcePlay(ALuint source); +AL_API void AL_APIENTRY alSourcePlay(ALuint source) AL_API_NOEXCEPT; /** Stop a source, setting its state to AL_STOPPED if playing or paused. */ -AL_API void AL_APIENTRY alSourceStop(ALuint source); +AL_API void AL_APIENTRY alSourceStop(ALuint source) AL_API_NOEXCEPT; /** Rewind a source, setting its state to AL_INITIAL. */ -AL_API void AL_APIENTRY alSourceRewind(ALuint source); +AL_API void AL_APIENTRY alSourceRewind(ALuint source) AL_API_NOEXCEPT; /** Pause a source, setting its state to AL_PAUSED if playing. */ -AL_API void AL_APIENTRY alSourcePause(ALuint source); +AL_API void AL_APIENTRY alSourcePause(ALuint source) AL_API_NOEXCEPT; /** Play, restart, or resume a list of sources atomically. */ -AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources); +AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Stop a list of sources atomically. */ -AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources); +AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Rewind a list of sources atomically. */ -AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources); +AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Pause a list of sources atomically. */ -AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources); +AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Queue buffers onto a source */ -AL_API void AL_APIENTRY alSourceQueueBuffers(ALuint source, ALsizei nb, const ALuint *buffers); +AL_API void AL_APIENTRY alSourceQueueBuffers(ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT; /** Unqueue processed buffers from a source */ -AL_API void AL_APIENTRY alSourceUnqueueBuffers(ALuint source, ALsizei nb, ALuint *buffers); +AL_API void AL_APIENTRY alSourceUnqueueBuffers(ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT; /** Create buffer objects */ -AL_API void AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers); +AL_API void AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers) AL_API_NOEXCEPT; /** Delete buffer objects */ -AL_API void AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers); +AL_API void AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT; /** Verify an ID is a valid buffer (including the NULL buffer) */ -AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer); +AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer) AL_API_NOEXCEPT; /** * Copies data into the buffer, interpreting it using the specified format and * samplerate. */ -AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate); +AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT; /* Set buffer parameters. */ -AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat value); -AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values); -AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value); -AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3); -AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values); +AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT; /* Get buffer parameters. */ -AL_API void AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value); -AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values); -AL_API void AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value); -AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3); -AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values); +AL_API void AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT; #endif /* AL_NO_PROTOTYPES */ /* Pointer-to-function types, useful for storing dynamically loaded AL entry * points. */ -typedef void (AL_APIENTRY *LPALENABLE)(ALenum capability); -typedef void (AL_APIENTRY *LPALDISABLE)(ALenum capability); -typedef ALboolean (AL_APIENTRY *LPALISENABLED)(ALenum capability); -typedef const ALchar* (AL_APIENTRY *LPALGETSTRING)(ALenum param); -typedef void (AL_APIENTRY *LPALGETBOOLEANV)(ALenum param, ALboolean *values); -typedef void (AL_APIENTRY *LPALGETINTEGERV)(ALenum param, ALint *values); -typedef void (AL_APIENTRY *LPALGETFLOATV)(ALenum param, ALfloat *values); -typedef void (AL_APIENTRY *LPALGETDOUBLEV)(ALenum param, ALdouble *values); -typedef ALboolean (AL_APIENTRY *LPALGETBOOLEAN)(ALenum param); -typedef ALint (AL_APIENTRY *LPALGETINTEGER)(ALenum param); -typedef ALfloat (AL_APIENTRY *LPALGETFLOAT)(ALenum param); -typedef ALdouble (AL_APIENTRY *LPALGETDOUBLE)(ALenum param); -typedef ALenum (AL_APIENTRY *LPALGETERROR)(void); -typedef ALboolean (AL_APIENTRY *LPALISEXTENSIONPRESENT)(const ALchar *extname); -typedef void* (AL_APIENTRY *LPALGETPROCADDRESS)(const ALchar *fname); -typedef ALenum (AL_APIENTRY *LPALGETENUMVALUE)(const ALchar *ename); -typedef void (AL_APIENTRY *LPALLISTENERF)(ALenum param, ALfloat value); -typedef void (AL_APIENTRY *LPALLISTENER3F)(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -typedef void (AL_APIENTRY *LPALLISTENERFV)(ALenum param, const ALfloat *values); -typedef void (AL_APIENTRY *LPALLISTENERI)(ALenum param, ALint value); -typedef void (AL_APIENTRY *LPALLISTENER3I)(ALenum param, ALint value1, ALint value2, ALint value3); -typedef void (AL_APIENTRY *LPALLISTENERIV)(ALenum param, const ALint *values); -typedef void (AL_APIENTRY *LPALGETLISTENERF)(ALenum param, ALfloat *value); -typedef void (AL_APIENTRY *LPALGETLISTENER3F)(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -typedef void (AL_APIENTRY *LPALGETLISTENERFV)(ALenum param, ALfloat *values); -typedef void (AL_APIENTRY *LPALGETLISTENERI)(ALenum param, ALint *value); -typedef void (AL_APIENTRY *LPALGETLISTENER3I)(ALenum param, ALint *value1, ALint *value2, ALint *value3); -typedef void (AL_APIENTRY *LPALGETLISTENERIV)(ALenum param, ALint *values); -typedef void (AL_APIENTRY *LPALGENSOURCES)(ALsizei n, ALuint *sources); -typedef void (AL_APIENTRY *LPALDELETESOURCES)(ALsizei n, const ALuint *sources); -typedef ALboolean (AL_APIENTRY *LPALISSOURCE)(ALuint source); -typedef void (AL_APIENTRY *LPALSOURCEF)(ALuint source, ALenum param, ALfloat value); -typedef void (AL_APIENTRY *LPALSOURCE3F)(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -typedef void (AL_APIENTRY *LPALSOURCEFV)(ALuint source, ALenum param, const ALfloat *values); -typedef void (AL_APIENTRY *LPALSOURCEI)(ALuint source, ALenum param, ALint value); -typedef void (AL_APIENTRY *LPALSOURCE3I)(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3); -typedef void (AL_APIENTRY *LPALSOURCEIV)(ALuint source, ALenum param, const ALint *values); -typedef void (AL_APIENTRY *LPALGETSOURCEF)(ALuint source, ALenum param, ALfloat *value); -typedef void (AL_APIENTRY *LPALGETSOURCE3F)(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -typedef void (AL_APIENTRY *LPALGETSOURCEFV)(ALuint source, ALenum param, ALfloat *values); -typedef void (AL_APIENTRY *LPALGETSOURCEI)(ALuint source, ALenum param, ALint *value); -typedef void (AL_APIENTRY *LPALGETSOURCE3I)(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3); -typedef void (AL_APIENTRY *LPALGETSOURCEIV)(ALuint source, ALenum param, ALint *values); -typedef void (AL_APIENTRY *LPALSOURCEPLAYV)(ALsizei n, const ALuint *sources); -typedef void (AL_APIENTRY *LPALSOURCESTOPV)(ALsizei n, const ALuint *sources); -typedef void (AL_APIENTRY *LPALSOURCEREWINDV)(ALsizei n, const ALuint *sources); -typedef void (AL_APIENTRY *LPALSOURCEPAUSEV)(ALsizei n, const ALuint *sources); -typedef void (AL_APIENTRY *LPALSOURCEPLAY)(ALuint source); -typedef void (AL_APIENTRY *LPALSOURCESTOP)(ALuint source); -typedef void (AL_APIENTRY *LPALSOURCEREWIND)(ALuint source); -typedef void (AL_APIENTRY *LPALSOURCEPAUSE)(ALuint source); -typedef void (AL_APIENTRY *LPALSOURCEQUEUEBUFFERS)(ALuint source, ALsizei nb, const ALuint *buffers); -typedef void (AL_APIENTRY *LPALSOURCEUNQUEUEBUFFERS)(ALuint source, ALsizei nb, ALuint *buffers); -typedef void (AL_APIENTRY *LPALGENBUFFERS)(ALsizei n, ALuint *buffers); -typedef void (AL_APIENTRY *LPALDELETEBUFFERS)(ALsizei n, const ALuint *buffers); -typedef ALboolean (AL_APIENTRY *LPALISBUFFER)(ALuint buffer); -typedef void (AL_APIENTRY *LPALBUFFERDATA)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate); -typedef void (AL_APIENTRY *LPALBUFFERF)(ALuint buffer, ALenum param, ALfloat value); -typedef void (AL_APIENTRY *LPALBUFFER3F)(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -typedef void (AL_APIENTRY *LPALBUFFERFV)(ALuint buffer, ALenum param, const ALfloat *values); -typedef void (AL_APIENTRY *LPALBUFFERI)(ALuint buffer, ALenum param, ALint value); -typedef void (AL_APIENTRY *LPALBUFFER3I)(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3); -typedef void (AL_APIENTRY *LPALBUFFERIV)(ALuint buffer, ALenum param, const ALint *values); -typedef void (AL_APIENTRY *LPALGETBUFFERF)(ALuint buffer, ALenum param, ALfloat *value); -typedef void (AL_APIENTRY *LPALGETBUFFER3F)(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -typedef void (AL_APIENTRY *LPALGETBUFFERFV)(ALuint buffer, ALenum param, ALfloat *values); -typedef void (AL_APIENTRY *LPALGETBUFFERI)(ALuint buffer, ALenum param, ALint *value); -typedef void (AL_APIENTRY *LPALGETBUFFER3I)(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3); -typedef void (AL_APIENTRY *LPALGETBUFFERIV)(ALuint buffer, ALenum param, ALint *values); -typedef void (AL_APIENTRY *LPALDOPPLERFACTOR)(ALfloat value); -typedef void (AL_APIENTRY *LPALDOPPLERVELOCITY)(ALfloat value); -typedef void (AL_APIENTRY *LPALSPEEDOFSOUND)(ALfloat value); -typedef void (AL_APIENTRY *LPALDISTANCEMODEL)(ALenum distanceModel); +typedef void (AL_APIENTRY *LPALENABLE)(ALenum capability) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDISABLE)(ALenum capability) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISENABLED)(ALenum capability) AL_API_NOEXCEPT17; +typedef const ALchar* (AL_APIENTRY *LPALGETSTRING)(ALenum param) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBOOLEANV)(ALenum param, ALboolean *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETINTEGERV)(ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFLOATV)(ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETDOUBLEV)(ALenum param, ALdouble *values) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALGETBOOLEAN)(ALenum param) AL_API_NOEXCEPT17; +typedef ALint (AL_APIENTRY *LPALGETINTEGER)(ALenum param) AL_API_NOEXCEPT17; +typedef ALfloat (AL_APIENTRY *LPALGETFLOAT)(ALenum param) AL_API_NOEXCEPT17; +typedef ALdouble (AL_APIENTRY *LPALGETDOUBLE)(ALenum param) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPALGETERROR)(void) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISEXTENSIONPRESENT)(const ALchar *extname) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY *LPALGETPROCADDRESS)(const ALchar *fname) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPALGETENUMVALUE)(const ALchar *ename) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERF)(ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENER3F)(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERFV)(ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERI)(ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENER3I)(ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERIV)(ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERF)(ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENER3F)(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERFV)(ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERI)(ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENER3I)(ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERIV)(ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENSOURCES)(ALsizei n, ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETESOURCES)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISSOURCE)(ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEF)(ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3F)(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEFV)(ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEI)(ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3I)(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEIV)(ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEF)(ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3F)(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEFV)(ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEI)(ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3I)(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEIV)(ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPLAYV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCESTOPV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEREWINDV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPAUSEV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPLAY)(ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCESTOP)(ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEREWIND)(ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPAUSE)(ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEQUEUEBUFFERS)(ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEUNQUEUEBUFFERS)(ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENBUFFERS)(ALsizei n, ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEBUFFERS)(ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISBUFFER)(ALuint buffer) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERDATA)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERF)(ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFER3F)(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERFV)(ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERI)(ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFER3I)(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERIV)(ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERF)(ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFER3F)(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERFV)(ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERI)(ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFER3I)(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERIV)(ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDOPPLERFACTOR)(ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDOPPLERVELOCITY)(ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSPEEDOFSOUND)(ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDISTANCEMODEL)(ALenum distanceModel) AL_API_NOEXCEPT17; #ifdef __cplusplus } /* extern "C" */ #endif +/* NOLINTEND */ #endif /* AL_AL_H */ diff --git a/Engine/lib/openal-soft/include/AL/alc.h b/Engine/lib/openal-soft/include/AL/alc.h index 6d2103335..3311b57fb 100644 --- a/Engine/lib/openal-soft/include/AL/alc.h +++ b/Engine/lib/openal-soft/include/AL/alc.h @@ -1,8 +1,28 @@ #ifndef AL_ALC_H #define AL_ALC_H +/* NOLINTBEGIN */ #ifdef __cplusplus extern "C" { + +#ifndef AL_DISABLE_NOEXCEPT +#define ALC_API_NOEXCEPT noexcept +#if __cplusplus >= 201703L +#define ALC_API_NOEXCEPT17 noexcept +#else +#define ALC_API_NOEXCEPT17 +#endif + +#else /* AL_DISABLE_NOEXCEPT */ + +#define ALC_API_NOEXCEPT +#define ALC_API_NOEXCEPT17 +#endif + +#else /* __cplusplus */ + +#define ALC_API_NOEXCEPT +#define ALC_API_NOEXCEPT17 #endif #ifndef ALC_API @@ -172,34 +192,34 @@ typedef void ALCvoid; /* Context management. */ /** Create and attach a context to the given device. */ -ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist); +ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist) ALC_API_NOEXCEPT; /** * Makes the given context the active process-wide context. Passing NULL clears * the active context. */ -ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context); +ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) ALC_API_NOEXCEPT; /** Resumes processing updates for the given context. */ -ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context); +ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) ALC_API_NOEXCEPT; /** Suspends updates for the given context. */ -ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context); +ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) ALC_API_NOEXCEPT; /** Remove a context from its device and destroys it. */ -ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context); +ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) ALC_API_NOEXCEPT; /** Returns the currently active context. */ -ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void); +ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) ALC_API_NOEXCEPT; /** Returns the device that a particular context is attached to. */ -ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context); +ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context) ALC_API_NOEXCEPT; /* Device management. */ /** Opens the named playback device. */ -ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename); +ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) ALC_API_NOEXCEPT; /** Closes the given playback device. */ -ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device); +ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) ALC_API_NOEXCEPT; /* Error support. */ /** Obtain the most recent Device error. */ -ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device); +ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) ALC_API_NOEXCEPT; /* Extension support. */ @@ -207,24 +227,24 @@ ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device); * Query for the presence of an extension on the device. Pass a NULL device to * query a device-inspecific extension. */ -ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname); +ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname) ALC_API_NOEXCEPT; /** * Retrieve the address of a function. Given a non-NULL device, the returned * function may be device-specific. */ -ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname); +ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname) ALC_API_NOEXCEPT; /** * Retrieve the value of an enum. Given a non-NULL device, the returned value * may be device-specific. */ -ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname); +ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname) ALC_API_NOEXCEPT; /* Query functions. */ /** Returns information about the device, and error strings. */ -ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param); +ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) ALC_API_NOEXCEPT; /** Returns information about the device and the version of OpenAL. */ -ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values); +ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) ALC_API_NOEXCEPT; /* Capture functions. */ @@ -232,43 +252,44 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum pa * Opens the named capture device with the given frequency, format, and buffer * size. */ -ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize); +ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) ALC_API_NOEXCEPT; /** Closes the given capture device. */ -ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device); +ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) ALC_API_NOEXCEPT; /** Starts capturing samples into the device buffer. */ -ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device); +ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) ALC_API_NOEXCEPT; /** Stops capturing samples. Samples in the device buffer remain available. */ -ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device); +ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) ALC_API_NOEXCEPT; /** Reads samples from the device buffer. */ -ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); +ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_API_NOEXCEPT; #endif /* ALC_NO_PROTOTYPES */ /* Pointer-to-function types, useful for storing dynamically loaded ALC entry * points. */ -typedef ALCcontext* (ALC_APIENTRY *LPALCCREATECONTEXT)(ALCdevice *device, const ALCint *attrlist); -typedef ALCboolean (ALC_APIENTRY *LPALCMAKECONTEXTCURRENT)(ALCcontext *context); -typedef void (ALC_APIENTRY *LPALCPROCESSCONTEXT)(ALCcontext *context); -typedef void (ALC_APIENTRY *LPALCSUSPENDCONTEXT)(ALCcontext *context); -typedef void (ALC_APIENTRY *LPALCDESTROYCONTEXT)(ALCcontext *context); -typedef ALCcontext* (ALC_APIENTRY *LPALCGETCURRENTCONTEXT)(void); -typedef ALCdevice* (ALC_APIENTRY *LPALCGETCONTEXTSDEVICE)(ALCcontext *context); -typedef ALCdevice* (ALC_APIENTRY *LPALCOPENDEVICE)(const ALCchar *devicename); -typedef ALCboolean (ALC_APIENTRY *LPALCCLOSEDEVICE)(ALCdevice *device); -typedef ALCenum (ALC_APIENTRY *LPALCGETERROR)(ALCdevice *device); -typedef ALCboolean (ALC_APIENTRY *LPALCISEXTENSIONPRESENT)(ALCdevice *device, const ALCchar *extname); -typedef ALCvoid* (ALC_APIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname); -typedef ALCenum (ALC_APIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname); -typedef const ALCchar* (ALC_APIENTRY *LPALCGETSTRING)(ALCdevice *device, ALCenum param); -typedef void (ALC_APIENTRY *LPALCGETINTEGERV)(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values); -typedef ALCdevice* (ALC_APIENTRY *LPALCCAPTUREOPENDEVICE)(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize); -typedef ALCboolean (ALC_APIENTRY *LPALCCAPTURECLOSEDEVICE)(ALCdevice *device); -typedef void (ALC_APIENTRY *LPALCCAPTURESTART)(ALCdevice *device); -typedef void (ALC_APIENTRY *LPALCCAPTURESTOP)(ALCdevice *device); -typedef void (ALC_APIENTRY *LPALCCAPTURESAMPLES)(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); +typedef ALCcontext* (ALC_APIENTRY *LPALCCREATECONTEXT)(ALCdevice *device, const ALCint *attrlist) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY *LPALCMAKECONTEXTCURRENT)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCPROCESSCONTEXT)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCSUSPENDCONTEXT)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCDESTROYCONTEXT)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef ALCcontext* (ALC_APIENTRY *LPALCGETCURRENTCONTEXT)(void) ALC_API_NOEXCEPT17; +typedef ALCdevice* (ALC_APIENTRY *LPALCGETCONTEXTSDEVICE)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef ALCdevice* (ALC_APIENTRY *LPALCOPENDEVICE)(const ALCchar *devicename) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY *LPALCCLOSEDEVICE)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef ALCenum (ALC_APIENTRY *LPALCGETERROR)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY *LPALCISEXTENSIONPRESENT)(ALCdevice *device, const ALCchar *extname) ALC_API_NOEXCEPT17; +typedef ALCvoid* (ALC_APIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname) ALC_API_NOEXCEPT17; +typedef ALCenum (ALC_APIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname) ALC_API_NOEXCEPT17; +typedef const ALCchar* (ALC_APIENTRY *LPALCGETSTRING)(ALCdevice *device, ALCenum param) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCGETINTEGERV)(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) ALC_API_NOEXCEPT17; +typedef ALCdevice* (ALC_APIENTRY *LPALCCAPTUREOPENDEVICE)(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY *LPALCCAPTURECLOSEDEVICE)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCCAPTURESTART)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCCAPTURESTOP)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCCAPTURESAMPLES)(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_API_NOEXCEPT17; #ifdef __cplusplus } /* extern "C" */ #endif +/* NOLINTEND */ #endif /* AL_ALC_H */ diff --git a/Engine/lib/openal-soft/include/AL/alext.h b/Engine/lib/openal-soft/include/AL/alext.h index d313a999a..02cfe2db7 100644 --- a/Engine/lib/openal-soft/include/AL/alext.h +++ b/Engine/lib/openal-soft/include/AL/alext.h @@ -1,6 +1,7 @@ #ifndef AL_ALEXT_H #define AL_ALEXT_H +/* NOLINTBEGIN */ #include /* Define int64 and uint64 types */ #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ @@ -25,6 +26,8 @@ typedef uint64_t _alsoft_uint64_t; extern "C" { #endif +struct _GUID; + #ifndef AL_LOKI_IMA_ADPCM_format #define AL_LOKI_IMA_ADPCM_format 1 #define AL_FORMAT_IMA_ADPCM_MONO16_EXT 0x10000 @@ -141,9 +144,9 @@ extern "C" { #ifndef AL_EXT_STATIC_BUFFER #define AL_EXT_STATIC_BUFFER 1 -typedef void (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALuint,ALenum,ALvoid*,ALsizei,ALsizei); +typedef void (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALuint,ALenum,ALvoid*,ALsizei,ALsizei) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -void AL_APIENTRY alBufferDataStatic(const ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq); +void AL_APIENTRY alBufferDataStatic(const ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT; #endif #endif @@ -159,11 +162,11 @@ void AL_APIENTRY alBufferDataStatic(const ALuint buffer, ALenum format, ALvoid * #ifndef ALC_EXT_thread_local_context #define ALC_EXT_thread_local_context 1 -typedef ALCboolean (ALC_APIENTRY*PFNALCSETTHREADCONTEXTPROC)(ALCcontext *context); -typedef ALCcontext* (ALC_APIENTRY*PFNALCGETTHREADCONTEXTPROC)(void); +typedef ALCboolean (ALC_APIENTRY*PFNALCSETTHREADCONTEXTPROC)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef ALCcontext* (ALC_APIENTRY*PFNALCGETTHREADCONTEXTPROC)(void) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context); -ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); +ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) ALC_API_NOEXCEPT; +ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) ALC_API_NOEXCEPT; #endif #endif @@ -176,9 +179,9 @@ ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); #define AL_SOFT_buffer_sub_data 1 #define AL_BYTE_RW_OFFSETS_SOFT 0x1031 #define AL_SAMPLE_RW_OFFSETS_SOFT 0x1032 -typedef void (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei); +typedef void (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length); +AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length) AL_API_NOEXCEPT; #endif #endif @@ -195,12 +198,12 @@ AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const AL #define AL_FOLDBACK_EVENT_STOP 0x4113 #define AL_FOLDBACK_MODE_MONO 0x4101 #define AL_FOLDBACK_MODE_STEREO 0x4102 -typedef void (AL_APIENTRY*LPALFOLDBACKCALLBACK)(ALenum,ALsizei); -typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTART)(ALenum,ALsizei,ALsizei,ALfloat*,LPALFOLDBACKCALLBACK); -typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTOP)(void); +typedef void (AL_APIENTRY*LPALFOLDBACKCALLBACK)(ALenum,ALsizei) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTART)(ALenum,ALsizei,ALsizei,ALfloat*,LPALFOLDBACKCALLBACK) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTOP)(void) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alRequestFoldbackStart(ALenum mode,ALsizei count,ALsizei length,ALfloat *mem,LPALFOLDBACKCALLBACK callback); -AL_API void AL_APIENTRY alRequestFoldbackStop(void); +AL_API void AL_APIENTRY alRequestFoldbackStart(ALenum mode,ALsizei count,ALsizei length,ALfloat *mem,LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alRequestFoldbackStop(void) AL_API_NOEXCEPT; #endif #endif @@ -263,15 +266,15 @@ AL_API void AL_APIENTRY alRequestFoldbackStop(void); #define AL_SAMPLE_LENGTH_SOFT 0x200A #define AL_SEC_LENGTH_SOFT 0x200B -typedef void (AL_APIENTRY*LPALBUFFERSAMPLESSOFT)(ALuint,ALuint,ALenum,ALsizei,ALenum,ALenum,const ALvoid*); -typedef void (AL_APIENTRY*LPALBUFFERSUBSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,const ALvoid*); -typedef void (AL_APIENTRY*LPALGETBUFFERSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,ALvoid*); -typedef ALboolean (AL_APIENTRY*LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum); +typedef void (AL_APIENTRY*LPALBUFFERSAMPLESSOFT)(ALuint,ALuint,ALenum,ALsizei,ALenum,ALenum,const ALvoid*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALBUFFERSUBSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,const ALvoid*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETBUFFERSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,ALvoid*) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY*LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); -AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); -AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data); -AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format); +AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format) AL_API_NOEXCEPT; #endif #endif @@ -302,13 +305,13 @@ AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format); #define ALC_6POINT1_SOFT 0x1505 #define ALC_7POINT1_SOFT 0x1506 -typedef ALCdevice* (ALC_APIENTRY*LPALCLOOPBACKOPENDEVICESOFT)(const ALCchar*); -typedef ALCboolean (ALC_APIENTRY*LPALCISRENDERFORMATSUPPORTEDSOFT)(ALCdevice*,ALCsizei,ALCenum,ALCenum); -typedef void (ALC_APIENTRY*LPALCRENDERSAMPLESSOFT)(ALCdevice*,ALCvoid*,ALCsizei); +typedef ALCdevice* (ALC_APIENTRY*LPALCLOOPBACKOPENDEVICESOFT)(const ALCchar*) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY*LPALCISRENDERFORMATSUPPORTEDSOFT)(ALCdevice*,ALCsizei,ALCenum,ALCenum) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY*LPALCRENDERSAMPLESSOFT)(ALCdevice*,ALCvoid*,ALCsizei) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName); -ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type); -ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); +ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) AL_API_NOEXCEPT; +ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) AL_API_NOEXCEPT; +ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) AL_API_NOEXCEPT; #endif #endif @@ -328,31 +331,31 @@ ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffe #define AL_SEC_OFFSET_LATENCY_SOFT 0x1201 typedef _alsoft_int64_t ALint64SOFT; typedef _alsoft_uint64_t ALuint64SOFT; -typedef void (AL_APIENTRY*LPALSOURCEDSOFT)(ALuint,ALenum,ALdouble); -typedef void (AL_APIENTRY*LPALSOURCE3DSOFT)(ALuint,ALenum,ALdouble,ALdouble,ALdouble); -typedef void (AL_APIENTRY*LPALSOURCEDVSOFT)(ALuint,ALenum,const ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCEDSOFT)(ALuint,ALenum,ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCE3DSOFT)(ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCEDVSOFT)(ALuint,ALenum,ALdouble*); -typedef void (AL_APIENTRY*LPALSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT); -typedef void (AL_APIENTRY*LPALSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT); -typedef void (AL_APIENTRY*LPALSOURCEI64VSOFT)(ALuint,ALenum,const ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCEI64VSOFT)(ALuint,ALenum,ALint64SOFT*); +typedef void (AL_APIENTRY*LPALSOURCEDSOFT)(ALuint,ALenum,ALdouble) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCE3DSOFT)(ALuint,ALenum,ALdouble,ALdouble,ALdouble) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCEDVSOFT)(ALuint,ALenum,const ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCEDSOFT)(ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCE3DSOFT)(ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCEDVSOFT)(ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCEI64VSOFT)(ALuint,ALenum,const ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCEI64VSOFT)(ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value); -AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3); -AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values); -AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value); -AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3); -AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values); -AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value); -AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3); -AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values); -AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value); -AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3); -AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values); +AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values) AL_API_NOEXCEPT; #endif #endif @@ -364,11 +367,11 @@ AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64 #ifndef AL_SOFT_deferred_updates #define AL_SOFT_deferred_updates 1 #define AL_DEFERRED_UPDATES_SOFT 0xC002 -typedef void (AL_APIENTRY*LPALDEFERUPDATESSOFT)(void); -typedef void (AL_APIENTRY*LPALPROCESSUPDATESSOFT)(void); +typedef void (AL_APIENTRY*LPALDEFERUPDATESSOFT)(void) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALPROCESSUPDATESSOFT)(void) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alDeferUpdatesSOFT(void); -AL_API void AL_APIENTRY alProcessUpdatesSOFT(void); +AL_API void AL_APIENTRY alDeferUpdatesSOFT(void) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alProcessUpdatesSOFT(void) AL_API_NOEXCEPT; #endif #endif @@ -400,11 +403,11 @@ AL_API void AL_APIENTRY alProcessUpdatesSOFT(void); #ifndef ALC_SOFT_pause_device #define ALC_SOFT_pause_device 1 -typedef void (ALC_APIENTRY*LPALCDEVICEPAUSESOFT)(ALCdevice *device); -typedef void (ALC_APIENTRY*LPALCDEVICERESUMESOFT)(ALCdevice *device); +typedef void (ALC_APIENTRY*LPALCDEVICEPAUSESOFT)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY*LPALCDEVICERESUMESOFT)(ALCdevice *device) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device); -ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device); +ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) ALC_API_NOEXCEPT; +ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) ALC_API_NOEXCEPT; #endif #endif @@ -448,11 +451,11 @@ ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device); #define ALC_NUM_HRTF_SPECIFIERS_SOFT 0x1994 #define ALC_HRTF_SPECIFIER_SOFT 0x1995 #define ALC_HRTF_ID_SOFT 0x1996 -typedef const ALCchar* (ALC_APIENTRY*LPALCGETSTRINGISOFT)(ALCdevice *device, ALCenum paramName, ALCsizei index); -typedef ALCboolean (ALC_APIENTRY*LPALCRESETDEVICESOFT)(ALCdevice *device, const ALCint *attribs); +typedef const ALCchar* (ALC_APIENTRY*LPALCGETSTRINGISOFT)(ALCdevice *device, ALCenum paramName, ALCsizei index) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY*LPALCRESETDEVICESOFT)(ALCdevice *device, const ALCint *attribs) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index); -ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs); +ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) ALC_API_NOEXCEPT; +ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) ALC_API_NOEXCEPT; #endif #endif @@ -467,9 +470,9 @@ ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCi #define AL_DEFAULT_RESAMPLER_SOFT 0x1211 #define AL_SOURCE_RESAMPLER_SOFT 0x1212 #define AL_RESAMPLER_NAME_SOFT 0x1213 -typedef const ALchar* (AL_APIENTRY*LPALGETSTRINGISOFT)(ALenum pname, ALsizei index); +typedef const ALchar* (AL_APIENTRY*LPALGETSTRINGISOFT)(ALenum pname, ALsizei index) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index); +AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index) AL_API_NOEXCEPT; #endif #endif @@ -493,9 +496,9 @@ typedef _alsoft_uint64_t ALCuint64SOFT; #define ALC_DEVICE_CLOCK_LATENCY_SOFT 0x1602 #define AL_SAMPLE_OFFSET_CLOCK_SOFT 0x1202 #define AL_SEC_OFFSET_CLOCK_SOFT 0x1203 -typedef void (ALC_APIENTRY*LPALCGETINTEGER64VSOFT)(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values); +typedef void (ALC_APIENTRY*LPALCGETINTEGER64VSOFT)(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values); +ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values) ALC_API_NOEXCEPT; #endif #endif @@ -552,27 +555,26 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, #define AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT 0x19A5 #define AL_EVENT_TYPE_DISCONNECTED_SOFT 0x19A6 typedef void (AL_APIENTRY*ALEVENTPROCSOFT)(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, - void *userParam); -typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable); -typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam); -typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname); -typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values); + ALsizei length, const ALchar *message, void *userParam) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable); -AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam); -AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname); -AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values); +AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT; +AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values) AL_API_NOEXCEPT; #endif #endif #ifndef ALC_SOFT_reopen_device #define ALC_SOFT_reopen_device typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device, - const ALCchar *deviceName, const ALCint *attribs); + const ALCchar *deviceName, const ALCint *attribs) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, const ALCchar *deviceName, - const ALCint *attribs); + const ALCint *attribs) ALC_API_NOEXCEPT; #endif #endif @@ -580,16 +582,16 @@ ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, const ALCchar *de #define AL_SOFT_callback_buffer #define AL_BUFFER_CALLBACK_FUNCTION_SOFT 0x19A0 #define AL_BUFFER_CALLBACK_USER_PARAM_SOFT 0x19A1 -typedef ALsizei (AL_APIENTRY*ALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numbytes); -typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value); -typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values); +typedef ALsizei (AL_APIENTRY*ALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numbytes) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr); -AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr); -AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2); -AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **ptr); +AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; #endif #endif @@ -640,16 +642,438 @@ AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid #ifndef AL_SOFT_source_start_delay #define AL_SOFT_source_start_delay -typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMESOFT)(ALuint source, ALint64SOFT start_time); -typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMEVSOFT)(ALsizei n, const ALuint *sources, ALint64SOFT start_time); +typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMESOFT)(ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMEVSOFT)(ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time); -void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time); +void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT; +#endif +#endif + +#ifndef ALC_EXT_debug +#define ALC_EXT_debug +#define ALC_CONTEXT_FLAGS_EXT 0x19CF +#define ALC_CONTEXT_DEBUG_BIT_EXT 0x0001 +#endif + +#ifndef AL_EXT_debug +#define AL_EXT_debug +#define AL_DONT_CARE_EXT 0x0002 +#define AL_DEBUG_OUTPUT_EXT 0x19B2 +#define AL_DEBUG_CALLBACK_FUNCTION_EXT 0x19B3 +#define AL_DEBUG_CALLBACK_USER_PARAM_EXT 0x19B4 +#define AL_DEBUG_SOURCE_API_EXT 0x19B5 +#define AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT 0x19B6 +#define AL_DEBUG_SOURCE_THIRD_PARTY_EXT 0x19B7 +#define AL_DEBUG_SOURCE_APPLICATION_EXT 0x19B8 +#define AL_DEBUG_SOURCE_OTHER_EXT 0x19B9 +#define AL_DEBUG_TYPE_ERROR_EXT 0x19BA +#define AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT 0x19BB +#define AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT 0x19BC +#define AL_DEBUG_TYPE_PORTABILITY_EXT 0x19BD +#define AL_DEBUG_TYPE_PERFORMANCE_EXT 0x19BE +#define AL_DEBUG_TYPE_MARKER_EXT 0x19BF +#define AL_DEBUG_TYPE_PUSH_GROUP_EXT 0x19C0 +#define AL_DEBUG_TYPE_POP_GROUP_EXT 0x19C1 +#define AL_DEBUG_TYPE_OTHER_EXT 0x19C2 +#define AL_DEBUG_SEVERITY_HIGH_EXT 0x19C3 +#define AL_DEBUG_SEVERITY_MEDIUM_EXT 0x19C4 +#define AL_DEBUG_SEVERITY_LOW_EXT 0x19C5 +#define AL_DEBUG_SEVERITY_NOTIFICATION_EXT 0x19C6 +#define AL_DEBUG_LOGGED_MESSAGES_EXT 0x19C7 +#define AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT 0x19C8 +#define AL_MAX_DEBUG_MESSAGE_LENGTH_EXT 0x19C9 +#define AL_MAX_DEBUG_LOGGED_MESSAGES_EXT 0x19CA +#define AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT 0x19CB +#define AL_MAX_LABEL_LENGTH_EXT 0x19CC +#define AL_STACK_OVERFLOW_EXT 0x19CD +#define AL_STACK_UNDERFLOW_EXT 0x19CE +#define AL_CONTEXT_FLAGS_EXT 0x19CF +#define AL_BUFFER_EXT 0x1009 /* Same as AL_BUFFER */ +#define AL_SOURCE_EXT 0x19D0 +#define AL_FILTER_EXT 0x19D1 +#define AL_EFFECT_EXT 0x19D2 +#define AL_AUXILIARY_EFFECT_SLOT_EXT 0x19D3 + +typedef void (AL_APIENTRY*ALDEBUGPROCEXT)(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message, void *userParam) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALDEBUGMESSAGECALLBACKEXT)(ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALDEBUGMESSAGEINSERTEXT)(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALDEBUGMESSAGECONTROLEXT)(ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALPUSHDEBUGGROUPEXT)(ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALPOPDEBUGGROUPEXT)(void) AL_API_NOEXCEPT17; +typedef ALuint (AL_APIENTRY*LPALGETDEBUGMESSAGELOGEXT)(ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALOBJECTLABELEXT)(ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETOBJECTLABELEXT)(ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT17; +#ifdef AL_ALEXT_PROTOTYPES +void AL_APIENTRY alDebugMessageCallbackEXT(ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT; +void AL_APIENTRY alDebugMessageInsertEXT(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; +void AL_APIENTRY alDebugMessageControlEXT(ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT; +void AL_APIENTRY alPushDebugGroupEXT(ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; +void AL_APIENTRY alPopDebugGroupEXT(void) AL_API_NOEXCEPT; +ALuint AL_APIENTRY alGetDebugMessageLogEXT(ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT; +void AL_APIENTRY alObjectLabelEXT(ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT; +void AL_APIENTRY alGetObjectLabelEXT(ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT; +#endif +#endif + +#ifndef ALC_SOFT_system_events +#define ALC_SOFT_system_events +#define ALC_PLAYBACK_DEVICE_SOFT 0x19D4 +#define ALC_CAPTURE_DEVICE_SOFT 0x19D5 +#define ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT 0x19D6 +#define ALC_EVENT_TYPE_DEVICE_ADDED_SOFT 0x19D7 +#define ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT 0x19D8 +#define ALC_EVENT_SUPPORTED_SOFT 0x19D9 +#define ALC_EVENT_NOT_SUPPORTED_SOFT 0x19DA +typedef void (ALC_APIENTRY*ALCEVENTPROCTYPESOFT)(ALCenum eventType, ALCenum deviceType, + ALCdevice *device, ALCsizei length, const ALCchar *message, void *userParam) ALC_API_NOEXCEPT17; +typedef ALCenum (ALC_APIENTRY*LPALCEVENTISSUPPORTEDSOFT)(ALCenum eventType, ALCenum deviceType) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY*LPALCEVENTCONTROLSOFT)(ALCsizei count, const ALCenum *events, ALCboolean enable) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY*LPALCEVENTCALLBACKSOFT)(ALCEVENTPROCTYPESOFT callback, void *userParam) ALC_API_NOEXCEPT17; +#ifdef AL_ALEXT_PROTOTYPES +ALCenum ALC_APIENTRY alcEventIsSupportedSOFT(ALCenum eventType, ALCenum deviceType) ALC_API_NOEXCEPT; +ALCboolean ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const ALCenum *events, ALCboolean enable) ALC_API_NOEXCEPT; +void ALC_APIENTRY alcEventCallbackSOFT(ALCEVENTPROCTYPESOFT callback, void *userParam) ALC_API_NOEXCEPT; +#endif +#endif + +#ifndef AL_EXT_direct_context +#define AL_EXT_direct_context +typedef ALCvoid* (ALC_APIENTRY *LPALCGETPROCADDRESS2)(ALCdevice *device, const ALCchar *funcname) AL_API_NOEXCEPT17; + +typedef void (AL_APIENTRY *LPALENABLEDIRECT)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDISABLEDIRECT)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISENABLEDDIRECT)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDOPPLERFACTORDIRECT)(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSPEEDOFSOUNDDIRECT)(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDISTANCEMODELDIRECT)(ALCcontext *context, ALenum distanceModel) AL_API_NOEXCEPT17; +typedef const ALchar* (AL_APIENTRY *LPALGETSTRINGDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBOOLEANVDIRECT)(ALCcontext *context, ALenum param, ALboolean *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETINTEGERVDIRECT)(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFLOATVDIRECT)(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETDOUBLEVDIRECT)(ALCcontext *context, ALenum param, ALdouble *values) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALGETBOOLEANDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; +typedef ALint (AL_APIENTRY *LPALGETINTEGERDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; +typedef ALfloat (AL_APIENTRY *LPALGETFLOATDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; +typedef ALdouble (AL_APIENTRY *LPALGETDOUBLEDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPALGETERRORDIRECT)(ALCcontext *context) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISEXTENSIONPRESENTDIRECT)(ALCcontext *context, const ALchar *extname) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY *LPALGETPROCADDRESSDIRECT)(ALCcontext *context, const ALchar *fname) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPALGETENUMVALUEDIRECT)(ALCcontext *context, const ALchar *ename) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERFDIRECT)(ALCcontext *context, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENER3FDIRECT)(ALCcontext *context, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERFVDIRECT)(ALCcontext *context, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERIDIRECT)(ALCcontext *context, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENER3IDIRECT)(ALCcontext *context, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERIVDIRECT)(ALCcontext *context, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERFDIRECT)(ALCcontext *context, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENER3FDIRECT)(ALCcontext *context, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERFVDIRECT)(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERIDIRECT)(ALCcontext *context, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENER3IDIRECT)(ALCcontext *context, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERIVDIRECT)(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENSOURCESDIRECT)(ALCcontext *context, ALsizei n, ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETESOURCESDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISSOURCEDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEFDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3FDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEFVDIRECT)(ALCcontext *context, ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEIDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3IDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEIVDIRECT)(ALCcontext *context, ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEFDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3FDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEFVDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEIDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3IDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEIVDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPLAYDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCESTOPDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEREWINDDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPAUSEDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPLAYVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCESTOPVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEREWINDVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPAUSEVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEQUEUEBUFFERSDIRECT)(ALCcontext *context, ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEUNQUEUEBUFFERSDIRECT)(ALCcontext *context, ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENBUFFERSDIRECT)(ALCcontext *context, ALsizei n, ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEBUFFERSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISBUFFERDIRECT)(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERDATADIRECT)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERFDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFER3FDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERFVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERIDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFER3IDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERIVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERFDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFER3FDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERFVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERIDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFER3IDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERIVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT17; +/* ALC_EXT_EFX */ +typedef void (AL_APIENTRY *LPALGENEFFECTSDIRECT)(ALCcontext *context, ALsizei n, ALuint *effects) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEEFFECTSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *effects) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISEFFECTDIRECT)(ALCcontext *context, ALuint effect) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTIDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTIVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTFDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTFVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTIDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTIVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTFDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTFVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENFILTERSDIRECT)(ALCcontext *context, ALsizei n, ALuint *filters) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEFILTERSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *filters) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISFILTERDIRECT)(ALCcontext *context, ALuint filter) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERIDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERIVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERFDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERFVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERIDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERIVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERFDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERFVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTSDIRECT)(ALCcontext *context, ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOTDIRECT)(ALCcontext *context, ALuint effectslot) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +/* AL_EXT_BUFFER_DATA_STATIC */ +typedef void (AL_APIENTRY *LPALBUFFERDATASTATICDIRECT)(ALCcontext *context, ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT17; +/* AL_EXT_debug */ +typedef void (AL_APIENTRY*LPALDEBUGMESSAGECALLBACKDIRECTEXT)(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALDEBUGMESSAGEINSERTDIRECTEXT)(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALDEBUGMESSAGECONTROLDIRECTEXT)(ALCcontext *context, ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALPUSHDEBUGGROUPDIRECTEXT)(ALCcontext *context, ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALPOPDEBUGGROUPDIRECTEXT)(ALCcontext *context) AL_API_NOEXCEPT17; +typedef ALuint (AL_APIENTRY*LPALGETDEBUGMESSAGELOGDIRECTEXT)(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALOBJECTLABELDIRECTEXT)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETOBJECTLABELDIRECTEXT)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT17; +/* AL_EXT_FOLDBACK */ +typedef void (AL_APIENTRY *LPALREQUESTFOLDBACKSTARTDIRECT)(ALCcontext *context, ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALREQUESTFOLDBACKSTOPDIRECT)(ALCcontext *context) AL_API_NOEXCEPT17; +/* AL_SOFT_buffer_sub_data */ +typedef void (AL_APIENTRY *LPALBUFFERSUBDATADIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17; +/* AL_SOFT_source_latency */ +typedef void (AL_APIENTRY *LPALSOURCEDDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3DDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble,ALdouble,ALdouble) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEDVDIRECTSOFT)(ALCcontext*,ALuint,ALenum,const ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEDDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3DDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEDVDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEI64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3I64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEI64VDIRECTSOFT)(ALCcontext*,ALuint,ALenum,const ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEI64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3I64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEI64VDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; +/* AL_SOFT_deferred_updates */ +typedef void (AL_APIENTRY *LPALDEFERUPDATESDIRECTSOFT)(ALCcontext *context) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALPROCESSUPDATESDIRECTSOFT)(ALCcontext *context) AL_API_NOEXCEPT17; +/* AL_SOFT_source_resampler */ +typedef const ALchar* (AL_APIENTRY *LPALGETSTRINGIDIRECTSOFT)(ALCcontext *context, ALenum pname, ALsizei index) AL_API_NOEXCEPT17; +/* AL_SOFT_events */ +typedef void (AL_APIENTRY *LPALEVENTCONTROLDIRECTSOFT)(ALCcontext *context, ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEVENTCALLBACKDIRECTSOFT)(ALCcontext *context, ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY *LPALGETPOINTERDIRECTSOFT)(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETPOINTERVDIRECTSOFT)(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT17; +/* AL_SOFT_callback_buffer */ +typedef void (AL_APIENTRY *LPALBUFFERCALLBACKDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERPTRDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFER3PTRDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERPTRVDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **values) AL_API_NOEXCEPT17; +/* AL_SOFT_source_start_delay */ +typedef void (AL_APIENTRY *LPALSOURCEPLAYATTIMEDIRECTSOFT)(ALCcontext *context, ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPLAYATTIMEVDIRECTSOFT)(ALCcontext *context, ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT17; +/* EAX */ +typedef ALenum (AL_APIENTRY *LPEAXSETDIRECT)(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPEAXGETDIRECT)(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPEAXSETBUFFERMODEDIRECT)(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPEAXGETBUFFERMODEDIRECT)(ALCcontext *context, ALuint buffer, ALint *pReserved) AL_API_NOEXCEPT17; +#ifdef AL_ALEXT_PROTOTYPES +ALCvoid* AL_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALchar *funcName) AL_API_NOEXCEPT; + +void AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; +void AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsEnabledDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; + +void AL_APIENTRY alDopplerFactorDirect(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT; +void AL_APIENTRY alSpeedOfSoundDirect(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT; +void AL_APIENTRY alDistanceModelDirect(ALCcontext *context, ALenum distanceModel) AL_API_NOEXCEPT; + +const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBooleanvDirect(ALCcontext *context, ALenum param, ALboolean *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetIntegervDirect(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetFloatvDirect(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetDoublevDirect(ALCcontext *context, ALenum param, ALdouble *values) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alGetBooleanDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; +ALint AL_APIENTRY alGetIntegerDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; +ALfloat AL_APIENTRY alGetFloatDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; +ALdouble AL_APIENTRY alGetDoubleDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; + +ALenum AL_APIENTRY alGetErrorDirect(ALCcontext *context) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsExtensionPresentDirect(ALCcontext *context, const ALchar *extname) AL_API_NOEXCEPT; +void* AL_APIENTRY alGetProcAddressDirect(ALCcontext *context, const ALchar *fname) AL_API_NOEXCEPT; +ALenum AL_APIENTRY alGetEnumValueDirect(ALCcontext *context, const ALchar *ename) AL_API_NOEXCEPT; + +void AL_APIENTRY alListenerfDirect(ALCcontext *context, ALenum param, ALfloat value) AL_API_NOEXCEPT; +void AL_APIENTRY alListener3fDirect(ALCcontext *context, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint value) AL_API_NOEXCEPT; +void AL_APIENTRY alListener3iDirect(ALCcontext *context, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +void AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum param, const ALint *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum param, ALfloat *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT; + +void AL_APIENTRY alGenSourcesDirect(ALCcontext *context, ALsizei n, ALuint *sources) AL_API_NOEXCEPT; +void AL_APIENTRY alDeleteSourcesDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsSourceDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcefDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT; +void AL_APIENTRY alSource3fDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceiDirect(ALCcontext *context, ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT; +void AL_APIENTRY alSource3iDirect(ALCcontext *context, ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceivDirect(ALCcontext *context, ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcefDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSource3fDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourceiDirect(ALCcontext *context, ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSource3iDirect(ALCcontext *context, ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourceivDirect(ALCcontext *context, ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePlayDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceStopDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceRewindDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePauseDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePlayvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceStopvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceRewindvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePausevDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceQueueBuffersDirect(ALCcontext *context, ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceUnqueueBuffersDirect(ALCcontext *context, ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT; + +void AL_APIENTRY alGenBuffersDirect(ALCcontext *context, ALsizei n, ALuint *buffers) AL_API_NOEXCEPT; +void AL_APIENTRY alDeleteBuffersDirect(ALCcontext *context, ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsBufferDirect(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferDataDirect(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT; +void AL_APIENTRY alBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT; +void AL_APIENTRY alBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT; + +void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) AL_API_NOEXCEPT; +void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei n, const ALuint *effects) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) AL_API_NOEXCEPT; +void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint iValue) AL_API_NOEXCEPT; +void AL_APIENTRY alEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; + +void AL_APIENTRY alGenFiltersDirect(ALCcontext *context, ALsizei n, ALuint *filters) AL_API_NOEXCEPT; +void AL_APIENTRY alDeleteFiltersDirect(ALCcontext *context, ALsizei n, const ALuint *filters) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) AL_API_NOEXCEPT; +void AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint iValue) AL_API_NOEXCEPT; +void AL_APIENTRY alFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +void AL_APIENTRY alFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; + +void AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT; +void AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsAuxiliaryEffectSlotDirect(ALCcontext *context, ALuint effectslot) AL_API_NOEXCEPT; +void AL_APIENTRY alAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint iValue) AL_API_NOEXCEPT; +void AL_APIENTRY alAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +void AL_APIENTRY alAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; + +void AL_APIENTRY alBufferDataStaticDirect(ALCcontext *context, ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT; + +void AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT; +void AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; +void AL_APIENTRY alDebugMessageControlDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT; +void AL_APIENTRY alPushDebugGroupDirectEXT(ALCcontext *context, ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; +void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) AL_API_NOEXCEPT; +ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT; +void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT; +void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT; + +void AL_APIENTRY alRequestFoldbackStartDirect(ALCcontext *context, ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT; +void AL_APIENTRY alRequestFoldbackStopDirect(ALCcontext *context) AL_API_NOEXCEPT; + +void AL_APIENTRY alBufferSubDataDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) AL_API_NOEXCEPT; + +void AL_APIENTRY alSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT; +void AL_APIENTRY alSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT; +void AL_APIENTRY alSource3i64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcei64vDirectSOFT(ALCcontext *context, ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSource3i64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcei64vDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *values) AL_API_NOEXCEPT; + +void AL_APIENTRY alDeferUpdatesDirectSOFT(ALCcontext *context) AL_API_NOEXCEPT; +void AL_APIENTRY alProcessUpdatesDirectSOFT(ALCcontext *context) AL_API_NOEXCEPT; + +const ALchar* AL_APIENTRY alGetStringiDirectSOFT(ALCcontext *context, ALenum pname, ALsizei index) AL_API_NOEXCEPT; + +void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT; +void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context, ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT; +void* AL_APIENTRY alGetPointerDirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT; +void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT; + +void AL_APIENTRY alBufferCallbackDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferPtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBuffer3PtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferPtrvDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; + +void AL_APIENTRY alSourcePlayAtTimeDirectSOFT(ALCcontext *context, ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePlayAtTimevDirectSOFT(ALCcontext *context, ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT; + +ALenum AL_APIENTRY EAXSetDirect(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT; +ALenum AL_APIENTRY EAXGetDirect(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY EAXSetBufferModeDirect(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) AL_API_NOEXCEPT; +ALenum AL_APIENTRY EAXGetBufferModeDirect(ALCcontext *context, ALuint buffer, ALint *pReserved) AL_API_NOEXCEPT; #endif #endif #ifdef __cplusplus } #endif +/* NOLINTEND */ #endif diff --git a/Engine/lib/openal-soft/include/AL/efx-presets.h b/Engine/lib/openal-soft/include/AL/efx-presets.h index 8539fd517..acd5bf398 100644 --- a/Engine/lib/openal-soft/include/AL/efx-presets.h +++ b/Engine/lib/openal-soft/include/AL/efx-presets.h @@ -2,6 +2,7 @@ #ifndef EFX_PRESETS_H #define EFX_PRESETS_H +/* NOLINTBEGIN */ #ifndef EFXEAXREVERBPROPERTIES_DEFINED #define EFXEAXREVERBPROPERTIES_DEFINED @@ -399,4 +400,5 @@ typedef struct { #define EFX_REVERB_PRESET_SMALLWATERROOM \ { 1.0000f, 0.7000f, 0.3162f, 0.4477f, 1.0000f, 1.5100f, 1.2500f, 1.1400f, 0.8913f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, 0.1900f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +/* NOLINTEND */ #endif /* EFX_PRESETS_H */ diff --git a/Engine/lib/openal-soft/include/AL/efx.h b/Engine/lib/openal-soft/include/AL/efx.h index 5ab64a64d..1e93bf222 100644 --- a/Engine/lib/openal-soft/include/AL/efx.h +++ b/Engine/lib/openal-soft/include/AL/efx.h @@ -1,6 +1,7 @@ #ifndef AL_EFX_H #define AL_EFX_H +/* NOLINTBEGIN */ #include #include "alc.h" @@ -204,80 +205,80 @@ extern "C" { /* Effect object function types. */ -typedef void (AL_APIENTRY *LPALGENEFFECTS)(ALsizei, ALuint*); -typedef void (AL_APIENTRY *LPALDELETEEFFECTS)(ALsizei, const ALuint*); -typedef ALboolean (AL_APIENTRY *LPALISEFFECT)(ALuint); -typedef void (AL_APIENTRY *LPALEFFECTI)(ALuint, ALenum, ALint); -typedef void (AL_APIENTRY *LPALEFFECTIV)(ALuint, ALenum, const ALint*); -typedef void (AL_APIENTRY *LPALEFFECTF)(ALuint, ALenum, ALfloat); -typedef void (AL_APIENTRY *LPALEFFECTFV)(ALuint, ALenum, const ALfloat*); -typedef void (AL_APIENTRY *LPALGETEFFECTI)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETEFFECTIV)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETEFFECTF)(ALuint, ALenum, ALfloat*); -typedef void (AL_APIENTRY *LPALGETEFFECTFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGENEFFECTS)(ALsizei, ALuint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEEFFECTS)(ALsizei, const ALuint*) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISEFFECT)(ALuint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTI)(ALuint, ALenum, ALint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTIV)(ALuint, ALenum, const ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTF)(ALuint, ALenum, ALfloat) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTFV)(ALuint, ALenum, const ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTI)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTIV)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTF)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTFV)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; /* Filter object function types. */ -typedef void (AL_APIENTRY *LPALGENFILTERS)(ALsizei, ALuint*); -typedef void (AL_APIENTRY *LPALDELETEFILTERS)(ALsizei, const ALuint*); -typedef ALboolean (AL_APIENTRY *LPALISFILTER)(ALuint); -typedef void (AL_APIENTRY *LPALFILTERI)(ALuint, ALenum, ALint); -typedef void (AL_APIENTRY *LPALFILTERIV)(ALuint, ALenum, const ALint*); -typedef void (AL_APIENTRY *LPALFILTERF)(ALuint, ALenum, ALfloat); -typedef void (AL_APIENTRY *LPALFILTERFV)(ALuint, ALenum, const ALfloat*); -typedef void (AL_APIENTRY *LPALGETFILTERI)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETFILTERIV)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETFILTERF)(ALuint, ALenum, ALfloat*); -typedef void (AL_APIENTRY *LPALGETFILTERFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGENFILTERS)(ALsizei, ALuint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEFILTERS)(ALsizei, const ALuint*) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISFILTER)(ALuint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERI)(ALuint, ALenum, ALint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERIV)(ALuint, ALenum, const ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERF)(ALuint, ALenum, ALfloat) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERFV)(ALuint, ALenum, const ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERI)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERIV)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERF)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERFV)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; /* Auxiliary Effect Slot object function types. */ -typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*); -typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei, const ALuint*); -typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOT)(ALuint); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, const ALint*); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, const ALfloat*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei, const ALuint*) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOT)(ALuint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, const ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, const ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects); -AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects); -AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect); -AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue); -AL_API void AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *piValues); -AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue); -AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *pflValues); -AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue); -AL_API void AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues); -AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue); -AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues); +AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; -AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters); -AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters); -AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter); -AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue); -AL_API void AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *piValues); -AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue); -AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *pflValues); -AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue); -AL_API void AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues); -AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue); -AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues); +AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; -AL_API void AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots); -AL_API void AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots); -AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot); -AL_API void AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *piValues); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *pflValues); -AL_API void AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue); -AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues); -AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue); -AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues); +AL_API void AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; #endif /* Filter ranges and defaults. */ @@ -758,5 +759,6 @@ AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum par #ifdef __cplusplus } /* extern "C" */ #endif +/* NOLINTEND */ #endif /* AL_EFX_H */ diff --git a/Engine/lib/openal-soft/router/al.cpp b/Engine/lib/openal-soft/router/al.cpp index 06c314eb8..57c8d1797 100644 --- a/Engine/lib/openal-soft/router/al.cpp +++ b/Engine/lib/openal-soft/router/al.cpp @@ -1,39 +1,42 @@ #include "config.h" -#include +#include #include "AL/al.h" #include "router.h" -std::atomic CurrentCtxDriver{nullptr}; - -#define DECL_THUNK1(R,n,T1) AL_API R AL_APIENTRY n(T1 a) \ +#define DECL_THUNK1(R,n,T1) \ +AL_API auto AL_APIENTRY n(T1 a) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ 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) \ +#define DECL_THUNK2(R,n,T1,T2) \ +AL_API auto AL_APIENTRY n(T1 a, T2 b) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ 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) \ +#define DECL_THUNK3(R,n,T1,T2,T3) \ +AL_API auto AL_APIENTRY n(T1 a, T2 b, T3 c) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ 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) \ +#define DECL_THUNK4(R,n,T1,T2,T3,T4) \ +AL_API auto AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ 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) \ +#define DECL_THUNK5(R,n,T1,T2,T3,T4,T5) \ +AL_API auto AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d, T5 e) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ @@ -44,7 +47,7 @@ std::atomic CurrentCtxDriver{nullptr}; /* Ugly hack for some apps calling alGetError without a current context, and * expecting it to be AL_NO_ERROR. */ -AL_API ALenum AL_APIENTRY alGetError(void) +AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum { DriverIface *iface = GetThreadDriver(); if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); diff --git a/Engine/lib/openal-soft/router/alc.cpp b/Engine/lib/openal-soft/router/alc.cpp index 3aa3382be..312ac6b73 100644 --- a/Engine/lib/openal-soft/router/alc.cpp +++ b/Engine/lib/openal-soft/router/alc.cpp @@ -1,26 +1,34 @@ #include "config.h" -#include -#include -#include -#include - -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "AL/alc.h" -#include "alstring.h" + +#include "almalloc.h" #include "router.h" -#define DECL(x) { #x, reinterpret_cast(x) } +namespace { + +using namespace std::string_view_literals; + struct FuncExportEntry { const char *funcName; void *address; }; -static const std::array alcFunctions{{ +#define DECL(x) FuncExportEntry{ #x, reinterpret_cast(x) } +const std::array alcFunctions{ DECL(alcCreateContext), DECL(alcMakeContextCurrent), DECL(alcProcessContext), @@ -153,15 +161,15 @@ static const std::array alcFunctions{{ DECL(alGetAuxiliaryEffectSlotfv), DECL(alGetAuxiliaryEffectSloti), DECL(alGetAuxiliaryEffectSlotiv), -}}; +}; #undef DECL -#define DECL(x) { #x, (x) } struct EnumExportEntry { const ALCchar *enumName; ALCenum value; }; -static const std::array alcEnumerations{{ +#define DECL(x) EnumExportEntry{ #x, (x) } +const std::array alcEnumerations{ DECL(ALC_INVALID), DECL(ALC_FALSE), DECL(ALC_TRUE), @@ -267,84 +275,100 @@ static const std::array alcEnumerations{{ DECL(AL_LINEAR_DISTANCE_CLAMPED), DECL(AL_EXPONENT_DISTANCE), DECL(AL_EXPONENT_DISTANCE_CLAMPED), -}}; +}; #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"; -static const ALCchar alcExtensionList[] = - "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " - "ALC_EXT_thread_local_context"; +[[nodiscard]] constexpr auto GetNoErrorString() noexcept { return "No Error"; } +[[nodiscard]] constexpr auto GetInvalidDeviceString() noexcept { return "Invalid Device"; } +[[nodiscard]] constexpr auto GetInvalidContextString() noexcept { return "Invalid Context"; } +[[nodiscard]] constexpr auto GetInvalidEnumString() noexcept { return "Invalid Enum"; } +[[nodiscard]] constexpr auto GetInvalidValueString() noexcept { return "Invalid Value"; } +[[nodiscard]] constexpr auto GetOutOfMemoryString() noexcept { return "Out of Memory"; } -static const ALCint alcMajorVersion = 1; -static const ALCint alcMinorVersion = 1; +[[nodiscard]] constexpr auto GetExtensionList() noexcept -> std::string_view +{ + return "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " + "ALC_EXT_thread_local_context"sv; +} + +constexpr ALCint alcMajorVersion = 1; +constexpr ALCint alcMinorVersion = 1; -static std::recursive_mutex EnumerationLock; -static std::mutex ContextSwitchLock; +std::recursive_mutex EnumerationLock; +std::mutex ContextSwitchLock; -static std::atomic LastError{ALC_NO_ERROR}; -static PtrIntMap DeviceIfaceMap; -static PtrIntMap ContextIfaceMap; +std::atomic LastError{ALC_NO_ERROR}; +std::unordered_map DeviceIfaceMap; +std::unordered_map ContextIfaceMap; + +template +auto maybe_get(std::unordered_map &list, V&& key) -> std::optional +{ + auto iter = list.find(std::forward(key)); + if(iter != list.end()) return iter->second; + return std::nullopt; +} -typedef struct EnumeratedList { +struct EnumeratedList { std::vector Names; - std::vector Indicies; + std::vector Indicies; 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) + void AppendDeviceList(const ALCchar *names, ALCuint idx); + [[nodiscard]] + auto GetDriverIndexForName(const std::string_view name) const -> std::optional; +}; +EnumeratedList DevicesList; +EnumeratedList AllDevicesList; +EnumeratedList CaptureDevicesList; + +void EnumeratedList::AppendDeviceList(const ALCchar* names, ALCuint idx) { const ALCchar *name_end = names; if(!name_end) return; - ALCsizei count = 0; + size_t count{0}; while(*name_end) { - TRACE("Enumerated \"%s\", driver %d\n", name_end, idx); + TRACE("Enumerated \"%s\", driver %u\n", name_end, idx); ++count; - name_end += strlen(name_end)+1; + name_end += strlen(name_end)+1; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ } if(names == name_end) return; - list->Names.reserve(list->Names.size() + (name_end - names) + 1); - list->Names.insert(list->Names.cend(), names, name_end); + Names.reserve(Names.size() + static_cast(name_end - names) + 1); + Names.insert(Names.cend(), names, name_end); - list->Indicies.reserve(list->Indicies.size() + count); - list->Indicies.insert(list->Indicies.cend(), count, idx); + Indicies.reserve(Indicies.size() + count); + Indicies.insert(Indicies.cend(), count, idx); } -static ALint GetDriverIndexForName(const EnumeratedList *list, const ALCchar *name) +auto EnumeratedList::GetDriverIndexForName(const std::string_view name) const -> std::optional { - const ALCchar *devnames = list->Names.data(); - const ALCint *index = list->Indicies.data(); + auto devnames = Names.cbegin(); + auto index = Indicies.cbegin(); - while(devnames && *devnames) + while(devnames != Names.cend() && *devnames) { - if(strcmp(name, devnames) == 0) - return *index; - devnames += strlen(devnames)+1; - index++; + const auto devname = std::string_view{al::to_address(devnames)}; + if(name == devname) return *index; + + devnames += ptrdiff_t(devname.size()+1); + ++index; } - return -1; + return std::nullopt; } -static void InitCtxFuncs(DriverIface &iface) +void InitCtxFuncs(DriverIface &iface) { ALCdevice *device{iface.alcGetContextsDevice(iface.alcGetCurrentContext())}; @@ -393,65 +417,67 @@ static void InitCtxFuncs(DriverIface &iface) #undef LOAD_PROC } +} /* namespace */ -ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) + +ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) noexcept { - ALCdevice *device = nullptr; - ALint idx = 0; + ALCdevice *device{nullptr}; + std::optional idx; /* 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 * way to map them. */ - if(devicename && (devicename[0] == '\0' || - strcmp(devicename, "DirectSound3D") == 0 || - strcmp(devicename, "DirectSound") == 0 || - strcmp(devicename, "MMSYSTEM") == 0)) - devicename = nullptr; - if(devicename) + if(devicename && *devicename != '\0' && devicename != "DirectSound3D"sv + && devicename != "DirectSound"sv && devicename != "MMSYSTEM"sv) { { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; if(DevicesList.Names.empty()) - (void)alcGetString(nullptr, ALC_DEVICE_SPECIFIER); - idx = GetDriverIndexForName(&DevicesList, devicename); - if(idx < 0) + std::ignore = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); + idx = DevicesList.GetDriverIndexForName(devicename); + if(!idx) { if(AllDevicesList.Names.empty()) - (void)alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); - idx = GetDriverIndexForName(&AllDevicesList, devicename); + std::ignore = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); + idx = AllDevicesList.GetDriverIndexForName(devicename); } } - if(idx < 0) + if(!idx) { LastError.store(ALC_INVALID_VALUE); TRACE("Failed to find driver for name \"%s\"\n", devicename); return nullptr; } - TRACE("Found driver %d for name \"%s\"\n", idx, devicename); - device = DriverList[idx]->alcOpenDevice(devicename); + TRACE("Found driver %u for name \"%s\"\n", *idx, devicename); + device = DriverList[*idx]->alcOpenDevice(devicename); } else { + ALCuint drvidx{0}; for(const auto &drv : DriverList) { if(drv->ALCVer >= MAKE_ALC_VER(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) { - TRACE("Using default device from driver %d\n", idx); + TRACE("Using default device from driver %u\n", drvidx); device = drv->alcOpenDevice(nullptr); + idx = drvidx; break; } - ++idx; + ++drvidx; } } if(device) { - if(DeviceIfaceMap.insert(device, idx) != ALC_NO_ERROR) - { - DriverList[idx]->alcCloseDevice(device); + try { + DeviceIfaceMap.emplace(device, idx.value()); + } + catch(...) { + DriverList[idx.value()]->alcCloseDevice(device); device = nullptr; } } @@ -459,38 +485,38 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) return device; } -ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) +ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept { - ALint idx; - - if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0) + if(const auto idx = maybe_get(DeviceIfaceMap, device)) { - LastError.store(ALC_INVALID_DEVICE); - return ALC_FALSE; + if(!DriverList[*idx]->alcCloseDevice(device)) + return ALC_FALSE; + DeviceIfaceMap.erase(device); + return ALC_TRUE; } - if(!DriverList[idx]->alcCloseDevice(device)) - return ALC_FALSE; - DeviceIfaceMap.removeByKey(device); - return ALC_TRUE; + + LastError.store(ALC_INVALID_DEVICE); + return ALC_FALSE; } -ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist) +ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist) noexcept { - ALCcontext *context; - ALint idx; - - if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0) + const auto idx = maybe_get(DeviceIfaceMap, device); + if(!idx) { LastError.store(ALC_INVALID_DEVICE); return nullptr; } - context = DriverList[idx]->alcCreateContext(device, attrlist); + + ALCcontext *context{DriverList[*idx]->alcCreateContext(device, attrlist)}; if(context) { - if(ContextIfaceMap.insert(context, idx) != ALC_NO_ERROR) - { - DriverList[idx]->alcDestroyContext(context); + try { + ContextIfaceMap.emplace(context, *idx); + } + catch(...) { + DriverList[*idx]->alcDestroyContext(context); context = nullptr; } } @@ -498,43 +524,42 @@ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCin return context; } -ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) +ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) noexcept { - ALint idx = -1; + std::lock_guard ctxlock{ContextSwitchLock}; - std::lock_guard _{ContextSwitchLock}; + std::optional idx; if(context) { - idx = ContextIfaceMap.lookupByKey(context); - if(idx < 0) + idx = maybe_get(ContextIfaceMap, context); + if(!idx) { LastError.store(ALC_INVALID_CONTEXT); return ALC_FALSE; } - if(!DriverList[idx]->alcMakeContextCurrent(context)) + if(!DriverList[*idx]->alcMakeContextCurrent(context)) return ALC_FALSE; - auto do_init = [idx]() { InitCtxFuncs(*DriverList[idx]); }; - std::call_once(DriverList[idx]->InitOnceCtx, do_init); + std::call_once(DriverList[*idx]->InitOnceCtx, [idx]{ InitCtxFuncs(*DriverList[*idx]); }); } /* Unset the context from the old driver if it's different from the new * current one. */ - if(idx < 0) + if(!idx) { - DriverIface *oldiface = GetThreadDriver(); + DriverIface *oldiface{GetThreadDriver()}; if(oldiface) oldiface->alcSetThreadContext(nullptr); oldiface = CurrentCtxDriver.exchange(nullptr); if(oldiface) oldiface->alcMakeContextCurrent(nullptr); } else { - DriverIface *oldiface = GetThreadDriver(); - if(oldiface && oldiface != DriverList[idx].get()) + DriverIface *oldiface{GetThreadDriver()}; + if(oldiface && oldiface != DriverList[*idx].get()) oldiface->alcSetThreadContext(nullptr); - oldiface = CurrentCtxDriver.exchange(DriverList[idx].get()); - if(oldiface && oldiface != DriverList[idx].get()) + oldiface = CurrentCtxDriver.exchange(DriverList[*idx].get()); + if(oldiface && oldiface != DriverList[*idx].get()) oldiface->alcMakeContextCurrent(nullptr); } SetThreadDriver(nullptr); @@ -542,116 +567,95 @@ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) return ALC_TRUE; } -ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept { - if(context) - { - ALint idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) - return DriverList[idx]->alcProcessContext(context); - } + if(const auto idx = maybe_get(ContextIfaceMap, context)) + return DriverList[*idx]->alcProcessContext(context); + LastError.store(ALC_INVALID_CONTEXT); } -ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) noexcept { - if(context) - { - ALint idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) - return DriverList[idx]->alcSuspendContext(context); - } + if(const auto idx = maybe_get(ContextIfaceMap, context)) + return DriverList[*idx]->alcSuspendContext(context); + LastError.store(ALC_INVALID_CONTEXT); } -ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept { - ALint idx; - - if(!context || (idx=ContextIfaceMap.lookupByKey(context)) < 0) + if(const auto idx = maybe_get(ContextIfaceMap, context)) { - LastError.store(ALC_INVALID_CONTEXT); + DriverList[*idx]->alcDestroyContext(context); + ContextIfaceMap.erase(context); return; } - - DriverList[idx]->alcDestroyContext(context); - ContextIfaceMap.removeByKey(context); + LastError.store(ALC_INVALID_CONTEXT); } -ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) +ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext() noexcept { - DriverIface *iface = GetThreadDriver(); + DriverIface *iface{GetThreadDriver()}; if(!iface) iface = CurrentCtxDriver.load(); return iface ? iface->alcGetCurrentContext() : nullptr; } -ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context) +ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context) noexcept { - if(context) - { - ALint idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) - return DriverList[idx]->alcGetContextsDevice(context); - } + if(const auto idx = maybe_get(ContextIfaceMap, context)) + return DriverList[*idx]->alcGetContextsDevice(context); + LastError.store(ALC_INVALID_CONTEXT); return nullptr; } -ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) +ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) noexcept { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) return ALC_INVALID_DEVICE; - return DriverList[idx]->alcGetError(device); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetError(device); + return ALC_INVALID_DEVICE; } return LastError.exchange(ALC_NO_ERROR); } -ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname) +ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname) noexcept { - const char *ptr; - size_t len; - if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return ALC_FALSE; - } - return DriverList[idx]->alcIsExtensionPresent(device, extname); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcIsExtensionPresent(device, extname); + + LastError.store(ALC_INVALID_DEVICE); + return ALC_FALSE; } - len = strlen(extname); - ptr = alcExtensionList; - while(ptr && *ptr) + const auto tofind = std::string_view{extname}; + const auto extlist = GetExtensionList(); + auto matchpos = extlist.find(tofind); + while(matchpos != std::string_view::npos) { - if(al::strncasecmp(ptr, extname, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) + const auto endpos = matchpos + tofind.size(); + if((matchpos == 0 || std::isspace(extlist[matchpos-1])) + && (endpos == extlist.size() || std::isspace(extlist[endpos]))) return ALC_TRUE; - if((ptr=strchr(ptr, ' ')) != nullptr) - { - do { - ++ptr; - } while(isspace(*ptr)); - } + matchpos = extlist.find(tofind, matchpos+1); } return ALC_FALSE; } -ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname) +ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname) noexcept { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return nullptr; - } - return DriverList[idx]->alcGetProcAddress(device, funcname); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetProcAddress(device, funcname); + + LastError.store(ALC_INVALID_DEVICE); + return nullptr; } auto iter = std::find_if(alcFunctions.cbegin(), alcFunctions.cend(), @@ -661,17 +665,15 @@ ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *f return (iter != alcFunctions.cend()) ? iter->address : nullptr; } -ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname) +ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname) noexcept { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return 0; - } - return DriverList[idx]->alcGetEnumValue(device, enumname); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetEnumValue(device, enumname); + + LastError.store(ALC_INVALID_DEVICE); + return 0; } auto iter = std::find_if(alcEnumerations.cbegin(), alcEnumerations.cend(), @@ -681,48 +683,38 @@ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *e return (iter != alcEnumerations.cend()) ? iter->value : 0; } -ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) +ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) noexcept { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return nullptr; - } - return DriverList[idx]->alcGetString(device, param); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetString(device, param); + + LastError.store(ALC_INVALID_DEVICE); + return nullptr; } switch(param) { - case ALC_NO_ERROR: - return alcNoError; - case ALC_INVALID_ENUM: - return alcErrInvalidEnum; - case ALC_INVALID_VALUE: - return alcErrInvalidValue; - case ALC_INVALID_DEVICE: - return alcErrInvalidDevice; - case ALC_INVALID_CONTEXT: - return alcErrInvalidContext; - case ALC_OUT_OF_MEMORY: - return alcErrOutOfMemory; - case ALC_EXTENSIONS: - return alcExtensionList; + case ALC_NO_ERROR: return GetNoErrorString(); + case ALC_INVALID_ENUM: return GetInvalidEnumString(); + case ALC_INVALID_VALUE: return GetInvalidValueString(); + case ALC_INVALID_DEVICE: return GetInvalidDeviceString(); + case ALC_INVALID_CONTEXT: return GetInvalidContextString(); + case ALC_OUT_OF_MEMORY: return GetOutOfMemoryString(); + case ALC_EXTENSIONS: return GetExtensionList().data(); case ALC_DEVICE_SPECIFIER: { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; DevicesList.clear(); - ALint idx{0}; + ALCuint idx{0}; for(const auto &drv : DriverList) { /* Only enumerate names from drivers that support it. */ if(drv->ALCVer >= MAKE_ALC_VER(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) - AppendDeviceList(&DevicesList, - drv->alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); + DevicesList.AppendDeviceList(drv->alcGetString(nullptr,ALC_DEVICE_SPECIFIER), idx); ++idx; } /* Ensure the list is double-null termianted. */ @@ -734,20 +726,20 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para case ALC_ALL_DEVICES_SPECIFIER: { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; AllDevicesList.clear(); - ALint idx{0}; + ALCuint idx{0}; for(const auto &drv : DriverList) { /* If the driver doesn't support ALC_ENUMERATE_ALL_EXT, substitute * standard enumeration. */ if(drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) - AppendDeviceList(&AllDevicesList, + AllDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER), idx); else if(drv->ALCVer >= MAKE_ALC_VER(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) - AppendDeviceList(&AllDevicesList, + AllDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); ++idx; } @@ -760,14 +752,14 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para case ALC_CAPTURE_DEVICE_SPECIFIER: { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; CaptureDevicesList.clear(); - ALint idx{0}; + ALCuint idx{0}; for(const auto &drv : DriverList) { if(drv->ALCVer >= MAKE_ALC_VER(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) - AppendDeviceList(&CaptureDevicesList, + CaptureDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER), idx); ++idx; } @@ -817,17 +809,15 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para return nullptr; } -ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) +ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) noexcept { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return; - } - return DriverList[idx]->alcGetIntegerv(device, param, size, values); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetIntegerv(device, param, size, values); + + LastError.store(ALC_INVALID_DEVICE); + return; } if(size <= 0 || values == nullptr) @@ -841,14 +831,15 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsi case ALC_MAJOR_VERSION: if(size >= 1) { - values[0] = alcMajorVersion; + *values = alcMajorVersion; return; } - /*fall-through*/ + LastError.store(ALC_INVALID_VALUE); + return; case ALC_MINOR_VERSION: if(size >= 1) { - values[0] = alcMinorVersion; + *values = alcMinorVersion; return; } LastError.store(ALC_INVALID_VALUE); @@ -872,51 +863,54 @@ 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) +ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, + ALCenum format, ALCsizei buffersize) noexcept { - ALCdevice *device = nullptr; - ALint idx = 0; + ALCdevice *device{nullptr}; + std::optional idx; - if(devicename && devicename[0] == '\0') - devicename = nullptr; - if(devicename) + if(devicename && *devicename != '\0') { { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; if(CaptureDevicesList.Names.empty()) - (void)alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); - idx = GetDriverIndexForName(&CaptureDevicesList, devicename); + std::ignore = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); + idx = CaptureDevicesList.GetDriverIndexForName(devicename); } - if(idx < 0) + if(!idx) { LastError.store(ALC_INVALID_VALUE); TRACE("Failed to find driver for name \"%s\"\n", devicename); return nullptr; } - TRACE("Found driver %d for name \"%s\"\n", idx, devicename); - device = DriverList[idx]->alcCaptureOpenDevice(devicename, frequency, format, buffersize); + TRACE("Found driver %u for name \"%s\"\n", *idx, devicename); + device = DriverList[*idx]->alcCaptureOpenDevice(devicename, frequency, format, buffersize); } else { + ALCuint drvidx{0}; for(const auto &drv : DriverList) { if(drv->ALCVer >= MAKE_ALC_VER(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) { - TRACE("Using default capture device from driver %d\n", idx); + TRACE("Using default capture device from driver %u\n", drvidx); device = drv->alcCaptureOpenDevice(nullptr, frequency, format, buffersize); + idx = drvidx; break; } - ++idx; + ++drvidx; } } if(device) { - if(DeviceIfaceMap.insert(device, idx) != ALC_NO_ERROR) - { - DriverList[idx]->alcCaptureCloseDevice(device); + try { + DeviceIfaceMap.emplace(device, idx.value()); + } + catch(...) { + DriverList[idx.value()]->alcCaptureCloseDevice(device); device = nullptr; } } @@ -924,94 +918,77 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, return device; } -ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) +ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) noexcept { - ALint idx; - - if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0) + if(const auto idx = maybe_get(DeviceIfaceMap, device)) { - LastError.store(ALC_INVALID_DEVICE); - return ALC_FALSE; + if(!DriverList[*idx]->alcCaptureCloseDevice(device)) + return ALC_FALSE; + DeviceIfaceMap.erase(device); + return ALC_TRUE; } - if(!DriverList[idx]->alcCaptureCloseDevice(device)) - return ALC_FALSE; - DeviceIfaceMap.removeByKey(device); - return ALC_TRUE; + + LastError.store(ALC_INVALID_DEVICE); + return ALC_FALSE; } -ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) +ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) noexcept { - if(device) - { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx >= 0) - return DriverList[idx]->alcCaptureStart(device); - } + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcCaptureStart(device); LastError.store(ALC_INVALID_DEVICE); } -ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) +ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) noexcept { - if(device) - { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx >= 0) - return DriverList[idx]->alcCaptureStop(device); - } + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcCaptureStop(device); LastError.store(ALC_INVALID_DEVICE); } -ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) +ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { - if(device) - { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx >= 0) - return DriverList[idx]->alcCaptureSamples(device, buffer, samples); - } + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcCaptureSamples(device, buffer, samples); LastError.store(ALC_INVALID_DEVICE); } -ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) +ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) noexcept { - ALCenum err = ALC_INVALID_CONTEXT; - ALint idx; - if(!context) { - DriverIface *oldiface = GetThreadDriver(); + DriverIface *oldiface{GetThreadDriver()}; if(oldiface && !oldiface->alcSetThreadContext(nullptr)) return ALC_FALSE; SetThreadDriver(nullptr); return ALC_TRUE; } - idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) + ALCenum err{ALC_INVALID_CONTEXT}; + if(const auto idx = maybe_get(ContextIfaceMap, context)) { - if(DriverList[idx]->alcSetThreadContext(context)) + if(DriverList[*idx]->alcSetThreadContext(context)) { - auto do_init = [idx]() { InitCtxFuncs(*DriverList[idx]); }; - std::call_once(DriverList[idx]->InitOnceCtx, do_init); + std::call_once(DriverList[*idx]->InitOnceCtx, [idx]{InitCtxFuncs(*DriverList[*idx]);}); - DriverIface *oldiface = GetThreadDriver(); - if(oldiface != DriverList[idx].get()) + DriverIface *oldiface{GetThreadDriver()}; + if(oldiface != DriverList[*idx].get()) { - SetThreadDriver(DriverList[idx].get()); + SetThreadDriver(DriverList[*idx].get()); if(oldiface) oldiface->alcSetThreadContext(nullptr); } return ALC_TRUE; } - err = DriverList[idx]->alcGetError(nullptr); + err = DriverList[*idx]->alcGetError(nullptr); } LastError.store(err); return ALC_FALSE; } -ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) +ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext() noexcept { - DriverIface *iface = GetThreadDriver(); - if(iface) return iface->alcGetThreadContext(); + if(DriverIface *iface{GetThreadDriver()}) + return iface->alcGetThreadContext(); return nullptr; } diff --git a/Engine/lib/openal-soft/router/router.cpp b/Engine/lib/openal-soft/router/router.cpp index 3c8910535..e6f4a62bf 100644 --- a/Engine/lib/openal-soft/router/router.cpp +++ b/Engine/lib/openal-soft/router/router.cpp @@ -4,83 +4,34 @@ #include "router.h" #include +#include #include #include #include +#include +#include +#include #include "AL/alc.h" #include "AL/al.h" -#include "almalloc.h" +#include "alstring.h" +#include "opthelpers.h" #include "strutils.h" #include "version.h" -std::vector DriverList; +eLogLevel LogLevel{eLogLevel::Error}; +gsl::owner LogFile; -thread_local DriverIface *ThreadCtxDriver; +namespace { -enum LogLevel LogLevel = LogLevel_Error; -FILE *LogFile; - -#ifdef __MINGW32__ -DriverIface *GetThreadDriver() noexcept { return ThreadCtxDriver; } -void SetThreadDriver(DriverIface *driver) noexcept { ThreadCtxDriver = driver; } -#endif - -static void LoadDriverList(void); +std::vector gAcceptList; +std::vector gRejectList; -BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*) -{ - switch(reason) - { - 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(l); - } - TRACE("Initializing router v0.1-%s %s\n", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); - LoadDriverList(); - - break; - - case DLL_THREAD_ATTACH: - break; - case DLL_THREAD_DETACH: - break; - - case DLL_PROCESS_DETACH: - DriverList.clear(); - - if(LogFile && LogFile != stderr) - fclose(LogFile); - LogFile = nullptr; - - break; - } - return TRUE; -} - - -static void AddModule(HMODULE module, const WCHAR *name) +void AddModule(HMODULE module, const std::wstring_view name) { for(auto &drv : DriverList) { @@ -92,26 +43,55 @@ static void AddModule(HMODULE module, const WCHAR *name) } if(drv->Name == name) { - TRACE("Skipping similarly-named module %ls\n", name); + TRACE("Skipping similarly-named module %.*ls\n", al::sizei(name), name.data()); + FreeLibrary(module); + return; + } + } + if(!gAcceptList.empty()) + { + auto iter = std::find_if(gAcceptList.cbegin(), gAcceptList.cend(), + [name](const std::wstring_view accept) + { return al::case_compare(name, accept) == 0; }); + if(iter == gAcceptList.cend()) + { + TRACE("%.*ls not found in ALROUTER_ACCEPT, skipping\n", al::sizei(name), name.data()); + FreeLibrary(module); + return; + } + } + if(!gRejectList.empty()) + { + auto iter = std::find_if(gRejectList.cbegin(), gRejectList.cend(), + [name](const std::wstring_view accept) + { return al::case_compare(name, accept) == 0; }); + if(iter != gRejectList.cend()) + { + TRACE("%.*ls found in ALROUTER_REJECT, skipping\n", al::sizei(name), name.data()); FreeLibrary(module); return; } } - DriverList.emplace_back(std::make_unique(name, module)); - DriverIface &newdrv = *DriverList.back(); + DriverIface &newdrv = *DriverList.emplace_back(std::make_unique(name, module)); /* Load required functions. */ - int err = 0; -#define LOAD_PROC(x) do { \ - newdrv.x = reinterpret_cast(reinterpret_cast( \ - GetProcAddress(module, #x))); \ - if(!newdrv.x) \ - { \ - ERR("Failed to find entry point for %s in %ls\n", #x, name); \ - err = 1; \ - } \ -} while(0) + bool loadok{true}; + auto do_load = [module,name](auto &func, const char *fname) -> bool + { + using func_t = std::remove_reference_t; + auto ptr = GetProcAddress(module, fname); + if(!ptr) + { + ERR("Failed to find entry point for %s in %.*ls\n", fname, al::sizei(name), + name.data()); + return false; + } + + func = reinterpret_cast(reinterpret_cast(ptr)); + return true; + }; +#define LOAD_PROC(x) loadok &= do_load(newdrv.x, #x) LOAD_PROC(alcCreateContext); LOAD_PROC(alcMakeContextCurrent); LOAD_PROC(alcProcessContext); @@ -194,264 +174,269 @@ static void AddModule(HMODULE module, const WCHAR *name) LOAD_PROC(alDopplerVelocity); LOAD_PROC(alSpeedOfSound); LOAD_PROC(alDistanceModel); - if(!err) +#undef LOAD_PROC + if(loadok) { - ALCint alc_ver[2] = { 0, 0 }; + std::array alc_ver{0, 0}; 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 { - WARN("Failed to query ALC version for %ls, assuming 1.0\n", name); + WARN("Failed to query ALC version for %.*ls, assuming 1.0\n", al::sizei(name), + name.data()); newdrv.ALCVer = MAKE_ALC_VER(1, 0); } + auto do_load2 = [module,name](auto &func, const char *fname) -> void + { + using func_t = std::remove_reference_t; + auto ptr = GetProcAddress(module, fname); + if(!ptr) + WARN("Failed to find optional entry point for %s in %.*ls\n", fname, + al::sizei(name), name.data()); + else + func = reinterpret_cast(reinterpret_cast(ptr)); + }; +#define LOAD_PROC(x) do_load2(newdrv.x, #x) + LOAD_PROC(alBufferf); + LOAD_PROC(alBuffer3f); + LOAD_PROC(alBufferfv); + LOAD_PROC(alBufferi); + LOAD_PROC(alBuffer3i); + LOAD_PROC(alBufferiv); + LOAD_PROC(alGetBufferf); + LOAD_PROC(alGetBuffer3f); + LOAD_PROC(alGetBufferfv); + LOAD_PROC(alGetBufferi); + LOAD_PROC(alGetBuffer3i); + LOAD_PROC(alGetBufferiv); #undef LOAD_PROC -#define LOAD_PROC(x) do { \ - newdrv.x = reinterpret_cast(reinterpret_cast( \ - GetProcAddress(module, #x))); \ - if(!newdrv.x) \ - { \ - WARN("Failed to find optional entry point for %s in %ls\n", #x, name); \ - } \ -} while(0) - LOAD_PROC(alBufferf); - LOAD_PROC(alBuffer3f); - LOAD_PROC(alBufferfv); - LOAD_PROC(alBufferi); - LOAD_PROC(alBuffer3i); - LOAD_PROC(alBufferiv); - LOAD_PROC(alGetBufferf); - LOAD_PROC(alGetBuffer3f); - LOAD_PROC(alGetBufferfv); - LOAD_PROC(alGetBufferi); - LOAD_PROC(alGetBuffer3i); - LOAD_PROC(alGetBufferiv); -#undef LOAD_PROC -#define LOAD_PROC(x) do { \ - newdrv.x = reinterpret_cast( \ - newdrv.alcGetProcAddress(nullptr, #x)); \ - if(!newdrv.x) \ - { \ - ERR("Failed to find entry point for %s in %ls\n", #x, name); \ - err = 1; \ - } \ -} while(0) + auto do_load3 = [name,&newdrv](auto &func, const char *fname) -> bool + { + using func_t = std::remove_reference_t; + auto ptr = newdrv.alcGetProcAddress(nullptr, fname); + if(!ptr) + { + ERR("Failed to find entry point for %s in %.*ls\n", fname, al::sizei(name), + name.data()); + return false; + } + + func = reinterpret_cast(ptr); + return true; + }; +#define LOAD_PROC(x) loadok &= do_load3(newdrv.x, #x) if(newdrv.alcIsExtensionPresent(nullptr, "ALC_EXT_thread_local_context")) { LOAD_PROC(alcSetThreadContext); LOAD_PROC(alcGetThreadContext); } +#undef LOAD_PROC } - if(err) + if(!loadok) { DriverList.pop_back(); return; } - TRACE("Loaded module %p, %ls, ALC %d.%d\n", decltype(std::declval()){module}, name, - newdrv.ALCVer>>8, newdrv.ALCVer&255); -#undef LOAD_PROC + TRACE("Loaded module %p, %.*ls, ALC %d.%d\n", decltype(std::declval()){module}, + al::sizei(name), name.data(), newdrv.ALCVer>>8, newdrv.ALCVer&255); } -static void SearchDrivers(WCHAR *path) +void SearchDrivers(const std::wstring_view path) { - WIN32_FIND_DATAW fdata; - - TRACE("Searching for drivers in %ls...\n", path); - std::wstring srchPath = path; + TRACE("Searching for drivers in %.*ls...\n", al::sizei(path), path.data()); + std::wstring srchPath{path}; srchPath += L"\\*oal.dll"; - HANDLE srchHdl = FindFirstFileW(srchPath.c_str(), &fdata); - if(srchHdl != INVALID_HANDLE_VALUE) - { - do { - HMODULE mod; + WIN32_FIND_DATAW fdata{}; + HANDLE srchHdl{FindFirstFileW(srchPath.c_str(), &fdata)}; + if(srchHdl == INVALID_HANDLE_VALUE) return; - srchPath = path; - srchPath += L"\\"; - srchPath += fdata.cFileName; - TRACE("Found %ls\n", srchPath.c_str()); + do { + srchPath = path; + srchPath += L"\\"; + srchPath += std::data(fdata.cFileName); + TRACE("Found %ls\n", srchPath.c_str()); - mod = LoadLibraryW(srchPath.c_str()); - if(!mod) - WARN("Could not load %ls\n", srchPath.c_str()); - else - AddModule(mod, fdata.cFileName); - } while(FindNextFileW(srchHdl, &fdata)); - FindClose(srchHdl); - } + HMODULE mod{LoadLibraryW(srchPath.c_str())}; + if(!mod) + WARN("Could not load %ls\n", srchPath.c_str()); + else + AddModule(mod, std::data(fdata.cFileName)); + } while(FindNextFileW(srchHdl, &fdata)); + FindClose(srchHdl); } -static WCHAR *strrchrW(WCHAR *str, WCHAR ch) +bool GetLoadedModuleDirectory(const WCHAR *name, std::wstring *moddir) { - WCHAR *res = nullptr; - while(str && *str != '\0') - { - if(*str == ch) - res = str; - ++str; - } - return res; -} - -static int GetLoadedModuleDirectory(const WCHAR *name, WCHAR *moddir, DWORD length) -{ - HMODULE module = nullptr; - WCHAR *sep0, *sep1; + HMODULE module{nullptr}; if(name) { module = GetModuleHandleW(name); - if(!module) return 0; + if(!module) return false; } - if(GetModuleFileNameW(module, moddir, length) == 0) - return 0; + moddir->assign(256, '\0'); + DWORD res{GetModuleFileNameW(module, moddir->data(), static_cast(moddir->size()))}; + if(res >= moddir->size()) + { + do { + moddir->append(256, '\0'); + res = GetModuleFileNameW(module, moddir->data(), static_cast(moddir->size())); + } while(res >= moddir->size()); + } + moddir->resize(res); - sep0 = strrchrW(moddir, '/'); - if(sep0) sep1 = strrchrW(sep0+1, '\\'); - else sep1 = strrchrW(moddir, '\\'); + auto sep0 = moddir->rfind('/'); + auto sep1 = moddir->rfind('\\'); + if(sep0 < moddir->size() && sep1 < moddir->size()) + moddir->resize(std::max(sep0, sep1)); + else if(sep0 < moddir->size()) + moddir->resize(sep0); + else if(sep1 < moddir->size()) + moddir->resize(sep1); + else + moddir->resize(0); - if(sep1) *sep1 = '\0'; - else if(sep0) *sep0 = '\0'; - else *moddir = '\0'; - - return 1; + return !moddir->empty(); } -void LoadDriverList(void) +void LoadDriverList() { - WCHAR dll_path[MAX_PATH+1] = L""; - WCHAR cwd_path[MAX_PATH+1] = L""; - WCHAR proc_path[MAX_PATH+1] = L""; - WCHAR sys_path[MAX_PATH+1] = L""; - int len; + if(auto list = al::getenv(L"ALROUTER_ACCEPT")) + { + std::wstring_view namelist{*list}; + while(!namelist.empty()) + { + auto seppos = namelist.find(','); + if(seppos > 0) + gAcceptList.emplace_back(namelist.substr(0, seppos)); + if(seppos < namelist.size()) + namelist.remove_prefix(seppos+1); + else + namelist.remove_prefix(namelist.size()); + } + } + if(auto list = al::getenv(L"ALROUTER_REJECT")) + { + std::wstring_view namelist{*list}; + while(!namelist.empty()) + { + auto seppos = namelist.find(','); + if(seppos > 0) + gRejectList.emplace_back(namelist.substr(0, seppos)); + if(seppos < namelist.size()) + namelist.remove_prefix(seppos+1); + else + namelist.remove_prefix(namelist.size()); + } + } - if(GetLoadedModuleDirectory(L"OpenAL32.dll", dll_path, MAX_PATH)) - TRACE("Got DLL path %ls\n", dll_path); + std::wstring dll_path; + if(GetLoadedModuleDirectory(L"OpenAL32.dll", &dll_path)) + TRACE("Got DLL path %ls\n", dll_path.c_str()); - GetCurrentDirectoryW(MAX_PATH, cwd_path); - len = lstrlenW(cwd_path); - if(len > 0 && (cwd_path[len-1] == '\\' || cwd_path[len-1] == '/')) - cwd_path[len-1] = '\0'; - TRACE("Got current working directory %ls\n", cwd_path); + std::wstring cwd_path; + if(DWORD pathlen{GetCurrentDirectoryW(0, nullptr)}) + { + do { + cwd_path.resize(pathlen); + pathlen = GetCurrentDirectoryW(pathlen, cwd_path.data()); + } while(pathlen >= cwd_path.size()); + cwd_path.resize(pathlen); + } + if(!cwd_path.empty() && (cwd_path.back() == '\\' || cwd_path.back() == '/')) + cwd_path.pop_back(); + if(!cwd_path.empty()) + TRACE("Got current working directory %ls\n", cwd_path.c_str()); - if(GetLoadedModuleDirectory(nullptr, proc_path, MAX_PATH)) - TRACE("Got proc path %ls\n", proc_path); + std::wstring proc_path; + if(GetLoadedModuleDirectory(nullptr, &proc_path)) + TRACE("Got proc path %ls\n", proc_path.c_str()); - GetSystemDirectoryW(sys_path, MAX_PATH); - len = lstrlenW(sys_path); - if(len > 0 && (sys_path[len-1] == '\\' || sys_path[len-1] == '/')) - sys_path[len-1] = '\0'; - TRACE("Got system path %ls\n", sys_path); + std::wstring sys_path; + if(UINT pathlen{GetSystemDirectoryW(nullptr, 0)}) + { + do { + sys_path.resize(pathlen); + pathlen = GetSystemDirectoryW(sys_path.data(), pathlen); + } while(pathlen >= sys_path.size()); + sys_path.resize(pathlen); + } + if(!sys_path.empty() && (sys_path.back() == '\\' || sys_path.back() == '/')) + sys_path.pop_back(); + if(!sys_path.empty()) + TRACE("Got system path %ls\n", sys_path.c_str()); /* Don't search the DLL's path if it is the same as the current working * directory, app's path, or system path (don't want to do duplicate * searches, or increase the priority of the app or system path). */ - if(dll_path[0] && - (!cwd_path[0] || wcscmp(dll_path, cwd_path) != 0) && - (!proc_path[0] || wcscmp(dll_path, proc_path) != 0) && - (!sys_path[0] || wcscmp(dll_path, sys_path) != 0)) + if(!dll_path.empty() && + (cwd_path.empty() || dll_path != cwd_path) && + (proc_path.empty() || dll_path != proc_path) && + (sys_path.empty() || dll_path != sys_path)) SearchDrivers(dll_path); - if(cwd_path[0] && - (!proc_path[0] || wcscmp(cwd_path, proc_path) != 0) && - (!sys_path[0] || wcscmp(cwd_path, sys_path) != 0)) + if(!cwd_path.empty() && + (proc_path.empty() || cwd_path != proc_path) && + (sys_path.empty() || cwd_path != sys_path)) SearchDrivers(cwd_path); - if(proc_path[0] && (!sys_path[0] || wcscmp(proc_path, sys_path) != 0)) + if(!proc_path.empty() && (sys_path.empty() || proc_path != sys_path)) SearchDrivers(proc_path); - if(sys_path[0]) + if(!sys_path.empty()) SearchDrivers(sys_path); } +} // namespace -PtrIntMap::~PtrIntMap() +BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*) { - std::lock_guard maplock{mLock}; - al_free(mKeys); - mKeys = nullptr; - mValues = nullptr; - mSize = 0; - mCapacity = 0; -} - -ALenum PtrIntMap::insert(void *key, int value) -{ - std::lock_guard maplock{mLock}; - auto iter = std::lower_bound(mKeys, mKeys+mSize, key); - auto pos = static_cast(std::distance(mKeys, iter)); - - if(pos == mSize || mKeys[pos] != key) + switch(reason) { - if(mSize == mCapacity) + case DLL_PROCESS_ATTACH: + if(auto logfname = al::getenv("ALROUTER_LOGFILE")) { - void **newkeys{nullptr}; - ALsizei newcap{mCapacity ? (mCapacity<<1) : 4}; - if(newcap > mCapacity) - newkeys = static_cast( - al_calloc(16, (sizeof(mKeys[0])+sizeof(mValues[0]))*newcap) - ); - if(!newkeys) - return AL_OUT_OF_MEMORY; - auto newvalues = reinterpret_cast(&newkeys[newcap]); - - if(mKeys) - { - std::copy_n(mKeys, mSize, newkeys); - std::copy_n(mValues, mSize, newvalues); - } - al_free(mKeys); - mKeys = newkeys; - mValues = newvalues; - mCapacity = newcap; + gsl::owner f{fopen(logfname->c_str(), "w")}; + if(f == nullptr) + ERR("Could not open log file: %s\n", logfname->c_str()); + else + LogFile = f; } - - if(pos < mSize) + if(auto loglev = al::getenv("ALROUTER_LOGLEVEL")) { - std::copy_backward(mKeys+pos, mKeys+mSize, mKeys+mSize+1); - std::copy_backward(mValues+pos, mValues+mSize, mValues+mSize+1); + 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 < al::to_underlying(eLogLevel::None) + || l > al::to_underlying(eLogLevel::Trace)) + ERR("Log level out of range: %s\n", loglev->c_str()); + else + LogLevel = static_cast(l); } - mSize++; + TRACE("Initializing router v0.1-%s %s\n", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); + LoadDriverList(); + + break; + + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: + DriverList.clear(); + + if(LogFile) + fclose(LogFile); + LogFile = nullptr; + + break; } - mKeys[pos] = key; - mValues[pos] = value; - - return AL_NO_ERROR; -} - -int PtrIntMap::removeByKey(void *key) -{ - int ret = -1; - - std::lock_guard maplock{mLock}; - auto iter = std::lower_bound(mKeys, mKeys+mSize, key); - auto pos = static_cast(std::distance(mKeys, iter)); - if(pos < mSize && mKeys[pos] == key) - { - ret = mValues[pos]; - if(pos+1 < mSize) - { - std::copy(mKeys+pos+1, mKeys+mSize, mKeys+pos); - std::copy(mValues+pos+1, mValues+mSize, mValues+pos); - } - mSize--; - } - - return ret; -} - -int PtrIntMap::lookupByKey(void *key) -{ - int ret = -1; - - std::lock_guard maplock{mLock}; - auto iter = std::lower_bound(mKeys, mKeys+mSize, key); - auto pos = static_cast(std::distance(mKeys, iter)); - if(pos < mSize && mKeys[pos] == key) - ret = mValues[pos]; - - return ret; + return TRUE; } diff --git a/Engine/lib/openal-soft/router/router.h b/Engine/lib/openal-soft/router/router.h index 2a126d42c..bd5c907a0 100644 --- a/Engine/lib/openal-soft/router/router.h +++ b/Engine/lib/openal-soft/router/router.h @@ -5,9 +5,8 @@ #include #include -#include - #include +#include #include #include #include @@ -18,15 +17,12 @@ #include "AL/al.h" #include "AL/alext.h" +#include "almalloc.h" + #define MAKE_ALC_VER(major, minor) (((major)<<8) | (minor)) struct DriverIface { - std::wstring Name; - HMODULE Module{nullptr}; - int ALCVer{0}; - std::once_flag InitOnceCtx{}; - LPALCCREATECONTEXT alcCreateContext{nullptr}; LPALCMAKECONTEXTCURRENT alcMakeContextCurrent{nullptr}; LPALCPROCESSCONTEXT alcProcessContext{nullptr}; @@ -160,83 +156,62 @@ struct DriverIface { LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti{nullptr}; LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv{nullptr}; + std::wstring Name; + HMODULE Module{nullptr}; + int ALCVer{0}; + std::once_flag InitOnceCtx{}; + template - DriverIface(T&& name, HMODULE mod) - : Name(std::forward(name)), Module(mod) - { } - ~DriverIface() - { - if(Module) - FreeLibrary(Module); - Module = nullptr; - } + DriverIface(T&& name, HMODULE mod) : Name(std::forward(name)), Module(mod) { } + ~DriverIface() { if(Module) FreeLibrary(Module); } + + DriverIface(const DriverIface&) = delete; + DriverIface(DriverIface&&) = delete; + DriverIface& operator=(const DriverIface&) = delete; + DriverIface& operator=(DriverIface&&) = delete; }; using DriverIfacePtr = std::unique_ptr; -extern std::vector DriverList; +inline std::vector DriverList; -extern thread_local DriverIface *ThreadCtxDriver; -extern std::atomic CurrentCtxDriver; +inline thread_local DriverIface *ThreadCtxDriver{}; +inline std::atomic CurrentCtxDriver{}; -/* HACK: MinGW generates bad code when accessing an extern thread_local object. - * Add a wrapper function for it that only accesses it where it's defined. - */ -#ifdef __MINGW32__ -DriverIface *GetThreadDriver() noexcept; -void SetThreadDriver(DriverIface *driver) noexcept; -#else inline DriverIface *GetThreadDriver() noexcept { return ThreadCtxDriver; } inline void SetThreadDriver(DriverIface *driver) noexcept { ThreadCtxDriver = driver; } -#endif -class PtrIntMap { - void **mKeys{nullptr}; - /* Shares memory with keys. */ - int *mValues{nullptr}; - - ALsizei mSize{0}; - ALsizei mCapacity{0}; - std::mutex mLock; - -public: - PtrIntMap() = default; - ~PtrIntMap(); - - ALenum insert(void *key, int value); - int removeByKey(void *key); - int lookupByKey(void *key); +enum class eLogLevel { + None = 0, + Error = 1, + Warn = 2, + Trace = 3, }; - - -enum LogLevel { - LogLevel_None = 0, - LogLevel_Error = 1, - LogLevel_Warn = 2, - LogLevel_Trace = 3, -}; -extern enum LogLevel LogLevel; -extern FILE *LogFile; +extern eLogLevel LogLevel; +extern gsl::owner LogFile; #define TRACE(...) do { \ - if(LogLevel >= LogLevel_Trace) \ + if(LogLevel >= eLogLevel::Trace) \ { \ - fprintf(LogFile, "AL Router (II): " __VA_ARGS__); \ - fflush(LogFile); \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fprintf(file, "AL Router (II): " __VA_ARGS__); \ + fflush(file); \ } \ } while(0) #define WARN(...) do { \ - if(LogLevel >= LogLevel_Warn) \ + if(LogLevel >= eLogLevel::Warn) \ { \ - fprintf(LogFile, "AL Router (WW): " __VA_ARGS__); \ - fflush(LogFile); \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fprintf(file, "AL Router (WW): " __VA_ARGS__); \ + fflush(file); \ } \ } while(0) #define ERR(...) do { \ - if(LogLevel >= LogLevel_Error) \ + if(LogLevel >= eLogLevel::Error) \ { \ - fprintf(LogFile, "AL Router (EE): " __VA_ARGS__); \ - fflush(LogFile); \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fprintf(file, "AL Router (EE): " __VA_ARGS__); \ + fflush(file); \ } \ } while(0) diff --git a/Engine/lib/openal-soft/tests/CMakeLists.txt b/Engine/lib/openal-soft/tests/CMakeLists.txt new file mode 100644 index 000000000..5c5a823cd --- /dev/null +++ b/Engine/lib/openal-soft/tests/CMakeLists.txt @@ -0,0 +1,24 @@ +add_executable(OpenAL_Tests) + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG main +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +target_link_libraries(OpenAL_Tests PRIVATE + OpenAL + GTest::gtest_main +) + +target_sources(OpenAL_Tests PRIVATE +example.t.cpp +) + +# This needs to come last +include(GoogleTest) +gtest_discover_tests(OpenAL_Tests) diff --git a/Engine/lib/openal-soft/tests/example.t.cpp b/Engine/lib/openal-soft/tests/example.t.cpp new file mode 100644 index 000000000..3d21900a7 --- /dev/null +++ b/Engine/lib/openal-soft/tests/example.t.cpp @@ -0,0 +1,16 @@ +#include +#include + +class ExampleTest : public ::testing::Test { +}; + + +TEST_F(ExampleTest, Basic) +{ + // just making sure we compile + ALuint source, buffer; + ALfloat offset; + ALenum state; +} + + diff --git a/Engine/lib/openal-soft/utils/CIAIR.def b/Engine/lib/openal-soft/utils/CIAIR.def index 5fabdb3f7..79910417d 100644 --- a/Engine/lib/openal-soft/utils/CIAIR.def +++ b/Engine/lib/openal-soft/utils/CIAIR.def @@ -37,3922 +37,3922 @@ azimuths = 1, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72 # The extension of the source data may be misleading, they're ASCII text # lists of floating point values (one per line). Left and right ear HRIRs # (from the respective files) are used to create a stereo HRTF. -[ 9, 0 ] = ascii (fp) : "./hrtfs/elev-45/L-45e000a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e000a.dat right -[ 9, 1 ] = ascii (fp) : "./hrtfs/elev-45/L-45e355a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e355a.dat right -[ 9, 2 ] = ascii (fp) : "./hrtfs/elev-45/L-45e350a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e350a.dat right -[ 9, 3 ] = ascii (fp) : "./hrtfs/elev-45/L-45e345a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e345a.dat right -[ 9, 4 ] = ascii (fp) : "./hrtfs/elev-45/L-45e340a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e340a.dat right -[ 9, 5 ] = ascii (fp) : "./hrtfs/elev-45/L-45e335a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e335a.dat right -[ 9, 6 ] = ascii (fp) : "./hrtfs/elev-45/L-45e330a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e330a.dat right -[ 9, 7 ] = ascii (fp) : "./hrtfs/elev-45/L-45e325a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e325a.dat right -[ 9, 8 ] = ascii (fp) : "./hrtfs/elev-45/L-45e320a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e320a.dat right -[ 9, 9 ] = ascii (fp) : "./hrtfs/elev-45/L-45e315a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e315a.dat right -[ 9, 10 ] = ascii (fp) : "./hrtfs/elev-45/L-45e310a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e310a.dat right -[ 9, 11 ] = ascii (fp) : "./hrtfs/elev-45/L-45e305a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e305a.dat right -[ 9, 12 ] = ascii (fp) : "./hrtfs/elev-45/L-45e300a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e300a.dat right -[ 9, 13 ] = ascii (fp) : "./hrtfs/elev-45/L-45e295a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e295a.dat right -[ 9, 14 ] = ascii (fp) : "./hrtfs/elev-45/L-45e290a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e290a.dat right -[ 9, 15 ] = ascii (fp) : "./hrtfs/elev-45/L-45e285a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e285a.dat right -[ 9, 16 ] = ascii (fp) : "./hrtfs/elev-45/L-45e280a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e280a.dat right -[ 9, 17 ] = ascii (fp) : "./hrtfs/elev-45/L-45e275a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e275a.dat right -[ 9, 18 ] = ascii (fp) : "./hrtfs/elev-45/L-45e270a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e270a.dat right -[ 9, 19 ] = ascii (fp) : "./hrtfs/elev-45/L-45e265a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e265a.dat right -[ 9, 20 ] = ascii (fp) : "./hrtfs/elev-45/L-45e260a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e260a.dat right -[ 9, 21 ] = ascii (fp) : "./hrtfs/elev-45/L-45e255a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e255a.dat right -[ 9, 22 ] = ascii (fp) : "./hrtfs/elev-45/L-45e250a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e250a.dat right -[ 9, 23 ] = ascii (fp) : "./hrtfs/elev-45/L-45e245a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e245a.dat right -[ 9, 24 ] = ascii (fp) : "./hrtfs/elev-45/L-45e240a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e240a.dat right -[ 9, 25 ] = ascii (fp) : "./hrtfs/elev-45/L-45e235a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e235a.dat right -[ 9, 26 ] = ascii (fp) : "./hrtfs/elev-45/L-45e230a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e230a.dat right -[ 9, 27 ] = ascii (fp) : "./hrtfs/elev-45/L-45e225a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e225a.dat right -[ 9, 28 ] = ascii (fp) : "./hrtfs/elev-45/L-45e220a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e220a.dat right -[ 9, 29 ] = ascii (fp) : "./hrtfs/elev-45/L-45e215a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e215a.dat right -[ 9, 30 ] = ascii (fp) : "./hrtfs/elev-45/L-45e210a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e210a.dat right -[ 9, 31 ] = ascii (fp) : "./hrtfs/elev-45/L-45e205a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e205a.dat right -[ 9, 32 ] = ascii (fp) : "./hrtfs/elev-45/L-45e200a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e200a.dat right -[ 9, 33 ] = ascii (fp) : "./hrtfs/elev-45/L-45e195a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e195a.dat right -[ 9, 34 ] = ascii (fp) : "./hrtfs/elev-45/L-45e190a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e190a.dat right -[ 9, 35 ] = ascii (fp) : "./hrtfs/elev-45/L-45e185a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e185a.dat right -[ 9, 36 ] = ascii (fp) : "./hrtfs/elev-45/L-45e180a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e180a.dat right -[ 9, 37 ] = ascii (fp) : "./hrtfs/elev-45/L-45e175a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e175a.dat right -[ 9, 38 ] = ascii (fp) : "./hrtfs/elev-45/L-45e170a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e170a.dat right -[ 9, 39 ] = ascii (fp) : "./hrtfs/elev-45/L-45e165a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e165a.dat right -[ 9, 40 ] = ascii (fp) : "./hrtfs/elev-45/L-45e160a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e160a.dat right -[ 9, 41 ] = ascii (fp) : "./hrtfs/elev-45/L-45e155a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e155a.dat right -[ 9, 42 ] = ascii (fp) : "./hrtfs/elev-45/L-45e150a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e150a.dat right -[ 9, 43 ] = ascii (fp) : "./hrtfs/elev-45/L-45e145a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e145a.dat right -[ 9, 44 ] = ascii (fp) : "./hrtfs/elev-45/L-45e140a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e140a.dat right -[ 9, 45 ] = ascii (fp) : "./hrtfs/elev-45/L-45e135a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e135a.dat right -[ 9, 46 ] = ascii (fp) : "./hrtfs/elev-45/L-45e130a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e130a.dat right -[ 9, 47 ] = ascii (fp) : "./hrtfs/elev-45/L-45e125a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e125a.dat right -[ 9, 48 ] = ascii (fp) : "./hrtfs/elev-45/L-45e120a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e120a.dat right -[ 9, 49 ] = ascii (fp) : "./hrtfs/elev-45/L-45e115a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e115a.dat right -[ 9, 50 ] = ascii (fp) : "./hrtfs/elev-45/L-45e110a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e110a.dat right -[ 9, 51 ] = ascii (fp) : "./hrtfs/elev-45/L-45e105a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e105a.dat right -[ 9, 52 ] = ascii (fp) : "./hrtfs/elev-45/L-45e100a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e100a.dat right -[ 9, 53 ] = ascii (fp) : "./hrtfs/elev-45/L-45e095a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e095a.dat right -[ 9, 54 ] = ascii (fp) : "./hrtfs/elev-45/L-45e090a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e090a.dat right -[ 9, 55 ] = ascii (fp) : "./hrtfs/elev-45/L-45e085a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e085a.dat right -[ 9, 56 ] = ascii (fp) : "./hrtfs/elev-45/L-45e080a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e080a.dat right -[ 9, 57 ] = ascii (fp) : "./hrtfs/elev-45/L-45e075a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e075a.dat right -[ 9, 58 ] = ascii (fp) : "./hrtfs/elev-45/L-45e070a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e070a.dat right -[ 9, 59 ] = ascii (fp) : "./hrtfs/elev-45/L-45e065a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e065a.dat right -[ 9, 60 ] = ascii (fp) : "./hrtfs/elev-45/L-45e060a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e060a.dat right -[ 9, 61 ] = ascii (fp) : "./hrtfs/elev-45/L-45e055a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e055a.dat right -[ 9, 62 ] = ascii (fp) : "./hrtfs/elev-45/L-45e050a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e050a.dat right -[ 9, 63 ] = ascii (fp) : "./hrtfs/elev-45/L-45e045a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e045a.dat right -[ 9, 64 ] = ascii (fp) : "./hrtfs/elev-45/L-45e040a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e040a.dat right -[ 9, 65 ] = ascii (fp) : "./hrtfs/elev-45/L-45e035a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e035a.dat right -[ 9, 66 ] = ascii (fp) : "./hrtfs/elev-45/L-45e030a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e030a.dat right -[ 9, 67 ] = ascii (fp) : "./hrtfs/elev-45/L-45e025a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e025a.dat right -[ 9, 68 ] = ascii (fp) : "./hrtfs/elev-45/L-45e020a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e020a.dat right -[ 9, 69 ] = ascii (fp) : "./hrtfs/elev-45/L-45e015a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e015a.dat right -[ 9, 70 ] = ascii (fp) : "./hrtfs/elev-45/L-45e010a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e010a.dat right -[ 9, 71 ] = ascii (fp) : "./hrtfs/elev-45/L-45e005a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e005a.dat right +[ 9, 0 ] = ascii (fp) : "./hrtfs/elev-45/L-45e000a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e000a.dat" right +[ 9, 1 ] = ascii (fp) : "./hrtfs/elev-45/L-45e355a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e355a.dat" right +[ 9, 2 ] = ascii (fp) : "./hrtfs/elev-45/L-45e350a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e350a.dat" right +[ 9, 3 ] = ascii (fp) : "./hrtfs/elev-45/L-45e345a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e345a.dat" right +[ 9, 4 ] = ascii (fp) : "./hrtfs/elev-45/L-45e340a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e340a.dat" right +[ 9, 5 ] = ascii (fp) : "./hrtfs/elev-45/L-45e335a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e335a.dat" right +[ 9, 6 ] = ascii (fp) : "./hrtfs/elev-45/L-45e330a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e330a.dat" right +[ 9, 7 ] = ascii (fp) : "./hrtfs/elev-45/L-45e325a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e325a.dat" right +[ 9, 8 ] = ascii (fp) : "./hrtfs/elev-45/L-45e320a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e320a.dat" right +[ 9, 9 ] = ascii (fp) : "./hrtfs/elev-45/L-45e315a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e315a.dat" right +[ 9, 10 ] = ascii (fp) : "./hrtfs/elev-45/L-45e310a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e310a.dat" right +[ 9, 11 ] = ascii (fp) : "./hrtfs/elev-45/L-45e305a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e305a.dat" right +[ 9, 12 ] = ascii (fp) : "./hrtfs/elev-45/L-45e300a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e300a.dat" right +[ 9, 13 ] = ascii (fp) : "./hrtfs/elev-45/L-45e295a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e295a.dat" right +[ 9, 14 ] = ascii (fp) : "./hrtfs/elev-45/L-45e290a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e290a.dat" right +[ 9, 15 ] = ascii (fp) : "./hrtfs/elev-45/L-45e285a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e285a.dat" right +[ 9, 16 ] = ascii (fp) : "./hrtfs/elev-45/L-45e280a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e280a.dat" right +[ 9, 17 ] = ascii (fp) : "./hrtfs/elev-45/L-45e275a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e275a.dat" right +[ 9, 18 ] = ascii (fp) : "./hrtfs/elev-45/L-45e270a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e270a.dat" right +[ 9, 19 ] = ascii (fp) : "./hrtfs/elev-45/L-45e265a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e265a.dat" right +[ 9, 20 ] = ascii (fp) : "./hrtfs/elev-45/L-45e260a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e260a.dat" right +[ 9, 21 ] = ascii (fp) : "./hrtfs/elev-45/L-45e255a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e255a.dat" right +[ 9, 22 ] = ascii (fp) : "./hrtfs/elev-45/L-45e250a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e250a.dat" right +[ 9, 23 ] = ascii (fp) : "./hrtfs/elev-45/L-45e245a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e245a.dat" right +[ 9, 24 ] = ascii (fp) : "./hrtfs/elev-45/L-45e240a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e240a.dat" right +[ 9, 25 ] = ascii (fp) : "./hrtfs/elev-45/L-45e235a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e235a.dat" right +[ 9, 26 ] = ascii (fp) : "./hrtfs/elev-45/L-45e230a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e230a.dat" right +[ 9, 27 ] = ascii (fp) : "./hrtfs/elev-45/L-45e225a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e225a.dat" right +[ 9, 28 ] = ascii (fp) : "./hrtfs/elev-45/L-45e220a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e220a.dat" right +[ 9, 29 ] = ascii (fp) : "./hrtfs/elev-45/L-45e215a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e215a.dat" right +[ 9, 30 ] = ascii (fp) : "./hrtfs/elev-45/L-45e210a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e210a.dat" right +[ 9, 31 ] = ascii (fp) : "./hrtfs/elev-45/L-45e205a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e205a.dat" right +[ 9, 32 ] = ascii (fp) : "./hrtfs/elev-45/L-45e200a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e200a.dat" right +[ 9, 33 ] = ascii (fp) : "./hrtfs/elev-45/L-45e195a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e195a.dat" right +[ 9, 34 ] = ascii (fp) : "./hrtfs/elev-45/L-45e190a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e190a.dat" right +[ 9, 35 ] = ascii (fp) : "./hrtfs/elev-45/L-45e185a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e185a.dat" right +[ 9, 36 ] = ascii (fp) : "./hrtfs/elev-45/L-45e180a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e180a.dat" right +[ 9, 37 ] = ascii (fp) : "./hrtfs/elev-45/L-45e175a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e175a.dat" right +[ 9, 38 ] = ascii (fp) : "./hrtfs/elev-45/L-45e170a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e170a.dat" right +[ 9, 39 ] = ascii (fp) : "./hrtfs/elev-45/L-45e165a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e165a.dat" right +[ 9, 40 ] = ascii (fp) : "./hrtfs/elev-45/L-45e160a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e160a.dat" right +[ 9, 41 ] = ascii (fp) : "./hrtfs/elev-45/L-45e155a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e155a.dat" right +[ 9, 42 ] = ascii (fp) : "./hrtfs/elev-45/L-45e150a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e150a.dat" right +[ 9, 43 ] = ascii (fp) : "./hrtfs/elev-45/L-45e145a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e145a.dat" right +[ 9, 44 ] = ascii (fp) : "./hrtfs/elev-45/L-45e140a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e140a.dat" right +[ 9, 45 ] = ascii (fp) : "./hrtfs/elev-45/L-45e135a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e135a.dat" right +[ 9, 46 ] = ascii (fp) : "./hrtfs/elev-45/L-45e130a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e130a.dat" right +[ 9, 47 ] = ascii (fp) : "./hrtfs/elev-45/L-45e125a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e125a.dat" right +[ 9, 48 ] = ascii (fp) : "./hrtfs/elev-45/L-45e120a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e120a.dat" right +[ 9, 49 ] = ascii (fp) : "./hrtfs/elev-45/L-45e115a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e115a.dat" right +[ 9, 50 ] = ascii (fp) : "./hrtfs/elev-45/L-45e110a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e110a.dat" right +[ 9, 51 ] = ascii (fp) : "./hrtfs/elev-45/L-45e105a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e105a.dat" right +[ 9, 52 ] = ascii (fp) : "./hrtfs/elev-45/L-45e100a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e100a.dat" right +[ 9, 53 ] = ascii (fp) : "./hrtfs/elev-45/L-45e095a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e095a.dat" right +[ 9, 54 ] = ascii (fp) : "./hrtfs/elev-45/L-45e090a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e090a.dat" right +[ 9, 55 ] = ascii (fp) : "./hrtfs/elev-45/L-45e085a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e085a.dat" right +[ 9, 56 ] = ascii (fp) : "./hrtfs/elev-45/L-45e080a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e080a.dat" right +[ 9, 57 ] = ascii (fp) : "./hrtfs/elev-45/L-45e075a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e075a.dat" right +[ 9, 58 ] = ascii (fp) : "./hrtfs/elev-45/L-45e070a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e070a.dat" right +[ 9, 59 ] = ascii (fp) : "./hrtfs/elev-45/L-45e065a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e065a.dat" right +[ 9, 60 ] = ascii (fp) : "./hrtfs/elev-45/L-45e060a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e060a.dat" right +[ 9, 61 ] = ascii (fp) : "./hrtfs/elev-45/L-45e055a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e055a.dat" right +[ 9, 62 ] = ascii (fp) : "./hrtfs/elev-45/L-45e050a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e050a.dat" right +[ 9, 63 ] = ascii (fp) : "./hrtfs/elev-45/L-45e045a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e045a.dat" right +[ 9, 64 ] = ascii (fp) : "./hrtfs/elev-45/L-45e040a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e040a.dat" right +[ 9, 65 ] = ascii (fp) : "./hrtfs/elev-45/L-45e035a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e035a.dat" right +[ 9, 66 ] = ascii (fp) : "./hrtfs/elev-45/L-45e030a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e030a.dat" right +[ 9, 67 ] = ascii (fp) : "./hrtfs/elev-45/L-45e025a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e025a.dat" right +[ 9, 68 ] = ascii (fp) : "./hrtfs/elev-45/L-45e020a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e020a.dat" right +[ 9, 69 ] = ascii (fp) : "./hrtfs/elev-45/L-45e015a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e015a.dat" right +[ 9, 70 ] = ascii (fp) : "./hrtfs/elev-45/L-45e010a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e010a.dat" right +[ 9, 71 ] = ascii (fp) : "./hrtfs/elev-45/L-45e005a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e005a.dat" right -[ 10, 0 ] = ascii (fp) : "./hrtfs/elev-40/L-40e000a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e000a.dat right -[ 10, 1 ] = ascii (fp) : "./hrtfs/elev-40/L-40e355a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e355a.dat right -[ 10, 2 ] = ascii (fp) : "./hrtfs/elev-40/L-40e350a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e350a.dat right -[ 10, 3 ] = ascii (fp) : "./hrtfs/elev-40/L-40e345a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e345a.dat right -[ 10, 4 ] = ascii (fp) : "./hrtfs/elev-40/L-40e340a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e340a.dat right -[ 10, 5 ] = ascii (fp) : "./hrtfs/elev-40/L-40e335a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e335a.dat right -[ 10, 6 ] = ascii (fp) : "./hrtfs/elev-40/L-40e330a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e330a.dat right -[ 10, 7 ] = ascii (fp) : "./hrtfs/elev-40/L-40e325a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e325a.dat right -[ 10, 8 ] = ascii (fp) : "./hrtfs/elev-40/L-40e320a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e320a.dat right -[ 10, 9 ] = ascii (fp) : "./hrtfs/elev-40/L-40e315a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e315a.dat right -[ 10, 10 ] = ascii (fp) : "./hrtfs/elev-40/L-40e310a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e310a.dat right -[ 10, 11 ] = ascii (fp) : "./hrtfs/elev-40/L-40e305a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e305a.dat right -[ 10, 12 ] = ascii (fp) : "./hrtfs/elev-40/L-40e300a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e300a.dat right -[ 10, 13 ] = ascii (fp) : "./hrtfs/elev-40/L-40e295a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e295a.dat right -[ 10, 14 ] = ascii (fp) : "./hrtfs/elev-40/L-40e290a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e290a.dat right -[ 10, 15 ] = ascii (fp) : "./hrtfs/elev-40/L-40e285a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e285a.dat right -[ 10, 16 ] = ascii (fp) : "./hrtfs/elev-40/L-40e280a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e280a.dat right -[ 10, 17 ] = ascii (fp) : "./hrtfs/elev-40/L-40e275a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e275a.dat right -[ 10, 18 ] = ascii (fp) : "./hrtfs/elev-40/L-40e270a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e270a.dat right -[ 10, 19 ] = ascii (fp) : "./hrtfs/elev-40/L-40e265a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e265a.dat right -[ 10, 20 ] = ascii (fp) : "./hrtfs/elev-40/L-40e260a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e260a.dat right -[ 10, 21 ] = ascii (fp) : "./hrtfs/elev-40/L-40e255a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e255a.dat right -[ 10, 22 ] = ascii (fp) : "./hrtfs/elev-40/L-40e250a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e250a.dat right -[ 10, 23 ] = ascii (fp) : "./hrtfs/elev-40/L-40e245a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e245a.dat right -[ 10, 24 ] = ascii (fp) : "./hrtfs/elev-40/L-40e240a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e240a.dat right -[ 10, 25 ] = ascii (fp) : "./hrtfs/elev-40/L-40e235a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e235a.dat right -[ 10, 26 ] = ascii (fp) : "./hrtfs/elev-40/L-40e230a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e230a.dat right -[ 10, 27 ] = ascii (fp) : "./hrtfs/elev-40/L-40e225a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e225a.dat right -[ 10, 28 ] = ascii (fp) : "./hrtfs/elev-40/L-40e220a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e220a.dat right -[ 10, 29 ] = ascii (fp) : "./hrtfs/elev-40/L-40e215a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e215a.dat right -[ 10, 30 ] = ascii (fp) : "./hrtfs/elev-40/L-40e210a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e210a.dat right -[ 10, 31 ] = ascii (fp) : "./hrtfs/elev-40/L-40e205a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e205a.dat right -[ 10, 32 ] = ascii (fp) : "./hrtfs/elev-40/L-40e200a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e200a.dat right -[ 10, 33 ] = ascii (fp) : "./hrtfs/elev-40/L-40e195a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e195a.dat right -[ 10, 34 ] = ascii (fp) : "./hrtfs/elev-40/L-40e190a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e190a.dat right -[ 10, 35 ] = ascii (fp) : "./hrtfs/elev-40/L-40e185a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e185a.dat right -[ 10, 36 ] = ascii (fp) : "./hrtfs/elev-40/L-40e180a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e180a.dat right -[ 10, 37 ] = ascii (fp) : "./hrtfs/elev-40/L-40e175a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e175a.dat right -[ 10, 38 ] = ascii (fp) : "./hrtfs/elev-40/L-40e170a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e170a.dat right -[ 10, 39 ] = ascii (fp) : "./hrtfs/elev-40/L-40e165a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e165a.dat right -[ 10, 40 ] = ascii (fp) : "./hrtfs/elev-40/L-40e160a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e160a.dat right -[ 10, 41 ] = ascii (fp) : "./hrtfs/elev-40/L-40e155a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e155a.dat right -[ 10, 42 ] = ascii (fp) : "./hrtfs/elev-40/L-40e150a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e150a.dat right -[ 10, 43 ] = ascii (fp) : "./hrtfs/elev-40/L-40e145a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e145a.dat right -[ 10, 44 ] = ascii (fp) : "./hrtfs/elev-40/L-40e140a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e140a.dat right -[ 10, 45 ] = ascii (fp) : "./hrtfs/elev-40/L-40e135a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e135a.dat right -[ 10, 46 ] = ascii (fp) : "./hrtfs/elev-40/L-40e130a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e130a.dat right -[ 10, 47 ] = ascii (fp) : "./hrtfs/elev-40/L-40e125a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e125a.dat right -[ 10, 48 ] = ascii (fp) : "./hrtfs/elev-40/L-40e120a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e120a.dat right -[ 10, 49 ] = ascii (fp) : "./hrtfs/elev-40/L-40e115a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e115a.dat right -[ 10, 50 ] = ascii (fp) : "./hrtfs/elev-40/L-40e110a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e110a.dat right -[ 10, 51 ] = ascii (fp) : "./hrtfs/elev-40/L-40e105a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e105a.dat right -[ 10, 52 ] = ascii (fp) : "./hrtfs/elev-40/L-40e100a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e100a.dat right -[ 10, 53 ] = ascii (fp) : "./hrtfs/elev-40/L-40e095a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e095a.dat right -[ 10, 54 ] = ascii (fp) : "./hrtfs/elev-40/L-40e090a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e090a.dat right -[ 10, 55 ] = ascii (fp) : "./hrtfs/elev-40/L-40e085a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e085a.dat right -[ 10, 56 ] = ascii (fp) : "./hrtfs/elev-40/L-40e080a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e080a.dat right -[ 10, 57 ] = ascii (fp) : "./hrtfs/elev-40/L-40e075a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e075a.dat right -[ 10, 58 ] = ascii (fp) : "./hrtfs/elev-40/L-40e070a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e070a.dat right -[ 10, 59 ] = ascii (fp) : "./hrtfs/elev-40/L-40e065a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e065a.dat right -[ 10, 60 ] = ascii (fp) : "./hrtfs/elev-40/L-40e060a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e060a.dat right -[ 10, 61 ] = ascii (fp) : "./hrtfs/elev-40/L-40e055a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e055a.dat right -[ 10, 62 ] = ascii (fp) : "./hrtfs/elev-40/L-40e050a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e050a.dat right -[ 10, 63 ] = ascii (fp) : "./hrtfs/elev-40/L-40e045a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e045a.dat right -[ 10, 64 ] = ascii (fp) : "./hrtfs/elev-40/L-40e040a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e040a.dat right -[ 10, 65 ] = ascii (fp) : "./hrtfs/elev-40/L-40e035a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e035a.dat right -[ 10, 66 ] = ascii (fp) : "./hrtfs/elev-40/L-40e030a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e030a.dat right -[ 10, 67 ] = ascii (fp) : "./hrtfs/elev-40/L-40e025a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e025a.dat right -[ 10, 68 ] = ascii (fp) : "./hrtfs/elev-40/L-40e020a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e020a.dat right -[ 10, 69 ] = ascii (fp) : "./hrtfs/elev-40/L-40e015a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e015a.dat right -[ 10, 70 ] = ascii (fp) : "./hrtfs/elev-40/L-40e010a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e010a.dat right -[ 10, 71 ] = ascii (fp) : "./hrtfs/elev-40/L-40e005a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e005a.dat right +[ 10, 0 ] = ascii (fp) : "./hrtfs/elev-40/L-40e000a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e000a.dat" right +[ 10, 1 ] = ascii (fp) : "./hrtfs/elev-40/L-40e355a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e355a.dat" right +[ 10, 2 ] = ascii (fp) : "./hrtfs/elev-40/L-40e350a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e350a.dat" right +[ 10, 3 ] = ascii (fp) : "./hrtfs/elev-40/L-40e345a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e345a.dat" right +[ 10, 4 ] = ascii (fp) : "./hrtfs/elev-40/L-40e340a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e340a.dat" right +[ 10, 5 ] = ascii (fp) : "./hrtfs/elev-40/L-40e335a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e335a.dat" right +[ 10, 6 ] = ascii (fp) : "./hrtfs/elev-40/L-40e330a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e330a.dat" right +[ 10, 7 ] = ascii (fp) : "./hrtfs/elev-40/L-40e325a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e325a.dat" right +[ 10, 8 ] = ascii (fp) : "./hrtfs/elev-40/L-40e320a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e320a.dat" right +[ 10, 9 ] = ascii (fp) : "./hrtfs/elev-40/L-40e315a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e315a.dat" right +[ 10, 10 ] = ascii (fp) : "./hrtfs/elev-40/L-40e310a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e310a.dat" right +[ 10, 11 ] = ascii (fp) : "./hrtfs/elev-40/L-40e305a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e305a.dat" right +[ 10, 12 ] = ascii (fp) : "./hrtfs/elev-40/L-40e300a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e300a.dat" right +[ 10, 13 ] = ascii (fp) : "./hrtfs/elev-40/L-40e295a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e295a.dat" right +[ 10, 14 ] = ascii (fp) : "./hrtfs/elev-40/L-40e290a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e290a.dat" right +[ 10, 15 ] = ascii (fp) : "./hrtfs/elev-40/L-40e285a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e285a.dat" right +[ 10, 16 ] = ascii (fp) : "./hrtfs/elev-40/L-40e280a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e280a.dat" right +[ 10, 17 ] = ascii (fp) : "./hrtfs/elev-40/L-40e275a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e275a.dat" right +[ 10, 18 ] = ascii (fp) : "./hrtfs/elev-40/L-40e270a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e270a.dat" right +[ 10, 19 ] = ascii (fp) : "./hrtfs/elev-40/L-40e265a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e265a.dat" right +[ 10, 20 ] = ascii (fp) : "./hrtfs/elev-40/L-40e260a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e260a.dat" right +[ 10, 21 ] = ascii (fp) : "./hrtfs/elev-40/L-40e255a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e255a.dat" right +[ 10, 22 ] = ascii (fp) : "./hrtfs/elev-40/L-40e250a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e250a.dat" right +[ 10, 23 ] = ascii (fp) : "./hrtfs/elev-40/L-40e245a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e245a.dat" right +[ 10, 24 ] = ascii (fp) : "./hrtfs/elev-40/L-40e240a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e240a.dat" right +[ 10, 25 ] = ascii (fp) : "./hrtfs/elev-40/L-40e235a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e235a.dat" right +[ 10, 26 ] = ascii (fp) : "./hrtfs/elev-40/L-40e230a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e230a.dat" right +[ 10, 27 ] = ascii (fp) : "./hrtfs/elev-40/L-40e225a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e225a.dat" right +[ 10, 28 ] = ascii (fp) : "./hrtfs/elev-40/L-40e220a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e220a.dat" right +[ 10, 29 ] = ascii (fp) : "./hrtfs/elev-40/L-40e215a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e215a.dat" right +[ 10, 30 ] = ascii (fp) : "./hrtfs/elev-40/L-40e210a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e210a.dat" right +[ 10, 31 ] = ascii (fp) : "./hrtfs/elev-40/L-40e205a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e205a.dat" right +[ 10, 32 ] = ascii (fp) : "./hrtfs/elev-40/L-40e200a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e200a.dat" right +[ 10, 33 ] = ascii (fp) : "./hrtfs/elev-40/L-40e195a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e195a.dat" right +[ 10, 34 ] = ascii (fp) : "./hrtfs/elev-40/L-40e190a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e190a.dat" right +[ 10, 35 ] = ascii (fp) : "./hrtfs/elev-40/L-40e185a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e185a.dat" right +[ 10, 36 ] = ascii (fp) : "./hrtfs/elev-40/L-40e180a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e180a.dat" right +[ 10, 37 ] = ascii (fp) : "./hrtfs/elev-40/L-40e175a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e175a.dat" right +[ 10, 38 ] = ascii (fp) : "./hrtfs/elev-40/L-40e170a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e170a.dat" right +[ 10, 39 ] = ascii (fp) : "./hrtfs/elev-40/L-40e165a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e165a.dat" right +[ 10, 40 ] = ascii (fp) : "./hrtfs/elev-40/L-40e160a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e160a.dat" right +[ 10, 41 ] = ascii (fp) : "./hrtfs/elev-40/L-40e155a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e155a.dat" right +[ 10, 42 ] = ascii (fp) : "./hrtfs/elev-40/L-40e150a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e150a.dat" right +[ 10, 43 ] = ascii (fp) : "./hrtfs/elev-40/L-40e145a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e145a.dat" right +[ 10, 44 ] = ascii (fp) : "./hrtfs/elev-40/L-40e140a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e140a.dat" right +[ 10, 45 ] = ascii (fp) : "./hrtfs/elev-40/L-40e135a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e135a.dat" right +[ 10, 46 ] = ascii (fp) : "./hrtfs/elev-40/L-40e130a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e130a.dat" right +[ 10, 47 ] = ascii (fp) : "./hrtfs/elev-40/L-40e125a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e125a.dat" right +[ 10, 48 ] = ascii (fp) : "./hrtfs/elev-40/L-40e120a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e120a.dat" right +[ 10, 49 ] = ascii (fp) : "./hrtfs/elev-40/L-40e115a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e115a.dat" right +[ 10, 50 ] = ascii (fp) : "./hrtfs/elev-40/L-40e110a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e110a.dat" right +[ 10, 51 ] = ascii (fp) : "./hrtfs/elev-40/L-40e105a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e105a.dat" right +[ 10, 52 ] = ascii (fp) : "./hrtfs/elev-40/L-40e100a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e100a.dat" right +[ 10, 53 ] = ascii (fp) : "./hrtfs/elev-40/L-40e095a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e095a.dat" right +[ 10, 54 ] = ascii (fp) : "./hrtfs/elev-40/L-40e090a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e090a.dat" right +[ 10, 55 ] = ascii (fp) : "./hrtfs/elev-40/L-40e085a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e085a.dat" right +[ 10, 56 ] = ascii (fp) : "./hrtfs/elev-40/L-40e080a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e080a.dat" right +[ 10, 57 ] = ascii (fp) : "./hrtfs/elev-40/L-40e075a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e075a.dat" right +[ 10, 58 ] = ascii (fp) : "./hrtfs/elev-40/L-40e070a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e070a.dat" right +[ 10, 59 ] = ascii (fp) : "./hrtfs/elev-40/L-40e065a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e065a.dat" right +[ 10, 60 ] = ascii (fp) : "./hrtfs/elev-40/L-40e060a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e060a.dat" right +[ 10, 61 ] = ascii (fp) : "./hrtfs/elev-40/L-40e055a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e055a.dat" right +[ 10, 62 ] = ascii (fp) : "./hrtfs/elev-40/L-40e050a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e050a.dat" right +[ 10, 63 ] = ascii (fp) : "./hrtfs/elev-40/L-40e045a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e045a.dat" right +[ 10, 64 ] = ascii (fp) : "./hrtfs/elev-40/L-40e040a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e040a.dat" right +[ 10, 65 ] = ascii (fp) : "./hrtfs/elev-40/L-40e035a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e035a.dat" right +[ 10, 66 ] = ascii (fp) : "./hrtfs/elev-40/L-40e030a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e030a.dat" right +[ 10, 67 ] = ascii (fp) : "./hrtfs/elev-40/L-40e025a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e025a.dat" right +[ 10, 68 ] = ascii (fp) : "./hrtfs/elev-40/L-40e020a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e020a.dat" right +[ 10, 69 ] = ascii (fp) : "./hrtfs/elev-40/L-40e015a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e015a.dat" right +[ 10, 70 ] = ascii (fp) : "./hrtfs/elev-40/L-40e010a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e010a.dat" right +[ 10, 71 ] = ascii (fp) : "./hrtfs/elev-40/L-40e005a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e005a.dat" right -[ 11, 0 ] = ascii (fp) : "./hrtfs/elev-35/L-35e000a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e000a.dat right -[ 11, 1 ] = ascii (fp) : "./hrtfs/elev-35/L-35e355a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e355a.dat right -[ 11, 2 ] = ascii (fp) : "./hrtfs/elev-35/L-35e350a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e350a.dat right -[ 11, 3 ] = ascii (fp) : "./hrtfs/elev-35/L-35e345a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e345a.dat right -[ 11, 4 ] = ascii (fp) : "./hrtfs/elev-35/L-35e340a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e340a.dat right -[ 11, 5 ] = ascii (fp) : "./hrtfs/elev-35/L-35e335a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e335a.dat right -[ 11, 6 ] = ascii (fp) : "./hrtfs/elev-35/L-35e330a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e330a.dat right -[ 11, 7 ] = ascii (fp) : "./hrtfs/elev-35/L-35e325a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e325a.dat right -[ 11, 8 ] = ascii (fp) : "./hrtfs/elev-35/L-35e320a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e320a.dat right -[ 11, 9 ] = ascii (fp) : "./hrtfs/elev-35/L-35e315a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e315a.dat right -[ 11, 10 ] = ascii (fp) : "./hrtfs/elev-35/L-35e310a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e310a.dat right -[ 11, 11 ] = ascii (fp) : "./hrtfs/elev-35/L-35e305a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e305a.dat right -[ 11, 12 ] = ascii (fp) : "./hrtfs/elev-35/L-35e300a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e300a.dat right -[ 11, 13 ] = ascii (fp) : "./hrtfs/elev-35/L-35e295a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e295a.dat right -[ 11, 14 ] = ascii (fp) : "./hrtfs/elev-35/L-35e290a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e290a.dat right -[ 11, 15 ] = ascii (fp) : "./hrtfs/elev-35/L-35e285a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e285a.dat right -[ 11, 16 ] = ascii (fp) : "./hrtfs/elev-35/L-35e280a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e280a.dat right -[ 11, 17 ] = ascii (fp) : "./hrtfs/elev-35/L-35e275a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e275a.dat right -[ 11, 18 ] = ascii (fp) : "./hrtfs/elev-35/L-35e270a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e270a.dat right -[ 11, 19 ] = ascii (fp) : "./hrtfs/elev-35/L-35e265a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e265a.dat right -[ 11, 20 ] = ascii (fp) : "./hrtfs/elev-35/L-35e260a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e260a.dat right -[ 11, 21 ] = ascii (fp) : "./hrtfs/elev-35/L-35e255a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e255a.dat right -[ 11, 22 ] = ascii (fp) : "./hrtfs/elev-35/L-35e250a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e250a.dat right -[ 11, 23 ] = ascii (fp) : "./hrtfs/elev-35/L-35e245a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e245a.dat right -[ 11, 24 ] = ascii (fp) : "./hrtfs/elev-35/L-35e240a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e240a.dat right -[ 11, 25 ] = ascii (fp) : "./hrtfs/elev-35/L-35e235a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e235a.dat right -[ 11, 26 ] = ascii (fp) : "./hrtfs/elev-35/L-35e230a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e230a.dat right -[ 11, 27 ] = ascii (fp) : "./hrtfs/elev-35/L-35e225a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e225a.dat right -[ 11, 28 ] = ascii (fp) : "./hrtfs/elev-35/L-35e220a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e220a.dat right -[ 11, 29 ] = ascii (fp) : "./hrtfs/elev-35/L-35e215a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e215a.dat right -[ 11, 30 ] = ascii (fp) : "./hrtfs/elev-35/L-35e210a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e210a.dat right -[ 11, 31 ] = ascii (fp) : "./hrtfs/elev-35/L-35e205a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e205a.dat right -[ 11, 32 ] = ascii (fp) : "./hrtfs/elev-35/L-35e200a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e200a.dat right -[ 11, 33 ] = ascii (fp) : "./hrtfs/elev-35/L-35e195a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e195a.dat right -[ 11, 34 ] = ascii (fp) : "./hrtfs/elev-35/L-35e190a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e190a.dat right -[ 11, 35 ] = ascii (fp) : "./hrtfs/elev-35/L-35e185a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e185a.dat right -[ 11, 36 ] = ascii (fp) : "./hrtfs/elev-35/L-35e180a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e180a.dat right -[ 11, 37 ] = ascii (fp) : "./hrtfs/elev-35/L-35e175a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e175a.dat right -[ 11, 38 ] = ascii (fp) : "./hrtfs/elev-35/L-35e170a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e170a.dat right -[ 11, 39 ] = ascii (fp) : "./hrtfs/elev-35/L-35e165a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e165a.dat right -[ 11, 40 ] = ascii (fp) : "./hrtfs/elev-35/L-35e160a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e160a.dat right -[ 11, 41 ] = ascii (fp) : "./hrtfs/elev-35/L-35e155a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e155a.dat right -[ 11, 42 ] = ascii (fp) : "./hrtfs/elev-35/L-35e150a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e150a.dat right -[ 11, 43 ] = ascii (fp) : "./hrtfs/elev-35/L-35e145a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e145a.dat right -[ 11, 44 ] = ascii (fp) : "./hrtfs/elev-35/L-35e140a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e140a.dat right -[ 11, 45 ] = ascii (fp) : "./hrtfs/elev-35/L-35e135a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e135a.dat right -[ 11, 46 ] = ascii (fp) : "./hrtfs/elev-35/L-35e130a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e130a.dat right -[ 11, 47 ] = ascii (fp) : "./hrtfs/elev-35/L-35e125a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e125a.dat right -[ 11, 48 ] = ascii (fp) : "./hrtfs/elev-35/L-35e120a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e120a.dat right -[ 11, 49 ] = ascii (fp) : "./hrtfs/elev-35/L-35e115a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e115a.dat right -[ 11, 50 ] = ascii (fp) : "./hrtfs/elev-35/L-35e110a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e110a.dat right -[ 11, 51 ] = ascii (fp) : "./hrtfs/elev-35/L-35e105a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e105a.dat right -[ 11, 52 ] = ascii (fp) : "./hrtfs/elev-35/L-35e100a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e100a.dat right -[ 11, 53 ] = ascii (fp) : "./hrtfs/elev-35/L-35e095a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e095a.dat right -[ 11, 54 ] = ascii (fp) : "./hrtfs/elev-35/L-35e090a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e090a.dat right -[ 11, 55 ] = ascii (fp) : "./hrtfs/elev-35/L-35e085a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e085a.dat right -[ 11, 56 ] = ascii (fp) : "./hrtfs/elev-35/L-35e080a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e080a.dat right -[ 11, 57 ] = ascii (fp) : "./hrtfs/elev-35/L-35e075a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e075a.dat right -[ 11, 58 ] = ascii (fp) : "./hrtfs/elev-35/L-35e070a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e070a.dat right -[ 11, 59 ] = ascii (fp) : "./hrtfs/elev-35/L-35e065a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e065a.dat right -[ 11, 60 ] = ascii (fp) : "./hrtfs/elev-35/L-35e060a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e060a.dat right -[ 11, 61 ] = ascii (fp) : "./hrtfs/elev-35/L-35e055a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e055a.dat right -[ 11, 62 ] = ascii (fp) : "./hrtfs/elev-35/L-35e050a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e050a.dat right -[ 11, 63 ] = ascii (fp) : "./hrtfs/elev-35/L-35e045a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e045a.dat right -[ 11, 64 ] = ascii (fp) : "./hrtfs/elev-35/L-35e040a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e040a.dat right -[ 11, 65 ] = ascii (fp) : "./hrtfs/elev-35/L-35e035a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e035a.dat right -[ 11, 66 ] = ascii (fp) : "./hrtfs/elev-35/L-35e030a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e030a.dat right -[ 11, 67 ] = ascii (fp) : "./hrtfs/elev-35/L-35e025a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e025a.dat right -[ 11, 68 ] = ascii (fp) : "./hrtfs/elev-35/L-35e020a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e020a.dat right -[ 11, 69 ] = ascii (fp) : "./hrtfs/elev-35/L-35e015a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e015a.dat right -[ 11, 70 ] = ascii (fp) : "./hrtfs/elev-35/L-35e010a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e010a.dat right -[ 11, 71 ] = ascii (fp) : "./hrtfs/elev-35/L-35e005a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e005a.dat right +[ 11, 0 ] = ascii (fp) : "./hrtfs/elev-35/L-35e000a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e000a.dat" right +[ 11, 1 ] = ascii (fp) : "./hrtfs/elev-35/L-35e355a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e355a.dat" right +[ 11, 2 ] = ascii (fp) : "./hrtfs/elev-35/L-35e350a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e350a.dat" right +[ 11, 3 ] = ascii (fp) : "./hrtfs/elev-35/L-35e345a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e345a.dat" right +[ 11, 4 ] = ascii (fp) : "./hrtfs/elev-35/L-35e340a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e340a.dat" right +[ 11, 5 ] = ascii (fp) : "./hrtfs/elev-35/L-35e335a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e335a.dat" right +[ 11, 6 ] = ascii (fp) : "./hrtfs/elev-35/L-35e330a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e330a.dat" right +[ 11, 7 ] = ascii (fp) : "./hrtfs/elev-35/L-35e325a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e325a.dat" right +[ 11, 8 ] = ascii (fp) : "./hrtfs/elev-35/L-35e320a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e320a.dat" right +[ 11, 9 ] = ascii (fp) : "./hrtfs/elev-35/L-35e315a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e315a.dat" right +[ 11, 10 ] = ascii (fp) : "./hrtfs/elev-35/L-35e310a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e310a.dat" right +[ 11, 11 ] = ascii (fp) : "./hrtfs/elev-35/L-35e305a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e305a.dat" right +[ 11, 12 ] = ascii (fp) : "./hrtfs/elev-35/L-35e300a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e300a.dat" right +[ 11, 13 ] = ascii (fp) : "./hrtfs/elev-35/L-35e295a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e295a.dat" right +[ 11, 14 ] = ascii (fp) : "./hrtfs/elev-35/L-35e290a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e290a.dat" right +[ 11, 15 ] = ascii (fp) : "./hrtfs/elev-35/L-35e285a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e285a.dat" right +[ 11, 16 ] = ascii (fp) : "./hrtfs/elev-35/L-35e280a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e280a.dat" right +[ 11, 17 ] = ascii (fp) : "./hrtfs/elev-35/L-35e275a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e275a.dat" right +[ 11, 18 ] = ascii (fp) : "./hrtfs/elev-35/L-35e270a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e270a.dat" right +[ 11, 19 ] = ascii (fp) : "./hrtfs/elev-35/L-35e265a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e265a.dat" right +[ 11, 20 ] = ascii (fp) : "./hrtfs/elev-35/L-35e260a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e260a.dat" right +[ 11, 21 ] = ascii (fp) : "./hrtfs/elev-35/L-35e255a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e255a.dat" right +[ 11, 22 ] = ascii (fp) : "./hrtfs/elev-35/L-35e250a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e250a.dat" right +[ 11, 23 ] = ascii (fp) : "./hrtfs/elev-35/L-35e245a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e245a.dat" right +[ 11, 24 ] = ascii (fp) : "./hrtfs/elev-35/L-35e240a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e240a.dat" right +[ 11, 25 ] = ascii (fp) : "./hrtfs/elev-35/L-35e235a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e235a.dat" right +[ 11, 26 ] = ascii (fp) : "./hrtfs/elev-35/L-35e230a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e230a.dat" right +[ 11, 27 ] = ascii (fp) : "./hrtfs/elev-35/L-35e225a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e225a.dat" right +[ 11, 28 ] = ascii (fp) : "./hrtfs/elev-35/L-35e220a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e220a.dat" right +[ 11, 29 ] = ascii (fp) : "./hrtfs/elev-35/L-35e215a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e215a.dat" right +[ 11, 30 ] = ascii (fp) : "./hrtfs/elev-35/L-35e210a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e210a.dat" right +[ 11, 31 ] = ascii (fp) : "./hrtfs/elev-35/L-35e205a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e205a.dat" right +[ 11, 32 ] = ascii (fp) : "./hrtfs/elev-35/L-35e200a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e200a.dat" right +[ 11, 33 ] = ascii (fp) : "./hrtfs/elev-35/L-35e195a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e195a.dat" right +[ 11, 34 ] = ascii (fp) : "./hrtfs/elev-35/L-35e190a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e190a.dat" right +[ 11, 35 ] = ascii (fp) : "./hrtfs/elev-35/L-35e185a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e185a.dat" right +[ 11, 36 ] = ascii (fp) : "./hrtfs/elev-35/L-35e180a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e180a.dat" right +[ 11, 37 ] = ascii (fp) : "./hrtfs/elev-35/L-35e175a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e175a.dat" right +[ 11, 38 ] = ascii (fp) : "./hrtfs/elev-35/L-35e170a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e170a.dat" right +[ 11, 39 ] = ascii (fp) : "./hrtfs/elev-35/L-35e165a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e165a.dat" right +[ 11, 40 ] = ascii (fp) : "./hrtfs/elev-35/L-35e160a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e160a.dat" right +[ 11, 41 ] = ascii (fp) : "./hrtfs/elev-35/L-35e155a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e155a.dat" right +[ 11, 42 ] = ascii (fp) : "./hrtfs/elev-35/L-35e150a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e150a.dat" right +[ 11, 43 ] = ascii (fp) : "./hrtfs/elev-35/L-35e145a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e145a.dat" right +[ 11, 44 ] = ascii (fp) : "./hrtfs/elev-35/L-35e140a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e140a.dat" right +[ 11, 45 ] = ascii (fp) : "./hrtfs/elev-35/L-35e135a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e135a.dat" right +[ 11, 46 ] = ascii (fp) : "./hrtfs/elev-35/L-35e130a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e130a.dat" right +[ 11, 47 ] = ascii (fp) : "./hrtfs/elev-35/L-35e125a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e125a.dat" right +[ 11, 48 ] = ascii (fp) : "./hrtfs/elev-35/L-35e120a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e120a.dat" right +[ 11, 49 ] = ascii (fp) : "./hrtfs/elev-35/L-35e115a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e115a.dat" right +[ 11, 50 ] = ascii (fp) : "./hrtfs/elev-35/L-35e110a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e110a.dat" right +[ 11, 51 ] = ascii (fp) : "./hrtfs/elev-35/L-35e105a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e105a.dat" right +[ 11, 52 ] = ascii (fp) : "./hrtfs/elev-35/L-35e100a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e100a.dat" right +[ 11, 53 ] = ascii (fp) : "./hrtfs/elev-35/L-35e095a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e095a.dat" right +[ 11, 54 ] = ascii (fp) : "./hrtfs/elev-35/L-35e090a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e090a.dat" right +[ 11, 55 ] = ascii (fp) : "./hrtfs/elev-35/L-35e085a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e085a.dat" right +[ 11, 56 ] = ascii (fp) : "./hrtfs/elev-35/L-35e080a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e080a.dat" right +[ 11, 57 ] = ascii (fp) : "./hrtfs/elev-35/L-35e075a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e075a.dat" right +[ 11, 58 ] = ascii (fp) : "./hrtfs/elev-35/L-35e070a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e070a.dat" right +[ 11, 59 ] = ascii (fp) : "./hrtfs/elev-35/L-35e065a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e065a.dat" right +[ 11, 60 ] = ascii (fp) : "./hrtfs/elev-35/L-35e060a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e060a.dat" right +[ 11, 61 ] = ascii (fp) : "./hrtfs/elev-35/L-35e055a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e055a.dat" right +[ 11, 62 ] = ascii (fp) : "./hrtfs/elev-35/L-35e050a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e050a.dat" right +[ 11, 63 ] = ascii (fp) : "./hrtfs/elev-35/L-35e045a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e045a.dat" right +[ 11, 64 ] = ascii (fp) : "./hrtfs/elev-35/L-35e040a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e040a.dat" right +[ 11, 65 ] = ascii (fp) : "./hrtfs/elev-35/L-35e035a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e035a.dat" right +[ 11, 66 ] = ascii (fp) : "./hrtfs/elev-35/L-35e030a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e030a.dat" right +[ 11, 67 ] = ascii (fp) : "./hrtfs/elev-35/L-35e025a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e025a.dat" right +[ 11, 68 ] = ascii (fp) : "./hrtfs/elev-35/L-35e020a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e020a.dat" right +[ 11, 69 ] = ascii (fp) : "./hrtfs/elev-35/L-35e015a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e015a.dat" right +[ 11, 70 ] = ascii (fp) : "./hrtfs/elev-35/L-35e010a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e010a.dat" right +[ 11, 71 ] = ascii (fp) : "./hrtfs/elev-35/L-35e005a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e005a.dat" right -[ 12, 0 ] = ascii (fp) : "./hrtfs/elev-30/L-30e000a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e000a.dat right -[ 12, 1 ] = ascii (fp) : "./hrtfs/elev-30/L-30e355a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e355a.dat right -[ 12, 2 ] = ascii (fp) : "./hrtfs/elev-30/L-30e350a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e350a.dat right -[ 12, 3 ] = ascii (fp) : "./hrtfs/elev-30/L-30e345a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e345a.dat right -[ 12, 4 ] = ascii (fp) : "./hrtfs/elev-30/L-30e340a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e340a.dat right -[ 12, 5 ] = ascii (fp) : "./hrtfs/elev-30/L-30e335a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e335a.dat right -[ 12, 6 ] = ascii (fp) : "./hrtfs/elev-30/L-30e330a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e330a.dat right -[ 12, 7 ] = ascii (fp) : "./hrtfs/elev-30/L-30e325a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e325a.dat right -[ 12, 8 ] = ascii (fp) : "./hrtfs/elev-30/L-30e320a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e320a.dat right -[ 12, 9 ] = ascii (fp) : "./hrtfs/elev-30/L-30e315a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e315a.dat right -[ 12, 10 ] = ascii (fp) : "./hrtfs/elev-30/L-30e310a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e310a.dat right -[ 12, 11 ] = ascii (fp) : "./hrtfs/elev-30/L-30e305a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e305a.dat right -[ 12, 12 ] = ascii (fp) : "./hrtfs/elev-30/L-30e300a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e300a.dat right -[ 12, 13 ] = ascii (fp) : "./hrtfs/elev-30/L-30e295a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e295a.dat right -[ 12, 14 ] = ascii (fp) : "./hrtfs/elev-30/L-30e290a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e290a.dat right -[ 12, 15 ] = ascii (fp) : "./hrtfs/elev-30/L-30e285a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e285a.dat right -[ 12, 16 ] = ascii (fp) : "./hrtfs/elev-30/L-30e280a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e280a.dat right -[ 12, 17 ] = ascii (fp) : "./hrtfs/elev-30/L-30e275a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e275a.dat right -[ 12, 18 ] = ascii (fp) : "./hrtfs/elev-30/L-30e270a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e270a.dat right -[ 12, 19 ] = ascii (fp) : "./hrtfs/elev-30/L-30e265a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e265a.dat right -[ 12, 20 ] = ascii (fp) : "./hrtfs/elev-30/L-30e260a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e260a.dat right -[ 12, 21 ] = ascii (fp) : "./hrtfs/elev-30/L-30e255a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e255a.dat right -[ 12, 22 ] = ascii (fp) : "./hrtfs/elev-30/L-30e250a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e250a.dat right -[ 12, 23 ] = ascii (fp) : "./hrtfs/elev-30/L-30e245a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e245a.dat right -[ 12, 24 ] = ascii (fp) : "./hrtfs/elev-30/L-30e240a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e240a.dat right -[ 12, 25 ] = ascii (fp) : "./hrtfs/elev-30/L-30e235a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e235a.dat right -[ 12, 26 ] = ascii (fp) : "./hrtfs/elev-30/L-30e230a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e230a.dat right -[ 12, 27 ] = ascii (fp) : "./hrtfs/elev-30/L-30e225a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e225a.dat right -[ 12, 28 ] = ascii (fp) : "./hrtfs/elev-30/L-30e220a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e220a.dat right -[ 12, 29 ] = ascii (fp) : "./hrtfs/elev-30/L-30e215a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e215a.dat right -[ 12, 30 ] = ascii (fp) : "./hrtfs/elev-30/L-30e210a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e210a.dat right -[ 12, 31 ] = ascii (fp) : "./hrtfs/elev-30/L-30e205a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e205a.dat right -[ 12, 32 ] = ascii (fp) : "./hrtfs/elev-30/L-30e200a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e200a.dat right -[ 12, 33 ] = ascii (fp) : "./hrtfs/elev-30/L-30e195a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e195a.dat right -[ 12, 34 ] = ascii (fp) : "./hrtfs/elev-30/L-30e190a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e190a.dat right -[ 12, 35 ] = ascii (fp) : "./hrtfs/elev-30/L-30e185a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e185a.dat right -[ 12, 36 ] = ascii (fp) : "./hrtfs/elev-30/L-30e180a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e180a.dat right -[ 12, 37 ] = ascii (fp) : "./hrtfs/elev-30/L-30e175a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e175a.dat right -[ 12, 38 ] = ascii (fp) : "./hrtfs/elev-30/L-30e170a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e170a.dat right -[ 12, 39 ] = ascii (fp) : "./hrtfs/elev-30/L-30e165a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e165a.dat right -[ 12, 40 ] = ascii (fp) : "./hrtfs/elev-30/L-30e160a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e160a.dat right -[ 12, 41 ] = ascii (fp) : "./hrtfs/elev-30/L-30e155a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e155a.dat right -[ 12, 42 ] = ascii (fp) : "./hrtfs/elev-30/L-30e150a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e150a.dat right -[ 12, 43 ] = ascii (fp) : "./hrtfs/elev-30/L-30e145a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e145a.dat right -[ 12, 44 ] = ascii (fp) : "./hrtfs/elev-30/L-30e140a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e140a.dat right -[ 12, 45 ] = ascii (fp) : "./hrtfs/elev-30/L-30e135a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e135a.dat right -[ 12, 46 ] = ascii (fp) : "./hrtfs/elev-30/L-30e130a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e130a.dat right -[ 12, 47 ] = ascii (fp) : "./hrtfs/elev-30/L-30e125a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e125a.dat right -[ 12, 48 ] = ascii (fp) : "./hrtfs/elev-30/L-30e120a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e120a.dat right -[ 12, 49 ] = ascii (fp) : "./hrtfs/elev-30/L-30e115a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e115a.dat right -[ 12, 50 ] = ascii (fp) : "./hrtfs/elev-30/L-30e110a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e110a.dat right -[ 12, 51 ] = ascii (fp) : "./hrtfs/elev-30/L-30e105a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e105a.dat right -[ 12, 52 ] = ascii (fp) : "./hrtfs/elev-30/L-30e100a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e100a.dat right -[ 12, 53 ] = ascii (fp) : "./hrtfs/elev-30/L-30e095a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e095a.dat right -[ 12, 54 ] = ascii (fp) : "./hrtfs/elev-30/L-30e090a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e090a.dat right -[ 12, 55 ] = ascii (fp) : "./hrtfs/elev-30/L-30e085a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e085a.dat right -[ 12, 56 ] = ascii (fp) : "./hrtfs/elev-30/L-30e080a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e080a.dat right -[ 12, 57 ] = ascii (fp) : "./hrtfs/elev-30/L-30e075a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e075a.dat right -[ 12, 58 ] = ascii (fp) : "./hrtfs/elev-30/L-30e070a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e070a.dat right -[ 12, 59 ] = ascii (fp) : "./hrtfs/elev-30/L-30e065a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e065a.dat right -[ 12, 60 ] = ascii (fp) : "./hrtfs/elev-30/L-30e060a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e060a.dat right -[ 12, 61 ] = ascii (fp) : "./hrtfs/elev-30/L-30e055a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e055a.dat right -[ 12, 62 ] = ascii (fp) : "./hrtfs/elev-30/L-30e050a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e050a.dat right -[ 12, 63 ] = ascii (fp) : "./hrtfs/elev-30/L-30e045a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e045a.dat right -[ 12, 64 ] = ascii (fp) : "./hrtfs/elev-30/L-30e040a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e040a.dat right -[ 12, 65 ] = ascii (fp) : "./hrtfs/elev-30/L-30e035a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e035a.dat right -[ 12, 66 ] = ascii (fp) : "./hrtfs/elev-30/L-30e030a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e030a.dat right -[ 12, 67 ] = ascii (fp) : "./hrtfs/elev-30/L-30e025a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e025a.dat right -[ 12, 68 ] = ascii (fp) : "./hrtfs/elev-30/L-30e020a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e020a.dat right -[ 12, 69 ] = ascii (fp) : "./hrtfs/elev-30/L-30e015a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e015a.dat right -[ 12, 70 ] = ascii (fp) : "./hrtfs/elev-30/L-30e010a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e010a.dat right -[ 12, 71 ] = ascii (fp) : "./hrtfs/elev-30/L-30e005a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e005a.dat right +[ 12, 0 ] = ascii (fp) : "./hrtfs/elev-30/L-30e000a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e000a.dat" right +[ 12, 1 ] = ascii (fp) : "./hrtfs/elev-30/L-30e355a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e355a.dat" right +[ 12, 2 ] = ascii (fp) : "./hrtfs/elev-30/L-30e350a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e350a.dat" right +[ 12, 3 ] = ascii (fp) : "./hrtfs/elev-30/L-30e345a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e345a.dat" right +[ 12, 4 ] = ascii (fp) : "./hrtfs/elev-30/L-30e340a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e340a.dat" right +[ 12, 5 ] = ascii (fp) : "./hrtfs/elev-30/L-30e335a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e335a.dat" right +[ 12, 6 ] = ascii (fp) : "./hrtfs/elev-30/L-30e330a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e330a.dat" right +[ 12, 7 ] = ascii (fp) : "./hrtfs/elev-30/L-30e325a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e325a.dat" right +[ 12, 8 ] = ascii (fp) : "./hrtfs/elev-30/L-30e320a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e320a.dat" right +[ 12, 9 ] = ascii (fp) : "./hrtfs/elev-30/L-30e315a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e315a.dat" right +[ 12, 10 ] = ascii (fp) : "./hrtfs/elev-30/L-30e310a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e310a.dat" right +[ 12, 11 ] = ascii (fp) : "./hrtfs/elev-30/L-30e305a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e305a.dat" right +[ 12, 12 ] = ascii (fp) : "./hrtfs/elev-30/L-30e300a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e300a.dat" right +[ 12, 13 ] = ascii (fp) : "./hrtfs/elev-30/L-30e295a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e295a.dat" right +[ 12, 14 ] = ascii (fp) : "./hrtfs/elev-30/L-30e290a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e290a.dat" right +[ 12, 15 ] = ascii (fp) : "./hrtfs/elev-30/L-30e285a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e285a.dat" right +[ 12, 16 ] = ascii (fp) : "./hrtfs/elev-30/L-30e280a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e280a.dat" right +[ 12, 17 ] = ascii (fp) : "./hrtfs/elev-30/L-30e275a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e275a.dat" right +[ 12, 18 ] = ascii (fp) : "./hrtfs/elev-30/L-30e270a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e270a.dat" right +[ 12, 19 ] = ascii (fp) : "./hrtfs/elev-30/L-30e265a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e265a.dat" right +[ 12, 20 ] = ascii (fp) : "./hrtfs/elev-30/L-30e260a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e260a.dat" right +[ 12, 21 ] = ascii (fp) : "./hrtfs/elev-30/L-30e255a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e255a.dat" right +[ 12, 22 ] = ascii (fp) : "./hrtfs/elev-30/L-30e250a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e250a.dat" right +[ 12, 23 ] = ascii (fp) : "./hrtfs/elev-30/L-30e245a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e245a.dat" right +[ 12, 24 ] = ascii (fp) : "./hrtfs/elev-30/L-30e240a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e240a.dat" right +[ 12, 25 ] = ascii (fp) : "./hrtfs/elev-30/L-30e235a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e235a.dat" right +[ 12, 26 ] = ascii (fp) : "./hrtfs/elev-30/L-30e230a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e230a.dat" right +[ 12, 27 ] = ascii (fp) : "./hrtfs/elev-30/L-30e225a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e225a.dat" right +[ 12, 28 ] = ascii (fp) : "./hrtfs/elev-30/L-30e220a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e220a.dat" right +[ 12, 29 ] = ascii (fp) : "./hrtfs/elev-30/L-30e215a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e215a.dat" right +[ 12, 30 ] = ascii (fp) : "./hrtfs/elev-30/L-30e210a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e210a.dat" right +[ 12, 31 ] = ascii (fp) : "./hrtfs/elev-30/L-30e205a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e205a.dat" right +[ 12, 32 ] = ascii (fp) : "./hrtfs/elev-30/L-30e200a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e200a.dat" right +[ 12, 33 ] = ascii (fp) : "./hrtfs/elev-30/L-30e195a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e195a.dat" right +[ 12, 34 ] = ascii (fp) : "./hrtfs/elev-30/L-30e190a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e190a.dat" right +[ 12, 35 ] = ascii (fp) : "./hrtfs/elev-30/L-30e185a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e185a.dat" right +[ 12, 36 ] = ascii (fp) : "./hrtfs/elev-30/L-30e180a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e180a.dat" right +[ 12, 37 ] = ascii (fp) : "./hrtfs/elev-30/L-30e175a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e175a.dat" right +[ 12, 38 ] = ascii (fp) : "./hrtfs/elev-30/L-30e170a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e170a.dat" right +[ 12, 39 ] = ascii (fp) : "./hrtfs/elev-30/L-30e165a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e165a.dat" right +[ 12, 40 ] = ascii (fp) : "./hrtfs/elev-30/L-30e160a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e160a.dat" right +[ 12, 41 ] = ascii (fp) : "./hrtfs/elev-30/L-30e155a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e155a.dat" right +[ 12, 42 ] = ascii (fp) : "./hrtfs/elev-30/L-30e150a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e150a.dat" right +[ 12, 43 ] = ascii (fp) : "./hrtfs/elev-30/L-30e145a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e145a.dat" right +[ 12, 44 ] = ascii (fp) : "./hrtfs/elev-30/L-30e140a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e140a.dat" right +[ 12, 45 ] = ascii (fp) : "./hrtfs/elev-30/L-30e135a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e135a.dat" right +[ 12, 46 ] = ascii (fp) : "./hrtfs/elev-30/L-30e130a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e130a.dat" right +[ 12, 47 ] = ascii (fp) : "./hrtfs/elev-30/L-30e125a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e125a.dat" right +[ 12, 48 ] = ascii (fp) : "./hrtfs/elev-30/L-30e120a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e120a.dat" right +[ 12, 49 ] = ascii (fp) : "./hrtfs/elev-30/L-30e115a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e115a.dat" right +[ 12, 50 ] = ascii (fp) : "./hrtfs/elev-30/L-30e110a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e110a.dat" right +[ 12, 51 ] = ascii (fp) : "./hrtfs/elev-30/L-30e105a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e105a.dat" right +[ 12, 52 ] = ascii (fp) : "./hrtfs/elev-30/L-30e100a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e100a.dat" right +[ 12, 53 ] = ascii (fp) : "./hrtfs/elev-30/L-30e095a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e095a.dat" right +[ 12, 54 ] = ascii (fp) : "./hrtfs/elev-30/L-30e090a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e090a.dat" right +[ 12, 55 ] = ascii (fp) : "./hrtfs/elev-30/L-30e085a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e085a.dat" right +[ 12, 56 ] = ascii (fp) : "./hrtfs/elev-30/L-30e080a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e080a.dat" right +[ 12, 57 ] = ascii (fp) : "./hrtfs/elev-30/L-30e075a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e075a.dat" right +[ 12, 58 ] = ascii (fp) : "./hrtfs/elev-30/L-30e070a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e070a.dat" right +[ 12, 59 ] = ascii (fp) : "./hrtfs/elev-30/L-30e065a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e065a.dat" right +[ 12, 60 ] = ascii (fp) : "./hrtfs/elev-30/L-30e060a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e060a.dat" right +[ 12, 61 ] = ascii (fp) : "./hrtfs/elev-30/L-30e055a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e055a.dat" right +[ 12, 62 ] = ascii (fp) : "./hrtfs/elev-30/L-30e050a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e050a.dat" right +[ 12, 63 ] = ascii (fp) : "./hrtfs/elev-30/L-30e045a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e045a.dat" right +[ 12, 64 ] = ascii (fp) : "./hrtfs/elev-30/L-30e040a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e040a.dat" right +[ 12, 65 ] = ascii (fp) : "./hrtfs/elev-30/L-30e035a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e035a.dat" right +[ 12, 66 ] = ascii (fp) : "./hrtfs/elev-30/L-30e030a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e030a.dat" right +[ 12, 67 ] = ascii (fp) : "./hrtfs/elev-30/L-30e025a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e025a.dat" right +[ 12, 68 ] = ascii (fp) : "./hrtfs/elev-30/L-30e020a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e020a.dat" right +[ 12, 69 ] = ascii (fp) : "./hrtfs/elev-30/L-30e015a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e015a.dat" right +[ 12, 70 ] = ascii (fp) : "./hrtfs/elev-30/L-30e010a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e010a.dat" right +[ 12, 71 ] = ascii (fp) : "./hrtfs/elev-30/L-30e005a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e005a.dat" right -[ 13, 0 ] = ascii (fp) : "./hrtfs/elev-25/L-25e000a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e000a.dat right -[ 13, 1 ] = ascii (fp) : "./hrtfs/elev-25/L-25e355a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e355a.dat right -[ 13, 2 ] = ascii (fp) : "./hrtfs/elev-25/L-25e350a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e350a.dat right -[ 13, 3 ] = ascii (fp) : "./hrtfs/elev-25/L-25e345a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e345a.dat right -[ 13, 4 ] = ascii (fp) : "./hrtfs/elev-25/L-25e340a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e340a.dat right -[ 13, 5 ] = ascii (fp) : "./hrtfs/elev-25/L-25e335a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e335a.dat right -[ 13, 6 ] = ascii (fp) : "./hrtfs/elev-25/L-25e330a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e330a.dat right -[ 13, 7 ] = ascii (fp) : "./hrtfs/elev-25/L-25e325a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e325a.dat right -[ 13, 8 ] = ascii (fp) : "./hrtfs/elev-25/L-25e320a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e320a.dat right -[ 13, 9 ] = ascii (fp) : "./hrtfs/elev-25/L-25e315a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e315a.dat right -[ 13, 10 ] = ascii (fp) : "./hrtfs/elev-25/L-25e310a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e310a.dat right -[ 13, 11 ] = ascii (fp) : "./hrtfs/elev-25/L-25e305a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e305a.dat right -[ 13, 12 ] = ascii (fp) : "./hrtfs/elev-25/L-25e300a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e300a.dat right -[ 13, 13 ] = ascii (fp) : "./hrtfs/elev-25/L-25e295a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e295a.dat right -[ 13, 14 ] = ascii (fp) : "./hrtfs/elev-25/L-25e290a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e290a.dat right -[ 13, 15 ] = ascii (fp) : "./hrtfs/elev-25/L-25e285a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e285a.dat right -[ 13, 16 ] = ascii (fp) : "./hrtfs/elev-25/L-25e280a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e280a.dat right -[ 13, 17 ] = ascii (fp) : "./hrtfs/elev-25/L-25e275a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e275a.dat right -[ 13, 18 ] = ascii (fp) : "./hrtfs/elev-25/L-25e270a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e270a.dat right -[ 13, 19 ] = ascii (fp) : "./hrtfs/elev-25/L-25e265a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e265a.dat right -[ 13, 20 ] = ascii (fp) : "./hrtfs/elev-25/L-25e260a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e260a.dat right -[ 13, 21 ] = ascii (fp) : "./hrtfs/elev-25/L-25e255a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e255a.dat right -[ 13, 22 ] = ascii (fp) : "./hrtfs/elev-25/L-25e250a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e250a.dat right -[ 13, 23 ] = ascii (fp) : "./hrtfs/elev-25/L-25e245a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e245a.dat right -[ 13, 24 ] = ascii (fp) : "./hrtfs/elev-25/L-25e240a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e240a.dat right -[ 13, 25 ] = ascii (fp) : "./hrtfs/elev-25/L-25e235a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e235a.dat right -[ 13, 26 ] = ascii (fp) : "./hrtfs/elev-25/L-25e230a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e230a.dat right -[ 13, 27 ] = ascii (fp) : "./hrtfs/elev-25/L-25e225a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e225a.dat right -[ 13, 28 ] = ascii (fp) : "./hrtfs/elev-25/L-25e220a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e220a.dat right -[ 13, 29 ] = ascii (fp) : "./hrtfs/elev-25/L-25e215a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e215a.dat right -[ 13, 30 ] = ascii (fp) : "./hrtfs/elev-25/L-25e210a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e210a.dat right -[ 13, 31 ] = ascii (fp) : "./hrtfs/elev-25/L-25e205a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e205a.dat right -[ 13, 32 ] = ascii (fp) : "./hrtfs/elev-25/L-25e200a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e200a.dat right -[ 13, 33 ] = ascii (fp) : "./hrtfs/elev-25/L-25e195a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e195a.dat right -[ 13, 34 ] = ascii (fp) : "./hrtfs/elev-25/L-25e190a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e190a.dat right -[ 13, 35 ] = ascii (fp) : "./hrtfs/elev-25/L-25e185a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e185a.dat right -[ 13, 36 ] = ascii (fp) : "./hrtfs/elev-25/L-25e180a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e180a.dat right -[ 13, 37 ] = ascii (fp) : "./hrtfs/elev-25/L-25e175a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e175a.dat right -[ 13, 38 ] = ascii (fp) : "./hrtfs/elev-25/L-25e170a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e170a.dat right -[ 13, 39 ] = ascii (fp) : "./hrtfs/elev-25/L-25e165a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e165a.dat right -[ 13, 40 ] = ascii (fp) : "./hrtfs/elev-25/L-25e160a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e160a.dat right -[ 13, 41 ] = ascii (fp) : "./hrtfs/elev-25/L-25e155a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e155a.dat right -[ 13, 42 ] = ascii (fp) : "./hrtfs/elev-25/L-25e150a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e150a.dat right -[ 13, 43 ] = ascii (fp) : "./hrtfs/elev-25/L-25e145a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e145a.dat right -[ 13, 44 ] = ascii (fp) : "./hrtfs/elev-25/L-25e140a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e140a.dat right -[ 13, 45 ] = ascii (fp) : "./hrtfs/elev-25/L-25e135a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e135a.dat right -[ 13, 46 ] = ascii (fp) : "./hrtfs/elev-25/L-25e130a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e130a.dat right -[ 13, 47 ] = ascii (fp) : "./hrtfs/elev-25/L-25e125a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e125a.dat right -[ 13, 48 ] = ascii (fp) : "./hrtfs/elev-25/L-25e120a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e120a.dat right -[ 13, 49 ] = ascii (fp) : "./hrtfs/elev-25/L-25e115a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e115a.dat right -[ 13, 50 ] = ascii (fp) : "./hrtfs/elev-25/L-25e110a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e110a.dat right -[ 13, 51 ] = ascii (fp) : "./hrtfs/elev-25/L-25e105a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e105a.dat right -[ 13, 52 ] = ascii (fp) : "./hrtfs/elev-25/L-25e100a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e100a.dat right -[ 13, 53 ] = ascii (fp) : "./hrtfs/elev-25/L-25e095a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e095a.dat right -[ 13, 54 ] = ascii (fp) : "./hrtfs/elev-25/L-25e090a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e090a.dat right -[ 13, 55 ] = ascii (fp) : "./hrtfs/elev-25/L-25e085a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e085a.dat right -[ 13, 56 ] = ascii (fp) : "./hrtfs/elev-25/L-25e080a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e080a.dat right -[ 13, 57 ] = ascii (fp) : "./hrtfs/elev-25/L-25e075a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e075a.dat right -[ 13, 58 ] = ascii (fp) : "./hrtfs/elev-25/L-25e070a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e070a.dat right -[ 13, 59 ] = ascii (fp) : "./hrtfs/elev-25/L-25e065a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e065a.dat right -[ 13, 60 ] = ascii (fp) : "./hrtfs/elev-25/L-25e060a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e060a.dat right -[ 13, 61 ] = ascii (fp) : "./hrtfs/elev-25/L-25e055a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e055a.dat right -[ 13, 62 ] = ascii (fp) : "./hrtfs/elev-25/L-25e050a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e050a.dat right -[ 13, 63 ] = ascii (fp) : "./hrtfs/elev-25/L-25e045a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e045a.dat right -[ 13, 64 ] = ascii (fp) : "./hrtfs/elev-25/L-25e040a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e040a.dat right -[ 13, 65 ] = ascii (fp) : "./hrtfs/elev-25/L-25e035a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e035a.dat right -[ 13, 66 ] = ascii (fp) : "./hrtfs/elev-25/L-25e030a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e030a.dat right -[ 13, 67 ] = ascii (fp) : "./hrtfs/elev-25/L-25e025a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e025a.dat right -[ 13, 68 ] = ascii (fp) : "./hrtfs/elev-25/L-25e020a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e020a.dat right -[ 13, 69 ] = ascii (fp) : "./hrtfs/elev-25/L-25e015a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e015a.dat right -[ 13, 70 ] = ascii (fp) : "./hrtfs/elev-25/L-25e010a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e010a.dat right -[ 13, 71 ] = ascii (fp) : "./hrtfs/elev-25/L-25e005a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e005a.dat right +[ 13, 0 ] = ascii (fp) : "./hrtfs/elev-25/L-25e000a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e000a.dat" right +[ 13, 1 ] = ascii (fp) : "./hrtfs/elev-25/L-25e355a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e355a.dat" right +[ 13, 2 ] = ascii (fp) : "./hrtfs/elev-25/L-25e350a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e350a.dat" right +[ 13, 3 ] = ascii (fp) : "./hrtfs/elev-25/L-25e345a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e345a.dat" right +[ 13, 4 ] = ascii (fp) : "./hrtfs/elev-25/L-25e340a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e340a.dat" right +[ 13, 5 ] = ascii (fp) : "./hrtfs/elev-25/L-25e335a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e335a.dat" right +[ 13, 6 ] = ascii (fp) : "./hrtfs/elev-25/L-25e330a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e330a.dat" right +[ 13, 7 ] = ascii (fp) : "./hrtfs/elev-25/L-25e325a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e325a.dat" right +[ 13, 8 ] = ascii (fp) : "./hrtfs/elev-25/L-25e320a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e320a.dat" right +[ 13, 9 ] = ascii (fp) : "./hrtfs/elev-25/L-25e315a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e315a.dat" right +[ 13, 10 ] = ascii (fp) : "./hrtfs/elev-25/L-25e310a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e310a.dat" right +[ 13, 11 ] = ascii (fp) : "./hrtfs/elev-25/L-25e305a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e305a.dat" right +[ 13, 12 ] = ascii (fp) : "./hrtfs/elev-25/L-25e300a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e300a.dat" right +[ 13, 13 ] = ascii (fp) : "./hrtfs/elev-25/L-25e295a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e295a.dat" right +[ 13, 14 ] = ascii (fp) : "./hrtfs/elev-25/L-25e290a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e290a.dat" right +[ 13, 15 ] = ascii (fp) : "./hrtfs/elev-25/L-25e285a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e285a.dat" right +[ 13, 16 ] = ascii (fp) : "./hrtfs/elev-25/L-25e280a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e280a.dat" right +[ 13, 17 ] = ascii (fp) : "./hrtfs/elev-25/L-25e275a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e275a.dat" right +[ 13, 18 ] = ascii (fp) : "./hrtfs/elev-25/L-25e270a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e270a.dat" right +[ 13, 19 ] = ascii (fp) : "./hrtfs/elev-25/L-25e265a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e265a.dat" right +[ 13, 20 ] = ascii (fp) : "./hrtfs/elev-25/L-25e260a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e260a.dat" right +[ 13, 21 ] = ascii (fp) : "./hrtfs/elev-25/L-25e255a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e255a.dat" right +[ 13, 22 ] = ascii (fp) : "./hrtfs/elev-25/L-25e250a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e250a.dat" right +[ 13, 23 ] = ascii (fp) : "./hrtfs/elev-25/L-25e245a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e245a.dat" right +[ 13, 24 ] = ascii (fp) : "./hrtfs/elev-25/L-25e240a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e240a.dat" right +[ 13, 25 ] = ascii (fp) : "./hrtfs/elev-25/L-25e235a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e235a.dat" right +[ 13, 26 ] = ascii (fp) : "./hrtfs/elev-25/L-25e230a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e230a.dat" right +[ 13, 27 ] = ascii (fp) : "./hrtfs/elev-25/L-25e225a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e225a.dat" right +[ 13, 28 ] = ascii (fp) : "./hrtfs/elev-25/L-25e220a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e220a.dat" right +[ 13, 29 ] = ascii (fp) : "./hrtfs/elev-25/L-25e215a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e215a.dat" right +[ 13, 30 ] = ascii (fp) : "./hrtfs/elev-25/L-25e210a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e210a.dat" right +[ 13, 31 ] = ascii (fp) : "./hrtfs/elev-25/L-25e205a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e205a.dat" right +[ 13, 32 ] = ascii (fp) : "./hrtfs/elev-25/L-25e200a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e200a.dat" right +[ 13, 33 ] = ascii (fp) : "./hrtfs/elev-25/L-25e195a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e195a.dat" right +[ 13, 34 ] = ascii (fp) : "./hrtfs/elev-25/L-25e190a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e190a.dat" right +[ 13, 35 ] = ascii (fp) : "./hrtfs/elev-25/L-25e185a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e185a.dat" right +[ 13, 36 ] = ascii (fp) : "./hrtfs/elev-25/L-25e180a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e180a.dat" right +[ 13, 37 ] = ascii (fp) : "./hrtfs/elev-25/L-25e175a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e175a.dat" right +[ 13, 38 ] = ascii (fp) : "./hrtfs/elev-25/L-25e170a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e170a.dat" right +[ 13, 39 ] = ascii (fp) : "./hrtfs/elev-25/L-25e165a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e165a.dat" right +[ 13, 40 ] = ascii (fp) : "./hrtfs/elev-25/L-25e160a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e160a.dat" right +[ 13, 41 ] = ascii (fp) : "./hrtfs/elev-25/L-25e155a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e155a.dat" right +[ 13, 42 ] = ascii (fp) : "./hrtfs/elev-25/L-25e150a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e150a.dat" right +[ 13, 43 ] = ascii (fp) : "./hrtfs/elev-25/L-25e145a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e145a.dat" right +[ 13, 44 ] = ascii (fp) : "./hrtfs/elev-25/L-25e140a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e140a.dat" right +[ 13, 45 ] = ascii (fp) : "./hrtfs/elev-25/L-25e135a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e135a.dat" right +[ 13, 46 ] = ascii (fp) : "./hrtfs/elev-25/L-25e130a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e130a.dat" right +[ 13, 47 ] = ascii (fp) : "./hrtfs/elev-25/L-25e125a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e125a.dat" right +[ 13, 48 ] = ascii (fp) : "./hrtfs/elev-25/L-25e120a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e120a.dat" right +[ 13, 49 ] = ascii (fp) : "./hrtfs/elev-25/L-25e115a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e115a.dat" right +[ 13, 50 ] = ascii (fp) : "./hrtfs/elev-25/L-25e110a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e110a.dat" right +[ 13, 51 ] = ascii (fp) : "./hrtfs/elev-25/L-25e105a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e105a.dat" right +[ 13, 52 ] = ascii (fp) : "./hrtfs/elev-25/L-25e100a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e100a.dat" right +[ 13, 53 ] = ascii (fp) : "./hrtfs/elev-25/L-25e095a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e095a.dat" right +[ 13, 54 ] = ascii (fp) : "./hrtfs/elev-25/L-25e090a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e090a.dat" right +[ 13, 55 ] = ascii (fp) : "./hrtfs/elev-25/L-25e085a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e085a.dat" right +[ 13, 56 ] = ascii (fp) : "./hrtfs/elev-25/L-25e080a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e080a.dat" right +[ 13, 57 ] = ascii (fp) : "./hrtfs/elev-25/L-25e075a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e075a.dat" right +[ 13, 58 ] = ascii (fp) : "./hrtfs/elev-25/L-25e070a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e070a.dat" right +[ 13, 59 ] = ascii (fp) : "./hrtfs/elev-25/L-25e065a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e065a.dat" right +[ 13, 60 ] = ascii (fp) : "./hrtfs/elev-25/L-25e060a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e060a.dat" right +[ 13, 61 ] = ascii (fp) : "./hrtfs/elev-25/L-25e055a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e055a.dat" right +[ 13, 62 ] = ascii (fp) : "./hrtfs/elev-25/L-25e050a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e050a.dat" right +[ 13, 63 ] = ascii (fp) : "./hrtfs/elev-25/L-25e045a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e045a.dat" right +[ 13, 64 ] = ascii (fp) : "./hrtfs/elev-25/L-25e040a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e040a.dat" right +[ 13, 65 ] = ascii (fp) : "./hrtfs/elev-25/L-25e035a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e035a.dat" right +[ 13, 66 ] = ascii (fp) : "./hrtfs/elev-25/L-25e030a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e030a.dat" right +[ 13, 67 ] = ascii (fp) : "./hrtfs/elev-25/L-25e025a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e025a.dat" right +[ 13, 68 ] = ascii (fp) : "./hrtfs/elev-25/L-25e020a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e020a.dat" right +[ 13, 69 ] = ascii (fp) : "./hrtfs/elev-25/L-25e015a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e015a.dat" right +[ 13, 70 ] = ascii (fp) : "./hrtfs/elev-25/L-25e010a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e010a.dat" right +[ 13, 71 ] = ascii (fp) : "./hrtfs/elev-25/L-25e005a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e005a.dat" right -[ 14, 0 ] = ascii (fp) : "./hrtfs/elev-20/L-20e000a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e000a.dat right -[ 14, 1 ] = ascii (fp) : "./hrtfs/elev-20/L-20e355a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e355a.dat right -[ 14, 2 ] = ascii (fp) : "./hrtfs/elev-20/L-20e350a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e350a.dat right -[ 14, 3 ] = ascii (fp) : "./hrtfs/elev-20/L-20e345a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e345a.dat right -[ 14, 4 ] = ascii (fp) : "./hrtfs/elev-20/L-20e340a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e340a.dat right -[ 14, 5 ] = ascii (fp) : "./hrtfs/elev-20/L-20e335a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e335a.dat right -[ 14, 6 ] = ascii (fp) : "./hrtfs/elev-20/L-20e330a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e330a.dat right -[ 14, 7 ] = ascii (fp) : "./hrtfs/elev-20/L-20e325a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e325a.dat right -[ 14, 8 ] = ascii (fp) : "./hrtfs/elev-20/L-20e320a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e320a.dat right -[ 14, 9 ] = ascii (fp) : "./hrtfs/elev-20/L-20e315a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e315a.dat right -[ 14, 10 ] = ascii (fp) : "./hrtfs/elev-20/L-20e310a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e310a.dat right -[ 14, 11 ] = ascii (fp) : "./hrtfs/elev-20/L-20e305a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e305a.dat right -[ 14, 12 ] = ascii (fp) : "./hrtfs/elev-20/L-20e300a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e300a.dat right -[ 14, 13 ] = ascii (fp) : "./hrtfs/elev-20/L-20e295a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e295a.dat right -[ 14, 14 ] = ascii (fp) : "./hrtfs/elev-20/L-20e290a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e290a.dat right -[ 14, 15 ] = ascii (fp) : "./hrtfs/elev-20/L-20e285a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e285a.dat right -[ 14, 16 ] = ascii (fp) : "./hrtfs/elev-20/L-20e280a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e280a.dat right -[ 14, 17 ] = ascii (fp) : "./hrtfs/elev-20/L-20e275a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e275a.dat right -[ 14, 18 ] = ascii (fp) : "./hrtfs/elev-20/L-20e270a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e270a.dat right -[ 14, 19 ] = ascii (fp) : "./hrtfs/elev-20/L-20e265a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e265a.dat right -[ 14, 20 ] = ascii (fp) : "./hrtfs/elev-20/L-20e260a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e260a.dat right -[ 14, 21 ] = ascii (fp) : "./hrtfs/elev-20/L-20e255a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e255a.dat right -[ 14, 22 ] = ascii (fp) : "./hrtfs/elev-20/L-20e250a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e250a.dat right -[ 14, 23 ] = ascii (fp) : "./hrtfs/elev-20/L-20e245a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e245a.dat right -[ 14, 24 ] = ascii (fp) : "./hrtfs/elev-20/L-20e240a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e240a.dat right -[ 14, 25 ] = ascii (fp) : "./hrtfs/elev-20/L-20e235a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e235a.dat right -[ 14, 26 ] = ascii (fp) : "./hrtfs/elev-20/L-20e230a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e230a.dat right -[ 14, 27 ] = ascii (fp) : "./hrtfs/elev-20/L-20e225a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e225a.dat right -[ 14, 28 ] = ascii (fp) : "./hrtfs/elev-20/L-20e220a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e220a.dat right -[ 14, 29 ] = ascii (fp) : "./hrtfs/elev-20/L-20e215a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e215a.dat right -[ 14, 30 ] = ascii (fp) : "./hrtfs/elev-20/L-20e210a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e210a.dat right -[ 14, 31 ] = ascii (fp) : "./hrtfs/elev-20/L-20e205a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e205a.dat right -[ 14, 32 ] = ascii (fp) : "./hrtfs/elev-20/L-20e200a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e200a.dat right -[ 14, 33 ] = ascii (fp) : "./hrtfs/elev-20/L-20e195a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e195a.dat right -[ 14, 34 ] = ascii (fp) : "./hrtfs/elev-20/L-20e190a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e190a.dat right -[ 14, 35 ] = ascii (fp) : "./hrtfs/elev-20/L-20e185a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e185a.dat right -[ 14, 36 ] = ascii (fp) : "./hrtfs/elev-20/L-20e180a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e180a.dat right -[ 14, 37 ] = ascii (fp) : "./hrtfs/elev-20/L-20e175a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e175a.dat right -[ 14, 38 ] = ascii (fp) : "./hrtfs/elev-20/L-20e170a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e170a.dat right -[ 14, 39 ] = ascii (fp) : "./hrtfs/elev-20/L-20e165a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e165a.dat right -[ 14, 40 ] = ascii (fp) : "./hrtfs/elev-20/L-20e160a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e160a.dat right -[ 14, 41 ] = ascii (fp) : "./hrtfs/elev-20/L-20e155a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e155a.dat right -[ 14, 42 ] = ascii (fp) : "./hrtfs/elev-20/L-20e150a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e150a.dat right -[ 14, 43 ] = ascii (fp) : "./hrtfs/elev-20/L-20e145a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e145a.dat right -[ 14, 44 ] = ascii (fp) : "./hrtfs/elev-20/L-20e140a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e140a.dat right -[ 14, 45 ] = ascii (fp) : "./hrtfs/elev-20/L-20e135a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e135a.dat right -[ 14, 46 ] = ascii (fp) : "./hrtfs/elev-20/L-20e130a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e130a.dat right -[ 14, 47 ] = ascii (fp) : "./hrtfs/elev-20/L-20e125a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e125a.dat right -[ 14, 48 ] = ascii (fp) : "./hrtfs/elev-20/L-20e120a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e120a.dat right -[ 14, 49 ] = ascii (fp) : "./hrtfs/elev-20/L-20e115a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e115a.dat right -[ 14, 50 ] = ascii (fp) : "./hrtfs/elev-20/L-20e110a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e110a.dat right -[ 14, 51 ] = ascii (fp) : "./hrtfs/elev-20/L-20e105a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e105a.dat right -[ 14, 52 ] = ascii (fp) : "./hrtfs/elev-20/L-20e100a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e100a.dat right -[ 14, 53 ] = ascii (fp) : "./hrtfs/elev-20/L-20e095a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e095a.dat right -[ 14, 54 ] = ascii (fp) : "./hrtfs/elev-20/L-20e090a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e090a.dat right -[ 14, 55 ] = ascii (fp) : "./hrtfs/elev-20/L-20e085a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e085a.dat right -[ 14, 56 ] = ascii (fp) : "./hrtfs/elev-20/L-20e080a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e080a.dat right -[ 14, 57 ] = ascii (fp) : "./hrtfs/elev-20/L-20e075a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e075a.dat right -[ 14, 58 ] = ascii (fp) : "./hrtfs/elev-20/L-20e070a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e070a.dat right -[ 14, 59 ] = ascii (fp) : "./hrtfs/elev-20/L-20e065a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e065a.dat right -[ 14, 60 ] = ascii (fp) : "./hrtfs/elev-20/L-20e060a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e060a.dat right -[ 14, 61 ] = ascii (fp) : "./hrtfs/elev-20/L-20e055a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e055a.dat right -[ 14, 62 ] = ascii (fp) : "./hrtfs/elev-20/L-20e050a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e050a.dat right -[ 14, 63 ] = ascii (fp) : "./hrtfs/elev-20/L-20e045a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e045a.dat right -[ 14, 64 ] = ascii (fp) : "./hrtfs/elev-20/L-20e040a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e040a.dat right -[ 14, 65 ] = ascii (fp) : "./hrtfs/elev-20/L-20e035a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e035a.dat right -[ 14, 66 ] = ascii (fp) : "./hrtfs/elev-20/L-20e030a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e030a.dat right -[ 14, 67 ] = ascii (fp) : "./hrtfs/elev-20/L-20e025a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e025a.dat right -[ 14, 68 ] = ascii (fp) : "./hrtfs/elev-20/L-20e020a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e020a.dat right -[ 14, 69 ] = ascii (fp) : "./hrtfs/elev-20/L-20e015a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e015a.dat right -[ 14, 70 ] = ascii (fp) : "./hrtfs/elev-20/L-20e010a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e010a.dat right -[ 14, 71 ] = ascii (fp) : "./hrtfs/elev-20/L-20e005a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e005a.dat right +[ 14, 0 ] = ascii (fp) : "./hrtfs/elev-20/L-20e000a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e000a.dat" right +[ 14, 1 ] = ascii (fp) : "./hrtfs/elev-20/L-20e355a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e355a.dat" right +[ 14, 2 ] = ascii (fp) : "./hrtfs/elev-20/L-20e350a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e350a.dat" right +[ 14, 3 ] = ascii (fp) : "./hrtfs/elev-20/L-20e345a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e345a.dat" right +[ 14, 4 ] = ascii (fp) : "./hrtfs/elev-20/L-20e340a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e340a.dat" right +[ 14, 5 ] = ascii (fp) : "./hrtfs/elev-20/L-20e335a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e335a.dat" right +[ 14, 6 ] = ascii (fp) : "./hrtfs/elev-20/L-20e330a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e330a.dat" right +[ 14, 7 ] = ascii (fp) : "./hrtfs/elev-20/L-20e325a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e325a.dat" right +[ 14, 8 ] = ascii (fp) : "./hrtfs/elev-20/L-20e320a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e320a.dat" right +[ 14, 9 ] = ascii (fp) : "./hrtfs/elev-20/L-20e315a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e315a.dat" right +[ 14, 10 ] = ascii (fp) : "./hrtfs/elev-20/L-20e310a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e310a.dat" right +[ 14, 11 ] = ascii (fp) : "./hrtfs/elev-20/L-20e305a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e305a.dat" right +[ 14, 12 ] = ascii (fp) : "./hrtfs/elev-20/L-20e300a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e300a.dat" right +[ 14, 13 ] = ascii (fp) : "./hrtfs/elev-20/L-20e295a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e295a.dat" right +[ 14, 14 ] = ascii (fp) : "./hrtfs/elev-20/L-20e290a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e290a.dat" right +[ 14, 15 ] = ascii (fp) : "./hrtfs/elev-20/L-20e285a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e285a.dat" right +[ 14, 16 ] = ascii (fp) : "./hrtfs/elev-20/L-20e280a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e280a.dat" right +[ 14, 17 ] = ascii (fp) : "./hrtfs/elev-20/L-20e275a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e275a.dat" right +[ 14, 18 ] = ascii (fp) : "./hrtfs/elev-20/L-20e270a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e270a.dat" right +[ 14, 19 ] = ascii (fp) : "./hrtfs/elev-20/L-20e265a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e265a.dat" right +[ 14, 20 ] = ascii (fp) : "./hrtfs/elev-20/L-20e260a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e260a.dat" right +[ 14, 21 ] = ascii (fp) : "./hrtfs/elev-20/L-20e255a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e255a.dat" right +[ 14, 22 ] = ascii (fp) : "./hrtfs/elev-20/L-20e250a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e250a.dat" right +[ 14, 23 ] = ascii (fp) : "./hrtfs/elev-20/L-20e245a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e245a.dat" right +[ 14, 24 ] = ascii (fp) : "./hrtfs/elev-20/L-20e240a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e240a.dat" right +[ 14, 25 ] = ascii (fp) : "./hrtfs/elev-20/L-20e235a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e235a.dat" right +[ 14, 26 ] = ascii (fp) : "./hrtfs/elev-20/L-20e230a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e230a.dat" right +[ 14, 27 ] = ascii (fp) : "./hrtfs/elev-20/L-20e225a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e225a.dat" right +[ 14, 28 ] = ascii (fp) : "./hrtfs/elev-20/L-20e220a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e220a.dat" right +[ 14, 29 ] = ascii (fp) : "./hrtfs/elev-20/L-20e215a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e215a.dat" right +[ 14, 30 ] = ascii (fp) : "./hrtfs/elev-20/L-20e210a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e210a.dat" right +[ 14, 31 ] = ascii (fp) : "./hrtfs/elev-20/L-20e205a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e205a.dat" right +[ 14, 32 ] = ascii (fp) : "./hrtfs/elev-20/L-20e200a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e200a.dat" right +[ 14, 33 ] = ascii (fp) : "./hrtfs/elev-20/L-20e195a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e195a.dat" right +[ 14, 34 ] = ascii (fp) : "./hrtfs/elev-20/L-20e190a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e190a.dat" right +[ 14, 35 ] = ascii (fp) : "./hrtfs/elev-20/L-20e185a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e185a.dat" right +[ 14, 36 ] = ascii (fp) : "./hrtfs/elev-20/L-20e180a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e180a.dat" right +[ 14, 37 ] = ascii (fp) : "./hrtfs/elev-20/L-20e175a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e175a.dat" right +[ 14, 38 ] = ascii (fp) : "./hrtfs/elev-20/L-20e170a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e170a.dat" right +[ 14, 39 ] = ascii (fp) : "./hrtfs/elev-20/L-20e165a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e165a.dat" right +[ 14, 40 ] = ascii (fp) : "./hrtfs/elev-20/L-20e160a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e160a.dat" right +[ 14, 41 ] = ascii (fp) : "./hrtfs/elev-20/L-20e155a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e155a.dat" right +[ 14, 42 ] = ascii (fp) : "./hrtfs/elev-20/L-20e150a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e150a.dat" right +[ 14, 43 ] = ascii (fp) : "./hrtfs/elev-20/L-20e145a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e145a.dat" right +[ 14, 44 ] = ascii (fp) : "./hrtfs/elev-20/L-20e140a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e140a.dat" right +[ 14, 45 ] = ascii (fp) : "./hrtfs/elev-20/L-20e135a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e135a.dat" right +[ 14, 46 ] = ascii (fp) : "./hrtfs/elev-20/L-20e130a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e130a.dat" right +[ 14, 47 ] = ascii (fp) : "./hrtfs/elev-20/L-20e125a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e125a.dat" right +[ 14, 48 ] = ascii (fp) : "./hrtfs/elev-20/L-20e120a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e120a.dat" right +[ 14, 49 ] = ascii (fp) : "./hrtfs/elev-20/L-20e115a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e115a.dat" right +[ 14, 50 ] = ascii (fp) : "./hrtfs/elev-20/L-20e110a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e110a.dat" right +[ 14, 51 ] = ascii (fp) : "./hrtfs/elev-20/L-20e105a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e105a.dat" right +[ 14, 52 ] = ascii (fp) : "./hrtfs/elev-20/L-20e100a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e100a.dat" right +[ 14, 53 ] = ascii (fp) : "./hrtfs/elev-20/L-20e095a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e095a.dat" right +[ 14, 54 ] = ascii (fp) : "./hrtfs/elev-20/L-20e090a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e090a.dat" right +[ 14, 55 ] = ascii (fp) : "./hrtfs/elev-20/L-20e085a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e085a.dat" right +[ 14, 56 ] = ascii (fp) : "./hrtfs/elev-20/L-20e080a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e080a.dat" right +[ 14, 57 ] = ascii (fp) : "./hrtfs/elev-20/L-20e075a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e075a.dat" right +[ 14, 58 ] = ascii (fp) : "./hrtfs/elev-20/L-20e070a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e070a.dat" right +[ 14, 59 ] = ascii (fp) : "./hrtfs/elev-20/L-20e065a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e065a.dat" right +[ 14, 60 ] = ascii (fp) : "./hrtfs/elev-20/L-20e060a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e060a.dat" right +[ 14, 61 ] = ascii (fp) : "./hrtfs/elev-20/L-20e055a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e055a.dat" right +[ 14, 62 ] = ascii (fp) : "./hrtfs/elev-20/L-20e050a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e050a.dat" right +[ 14, 63 ] = ascii (fp) : "./hrtfs/elev-20/L-20e045a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e045a.dat" right +[ 14, 64 ] = ascii (fp) : "./hrtfs/elev-20/L-20e040a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e040a.dat" right +[ 14, 65 ] = ascii (fp) : "./hrtfs/elev-20/L-20e035a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e035a.dat" right +[ 14, 66 ] = ascii (fp) : "./hrtfs/elev-20/L-20e030a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e030a.dat" right +[ 14, 67 ] = ascii (fp) : "./hrtfs/elev-20/L-20e025a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e025a.dat" right +[ 14, 68 ] = ascii (fp) : "./hrtfs/elev-20/L-20e020a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e020a.dat" right +[ 14, 69 ] = ascii (fp) : "./hrtfs/elev-20/L-20e015a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e015a.dat" right +[ 14, 70 ] = ascii (fp) : "./hrtfs/elev-20/L-20e010a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e010a.dat" right +[ 14, 71 ] = ascii (fp) : "./hrtfs/elev-20/L-20e005a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e005a.dat" right -[ 15, 0 ] = ascii (fp) : "./hrtfs/elev-15/L-15e000a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e000a.dat right -[ 15, 1 ] = ascii (fp) : "./hrtfs/elev-15/L-15e355a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e355a.dat right -[ 15, 2 ] = ascii (fp) : "./hrtfs/elev-15/L-15e350a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e350a.dat right -[ 15, 3 ] = ascii (fp) : "./hrtfs/elev-15/L-15e345a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e345a.dat right -[ 15, 4 ] = ascii (fp) : "./hrtfs/elev-15/L-15e340a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e340a.dat right -[ 15, 5 ] = ascii (fp) : "./hrtfs/elev-15/L-15e335a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e335a.dat right -[ 15, 6 ] = ascii (fp) : "./hrtfs/elev-15/L-15e330a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e330a.dat right -[ 15, 7 ] = ascii (fp) : "./hrtfs/elev-15/L-15e325a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e325a.dat right -[ 15, 8 ] = ascii (fp) : "./hrtfs/elev-15/L-15e320a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e320a.dat right -[ 15, 9 ] = ascii (fp) : "./hrtfs/elev-15/L-15e315a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e315a.dat right -[ 15, 10 ] = ascii (fp) : "./hrtfs/elev-15/L-15e310a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e310a.dat right -[ 15, 11 ] = ascii (fp) : "./hrtfs/elev-15/L-15e305a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e305a.dat right -[ 15, 12 ] = ascii (fp) : "./hrtfs/elev-15/L-15e300a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e300a.dat right -[ 15, 13 ] = ascii (fp) : "./hrtfs/elev-15/L-15e295a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e295a.dat right -[ 15, 14 ] = ascii (fp) : "./hrtfs/elev-15/L-15e290a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e290a.dat right -[ 15, 15 ] = ascii (fp) : "./hrtfs/elev-15/L-15e285a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e285a.dat right -[ 15, 16 ] = ascii (fp) : "./hrtfs/elev-15/L-15e280a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e280a.dat right -[ 15, 17 ] = ascii (fp) : "./hrtfs/elev-15/L-15e275a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e275a.dat right -[ 15, 18 ] = ascii (fp) : "./hrtfs/elev-15/L-15e270a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e270a.dat right -[ 15, 19 ] = ascii (fp) : "./hrtfs/elev-15/L-15e265a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e265a.dat right -[ 15, 20 ] = ascii (fp) : "./hrtfs/elev-15/L-15e260a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e260a.dat right -[ 15, 21 ] = ascii (fp) : "./hrtfs/elev-15/L-15e255a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e255a.dat right -[ 15, 22 ] = ascii (fp) : "./hrtfs/elev-15/L-15e250a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e250a.dat right -[ 15, 23 ] = ascii (fp) : "./hrtfs/elev-15/L-15e245a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e245a.dat right -[ 15, 24 ] = ascii (fp) : "./hrtfs/elev-15/L-15e240a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e240a.dat right -[ 15, 25 ] = ascii (fp) : "./hrtfs/elev-15/L-15e235a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e235a.dat right -[ 15, 26 ] = ascii (fp) : "./hrtfs/elev-15/L-15e230a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e230a.dat right -[ 15, 27 ] = ascii (fp) : "./hrtfs/elev-15/L-15e225a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e225a.dat right -[ 15, 28 ] = ascii (fp) : "./hrtfs/elev-15/L-15e220a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e220a.dat right -[ 15, 29 ] = ascii (fp) : "./hrtfs/elev-15/L-15e215a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e215a.dat right -[ 15, 30 ] = ascii (fp) : "./hrtfs/elev-15/L-15e210a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e210a.dat right -[ 15, 31 ] = ascii (fp) : "./hrtfs/elev-15/L-15e205a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e205a.dat right -[ 15, 32 ] = ascii (fp) : "./hrtfs/elev-15/L-15e200a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e200a.dat right -[ 15, 33 ] = ascii (fp) : "./hrtfs/elev-15/L-15e195a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e195a.dat right -[ 15, 34 ] = ascii (fp) : "./hrtfs/elev-15/L-15e190a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e190a.dat right -[ 15, 35 ] = ascii (fp) : "./hrtfs/elev-15/L-15e185a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e185a.dat right -[ 15, 36 ] = ascii (fp) : "./hrtfs/elev-15/L-15e180a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e180a.dat right -[ 15, 37 ] = ascii (fp) : "./hrtfs/elev-15/L-15e175a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e175a.dat right -[ 15, 38 ] = ascii (fp) : "./hrtfs/elev-15/L-15e170a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e170a.dat right -[ 15, 39 ] = ascii (fp) : "./hrtfs/elev-15/L-15e165a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e165a.dat right -[ 15, 40 ] = ascii (fp) : "./hrtfs/elev-15/L-15e160a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e160a.dat right -[ 15, 41 ] = ascii (fp) : "./hrtfs/elev-15/L-15e155a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e155a.dat right -[ 15, 42 ] = ascii (fp) : "./hrtfs/elev-15/L-15e150a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e150a.dat right -[ 15, 43 ] = ascii (fp) : "./hrtfs/elev-15/L-15e145a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e145a.dat right -[ 15, 44 ] = ascii (fp) : "./hrtfs/elev-15/L-15e140a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e140a.dat right -[ 15, 45 ] = ascii (fp) : "./hrtfs/elev-15/L-15e135a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e135a.dat right -[ 15, 46 ] = ascii (fp) : "./hrtfs/elev-15/L-15e130a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e130a.dat right -[ 15, 47 ] = ascii (fp) : "./hrtfs/elev-15/L-15e125a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e125a.dat right -[ 15, 48 ] = ascii (fp) : "./hrtfs/elev-15/L-15e120a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e120a.dat right -[ 15, 49 ] = ascii (fp) : "./hrtfs/elev-15/L-15e115a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e115a.dat right -[ 15, 50 ] = ascii (fp) : "./hrtfs/elev-15/L-15e110a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e110a.dat right -[ 15, 51 ] = ascii (fp) : "./hrtfs/elev-15/L-15e105a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e105a.dat right -[ 15, 52 ] = ascii (fp) : "./hrtfs/elev-15/L-15e100a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e100a.dat right -[ 15, 53 ] = ascii (fp) : "./hrtfs/elev-15/L-15e095a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e095a.dat right -[ 15, 54 ] = ascii (fp) : "./hrtfs/elev-15/L-15e090a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e090a.dat right -[ 15, 55 ] = ascii (fp) : "./hrtfs/elev-15/L-15e085a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e085a.dat right -[ 15, 56 ] = ascii (fp) : "./hrtfs/elev-15/L-15e080a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e080a.dat right -[ 15, 57 ] = ascii (fp) : "./hrtfs/elev-15/L-15e075a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e075a.dat right -[ 15, 58 ] = ascii (fp) : "./hrtfs/elev-15/L-15e070a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e070a.dat right -[ 15, 59 ] = ascii (fp) : "./hrtfs/elev-15/L-15e065a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e065a.dat right -[ 15, 60 ] = ascii (fp) : "./hrtfs/elev-15/L-15e060a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e060a.dat right -[ 15, 61 ] = ascii (fp) : "./hrtfs/elev-15/L-15e055a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e055a.dat right -[ 15, 62 ] = ascii (fp) : "./hrtfs/elev-15/L-15e050a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e050a.dat right -[ 15, 63 ] = ascii (fp) : "./hrtfs/elev-15/L-15e045a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e045a.dat right -[ 15, 64 ] = ascii (fp) : "./hrtfs/elev-15/L-15e040a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e040a.dat right -[ 15, 65 ] = ascii (fp) : "./hrtfs/elev-15/L-15e035a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e035a.dat right -[ 15, 66 ] = ascii (fp) : "./hrtfs/elev-15/L-15e030a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e030a.dat right -[ 15, 67 ] = ascii (fp) : "./hrtfs/elev-15/L-15e025a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e025a.dat right -[ 15, 68 ] = ascii (fp) : "./hrtfs/elev-15/L-15e020a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e020a.dat right -[ 15, 69 ] = ascii (fp) : "./hrtfs/elev-15/L-15e015a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e015a.dat right -[ 15, 70 ] = ascii (fp) : "./hrtfs/elev-15/L-15e010a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e010a.dat right -[ 15, 71 ] = ascii (fp) : "./hrtfs/elev-15/L-15e005a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e005a.dat right +[ 15, 0 ] = ascii (fp) : "./hrtfs/elev-15/L-15e000a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e000a.dat" right +[ 15, 1 ] = ascii (fp) : "./hrtfs/elev-15/L-15e355a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e355a.dat" right +[ 15, 2 ] = ascii (fp) : "./hrtfs/elev-15/L-15e350a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e350a.dat" right +[ 15, 3 ] = ascii (fp) : "./hrtfs/elev-15/L-15e345a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e345a.dat" right +[ 15, 4 ] = ascii (fp) : "./hrtfs/elev-15/L-15e340a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e340a.dat" right +[ 15, 5 ] = ascii (fp) : "./hrtfs/elev-15/L-15e335a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e335a.dat" right +[ 15, 6 ] = ascii (fp) : "./hrtfs/elev-15/L-15e330a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e330a.dat" right +[ 15, 7 ] = ascii (fp) : "./hrtfs/elev-15/L-15e325a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e325a.dat" right +[ 15, 8 ] = ascii (fp) : "./hrtfs/elev-15/L-15e320a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e320a.dat" right +[ 15, 9 ] = ascii (fp) : "./hrtfs/elev-15/L-15e315a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e315a.dat" right +[ 15, 10 ] = ascii (fp) : "./hrtfs/elev-15/L-15e310a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e310a.dat" right +[ 15, 11 ] = ascii (fp) : "./hrtfs/elev-15/L-15e305a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e305a.dat" right +[ 15, 12 ] = ascii (fp) : "./hrtfs/elev-15/L-15e300a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e300a.dat" right +[ 15, 13 ] = ascii (fp) : "./hrtfs/elev-15/L-15e295a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e295a.dat" right +[ 15, 14 ] = ascii (fp) : "./hrtfs/elev-15/L-15e290a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e290a.dat" right +[ 15, 15 ] = ascii (fp) : "./hrtfs/elev-15/L-15e285a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e285a.dat" right +[ 15, 16 ] = ascii (fp) : "./hrtfs/elev-15/L-15e280a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e280a.dat" right +[ 15, 17 ] = ascii (fp) : "./hrtfs/elev-15/L-15e275a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e275a.dat" right +[ 15, 18 ] = ascii (fp) : "./hrtfs/elev-15/L-15e270a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e270a.dat" right +[ 15, 19 ] = ascii (fp) : "./hrtfs/elev-15/L-15e265a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e265a.dat" right +[ 15, 20 ] = ascii (fp) : "./hrtfs/elev-15/L-15e260a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e260a.dat" right +[ 15, 21 ] = ascii (fp) : "./hrtfs/elev-15/L-15e255a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e255a.dat" right +[ 15, 22 ] = ascii (fp) : "./hrtfs/elev-15/L-15e250a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e250a.dat" right +[ 15, 23 ] = ascii (fp) : "./hrtfs/elev-15/L-15e245a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e245a.dat" right +[ 15, 24 ] = ascii (fp) : "./hrtfs/elev-15/L-15e240a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e240a.dat" right +[ 15, 25 ] = ascii (fp) : "./hrtfs/elev-15/L-15e235a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e235a.dat" right +[ 15, 26 ] = ascii (fp) : "./hrtfs/elev-15/L-15e230a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e230a.dat" right +[ 15, 27 ] = ascii (fp) : "./hrtfs/elev-15/L-15e225a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e225a.dat" right +[ 15, 28 ] = ascii (fp) : "./hrtfs/elev-15/L-15e220a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e220a.dat" right +[ 15, 29 ] = ascii (fp) : "./hrtfs/elev-15/L-15e215a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e215a.dat" right +[ 15, 30 ] = ascii (fp) : "./hrtfs/elev-15/L-15e210a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e210a.dat" right +[ 15, 31 ] = ascii (fp) : "./hrtfs/elev-15/L-15e205a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e205a.dat" right +[ 15, 32 ] = ascii (fp) : "./hrtfs/elev-15/L-15e200a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e200a.dat" right +[ 15, 33 ] = ascii (fp) : "./hrtfs/elev-15/L-15e195a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e195a.dat" right +[ 15, 34 ] = ascii (fp) : "./hrtfs/elev-15/L-15e190a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e190a.dat" right +[ 15, 35 ] = ascii (fp) : "./hrtfs/elev-15/L-15e185a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e185a.dat" right +[ 15, 36 ] = ascii (fp) : "./hrtfs/elev-15/L-15e180a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e180a.dat" right +[ 15, 37 ] = ascii (fp) : "./hrtfs/elev-15/L-15e175a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e175a.dat" right +[ 15, 38 ] = ascii (fp) : "./hrtfs/elev-15/L-15e170a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e170a.dat" right +[ 15, 39 ] = ascii (fp) : "./hrtfs/elev-15/L-15e165a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e165a.dat" right +[ 15, 40 ] = ascii (fp) : "./hrtfs/elev-15/L-15e160a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e160a.dat" right +[ 15, 41 ] = ascii (fp) : "./hrtfs/elev-15/L-15e155a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e155a.dat" right +[ 15, 42 ] = ascii (fp) : "./hrtfs/elev-15/L-15e150a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e150a.dat" right +[ 15, 43 ] = ascii (fp) : "./hrtfs/elev-15/L-15e145a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e145a.dat" right +[ 15, 44 ] = ascii (fp) : "./hrtfs/elev-15/L-15e140a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e140a.dat" right +[ 15, 45 ] = ascii (fp) : "./hrtfs/elev-15/L-15e135a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e135a.dat" right +[ 15, 46 ] = ascii (fp) : "./hrtfs/elev-15/L-15e130a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e130a.dat" right +[ 15, 47 ] = ascii (fp) : "./hrtfs/elev-15/L-15e125a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e125a.dat" right +[ 15, 48 ] = ascii (fp) : "./hrtfs/elev-15/L-15e120a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e120a.dat" right +[ 15, 49 ] = ascii (fp) : "./hrtfs/elev-15/L-15e115a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e115a.dat" right +[ 15, 50 ] = ascii (fp) : "./hrtfs/elev-15/L-15e110a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e110a.dat" right +[ 15, 51 ] = ascii (fp) : "./hrtfs/elev-15/L-15e105a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e105a.dat" right +[ 15, 52 ] = ascii (fp) : "./hrtfs/elev-15/L-15e100a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e100a.dat" right +[ 15, 53 ] = ascii (fp) : "./hrtfs/elev-15/L-15e095a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e095a.dat" right +[ 15, 54 ] = ascii (fp) : "./hrtfs/elev-15/L-15e090a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e090a.dat" right +[ 15, 55 ] = ascii (fp) : "./hrtfs/elev-15/L-15e085a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e085a.dat" right +[ 15, 56 ] = ascii (fp) : "./hrtfs/elev-15/L-15e080a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e080a.dat" right +[ 15, 57 ] = ascii (fp) : "./hrtfs/elev-15/L-15e075a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e075a.dat" right +[ 15, 58 ] = ascii (fp) : "./hrtfs/elev-15/L-15e070a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e070a.dat" right +[ 15, 59 ] = ascii (fp) : "./hrtfs/elev-15/L-15e065a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e065a.dat" right +[ 15, 60 ] = ascii (fp) : "./hrtfs/elev-15/L-15e060a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e060a.dat" right +[ 15, 61 ] = ascii (fp) : "./hrtfs/elev-15/L-15e055a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e055a.dat" right +[ 15, 62 ] = ascii (fp) : "./hrtfs/elev-15/L-15e050a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e050a.dat" right +[ 15, 63 ] = ascii (fp) : "./hrtfs/elev-15/L-15e045a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e045a.dat" right +[ 15, 64 ] = ascii (fp) : "./hrtfs/elev-15/L-15e040a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e040a.dat" right +[ 15, 65 ] = ascii (fp) : "./hrtfs/elev-15/L-15e035a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e035a.dat" right +[ 15, 66 ] = ascii (fp) : "./hrtfs/elev-15/L-15e030a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e030a.dat" right +[ 15, 67 ] = ascii (fp) : "./hrtfs/elev-15/L-15e025a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e025a.dat" right +[ 15, 68 ] = ascii (fp) : "./hrtfs/elev-15/L-15e020a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e020a.dat" right +[ 15, 69 ] = ascii (fp) : "./hrtfs/elev-15/L-15e015a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e015a.dat" right +[ 15, 70 ] = ascii (fp) : "./hrtfs/elev-15/L-15e010a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e010a.dat" right +[ 15, 71 ] = ascii (fp) : "./hrtfs/elev-15/L-15e005a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e005a.dat" right -[ 16, 0 ] = ascii (fp) : "./hrtfs/elev-10/L-10e000a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e000a.dat right -[ 16, 1 ] = ascii (fp) : "./hrtfs/elev-10/L-10e355a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e355a.dat right -[ 16, 2 ] = ascii (fp) : "./hrtfs/elev-10/L-10e350a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e350a.dat right -[ 16, 3 ] = ascii (fp) : "./hrtfs/elev-10/L-10e345a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e345a.dat right -[ 16, 4 ] = ascii (fp) : "./hrtfs/elev-10/L-10e340a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e340a.dat right -[ 16, 5 ] = ascii (fp) : "./hrtfs/elev-10/L-10e335a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e335a.dat right -[ 16, 6 ] = ascii (fp) : "./hrtfs/elev-10/L-10e330a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e330a.dat right -[ 16, 7 ] = ascii (fp) : "./hrtfs/elev-10/L-10e325a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e325a.dat right -[ 16, 8 ] = ascii (fp) : "./hrtfs/elev-10/L-10e320a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e320a.dat right -[ 16, 9 ] = ascii (fp) : "./hrtfs/elev-10/L-10e315a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e315a.dat right -[ 16, 10 ] = ascii (fp) : "./hrtfs/elev-10/L-10e310a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e310a.dat right -[ 16, 11 ] = ascii (fp) : "./hrtfs/elev-10/L-10e305a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e305a.dat right -[ 16, 12 ] = ascii (fp) : "./hrtfs/elev-10/L-10e300a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e300a.dat right -[ 16, 13 ] = ascii (fp) : "./hrtfs/elev-10/L-10e295a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e295a.dat right -[ 16, 14 ] = ascii (fp) : "./hrtfs/elev-10/L-10e290a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e290a.dat right -[ 16, 15 ] = ascii (fp) : "./hrtfs/elev-10/L-10e285a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e285a.dat right -[ 16, 16 ] = ascii (fp) : "./hrtfs/elev-10/L-10e280a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e280a.dat right -[ 16, 17 ] = ascii (fp) : "./hrtfs/elev-10/L-10e275a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e275a.dat right -[ 16, 18 ] = ascii (fp) : "./hrtfs/elev-10/L-10e270a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e270a.dat right -[ 16, 19 ] = ascii (fp) : "./hrtfs/elev-10/L-10e265a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e265a.dat right -[ 16, 20 ] = ascii (fp) : "./hrtfs/elev-10/L-10e260a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e260a.dat right -[ 16, 21 ] = ascii (fp) : "./hrtfs/elev-10/L-10e255a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e255a.dat right -[ 16, 22 ] = ascii (fp) : "./hrtfs/elev-10/L-10e250a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e250a.dat right -[ 16, 23 ] = ascii (fp) : "./hrtfs/elev-10/L-10e245a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e245a.dat right -[ 16, 24 ] = ascii (fp) : "./hrtfs/elev-10/L-10e240a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e240a.dat right -[ 16, 25 ] = ascii (fp) : "./hrtfs/elev-10/L-10e235a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e235a.dat right -[ 16, 26 ] = ascii (fp) : "./hrtfs/elev-10/L-10e230a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e230a.dat right -[ 16, 27 ] = ascii (fp) : "./hrtfs/elev-10/L-10e225a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e225a.dat right -[ 16, 28 ] = ascii (fp) : "./hrtfs/elev-10/L-10e220a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e220a.dat right -[ 16, 29 ] = ascii (fp) : "./hrtfs/elev-10/L-10e215a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e215a.dat right -[ 16, 30 ] = ascii (fp) : "./hrtfs/elev-10/L-10e210a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e210a.dat right -[ 16, 31 ] = ascii (fp) : "./hrtfs/elev-10/L-10e205a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e205a.dat right -[ 16, 32 ] = ascii (fp) : "./hrtfs/elev-10/L-10e200a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e200a.dat right -[ 16, 33 ] = ascii (fp) : "./hrtfs/elev-10/L-10e195a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e195a.dat right -[ 16, 34 ] = ascii (fp) : "./hrtfs/elev-10/L-10e190a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e190a.dat right -[ 16, 35 ] = ascii (fp) : "./hrtfs/elev-10/L-10e185a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e185a.dat right -[ 16, 36 ] = ascii (fp) : "./hrtfs/elev-10/L-10e180a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e180a.dat right -[ 16, 37 ] = ascii (fp) : "./hrtfs/elev-10/L-10e175a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e175a.dat right -[ 16, 38 ] = ascii (fp) : "./hrtfs/elev-10/L-10e170a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e170a.dat right -[ 16, 39 ] = ascii (fp) : "./hrtfs/elev-10/L-10e165a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e165a.dat right -[ 16, 40 ] = ascii (fp) : "./hrtfs/elev-10/L-10e160a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e160a.dat right -[ 16, 41 ] = ascii (fp) : "./hrtfs/elev-10/L-10e155a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e155a.dat right -[ 16, 42 ] = ascii (fp) : "./hrtfs/elev-10/L-10e150a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e150a.dat right -[ 16, 43 ] = ascii (fp) : "./hrtfs/elev-10/L-10e145a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e145a.dat right -[ 16, 44 ] = ascii (fp) : "./hrtfs/elev-10/L-10e140a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e140a.dat right -[ 16, 45 ] = ascii (fp) : "./hrtfs/elev-10/L-10e135a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e135a.dat right -[ 16, 46 ] = ascii (fp) : "./hrtfs/elev-10/L-10e130a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e130a.dat right -[ 16, 47 ] = ascii (fp) : "./hrtfs/elev-10/L-10e125a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e125a.dat right -[ 16, 48 ] = ascii (fp) : "./hrtfs/elev-10/L-10e120a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e120a.dat right -[ 16, 49 ] = ascii (fp) : "./hrtfs/elev-10/L-10e115a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e115a.dat right -[ 16, 50 ] = ascii (fp) : "./hrtfs/elev-10/L-10e110a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e110a.dat right -[ 16, 51 ] = ascii (fp) : "./hrtfs/elev-10/L-10e105a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e105a.dat right -[ 16, 52 ] = ascii (fp) : "./hrtfs/elev-10/L-10e100a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e100a.dat right -[ 16, 53 ] = ascii (fp) : "./hrtfs/elev-10/L-10e095a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e095a.dat right -[ 16, 54 ] = ascii (fp) : "./hrtfs/elev-10/L-10e090a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e090a.dat right -[ 16, 55 ] = ascii (fp) : "./hrtfs/elev-10/L-10e085a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e085a.dat right -[ 16, 56 ] = ascii (fp) : "./hrtfs/elev-10/L-10e080a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e080a.dat right -[ 16, 57 ] = ascii (fp) : "./hrtfs/elev-10/L-10e075a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e075a.dat right -[ 16, 58 ] = ascii (fp) : "./hrtfs/elev-10/L-10e070a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e070a.dat right -[ 16, 59 ] = ascii (fp) : "./hrtfs/elev-10/L-10e065a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e065a.dat right -[ 16, 60 ] = ascii (fp) : "./hrtfs/elev-10/L-10e060a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e060a.dat right -[ 16, 61 ] = ascii (fp) : "./hrtfs/elev-10/L-10e055a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e055a.dat right -[ 16, 62 ] = ascii (fp) : "./hrtfs/elev-10/L-10e050a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e050a.dat right -[ 16, 63 ] = ascii (fp) : "./hrtfs/elev-10/L-10e045a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e045a.dat right -[ 16, 64 ] = ascii (fp) : "./hrtfs/elev-10/L-10e040a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e040a.dat right -[ 16, 65 ] = ascii (fp) : "./hrtfs/elev-10/L-10e035a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e035a.dat right -[ 16, 66 ] = ascii (fp) : "./hrtfs/elev-10/L-10e030a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e030a.dat right -[ 16, 67 ] = ascii (fp) : "./hrtfs/elev-10/L-10e025a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e025a.dat right -[ 16, 68 ] = ascii (fp) : "./hrtfs/elev-10/L-10e020a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e020a.dat right -[ 16, 69 ] = ascii (fp) : "./hrtfs/elev-10/L-10e015a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e015a.dat right -[ 16, 70 ] = ascii (fp) : "./hrtfs/elev-10/L-10e010a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e010a.dat right -[ 16, 71 ] = ascii (fp) : "./hrtfs/elev-10/L-10e005a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e005a.dat right +[ 16, 0 ] = ascii (fp) : "./hrtfs/elev-10/L-10e000a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e000a.dat" right +[ 16, 1 ] = ascii (fp) : "./hrtfs/elev-10/L-10e355a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e355a.dat" right +[ 16, 2 ] = ascii (fp) : "./hrtfs/elev-10/L-10e350a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e350a.dat" right +[ 16, 3 ] = ascii (fp) : "./hrtfs/elev-10/L-10e345a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e345a.dat" right +[ 16, 4 ] = ascii (fp) : "./hrtfs/elev-10/L-10e340a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e340a.dat" right +[ 16, 5 ] = ascii (fp) : "./hrtfs/elev-10/L-10e335a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e335a.dat" right +[ 16, 6 ] = ascii (fp) : "./hrtfs/elev-10/L-10e330a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e330a.dat" right +[ 16, 7 ] = ascii (fp) : "./hrtfs/elev-10/L-10e325a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e325a.dat" right +[ 16, 8 ] = ascii (fp) : "./hrtfs/elev-10/L-10e320a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e320a.dat" right +[ 16, 9 ] = ascii (fp) : "./hrtfs/elev-10/L-10e315a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e315a.dat" right +[ 16, 10 ] = ascii (fp) : "./hrtfs/elev-10/L-10e310a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e310a.dat" right +[ 16, 11 ] = ascii (fp) : "./hrtfs/elev-10/L-10e305a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e305a.dat" right +[ 16, 12 ] = ascii (fp) : "./hrtfs/elev-10/L-10e300a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e300a.dat" right +[ 16, 13 ] = ascii (fp) : "./hrtfs/elev-10/L-10e295a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e295a.dat" right +[ 16, 14 ] = ascii (fp) : "./hrtfs/elev-10/L-10e290a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e290a.dat" right +[ 16, 15 ] = ascii (fp) : "./hrtfs/elev-10/L-10e285a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e285a.dat" right +[ 16, 16 ] = ascii (fp) : "./hrtfs/elev-10/L-10e280a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e280a.dat" right +[ 16, 17 ] = ascii (fp) : "./hrtfs/elev-10/L-10e275a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e275a.dat" right +[ 16, 18 ] = ascii (fp) : "./hrtfs/elev-10/L-10e270a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e270a.dat" right +[ 16, 19 ] = ascii (fp) : "./hrtfs/elev-10/L-10e265a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e265a.dat" right +[ 16, 20 ] = ascii (fp) : "./hrtfs/elev-10/L-10e260a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e260a.dat" right +[ 16, 21 ] = ascii (fp) : "./hrtfs/elev-10/L-10e255a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e255a.dat" right +[ 16, 22 ] = ascii (fp) : "./hrtfs/elev-10/L-10e250a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e250a.dat" right +[ 16, 23 ] = ascii (fp) : "./hrtfs/elev-10/L-10e245a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e245a.dat" right +[ 16, 24 ] = ascii (fp) : "./hrtfs/elev-10/L-10e240a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e240a.dat" right +[ 16, 25 ] = ascii (fp) : "./hrtfs/elev-10/L-10e235a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e235a.dat" right +[ 16, 26 ] = ascii (fp) : "./hrtfs/elev-10/L-10e230a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e230a.dat" right +[ 16, 27 ] = ascii (fp) : "./hrtfs/elev-10/L-10e225a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e225a.dat" right +[ 16, 28 ] = ascii (fp) : "./hrtfs/elev-10/L-10e220a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e220a.dat" right +[ 16, 29 ] = ascii (fp) : "./hrtfs/elev-10/L-10e215a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e215a.dat" right +[ 16, 30 ] = ascii (fp) : "./hrtfs/elev-10/L-10e210a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e210a.dat" right +[ 16, 31 ] = ascii (fp) : "./hrtfs/elev-10/L-10e205a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e205a.dat" right +[ 16, 32 ] = ascii (fp) : "./hrtfs/elev-10/L-10e200a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e200a.dat" right +[ 16, 33 ] = ascii (fp) : "./hrtfs/elev-10/L-10e195a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e195a.dat" right +[ 16, 34 ] = ascii (fp) : "./hrtfs/elev-10/L-10e190a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e190a.dat" right +[ 16, 35 ] = ascii (fp) : "./hrtfs/elev-10/L-10e185a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e185a.dat" right +[ 16, 36 ] = ascii (fp) : "./hrtfs/elev-10/L-10e180a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e180a.dat" right +[ 16, 37 ] = ascii (fp) : "./hrtfs/elev-10/L-10e175a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e175a.dat" right +[ 16, 38 ] = ascii (fp) : "./hrtfs/elev-10/L-10e170a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e170a.dat" right +[ 16, 39 ] = ascii (fp) : "./hrtfs/elev-10/L-10e165a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e165a.dat" right +[ 16, 40 ] = ascii (fp) : "./hrtfs/elev-10/L-10e160a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e160a.dat" right +[ 16, 41 ] = ascii (fp) : "./hrtfs/elev-10/L-10e155a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e155a.dat" right +[ 16, 42 ] = ascii (fp) : "./hrtfs/elev-10/L-10e150a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e150a.dat" right +[ 16, 43 ] = ascii (fp) : "./hrtfs/elev-10/L-10e145a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e145a.dat" right +[ 16, 44 ] = ascii (fp) : "./hrtfs/elev-10/L-10e140a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e140a.dat" right +[ 16, 45 ] = ascii (fp) : "./hrtfs/elev-10/L-10e135a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e135a.dat" right +[ 16, 46 ] = ascii (fp) : "./hrtfs/elev-10/L-10e130a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e130a.dat" right +[ 16, 47 ] = ascii (fp) : "./hrtfs/elev-10/L-10e125a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e125a.dat" right +[ 16, 48 ] = ascii (fp) : "./hrtfs/elev-10/L-10e120a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e120a.dat" right +[ 16, 49 ] = ascii (fp) : "./hrtfs/elev-10/L-10e115a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e115a.dat" right +[ 16, 50 ] = ascii (fp) : "./hrtfs/elev-10/L-10e110a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e110a.dat" right +[ 16, 51 ] = ascii (fp) : "./hrtfs/elev-10/L-10e105a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e105a.dat" right +[ 16, 52 ] = ascii (fp) : "./hrtfs/elev-10/L-10e100a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e100a.dat" right +[ 16, 53 ] = ascii (fp) : "./hrtfs/elev-10/L-10e095a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e095a.dat" right +[ 16, 54 ] = ascii (fp) : "./hrtfs/elev-10/L-10e090a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e090a.dat" right +[ 16, 55 ] = ascii (fp) : "./hrtfs/elev-10/L-10e085a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e085a.dat" right +[ 16, 56 ] = ascii (fp) : "./hrtfs/elev-10/L-10e080a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e080a.dat" right +[ 16, 57 ] = ascii (fp) : "./hrtfs/elev-10/L-10e075a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e075a.dat" right +[ 16, 58 ] = ascii (fp) : "./hrtfs/elev-10/L-10e070a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e070a.dat" right +[ 16, 59 ] = ascii (fp) : "./hrtfs/elev-10/L-10e065a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e065a.dat" right +[ 16, 60 ] = ascii (fp) : "./hrtfs/elev-10/L-10e060a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e060a.dat" right +[ 16, 61 ] = ascii (fp) : "./hrtfs/elev-10/L-10e055a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e055a.dat" right +[ 16, 62 ] = ascii (fp) : "./hrtfs/elev-10/L-10e050a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e050a.dat" right +[ 16, 63 ] = ascii (fp) : "./hrtfs/elev-10/L-10e045a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e045a.dat" right +[ 16, 64 ] = ascii (fp) : "./hrtfs/elev-10/L-10e040a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e040a.dat" right +[ 16, 65 ] = ascii (fp) : "./hrtfs/elev-10/L-10e035a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e035a.dat" right +[ 16, 66 ] = ascii (fp) : "./hrtfs/elev-10/L-10e030a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e030a.dat" right +[ 16, 67 ] = ascii (fp) : "./hrtfs/elev-10/L-10e025a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e025a.dat" right +[ 16, 68 ] = ascii (fp) : "./hrtfs/elev-10/L-10e020a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e020a.dat" right +[ 16, 69 ] = ascii (fp) : "./hrtfs/elev-10/L-10e015a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e015a.dat" right +[ 16, 70 ] = ascii (fp) : "./hrtfs/elev-10/L-10e010a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e010a.dat" right +[ 16, 71 ] = ascii (fp) : "./hrtfs/elev-10/L-10e005a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e005a.dat" right -[ 17, 0 ] = ascii (fp) : "./hrtfs/elev-5/L-5e000a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e000a.dat right -[ 17, 1 ] = ascii (fp) : "./hrtfs/elev-5/L-5e355a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e355a.dat right -[ 17, 2 ] = ascii (fp) : "./hrtfs/elev-5/L-5e350a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e350a.dat right -[ 17, 3 ] = ascii (fp) : "./hrtfs/elev-5/L-5e345a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e345a.dat right -[ 17, 4 ] = ascii (fp) : "./hrtfs/elev-5/L-5e340a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e340a.dat right -[ 17, 5 ] = ascii (fp) : "./hrtfs/elev-5/L-5e335a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e335a.dat right -[ 17, 6 ] = ascii (fp) : "./hrtfs/elev-5/L-5e330a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e330a.dat right -[ 17, 7 ] = ascii (fp) : "./hrtfs/elev-5/L-5e325a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e325a.dat right -[ 17, 8 ] = ascii (fp) : "./hrtfs/elev-5/L-5e320a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e320a.dat right -[ 17, 9 ] = ascii (fp) : "./hrtfs/elev-5/L-5e315a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e315a.dat right -[ 17, 10 ] = ascii (fp) : "./hrtfs/elev-5/L-5e310a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e310a.dat right -[ 17, 11 ] = ascii (fp) : "./hrtfs/elev-5/L-5e305a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e305a.dat right -[ 17, 12 ] = ascii (fp) : "./hrtfs/elev-5/L-5e300a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e300a.dat right -[ 17, 13 ] = ascii (fp) : "./hrtfs/elev-5/L-5e295a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e295a.dat right -[ 17, 14 ] = ascii (fp) : "./hrtfs/elev-5/L-5e290a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e290a.dat right -[ 17, 15 ] = ascii (fp) : "./hrtfs/elev-5/L-5e285a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e285a.dat right -[ 17, 16 ] = ascii (fp) : "./hrtfs/elev-5/L-5e280a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e280a.dat right -[ 17, 17 ] = ascii (fp) : "./hrtfs/elev-5/L-5e275a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e275a.dat right -[ 17, 18 ] = ascii (fp) : "./hrtfs/elev-5/L-5e270a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e270a.dat right -[ 17, 19 ] = ascii (fp) : "./hrtfs/elev-5/L-5e265a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e265a.dat right -[ 17, 20 ] = ascii (fp) : "./hrtfs/elev-5/L-5e260a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e260a.dat right -[ 17, 21 ] = ascii (fp) : "./hrtfs/elev-5/L-5e255a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e255a.dat right -[ 17, 22 ] = ascii (fp) : "./hrtfs/elev-5/L-5e250a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e250a.dat right -[ 17, 23 ] = ascii (fp) : "./hrtfs/elev-5/L-5e245a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e245a.dat right -[ 17, 24 ] = ascii (fp) : "./hrtfs/elev-5/L-5e240a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e240a.dat right -[ 17, 25 ] = ascii (fp) : "./hrtfs/elev-5/L-5e235a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e235a.dat right -[ 17, 26 ] = ascii (fp) : "./hrtfs/elev-5/L-5e230a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e230a.dat right -[ 17, 27 ] = ascii (fp) : "./hrtfs/elev-5/L-5e225a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e225a.dat right -[ 17, 28 ] = ascii (fp) : "./hrtfs/elev-5/L-5e220a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e220a.dat right -[ 17, 29 ] = ascii (fp) : "./hrtfs/elev-5/L-5e215a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e215a.dat right -[ 17, 30 ] = ascii (fp) : "./hrtfs/elev-5/L-5e210a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e210a.dat right -[ 17, 31 ] = ascii (fp) : "./hrtfs/elev-5/L-5e205a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e205a.dat right -[ 17, 32 ] = ascii (fp) : "./hrtfs/elev-5/L-5e200a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e200a.dat right -[ 17, 33 ] = ascii (fp) : "./hrtfs/elev-5/L-5e195a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e195a.dat right -[ 17, 34 ] = ascii (fp) : "./hrtfs/elev-5/L-5e190a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e190a.dat right -[ 17, 35 ] = ascii (fp) : "./hrtfs/elev-5/L-5e185a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e185a.dat right -[ 17, 36 ] = ascii (fp) : "./hrtfs/elev-5/L-5e180a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e180a.dat right -[ 17, 37 ] = ascii (fp) : "./hrtfs/elev-5/L-5e175a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e175a.dat right -[ 17, 38 ] = ascii (fp) : "./hrtfs/elev-5/L-5e170a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e170a.dat right -[ 17, 39 ] = ascii (fp) : "./hrtfs/elev-5/L-5e165a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e165a.dat right -[ 17, 40 ] = ascii (fp) : "./hrtfs/elev-5/L-5e160a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e160a.dat right -[ 17, 41 ] = ascii (fp) : "./hrtfs/elev-5/L-5e155a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e155a.dat right -[ 17, 42 ] = ascii (fp) : "./hrtfs/elev-5/L-5e150a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e150a.dat right -[ 17, 43 ] = ascii (fp) : "./hrtfs/elev-5/L-5e145a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e145a.dat right -[ 17, 44 ] = ascii (fp) : "./hrtfs/elev-5/L-5e140a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e140a.dat right -[ 17, 45 ] = ascii (fp) : "./hrtfs/elev-5/L-5e135a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e135a.dat right -[ 17, 46 ] = ascii (fp) : "./hrtfs/elev-5/L-5e130a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e130a.dat right -[ 17, 47 ] = ascii (fp) : "./hrtfs/elev-5/L-5e125a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e125a.dat right -[ 17, 48 ] = ascii (fp) : "./hrtfs/elev-5/L-5e120a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e120a.dat right -[ 17, 49 ] = ascii (fp) : "./hrtfs/elev-5/L-5e115a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e115a.dat right -[ 17, 50 ] = ascii (fp) : "./hrtfs/elev-5/L-5e110a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e110a.dat right -[ 17, 51 ] = ascii (fp) : "./hrtfs/elev-5/L-5e105a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e105a.dat right -[ 17, 52 ] = ascii (fp) : "./hrtfs/elev-5/L-5e100a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e100a.dat right -[ 17, 53 ] = ascii (fp) : "./hrtfs/elev-5/L-5e095a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e095a.dat right -[ 17, 54 ] = ascii (fp) : "./hrtfs/elev-5/L-5e090a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e090a.dat right -[ 17, 55 ] = ascii (fp) : "./hrtfs/elev-5/L-5e085a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e085a.dat right -[ 17, 56 ] = ascii (fp) : "./hrtfs/elev-5/L-5e080a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e080a.dat right -[ 17, 57 ] = ascii (fp) : "./hrtfs/elev-5/L-5e075a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e075a.dat right -[ 17, 58 ] = ascii (fp) : "./hrtfs/elev-5/L-5e070a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e070a.dat right -[ 17, 59 ] = ascii (fp) : "./hrtfs/elev-5/L-5e065a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e065a.dat right -[ 17, 60 ] = ascii (fp) : "./hrtfs/elev-5/L-5e060a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e060a.dat right -[ 17, 61 ] = ascii (fp) : "./hrtfs/elev-5/L-5e055a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e055a.dat right -[ 17, 62 ] = ascii (fp) : "./hrtfs/elev-5/L-5e050a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e050a.dat right -[ 17, 63 ] = ascii (fp) : "./hrtfs/elev-5/L-5e045a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e045a.dat right -[ 17, 64 ] = ascii (fp) : "./hrtfs/elev-5/L-5e040a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e040a.dat right -[ 17, 65 ] = ascii (fp) : "./hrtfs/elev-5/L-5e035a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e035a.dat right -[ 17, 66 ] = ascii (fp) : "./hrtfs/elev-5/L-5e030a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e030a.dat right -[ 17, 67 ] = ascii (fp) : "./hrtfs/elev-5/L-5e025a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e025a.dat right -[ 17, 68 ] = ascii (fp) : "./hrtfs/elev-5/L-5e020a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e020a.dat right -[ 17, 69 ] = ascii (fp) : "./hrtfs/elev-5/L-5e015a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e015a.dat right -[ 17, 70 ] = ascii (fp) : "./hrtfs/elev-5/L-5e010a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e010a.dat right -[ 17, 71 ] = ascii (fp) : "./hrtfs/elev-5/L-5e005a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e005a.dat right +[ 17, 0 ] = ascii (fp) : "./hrtfs/elev-5/L-5e000a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e000a.dat" right +[ 17, 1 ] = ascii (fp) : "./hrtfs/elev-5/L-5e355a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e355a.dat" right +[ 17, 2 ] = ascii (fp) : "./hrtfs/elev-5/L-5e350a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e350a.dat" right +[ 17, 3 ] = ascii (fp) : "./hrtfs/elev-5/L-5e345a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e345a.dat" right +[ 17, 4 ] = ascii (fp) : "./hrtfs/elev-5/L-5e340a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e340a.dat" right +[ 17, 5 ] = ascii (fp) : "./hrtfs/elev-5/L-5e335a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e335a.dat" right +[ 17, 6 ] = ascii (fp) : "./hrtfs/elev-5/L-5e330a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e330a.dat" right +[ 17, 7 ] = ascii (fp) : "./hrtfs/elev-5/L-5e325a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e325a.dat" right +[ 17, 8 ] = ascii (fp) : "./hrtfs/elev-5/L-5e320a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e320a.dat" right +[ 17, 9 ] = ascii (fp) : "./hrtfs/elev-5/L-5e315a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e315a.dat" right +[ 17, 10 ] = ascii (fp) : "./hrtfs/elev-5/L-5e310a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e310a.dat" right +[ 17, 11 ] = ascii (fp) : "./hrtfs/elev-5/L-5e305a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e305a.dat" right +[ 17, 12 ] = ascii (fp) : "./hrtfs/elev-5/L-5e300a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e300a.dat" right +[ 17, 13 ] = ascii (fp) : "./hrtfs/elev-5/L-5e295a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e295a.dat" right +[ 17, 14 ] = ascii (fp) : "./hrtfs/elev-5/L-5e290a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e290a.dat" right +[ 17, 15 ] = ascii (fp) : "./hrtfs/elev-5/L-5e285a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e285a.dat" right +[ 17, 16 ] = ascii (fp) : "./hrtfs/elev-5/L-5e280a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e280a.dat" right +[ 17, 17 ] = ascii (fp) : "./hrtfs/elev-5/L-5e275a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e275a.dat" right +[ 17, 18 ] = ascii (fp) : "./hrtfs/elev-5/L-5e270a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e270a.dat" right +[ 17, 19 ] = ascii (fp) : "./hrtfs/elev-5/L-5e265a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e265a.dat" right +[ 17, 20 ] = ascii (fp) : "./hrtfs/elev-5/L-5e260a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e260a.dat" right +[ 17, 21 ] = ascii (fp) : "./hrtfs/elev-5/L-5e255a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e255a.dat" right +[ 17, 22 ] = ascii (fp) : "./hrtfs/elev-5/L-5e250a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e250a.dat" right +[ 17, 23 ] = ascii (fp) : "./hrtfs/elev-5/L-5e245a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e245a.dat" right +[ 17, 24 ] = ascii (fp) : "./hrtfs/elev-5/L-5e240a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e240a.dat" right +[ 17, 25 ] = ascii (fp) : "./hrtfs/elev-5/L-5e235a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e235a.dat" right +[ 17, 26 ] = ascii (fp) : "./hrtfs/elev-5/L-5e230a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e230a.dat" right +[ 17, 27 ] = ascii (fp) : "./hrtfs/elev-5/L-5e225a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e225a.dat" right +[ 17, 28 ] = ascii (fp) : "./hrtfs/elev-5/L-5e220a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e220a.dat" right +[ 17, 29 ] = ascii (fp) : "./hrtfs/elev-5/L-5e215a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e215a.dat" right +[ 17, 30 ] = ascii (fp) : "./hrtfs/elev-5/L-5e210a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e210a.dat" right +[ 17, 31 ] = ascii (fp) : "./hrtfs/elev-5/L-5e205a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e205a.dat" right +[ 17, 32 ] = ascii (fp) : "./hrtfs/elev-5/L-5e200a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e200a.dat" right +[ 17, 33 ] = ascii (fp) : "./hrtfs/elev-5/L-5e195a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e195a.dat" right +[ 17, 34 ] = ascii (fp) : "./hrtfs/elev-5/L-5e190a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e190a.dat" right +[ 17, 35 ] = ascii (fp) : "./hrtfs/elev-5/L-5e185a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e185a.dat" right +[ 17, 36 ] = ascii (fp) : "./hrtfs/elev-5/L-5e180a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e180a.dat" right +[ 17, 37 ] = ascii (fp) : "./hrtfs/elev-5/L-5e175a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e175a.dat" right +[ 17, 38 ] = ascii (fp) : "./hrtfs/elev-5/L-5e170a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e170a.dat" right +[ 17, 39 ] = ascii (fp) : "./hrtfs/elev-5/L-5e165a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e165a.dat" right +[ 17, 40 ] = ascii (fp) : "./hrtfs/elev-5/L-5e160a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e160a.dat" right +[ 17, 41 ] = ascii (fp) : "./hrtfs/elev-5/L-5e155a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e155a.dat" right +[ 17, 42 ] = ascii (fp) : "./hrtfs/elev-5/L-5e150a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e150a.dat" right +[ 17, 43 ] = ascii (fp) : "./hrtfs/elev-5/L-5e145a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e145a.dat" right +[ 17, 44 ] = ascii (fp) : "./hrtfs/elev-5/L-5e140a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e140a.dat" right +[ 17, 45 ] = ascii (fp) : "./hrtfs/elev-5/L-5e135a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e135a.dat" right +[ 17, 46 ] = ascii (fp) : "./hrtfs/elev-5/L-5e130a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e130a.dat" right +[ 17, 47 ] = ascii (fp) : "./hrtfs/elev-5/L-5e125a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e125a.dat" right +[ 17, 48 ] = ascii (fp) : "./hrtfs/elev-5/L-5e120a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e120a.dat" right +[ 17, 49 ] = ascii (fp) : "./hrtfs/elev-5/L-5e115a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e115a.dat" right +[ 17, 50 ] = ascii (fp) : "./hrtfs/elev-5/L-5e110a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e110a.dat" right +[ 17, 51 ] = ascii (fp) : "./hrtfs/elev-5/L-5e105a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e105a.dat" right +[ 17, 52 ] = ascii (fp) : "./hrtfs/elev-5/L-5e100a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e100a.dat" right +[ 17, 53 ] = ascii (fp) : "./hrtfs/elev-5/L-5e095a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e095a.dat" right +[ 17, 54 ] = ascii (fp) : "./hrtfs/elev-5/L-5e090a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e090a.dat" right +[ 17, 55 ] = ascii (fp) : "./hrtfs/elev-5/L-5e085a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e085a.dat" right +[ 17, 56 ] = ascii (fp) : "./hrtfs/elev-5/L-5e080a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e080a.dat" right +[ 17, 57 ] = ascii (fp) : "./hrtfs/elev-5/L-5e075a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e075a.dat" right +[ 17, 58 ] = ascii (fp) : "./hrtfs/elev-5/L-5e070a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e070a.dat" right +[ 17, 59 ] = ascii (fp) : "./hrtfs/elev-5/L-5e065a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e065a.dat" right +[ 17, 60 ] = ascii (fp) : "./hrtfs/elev-5/L-5e060a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e060a.dat" right +[ 17, 61 ] = ascii (fp) : "./hrtfs/elev-5/L-5e055a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e055a.dat" right +[ 17, 62 ] = ascii (fp) : "./hrtfs/elev-5/L-5e050a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e050a.dat" right +[ 17, 63 ] = ascii (fp) : "./hrtfs/elev-5/L-5e045a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e045a.dat" right +[ 17, 64 ] = ascii (fp) : "./hrtfs/elev-5/L-5e040a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e040a.dat" right +[ 17, 65 ] = ascii (fp) : "./hrtfs/elev-5/L-5e035a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e035a.dat" right +[ 17, 66 ] = ascii (fp) : "./hrtfs/elev-5/L-5e030a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e030a.dat" right +[ 17, 67 ] = ascii (fp) : "./hrtfs/elev-5/L-5e025a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e025a.dat" right +[ 17, 68 ] = ascii (fp) : "./hrtfs/elev-5/L-5e020a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e020a.dat" right +[ 17, 69 ] = ascii (fp) : "./hrtfs/elev-5/L-5e015a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e015a.dat" right +[ 17, 70 ] = ascii (fp) : "./hrtfs/elev-5/L-5e010a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e010a.dat" right +[ 17, 71 ] = ascii (fp) : "./hrtfs/elev-5/L-5e005a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e005a.dat" right -[ 18, 0 ] = ascii (fp) : "./hrtfs/elev0/L0e000a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e000a.dat right -[ 18, 1 ] = ascii (fp) : "./hrtfs/elev0/L0e355a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e355a.dat right -[ 18, 2 ] = ascii (fp) : "./hrtfs/elev0/L0e350a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e350a.dat right -[ 18, 3 ] = ascii (fp) : "./hrtfs/elev0/L0e345a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e345a.dat right -[ 18, 4 ] = ascii (fp) : "./hrtfs/elev0/L0e340a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e340a.dat right -[ 18, 5 ] = ascii (fp) : "./hrtfs/elev0/L0e335a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e335a.dat right -[ 18, 6 ] = ascii (fp) : "./hrtfs/elev0/L0e330a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e330a.dat right -[ 18, 7 ] = ascii (fp) : "./hrtfs/elev0/L0e325a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e325a.dat right -[ 18, 8 ] = ascii (fp) : "./hrtfs/elev0/L0e320a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e320a.dat right -[ 18, 9 ] = ascii (fp) : "./hrtfs/elev0/L0e315a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e315a.dat right -[ 18, 10 ] = ascii (fp) : "./hrtfs/elev0/L0e310a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e310a.dat right -[ 18, 11 ] = ascii (fp) : "./hrtfs/elev0/L0e305a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e305a.dat right -[ 18, 12 ] = ascii (fp) : "./hrtfs/elev0/L0e300a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e300a.dat right -[ 18, 13 ] = ascii (fp) : "./hrtfs/elev0/L0e295a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e295a.dat right -[ 18, 14 ] = ascii (fp) : "./hrtfs/elev0/L0e290a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e290a.dat right -[ 18, 15 ] = ascii (fp) : "./hrtfs/elev0/L0e285a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e285a.dat right -[ 18, 16 ] = ascii (fp) : "./hrtfs/elev0/L0e280a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e280a.dat right -[ 18, 17 ] = ascii (fp) : "./hrtfs/elev0/L0e275a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e275a.dat right -[ 18, 18 ] = ascii (fp) : "./hrtfs/elev0/L0e270a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e270a.dat right -[ 18, 19 ] = ascii (fp) : "./hrtfs/elev0/L0e265a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e265a.dat right -[ 18, 20 ] = ascii (fp) : "./hrtfs/elev0/L0e260a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e260a.dat right -[ 18, 21 ] = ascii (fp) : "./hrtfs/elev0/L0e255a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e255a.dat right -[ 18, 22 ] = ascii (fp) : "./hrtfs/elev0/L0e250a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e250a.dat right -[ 18, 23 ] = ascii (fp) : "./hrtfs/elev0/L0e245a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e245a.dat right -[ 18, 24 ] = ascii (fp) : "./hrtfs/elev0/L0e240a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e240a.dat right -[ 18, 25 ] = ascii (fp) : "./hrtfs/elev0/L0e235a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e235a.dat right -[ 18, 26 ] = ascii (fp) : "./hrtfs/elev0/L0e230a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e230a.dat right -[ 18, 27 ] = ascii (fp) : "./hrtfs/elev0/L0e225a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e225a.dat right -[ 18, 28 ] = ascii (fp) : "./hrtfs/elev0/L0e220a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e220a.dat right -[ 18, 29 ] = ascii (fp) : "./hrtfs/elev0/L0e215a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e215a.dat right -[ 18, 30 ] = ascii (fp) : "./hrtfs/elev0/L0e210a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e210a.dat right -[ 18, 31 ] = ascii (fp) : "./hrtfs/elev0/L0e205a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e205a.dat right -[ 18, 32 ] = ascii (fp) : "./hrtfs/elev0/L0e200a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e200a.dat right -[ 18, 33 ] = ascii (fp) : "./hrtfs/elev0/L0e195a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e195a.dat right -[ 18, 34 ] = ascii (fp) : "./hrtfs/elev0/L0e190a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e190a.dat right -[ 18, 35 ] = ascii (fp) : "./hrtfs/elev0/L0e185a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e185a.dat right -[ 18, 36 ] = ascii (fp) : "./hrtfs/elev0/L0e180a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e180a.dat right -[ 18, 37 ] = ascii (fp) : "./hrtfs/elev0/L0e175a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e175a.dat right -[ 18, 38 ] = ascii (fp) : "./hrtfs/elev0/L0e170a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e170a.dat right -[ 18, 39 ] = ascii (fp) : "./hrtfs/elev0/L0e165a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e165a.dat right -[ 18, 40 ] = ascii (fp) : "./hrtfs/elev0/L0e160a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e160a.dat right -[ 18, 41 ] = ascii (fp) : "./hrtfs/elev0/L0e155a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e155a.dat right -[ 18, 42 ] = ascii (fp) : "./hrtfs/elev0/L0e150a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e150a.dat right -[ 18, 43 ] = ascii (fp) : "./hrtfs/elev0/L0e145a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e145a.dat right -[ 18, 44 ] = ascii (fp) : "./hrtfs/elev0/L0e140a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e140a.dat right -[ 18, 45 ] = ascii (fp) : "./hrtfs/elev0/L0e135a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e135a.dat right -[ 18, 46 ] = ascii (fp) : "./hrtfs/elev0/L0e130a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e130a.dat right -[ 18, 47 ] = ascii (fp) : "./hrtfs/elev0/L0e125a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e125a.dat right -[ 18, 48 ] = ascii (fp) : "./hrtfs/elev0/L0e120a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e120a.dat right -[ 18, 49 ] = ascii (fp) : "./hrtfs/elev0/L0e115a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e115a.dat right -[ 18, 50 ] = ascii (fp) : "./hrtfs/elev0/L0e110a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e110a.dat right -[ 18, 51 ] = ascii (fp) : "./hrtfs/elev0/L0e105a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e105a.dat right -[ 18, 52 ] = ascii (fp) : "./hrtfs/elev0/L0e100a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e100a.dat right -[ 18, 53 ] = ascii (fp) : "./hrtfs/elev0/L0e095a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e095a.dat right -[ 18, 54 ] = ascii (fp) : "./hrtfs/elev0/L0e090a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e090a.dat right -[ 18, 55 ] = ascii (fp) : "./hrtfs/elev0/L0e085a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e085a.dat right -[ 18, 56 ] = ascii (fp) : "./hrtfs/elev0/L0e080a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e080a.dat right -[ 18, 57 ] = ascii (fp) : "./hrtfs/elev0/L0e075a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e075a.dat right -[ 18, 58 ] = ascii (fp) : "./hrtfs/elev0/L0e070a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e070a.dat right -[ 18, 59 ] = ascii (fp) : "./hrtfs/elev0/L0e065a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e065a.dat right -[ 18, 60 ] = ascii (fp) : "./hrtfs/elev0/L0e060a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e060a.dat right -[ 18, 61 ] = ascii (fp) : "./hrtfs/elev0/L0e055a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e055a.dat right -[ 18, 62 ] = ascii (fp) : "./hrtfs/elev0/L0e050a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e050a.dat right -[ 18, 63 ] = ascii (fp) : "./hrtfs/elev0/L0e045a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e045a.dat right -[ 18, 64 ] = ascii (fp) : "./hrtfs/elev0/L0e040a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e040a.dat right -[ 18, 65 ] = ascii (fp) : "./hrtfs/elev0/L0e035a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e035a.dat right -[ 18, 66 ] = ascii (fp) : "./hrtfs/elev0/L0e030a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e030a.dat right -[ 18, 67 ] = ascii (fp) : "./hrtfs/elev0/L0e025a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e025a.dat right -[ 18, 68 ] = ascii (fp) : "./hrtfs/elev0/L0e020a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e020a.dat right -[ 18, 69 ] = ascii (fp) : "./hrtfs/elev0/L0e015a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e015a.dat right -[ 18, 70 ] = ascii (fp) : "./hrtfs/elev0/L0e010a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e010a.dat right -[ 18, 71 ] = ascii (fp) : "./hrtfs/elev0/L0e005a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e005a.dat right +[ 18, 0 ] = ascii (fp) : "./hrtfs/elev0/L0e000a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e000a.dat" right +[ 18, 1 ] = ascii (fp) : "./hrtfs/elev0/L0e355a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e355a.dat" right +[ 18, 2 ] = ascii (fp) : "./hrtfs/elev0/L0e350a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e350a.dat" right +[ 18, 3 ] = ascii (fp) : "./hrtfs/elev0/L0e345a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e345a.dat" right +[ 18, 4 ] = ascii (fp) : "./hrtfs/elev0/L0e340a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e340a.dat" right +[ 18, 5 ] = ascii (fp) : "./hrtfs/elev0/L0e335a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e335a.dat" right +[ 18, 6 ] = ascii (fp) : "./hrtfs/elev0/L0e330a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e330a.dat" right +[ 18, 7 ] = ascii (fp) : "./hrtfs/elev0/L0e325a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e325a.dat" right +[ 18, 8 ] = ascii (fp) : "./hrtfs/elev0/L0e320a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e320a.dat" right +[ 18, 9 ] = ascii (fp) : "./hrtfs/elev0/L0e315a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e315a.dat" right +[ 18, 10 ] = ascii (fp) : "./hrtfs/elev0/L0e310a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e310a.dat" right +[ 18, 11 ] = ascii (fp) : "./hrtfs/elev0/L0e305a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e305a.dat" right +[ 18, 12 ] = ascii (fp) : "./hrtfs/elev0/L0e300a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e300a.dat" right +[ 18, 13 ] = ascii (fp) : "./hrtfs/elev0/L0e295a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e295a.dat" right +[ 18, 14 ] = ascii (fp) : "./hrtfs/elev0/L0e290a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e290a.dat" right +[ 18, 15 ] = ascii (fp) : "./hrtfs/elev0/L0e285a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e285a.dat" right +[ 18, 16 ] = ascii (fp) : "./hrtfs/elev0/L0e280a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e280a.dat" right +[ 18, 17 ] = ascii (fp) : "./hrtfs/elev0/L0e275a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e275a.dat" right +[ 18, 18 ] = ascii (fp) : "./hrtfs/elev0/L0e270a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e270a.dat" right +[ 18, 19 ] = ascii (fp) : "./hrtfs/elev0/L0e265a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e265a.dat" right +[ 18, 20 ] = ascii (fp) : "./hrtfs/elev0/L0e260a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e260a.dat" right +[ 18, 21 ] = ascii (fp) : "./hrtfs/elev0/L0e255a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e255a.dat" right +[ 18, 22 ] = ascii (fp) : "./hrtfs/elev0/L0e250a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e250a.dat" right +[ 18, 23 ] = ascii (fp) : "./hrtfs/elev0/L0e245a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e245a.dat" right +[ 18, 24 ] = ascii (fp) : "./hrtfs/elev0/L0e240a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e240a.dat" right +[ 18, 25 ] = ascii (fp) : "./hrtfs/elev0/L0e235a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e235a.dat" right +[ 18, 26 ] = ascii (fp) : "./hrtfs/elev0/L0e230a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e230a.dat" right +[ 18, 27 ] = ascii (fp) : "./hrtfs/elev0/L0e225a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e225a.dat" right +[ 18, 28 ] = ascii (fp) : "./hrtfs/elev0/L0e220a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e220a.dat" right +[ 18, 29 ] = ascii (fp) : "./hrtfs/elev0/L0e215a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e215a.dat" right +[ 18, 30 ] = ascii (fp) : "./hrtfs/elev0/L0e210a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e210a.dat" right +[ 18, 31 ] = ascii (fp) : "./hrtfs/elev0/L0e205a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e205a.dat" right +[ 18, 32 ] = ascii (fp) : "./hrtfs/elev0/L0e200a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e200a.dat" right +[ 18, 33 ] = ascii (fp) : "./hrtfs/elev0/L0e195a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e195a.dat" right +[ 18, 34 ] = ascii (fp) : "./hrtfs/elev0/L0e190a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e190a.dat" right +[ 18, 35 ] = ascii (fp) : "./hrtfs/elev0/L0e185a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e185a.dat" right +[ 18, 36 ] = ascii (fp) : "./hrtfs/elev0/L0e180a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e180a.dat" right +[ 18, 37 ] = ascii (fp) : "./hrtfs/elev0/L0e175a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e175a.dat" right +[ 18, 38 ] = ascii (fp) : "./hrtfs/elev0/L0e170a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e170a.dat" right +[ 18, 39 ] = ascii (fp) : "./hrtfs/elev0/L0e165a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e165a.dat" right +[ 18, 40 ] = ascii (fp) : "./hrtfs/elev0/L0e160a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e160a.dat" right +[ 18, 41 ] = ascii (fp) : "./hrtfs/elev0/L0e155a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e155a.dat" right +[ 18, 42 ] = ascii (fp) : "./hrtfs/elev0/L0e150a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e150a.dat" right +[ 18, 43 ] = ascii (fp) : "./hrtfs/elev0/L0e145a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e145a.dat" right +[ 18, 44 ] = ascii (fp) : "./hrtfs/elev0/L0e140a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e140a.dat" right +[ 18, 45 ] = ascii (fp) : "./hrtfs/elev0/L0e135a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e135a.dat" right +[ 18, 46 ] = ascii (fp) : "./hrtfs/elev0/L0e130a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e130a.dat" right +[ 18, 47 ] = ascii (fp) : "./hrtfs/elev0/L0e125a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e125a.dat" right +[ 18, 48 ] = ascii (fp) : "./hrtfs/elev0/L0e120a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e120a.dat" right +[ 18, 49 ] = ascii (fp) : "./hrtfs/elev0/L0e115a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e115a.dat" right +[ 18, 50 ] = ascii (fp) : "./hrtfs/elev0/L0e110a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e110a.dat" right +[ 18, 51 ] = ascii (fp) : "./hrtfs/elev0/L0e105a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e105a.dat" right +[ 18, 52 ] = ascii (fp) : "./hrtfs/elev0/L0e100a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e100a.dat" right +[ 18, 53 ] = ascii (fp) : "./hrtfs/elev0/L0e095a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e095a.dat" right +[ 18, 54 ] = ascii (fp) : "./hrtfs/elev0/L0e090a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e090a.dat" right +[ 18, 55 ] = ascii (fp) : "./hrtfs/elev0/L0e085a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e085a.dat" right +[ 18, 56 ] = ascii (fp) : "./hrtfs/elev0/L0e080a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e080a.dat" right +[ 18, 57 ] = ascii (fp) : "./hrtfs/elev0/L0e075a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e075a.dat" right +[ 18, 58 ] = ascii (fp) : "./hrtfs/elev0/L0e070a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e070a.dat" right +[ 18, 59 ] = ascii (fp) : "./hrtfs/elev0/L0e065a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e065a.dat" right +[ 18, 60 ] = ascii (fp) : "./hrtfs/elev0/L0e060a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e060a.dat" right +[ 18, 61 ] = ascii (fp) : "./hrtfs/elev0/L0e055a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e055a.dat" right +[ 18, 62 ] = ascii (fp) : "./hrtfs/elev0/L0e050a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e050a.dat" right +[ 18, 63 ] = ascii (fp) : "./hrtfs/elev0/L0e045a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e045a.dat" right +[ 18, 64 ] = ascii (fp) : "./hrtfs/elev0/L0e040a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e040a.dat" right +[ 18, 65 ] = ascii (fp) : "./hrtfs/elev0/L0e035a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e035a.dat" right +[ 18, 66 ] = ascii (fp) : "./hrtfs/elev0/L0e030a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e030a.dat" right +[ 18, 67 ] = ascii (fp) : "./hrtfs/elev0/L0e025a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e025a.dat" right +[ 18, 68 ] = ascii (fp) : "./hrtfs/elev0/L0e020a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e020a.dat" right +[ 18, 69 ] = ascii (fp) : "./hrtfs/elev0/L0e015a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e015a.dat" right +[ 18, 70 ] = ascii (fp) : "./hrtfs/elev0/L0e010a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e010a.dat" right +[ 18, 71 ] = ascii (fp) : "./hrtfs/elev0/L0e005a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e005a.dat" right -[ 19, 0 ] = ascii (fp) : "./hrtfs/elev5/L5e000a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e000a.dat right -[ 19, 1 ] = ascii (fp) : "./hrtfs/elev5/L5e355a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e355a.dat right -[ 19, 2 ] = ascii (fp) : "./hrtfs/elev5/L5e350a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e350a.dat right -[ 19, 3 ] = ascii (fp) : "./hrtfs/elev5/L5e345a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e345a.dat right -[ 19, 4 ] = ascii (fp) : "./hrtfs/elev5/L5e340a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e340a.dat right -[ 19, 5 ] = ascii (fp) : "./hrtfs/elev5/L5e335a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e335a.dat right -[ 19, 6 ] = ascii (fp) : "./hrtfs/elev5/L5e330a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e330a.dat right -[ 19, 7 ] = ascii (fp) : "./hrtfs/elev5/L5e325a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e325a.dat right -[ 19, 8 ] = ascii (fp) : "./hrtfs/elev5/L5e320a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e320a.dat right -[ 19, 9 ] = ascii (fp) : "./hrtfs/elev5/L5e315a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e315a.dat right -[ 19, 10 ] = ascii (fp) : "./hrtfs/elev5/L5e310a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e310a.dat right -[ 19, 11 ] = ascii (fp) : "./hrtfs/elev5/L5e305a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e305a.dat right -[ 19, 12 ] = ascii (fp) : "./hrtfs/elev5/L5e300a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e300a.dat right -[ 19, 13 ] = ascii (fp) : "./hrtfs/elev5/L5e295a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e295a.dat right -[ 19, 14 ] = ascii (fp) : "./hrtfs/elev5/L5e290a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e290a.dat right -[ 19, 15 ] = ascii (fp) : "./hrtfs/elev5/L5e285a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e285a.dat right -[ 19, 16 ] = ascii (fp) : "./hrtfs/elev5/L5e280a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e280a.dat right -[ 19, 17 ] = ascii (fp) : "./hrtfs/elev5/L5e275a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e275a.dat right -[ 19, 18 ] = ascii (fp) : "./hrtfs/elev5/L5e270a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e270a.dat right -[ 19, 19 ] = ascii (fp) : "./hrtfs/elev5/L5e265a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e265a.dat right -[ 19, 20 ] = ascii (fp) : "./hrtfs/elev5/L5e260a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e260a.dat right -[ 19, 21 ] = ascii (fp) : "./hrtfs/elev5/L5e255a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e255a.dat right -[ 19, 22 ] = ascii (fp) : "./hrtfs/elev5/L5e250a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e250a.dat right -[ 19, 23 ] = ascii (fp) : "./hrtfs/elev5/L5e245a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e245a.dat right -[ 19, 24 ] = ascii (fp) : "./hrtfs/elev5/L5e240a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e240a.dat right -[ 19, 25 ] = ascii (fp) : "./hrtfs/elev5/L5e235a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e235a.dat right -[ 19, 26 ] = ascii (fp) : "./hrtfs/elev5/L5e230a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e230a.dat right -[ 19, 27 ] = ascii (fp) : "./hrtfs/elev5/L5e225a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e225a.dat right -[ 19, 28 ] = ascii (fp) : "./hrtfs/elev5/L5e220a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e220a.dat right -[ 19, 29 ] = ascii (fp) : "./hrtfs/elev5/L5e215a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e215a.dat right -[ 19, 30 ] = ascii (fp) : "./hrtfs/elev5/L5e210a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e210a.dat right -[ 19, 31 ] = ascii (fp) : "./hrtfs/elev5/L5e205a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e205a.dat right -[ 19, 32 ] = ascii (fp) : "./hrtfs/elev5/L5e200a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e200a.dat right -[ 19, 33 ] = ascii (fp) : "./hrtfs/elev5/L5e195a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e195a.dat right -[ 19, 34 ] = ascii (fp) : "./hrtfs/elev5/L5e190a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e190a.dat right -[ 19, 35 ] = ascii (fp) : "./hrtfs/elev5/L5e185a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e185a.dat right -[ 19, 36 ] = ascii (fp) : "./hrtfs/elev5/L5e180a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e180a.dat right -[ 19, 37 ] = ascii (fp) : "./hrtfs/elev5/L5e175a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e175a.dat right -[ 19, 38 ] = ascii (fp) : "./hrtfs/elev5/L5e170a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e170a.dat right -[ 19, 39 ] = ascii (fp) : "./hrtfs/elev5/L5e165a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e165a.dat right -[ 19, 40 ] = ascii (fp) : "./hrtfs/elev5/L5e160a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e160a.dat right -[ 19, 41 ] = ascii (fp) : "./hrtfs/elev5/L5e155a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e155a.dat right -[ 19, 42 ] = ascii (fp) : "./hrtfs/elev5/L5e150a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e150a.dat right -[ 19, 43 ] = ascii (fp) : "./hrtfs/elev5/L5e145a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e145a.dat right -[ 19, 44 ] = ascii (fp) : "./hrtfs/elev5/L5e140a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e140a.dat right -[ 19, 45 ] = ascii (fp) : "./hrtfs/elev5/L5e135a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e135a.dat right -[ 19, 46 ] = ascii (fp) : "./hrtfs/elev5/L5e130a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e130a.dat right -[ 19, 47 ] = ascii (fp) : "./hrtfs/elev5/L5e125a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e125a.dat right -[ 19, 48 ] = ascii (fp) : "./hrtfs/elev5/L5e120a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e120a.dat right -[ 19, 49 ] = ascii (fp) : "./hrtfs/elev5/L5e115a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e115a.dat right -[ 19, 50 ] = ascii (fp) : "./hrtfs/elev5/L5e110a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e110a.dat right -[ 19, 51 ] = ascii (fp) : "./hrtfs/elev5/L5e105a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e105a.dat right -[ 19, 52 ] = ascii (fp) : "./hrtfs/elev5/L5e100a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e100a.dat right -[ 19, 53 ] = ascii (fp) : "./hrtfs/elev5/L5e095a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e095a.dat right -[ 19, 54 ] = ascii (fp) : "./hrtfs/elev5/L5e090a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e090a.dat right -[ 19, 55 ] = ascii (fp) : "./hrtfs/elev5/L5e085a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e085a.dat right -[ 19, 56 ] = ascii (fp) : "./hrtfs/elev5/L5e080a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e080a.dat right -[ 19, 57 ] = ascii (fp) : "./hrtfs/elev5/L5e075a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e075a.dat right -[ 19, 58 ] = ascii (fp) : "./hrtfs/elev5/L5e070a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e070a.dat right -[ 19, 59 ] = ascii (fp) : "./hrtfs/elev5/L5e065a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e065a.dat right -[ 19, 60 ] = ascii (fp) : "./hrtfs/elev5/L5e060a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e060a.dat right -[ 19, 61 ] = ascii (fp) : "./hrtfs/elev5/L5e055a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e055a.dat right -[ 19, 62 ] = ascii (fp) : "./hrtfs/elev5/L5e050a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e050a.dat right -[ 19, 63 ] = ascii (fp) : "./hrtfs/elev5/L5e045a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e045a.dat right -[ 19, 64 ] = ascii (fp) : "./hrtfs/elev5/L5e040a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e040a.dat right -[ 19, 65 ] = ascii (fp) : "./hrtfs/elev5/L5e035a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e035a.dat right -[ 19, 66 ] = ascii (fp) : "./hrtfs/elev5/L5e030a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e030a.dat right -[ 19, 67 ] = ascii (fp) : "./hrtfs/elev5/L5e025a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e025a.dat right -[ 19, 68 ] = ascii (fp) : "./hrtfs/elev5/L5e020a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e020a.dat right -[ 19, 69 ] = ascii (fp) : "./hrtfs/elev5/L5e015a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e015a.dat right -[ 19, 70 ] = ascii (fp) : "./hrtfs/elev5/L5e010a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e010a.dat right -[ 19, 71 ] = ascii (fp) : "./hrtfs/elev5/L5e005a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e005a.dat right +[ 19, 0 ] = ascii (fp) : "./hrtfs/elev5/L5e000a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e000a.dat" right +[ 19, 1 ] = ascii (fp) : "./hrtfs/elev5/L5e355a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e355a.dat" right +[ 19, 2 ] = ascii (fp) : "./hrtfs/elev5/L5e350a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e350a.dat" right +[ 19, 3 ] = ascii (fp) : "./hrtfs/elev5/L5e345a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e345a.dat" right +[ 19, 4 ] = ascii (fp) : "./hrtfs/elev5/L5e340a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e340a.dat" right +[ 19, 5 ] = ascii (fp) : "./hrtfs/elev5/L5e335a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e335a.dat" right +[ 19, 6 ] = ascii (fp) : "./hrtfs/elev5/L5e330a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e330a.dat" right +[ 19, 7 ] = ascii (fp) : "./hrtfs/elev5/L5e325a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e325a.dat" right +[ 19, 8 ] = ascii (fp) : "./hrtfs/elev5/L5e320a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e320a.dat" right +[ 19, 9 ] = ascii (fp) : "./hrtfs/elev5/L5e315a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e315a.dat" right +[ 19, 10 ] = ascii (fp) : "./hrtfs/elev5/L5e310a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e310a.dat" right +[ 19, 11 ] = ascii (fp) : "./hrtfs/elev5/L5e305a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e305a.dat" right +[ 19, 12 ] = ascii (fp) : "./hrtfs/elev5/L5e300a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e300a.dat" right +[ 19, 13 ] = ascii (fp) : "./hrtfs/elev5/L5e295a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e295a.dat" right +[ 19, 14 ] = ascii (fp) : "./hrtfs/elev5/L5e290a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e290a.dat" right +[ 19, 15 ] = ascii (fp) : "./hrtfs/elev5/L5e285a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e285a.dat" right +[ 19, 16 ] = ascii (fp) : "./hrtfs/elev5/L5e280a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e280a.dat" right +[ 19, 17 ] = ascii (fp) : "./hrtfs/elev5/L5e275a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e275a.dat" right +[ 19, 18 ] = ascii (fp) : "./hrtfs/elev5/L5e270a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e270a.dat" right +[ 19, 19 ] = ascii (fp) : "./hrtfs/elev5/L5e265a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e265a.dat" right +[ 19, 20 ] = ascii (fp) : "./hrtfs/elev5/L5e260a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e260a.dat" right +[ 19, 21 ] = ascii (fp) : "./hrtfs/elev5/L5e255a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e255a.dat" right +[ 19, 22 ] = ascii (fp) : "./hrtfs/elev5/L5e250a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e250a.dat" right +[ 19, 23 ] = ascii (fp) : "./hrtfs/elev5/L5e245a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e245a.dat" right +[ 19, 24 ] = ascii (fp) : "./hrtfs/elev5/L5e240a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e240a.dat" right +[ 19, 25 ] = ascii (fp) : "./hrtfs/elev5/L5e235a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e235a.dat" right +[ 19, 26 ] = ascii (fp) : "./hrtfs/elev5/L5e230a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e230a.dat" right +[ 19, 27 ] = ascii (fp) : "./hrtfs/elev5/L5e225a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e225a.dat" right +[ 19, 28 ] = ascii (fp) : "./hrtfs/elev5/L5e220a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e220a.dat" right +[ 19, 29 ] = ascii (fp) : "./hrtfs/elev5/L5e215a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e215a.dat" right +[ 19, 30 ] = ascii (fp) : "./hrtfs/elev5/L5e210a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e210a.dat" right +[ 19, 31 ] = ascii (fp) : "./hrtfs/elev5/L5e205a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e205a.dat" right +[ 19, 32 ] = ascii (fp) : "./hrtfs/elev5/L5e200a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e200a.dat" right +[ 19, 33 ] = ascii (fp) : "./hrtfs/elev5/L5e195a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e195a.dat" right +[ 19, 34 ] = ascii (fp) : "./hrtfs/elev5/L5e190a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e190a.dat" right +[ 19, 35 ] = ascii (fp) : "./hrtfs/elev5/L5e185a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e185a.dat" right +[ 19, 36 ] = ascii (fp) : "./hrtfs/elev5/L5e180a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e180a.dat" right +[ 19, 37 ] = ascii (fp) : "./hrtfs/elev5/L5e175a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e175a.dat" right +[ 19, 38 ] = ascii (fp) : "./hrtfs/elev5/L5e170a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e170a.dat" right +[ 19, 39 ] = ascii (fp) : "./hrtfs/elev5/L5e165a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e165a.dat" right +[ 19, 40 ] = ascii (fp) : "./hrtfs/elev5/L5e160a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e160a.dat" right +[ 19, 41 ] = ascii (fp) : "./hrtfs/elev5/L5e155a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e155a.dat" right +[ 19, 42 ] = ascii (fp) : "./hrtfs/elev5/L5e150a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e150a.dat" right +[ 19, 43 ] = ascii (fp) : "./hrtfs/elev5/L5e145a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e145a.dat" right +[ 19, 44 ] = ascii (fp) : "./hrtfs/elev5/L5e140a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e140a.dat" right +[ 19, 45 ] = ascii (fp) : "./hrtfs/elev5/L5e135a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e135a.dat" right +[ 19, 46 ] = ascii (fp) : "./hrtfs/elev5/L5e130a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e130a.dat" right +[ 19, 47 ] = ascii (fp) : "./hrtfs/elev5/L5e125a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e125a.dat" right +[ 19, 48 ] = ascii (fp) : "./hrtfs/elev5/L5e120a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e120a.dat" right +[ 19, 49 ] = ascii (fp) : "./hrtfs/elev5/L5e115a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e115a.dat" right +[ 19, 50 ] = ascii (fp) : "./hrtfs/elev5/L5e110a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e110a.dat" right +[ 19, 51 ] = ascii (fp) : "./hrtfs/elev5/L5e105a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e105a.dat" right +[ 19, 52 ] = ascii (fp) : "./hrtfs/elev5/L5e100a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e100a.dat" right +[ 19, 53 ] = ascii (fp) : "./hrtfs/elev5/L5e095a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e095a.dat" right +[ 19, 54 ] = ascii (fp) : "./hrtfs/elev5/L5e090a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e090a.dat" right +[ 19, 55 ] = ascii (fp) : "./hrtfs/elev5/L5e085a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e085a.dat" right +[ 19, 56 ] = ascii (fp) : "./hrtfs/elev5/L5e080a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e080a.dat" right +[ 19, 57 ] = ascii (fp) : "./hrtfs/elev5/L5e075a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e075a.dat" right +[ 19, 58 ] = ascii (fp) : "./hrtfs/elev5/L5e070a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e070a.dat" right +[ 19, 59 ] = ascii (fp) : "./hrtfs/elev5/L5e065a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e065a.dat" right +[ 19, 60 ] = ascii (fp) : "./hrtfs/elev5/L5e060a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e060a.dat" right +[ 19, 61 ] = ascii (fp) : "./hrtfs/elev5/L5e055a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e055a.dat" right +[ 19, 62 ] = ascii (fp) : "./hrtfs/elev5/L5e050a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e050a.dat" right +[ 19, 63 ] = ascii (fp) : "./hrtfs/elev5/L5e045a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e045a.dat" right +[ 19, 64 ] = ascii (fp) : "./hrtfs/elev5/L5e040a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e040a.dat" right +[ 19, 65 ] = ascii (fp) : "./hrtfs/elev5/L5e035a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e035a.dat" right +[ 19, 66 ] = ascii (fp) : "./hrtfs/elev5/L5e030a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e030a.dat" right +[ 19, 67 ] = ascii (fp) : "./hrtfs/elev5/L5e025a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e025a.dat" right +[ 19, 68 ] = ascii (fp) : "./hrtfs/elev5/L5e020a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e020a.dat" right +[ 19, 69 ] = ascii (fp) : "./hrtfs/elev5/L5e015a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e015a.dat" right +[ 19, 70 ] = ascii (fp) : "./hrtfs/elev5/L5e010a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e010a.dat" right +[ 19, 71 ] = ascii (fp) : "./hrtfs/elev5/L5e005a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e005a.dat" right -[ 20, 0 ] = ascii (fp) : "./hrtfs/elev10/L10e000a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e000a.dat right -[ 20, 1 ] = ascii (fp) : "./hrtfs/elev10/L10e355a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e355a.dat right -[ 20, 2 ] = ascii (fp) : "./hrtfs/elev10/L10e350a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e350a.dat right -[ 20, 3 ] = ascii (fp) : "./hrtfs/elev10/L10e345a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e345a.dat right -[ 20, 4 ] = ascii (fp) : "./hrtfs/elev10/L10e340a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e340a.dat right -[ 20, 5 ] = ascii (fp) : "./hrtfs/elev10/L10e335a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e335a.dat right -[ 20, 6 ] = ascii (fp) : "./hrtfs/elev10/L10e330a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e330a.dat right -[ 20, 7 ] = ascii (fp) : "./hrtfs/elev10/L10e325a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e325a.dat right -[ 20, 8 ] = ascii (fp) : "./hrtfs/elev10/L10e320a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e320a.dat right -[ 20, 9 ] = ascii (fp) : "./hrtfs/elev10/L10e315a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e315a.dat right -[ 20, 10 ] = ascii (fp) : "./hrtfs/elev10/L10e310a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e310a.dat right -[ 20, 11 ] = ascii (fp) : "./hrtfs/elev10/L10e305a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e305a.dat right -[ 20, 12 ] = ascii (fp) : "./hrtfs/elev10/L10e300a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e300a.dat right -[ 20, 13 ] = ascii (fp) : "./hrtfs/elev10/L10e295a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e295a.dat right -[ 20, 14 ] = ascii (fp) : "./hrtfs/elev10/L10e290a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e290a.dat right -[ 20, 15 ] = ascii (fp) : "./hrtfs/elev10/L10e285a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e285a.dat right -[ 20, 16 ] = ascii (fp) : "./hrtfs/elev10/L10e280a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e280a.dat right -[ 20, 17 ] = ascii (fp) : "./hrtfs/elev10/L10e275a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e275a.dat right -[ 20, 18 ] = ascii (fp) : "./hrtfs/elev10/L10e270a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e270a.dat right -[ 20, 19 ] = ascii (fp) : "./hrtfs/elev10/L10e265a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e265a.dat right -[ 20, 20 ] = ascii (fp) : "./hrtfs/elev10/L10e260a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e260a.dat right -[ 20, 21 ] = ascii (fp) : "./hrtfs/elev10/L10e255a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e255a.dat right -[ 20, 22 ] = ascii (fp) : "./hrtfs/elev10/L10e250a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e250a.dat right -[ 20, 23 ] = ascii (fp) : "./hrtfs/elev10/L10e245a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e245a.dat right -[ 20, 24 ] = ascii (fp) : "./hrtfs/elev10/L10e240a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e240a.dat right -[ 20, 25 ] = ascii (fp) : "./hrtfs/elev10/L10e235a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e235a.dat right -[ 20, 26 ] = ascii (fp) : "./hrtfs/elev10/L10e230a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e230a.dat right -[ 20, 27 ] = ascii (fp) : "./hrtfs/elev10/L10e225a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e225a.dat right -[ 20, 28 ] = ascii (fp) : "./hrtfs/elev10/L10e220a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e220a.dat right -[ 20, 29 ] = ascii (fp) : "./hrtfs/elev10/L10e215a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e215a.dat right -[ 20, 30 ] = ascii (fp) : "./hrtfs/elev10/L10e210a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e210a.dat right -[ 20, 31 ] = ascii (fp) : "./hrtfs/elev10/L10e205a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e205a.dat right -[ 20, 32 ] = ascii (fp) : "./hrtfs/elev10/L10e200a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e200a.dat right -[ 20, 33 ] = ascii (fp) : "./hrtfs/elev10/L10e195a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e195a.dat right -[ 20, 34 ] = ascii (fp) : "./hrtfs/elev10/L10e190a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e190a.dat right -[ 20, 35 ] = ascii (fp) : "./hrtfs/elev10/L10e185a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e185a.dat right -[ 20, 36 ] = ascii (fp) : "./hrtfs/elev10/L10e180a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e180a.dat right -[ 20, 37 ] = ascii (fp) : "./hrtfs/elev10/L10e175a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e175a.dat right -[ 20, 38 ] = ascii (fp) : "./hrtfs/elev10/L10e170a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e170a.dat right -[ 20, 39 ] = ascii (fp) : "./hrtfs/elev10/L10e165a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e165a.dat right -[ 20, 40 ] = ascii (fp) : "./hrtfs/elev10/L10e160a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e160a.dat right -[ 20, 41 ] = ascii (fp) : "./hrtfs/elev10/L10e155a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e155a.dat right -[ 20, 42 ] = ascii (fp) : "./hrtfs/elev10/L10e150a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e150a.dat right -[ 20, 43 ] = ascii (fp) : "./hrtfs/elev10/L10e145a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e145a.dat right -[ 20, 44 ] = ascii (fp) : "./hrtfs/elev10/L10e140a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e140a.dat right -[ 20, 45 ] = ascii (fp) : "./hrtfs/elev10/L10e135a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e135a.dat right -[ 20, 46 ] = ascii (fp) : "./hrtfs/elev10/L10e130a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e130a.dat right -[ 20, 47 ] = ascii (fp) : "./hrtfs/elev10/L10e125a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e125a.dat right -[ 20, 48 ] = ascii (fp) : "./hrtfs/elev10/L10e120a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e120a.dat right -[ 20, 49 ] = ascii (fp) : "./hrtfs/elev10/L10e115a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e115a.dat right -[ 20, 50 ] = ascii (fp) : "./hrtfs/elev10/L10e110a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e110a.dat right -[ 20, 51 ] = ascii (fp) : "./hrtfs/elev10/L10e105a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e105a.dat right -[ 20, 52 ] = ascii (fp) : "./hrtfs/elev10/L10e100a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e100a.dat right -[ 20, 53 ] = ascii (fp) : "./hrtfs/elev10/L10e095a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e095a.dat right -[ 20, 54 ] = ascii (fp) : "./hrtfs/elev10/L10e090a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e090a.dat right -[ 20, 55 ] = ascii (fp) : "./hrtfs/elev10/L10e085a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e085a.dat right -[ 20, 56 ] = ascii (fp) : "./hrtfs/elev10/L10e080a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e080a.dat right -[ 20, 57 ] = ascii (fp) : "./hrtfs/elev10/L10e075a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e075a.dat right -[ 20, 58 ] = ascii (fp) : "./hrtfs/elev10/L10e070a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e070a.dat right -[ 20, 59 ] = ascii (fp) : "./hrtfs/elev10/L10e065a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e065a.dat right -[ 20, 60 ] = ascii (fp) : "./hrtfs/elev10/L10e060a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e060a.dat right -[ 20, 61 ] = ascii (fp) : "./hrtfs/elev10/L10e055a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e055a.dat right -[ 20, 62 ] = ascii (fp) : "./hrtfs/elev10/L10e050a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e050a.dat right -[ 20, 63 ] = ascii (fp) : "./hrtfs/elev10/L10e045a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e045a.dat right -[ 20, 64 ] = ascii (fp) : "./hrtfs/elev10/L10e040a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e040a.dat right -[ 20, 65 ] = ascii (fp) : "./hrtfs/elev10/L10e035a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e035a.dat right -[ 20, 66 ] = ascii (fp) : "./hrtfs/elev10/L10e030a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e030a.dat right -[ 20, 67 ] = ascii (fp) : "./hrtfs/elev10/L10e025a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e025a.dat right -[ 20, 68 ] = ascii (fp) : "./hrtfs/elev10/L10e020a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e020a.dat right -[ 20, 69 ] = ascii (fp) : "./hrtfs/elev10/L10e015a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e015a.dat right -[ 20, 70 ] = ascii (fp) : "./hrtfs/elev10/L10e010a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e010a.dat right -[ 20, 71 ] = ascii (fp) : "./hrtfs/elev10/L10e005a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e005a.dat right +[ 20, 0 ] = ascii (fp) : "./hrtfs/elev10/L10e000a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e000a.dat" right +[ 20, 1 ] = ascii (fp) : "./hrtfs/elev10/L10e355a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e355a.dat" right +[ 20, 2 ] = ascii (fp) : "./hrtfs/elev10/L10e350a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e350a.dat" right +[ 20, 3 ] = ascii (fp) : "./hrtfs/elev10/L10e345a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e345a.dat" right +[ 20, 4 ] = ascii (fp) : "./hrtfs/elev10/L10e340a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e340a.dat" right +[ 20, 5 ] = ascii (fp) : "./hrtfs/elev10/L10e335a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e335a.dat" right +[ 20, 6 ] = ascii (fp) : "./hrtfs/elev10/L10e330a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e330a.dat" right +[ 20, 7 ] = ascii (fp) : "./hrtfs/elev10/L10e325a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e325a.dat" right +[ 20, 8 ] = ascii (fp) : "./hrtfs/elev10/L10e320a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e320a.dat" right +[ 20, 9 ] = ascii (fp) : "./hrtfs/elev10/L10e315a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e315a.dat" right +[ 20, 10 ] = ascii (fp) : "./hrtfs/elev10/L10e310a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e310a.dat" right +[ 20, 11 ] = ascii (fp) : "./hrtfs/elev10/L10e305a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e305a.dat" right +[ 20, 12 ] = ascii (fp) : "./hrtfs/elev10/L10e300a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e300a.dat" right +[ 20, 13 ] = ascii (fp) : "./hrtfs/elev10/L10e295a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e295a.dat" right +[ 20, 14 ] = ascii (fp) : "./hrtfs/elev10/L10e290a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e290a.dat" right +[ 20, 15 ] = ascii (fp) : "./hrtfs/elev10/L10e285a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e285a.dat" right +[ 20, 16 ] = ascii (fp) : "./hrtfs/elev10/L10e280a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e280a.dat" right +[ 20, 17 ] = ascii (fp) : "./hrtfs/elev10/L10e275a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e275a.dat" right +[ 20, 18 ] = ascii (fp) : "./hrtfs/elev10/L10e270a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e270a.dat" right +[ 20, 19 ] = ascii (fp) : "./hrtfs/elev10/L10e265a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e265a.dat" right +[ 20, 20 ] = ascii (fp) : "./hrtfs/elev10/L10e260a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e260a.dat" right +[ 20, 21 ] = ascii (fp) : "./hrtfs/elev10/L10e255a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e255a.dat" right +[ 20, 22 ] = ascii (fp) : "./hrtfs/elev10/L10e250a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e250a.dat" right +[ 20, 23 ] = ascii (fp) : "./hrtfs/elev10/L10e245a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e245a.dat" right +[ 20, 24 ] = ascii (fp) : "./hrtfs/elev10/L10e240a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e240a.dat" right +[ 20, 25 ] = ascii (fp) : "./hrtfs/elev10/L10e235a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e235a.dat" right +[ 20, 26 ] = ascii (fp) : "./hrtfs/elev10/L10e230a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e230a.dat" right +[ 20, 27 ] = ascii (fp) : "./hrtfs/elev10/L10e225a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e225a.dat" right +[ 20, 28 ] = ascii (fp) : "./hrtfs/elev10/L10e220a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e220a.dat" right +[ 20, 29 ] = ascii (fp) : "./hrtfs/elev10/L10e215a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e215a.dat" right +[ 20, 30 ] = ascii (fp) : "./hrtfs/elev10/L10e210a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e210a.dat" right +[ 20, 31 ] = ascii (fp) : "./hrtfs/elev10/L10e205a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e205a.dat" right +[ 20, 32 ] = ascii (fp) : "./hrtfs/elev10/L10e200a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e200a.dat" right +[ 20, 33 ] = ascii (fp) : "./hrtfs/elev10/L10e195a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e195a.dat" right +[ 20, 34 ] = ascii (fp) : "./hrtfs/elev10/L10e190a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e190a.dat" right +[ 20, 35 ] = ascii (fp) : "./hrtfs/elev10/L10e185a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e185a.dat" right +[ 20, 36 ] = ascii (fp) : "./hrtfs/elev10/L10e180a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e180a.dat" right +[ 20, 37 ] = ascii (fp) : "./hrtfs/elev10/L10e175a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e175a.dat" right +[ 20, 38 ] = ascii (fp) : "./hrtfs/elev10/L10e170a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e170a.dat" right +[ 20, 39 ] = ascii (fp) : "./hrtfs/elev10/L10e165a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e165a.dat" right +[ 20, 40 ] = ascii (fp) : "./hrtfs/elev10/L10e160a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e160a.dat" right +[ 20, 41 ] = ascii (fp) : "./hrtfs/elev10/L10e155a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e155a.dat" right +[ 20, 42 ] = ascii (fp) : "./hrtfs/elev10/L10e150a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e150a.dat" right +[ 20, 43 ] = ascii (fp) : "./hrtfs/elev10/L10e145a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e145a.dat" right +[ 20, 44 ] = ascii (fp) : "./hrtfs/elev10/L10e140a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e140a.dat" right +[ 20, 45 ] = ascii (fp) : "./hrtfs/elev10/L10e135a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e135a.dat" right +[ 20, 46 ] = ascii (fp) : "./hrtfs/elev10/L10e130a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e130a.dat" right +[ 20, 47 ] = ascii (fp) : "./hrtfs/elev10/L10e125a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e125a.dat" right +[ 20, 48 ] = ascii (fp) : "./hrtfs/elev10/L10e120a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e120a.dat" right +[ 20, 49 ] = ascii (fp) : "./hrtfs/elev10/L10e115a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e115a.dat" right +[ 20, 50 ] = ascii (fp) : "./hrtfs/elev10/L10e110a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e110a.dat" right +[ 20, 51 ] = ascii (fp) : "./hrtfs/elev10/L10e105a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e105a.dat" right +[ 20, 52 ] = ascii (fp) : "./hrtfs/elev10/L10e100a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e100a.dat" right +[ 20, 53 ] = ascii (fp) : "./hrtfs/elev10/L10e095a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e095a.dat" right +[ 20, 54 ] = ascii (fp) : "./hrtfs/elev10/L10e090a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e090a.dat" right +[ 20, 55 ] = ascii (fp) : "./hrtfs/elev10/L10e085a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e085a.dat" right +[ 20, 56 ] = ascii (fp) : "./hrtfs/elev10/L10e080a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e080a.dat" right +[ 20, 57 ] = ascii (fp) : "./hrtfs/elev10/L10e075a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e075a.dat" right +[ 20, 58 ] = ascii (fp) : "./hrtfs/elev10/L10e070a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e070a.dat" right +[ 20, 59 ] = ascii (fp) : "./hrtfs/elev10/L10e065a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e065a.dat" right +[ 20, 60 ] = ascii (fp) : "./hrtfs/elev10/L10e060a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e060a.dat" right +[ 20, 61 ] = ascii (fp) : "./hrtfs/elev10/L10e055a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e055a.dat" right +[ 20, 62 ] = ascii (fp) : "./hrtfs/elev10/L10e050a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e050a.dat" right +[ 20, 63 ] = ascii (fp) : "./hrtfs/elev10/L10e045a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e045a.dat" right +[ 20, 64 ] = ascii (fp) : "./hrtfs/elev10/L10e040a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e040a.dat" right +[ 20, 65 ] = ascii (fp) : "./hrtfs/elev10/L10e035a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e035a.dat" right +[ 20, 66 ] = ascii (fp) : "./hrtfs/elev10/L10e030a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e030a.dat" right +[ 20, 67 ] = ascii (fp) : "./hrtfs/elev10/L10e025a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e025a.dat" right +[ 20, 68 ] = ascii (fp) : "./hrtfs/elev10/L10e020a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e020a.dat" right +[ 20, 69 ] = ascii (fp) : "./hrtfs/elev10/L10e015a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e015a.dat" right +[ 20, 70 ] = ascii (fp) : "./hrtfs/elev10/L10e010a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e010a.dat" right +[ 20, 71 ] = ascii (fp) : "./hrtfs/elev10/L10e005a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e005a.dat" right -[ 21, 0 ] = ascii (fp) : "./hrtfs/elev15/L15e000a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e000a.dat right -[ 21, 1 ] = ascii (fp) : "./hrtfs/elev15/L15e355a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e355a.dat right -[ 21, 2 ] = ascii (fp) : "./hrtfs/elev15/L15e350a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e350a.dat right -[ 21, 3 ] = ascii (fp) : "./hrtfs/elev15/L15e345a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e345a.dat right -[ 21, 4 ] = ascii (fp) : "./hrtfs/elev15/L15e340a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e340a.dat right -[ 21, 5 ] = ascii (fp) : "./hrtfs/elev15/L15e335a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e335a.dat right -[ 21, 6 ] = ascii (fp) : "./hrtfs/elev15/L15e330a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e330a.dat right -[ 21, 7 ] = ascii (fp) : "./hrtfs/elev15/L15e325a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e325a.dat right -[ 21, 8 ] = ascii (fp) : "./hrtfs/elev15/L15e320a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e320a.dat right -[ 21, 9 ] = ascii (fp) : "./hrtfs/elev15/L15e315a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e315a.dat right -[ 21, 10 ] = ascii (fp) : "./hrtfs/elev15/L15e310a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e310a.dat right -[ 21, 11 ] = ascii (fp) : "./hrtfs/elev15/L15e305a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e305a.dat right -[ 21, 12 ] = ascii (fp) : "./hrtfs/elev15/L15e300a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e300a.dat right -[ 21, 13 ] = ascii (fp) : "./hrtfs/elev15/L15e295a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e295a.dat right -[ 21, 14 ] = ascii (fp) : "./hrtfs/elev15/L15e290a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e290a.dat right -[ 21, 15 ] = ascii (fp) : "./hrtfs/elev15/L15e285a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e285a.dat right -[ 21, 16 ] = ascii (fp) : "./hrtfs/elev15/L15e280a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e280a.dat right -[ 21, 17 ] = ascii (fp) : "./hrtfs/elev15/L15e275a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e275a.dat right -[ 21, 18 ] = ascii (fp) : "./hrtfs/elev15/L15e270a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e270a.dat right -[ 21, 19 ] = ascii (fp) : "./hrtfs/elev15/L15e265a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e265a.dat right -[ 21, 20 ] = ascii (fp) : "./hrtfs/elev15/L15e260a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e260a.dat right -[ 21, 21 ] = ascii (fp) : "./hrtfs/elev15/L15e255a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e255a.dat right -[ 21, 22 ] = ascii (fp) : "./hrtfs/elev15/L15e250a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e250a.dat right -[ 21, 23 ] = ascii (fp) : "./hrtfs/elev15/L15e245a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e245a.dat right -[ 21, 24 ] = ascii (fp) : "./hrtfs/elev15/L15e240a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e240a.dat right -[ 21, 25 ] = ascii (fp) : "./hrtfs/elev15/L15e235a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e235a.dat right -[ 21, 26 ] = ascii (fp) : "./hrtfs/elev15/L15e230a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e230a.dat right -[ 21, 27 ] = ascii (fp) : "./hrtfs/elev15/L15e225a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e225a.dat right -[ 21, 28 ] = ascii (fp) : "./hrtfs/elev15/L15e220a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e220a.dat right -[ 21, 29 ] = ascii (fp) : "./hrtfs/elev15/L15e215a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e215a.dat right -[ 21, 30 ] = ascii (fp) : "./hrtfs/elev15/L15e210a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e210a.dat right -[ 21, 31 ] = ascii (fp) : "./hrtfs/elev15/L15e205a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e205a.dat right -[ 21, 32 ] = ascii (fp) : "./hrtfs/elev15/L15e200a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e200a.dat right -[ 21, 33 ] = ascii (fp) : "./hrtfs/elev15/L15e195a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e195a.dat right -[ 21, 34 ] = ascii (fp) : "./hrtfs/elev15/L15e190a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e190a.dat right -[ 21, 35 ] = ascii (fp) : "./hrtfs/elev15/L15e185a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e185a.dat right -[ 21, 36 ] = ascii (fp) : "./hrtfs/elev15/L15e180a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e180a.dat right -[ 21, 37 ] = ascii (fp) : "./hrtfs/elev15/L15e175a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e175a.dat right -[ 21, 38 ] = ascii (fp) : "./hrtfs/elev15/L15e170a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e170a.dat right -[ 21, 39 ] = ascii (fp) : "./hrtfs/elev15/L15e165a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e165a.dat right -[ 21, 40 ] = ascii (fp) : "./hrtfs/elev15/L15e160a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e160a.dat right -[ 21, 41 ] = ascii (fp) : "./hrtfs/elev15/L15e155a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e155a.dat right -[ 21, 42 ] = ascii (fp) : "./hrtfs/elev15/L15e150a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e150a.dat right -[ 21, 43 ] = ascii (fp) : "./hrtfs/elev15/L15e145a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e145a.dat right -[ 21, 44 ] = ascii (fp) : "./hrtfs/elev15/L15e140a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e140a.dat right -[ 21, 45 ] = ascii (fp) : "./hrtfs/elev15/L15e135a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e135a.dat right -[ 21, 46 ] = ascii (fp) : "./hrtfs/elev15/L15e130a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e130a.dat right -[ 21, 47 ] = ascii (fp) : "./hrtfs/elev15/L15e125a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e125a.dat right -[ 21, 48 ] = ascii (fp) : "./hrtfs/elev15/L15e120a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e120a.dat right -[ 21, 49 ] = ascii (fp) : "./hrtfs/elev15/L15e115a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e115a.dat right -[ 21, 50 ] = ascii (fp) : "./hrtfs/elev15/L15e110a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e110a.dat right -[ 21, 51 ] = ascii (fp) : "./hrtfs/elev15/L15e105a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e105a.dat right -[ 21, 52 ] = ascii (fp) : "./hrtfs/elev15/L15e100a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e100a.dat right -[ 21, 53 ] = ascii (fp) : "./hrtfs/elev15/L15e095a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e095a.dat right -[ 21, 54 ] = ascii (fp) : "./hrtfs/elev15/L15e090a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e090a.dat right -[ 21, 55 ] = ascii (fp) : "./hrtfs/elev15/L15e085a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e085a.dat right -[ 21, 56 ] = ascii (fp) : "./hrtfs/elev15/L15e080a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e080a.dat right -[ 21, 57 ] = ascii (fp) : "./hrtfs/elev15/L15e075a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e075a.dat right -[ 21, 58 ] = ascii (fp) : "./hrtfs/elev15/L15e070a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e070a.dat right -[ 21, 59 ] = ascii (fp) : "./hrtfs/elev15/L15e065a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e065a.dat right -[ 21, 60 ] = ascii (fp) : "./hrtfs/elev15/L15e060a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e060a.dat right -[ 21, 61 ] = ascii (fp) : "./hrtfs/elev15/L15e055a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e055a.dat right -[ 21, 62 ] = ascii (fp) : "./hrtfs/elev15/L15e050a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e050a.dat right -[ 21, 63 ] = ascii (fp) : "./hrtfs/elev15/L15e045a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e045a.dat right -[ 21, 64 ] = ascii (fp) : "./hrtfs/elev15/L15e040a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e040a.dat right -[ 21, 65 ] = ascii (fp) : "./hrtfs/elev15/L15e035a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e035a.dat right -[ 21, 66 ] = ascii (fp) : "./hrtfs/elev15/L15e030a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e030a.dat right -[ 21, 67 ] = ascii (fp) : "./hrtfs/elev15/L15e025a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e025a.dat right -[ 21, 68 ] = ascii (fp) : "./hrtfs/elev15/L15e020a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e020a.dat right -[ 21, 69 ] = ascii (fp) : "./hrtfs/elev15/L15e015a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e015a.dat right -[ 21, 70 ] = ascii (fp) : "./hrtfs/elev15/L15e010a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e010a.dat right -[ 21, 71 ] = ascii (fp) : "./hrtfs/elev15/L15e005a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e005a.dat right +[ 21, 0 ] = ascii (fp) : "./hrtfs/elev15/L15e000a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e000a.dat" right +[ 21, 1 ] = ascii (fp) : "./hrtfs/elev15/L15e355a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e355a.dat" right +[ 21, 2 ] = ascii (fp) : "./hrtfs/elev15/L15e350a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e350a.dat" right +[ 21, 3 ] = ascii (fp) : "./hrtfs/elev15/L15e345a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e345a.dat" right +[ 21, 4 ] = ascii (fp) : "./hrtfs/elev15/L15e340a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e340a.dat" right +[ 21, 5 ] = ascii (fp) : "./hrtfs/elev15/L15e335a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e335a.dat" right +[ 21, 6 ] = ascii (fp) : "./hrtfs/elev15/L15e330a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e330a.dat" right +[ 21, 7 ] = ascii (fp) : "./hrtfs/elev15/L15e325a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e325a.dat" right +[ 21, 8 ] = ascii (fp) : "./hrtfs/elev15/L15e320a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e320a.dat" right +[ 21, 9 ] = ascii (fp) : "./hrtfs/elev15/L15e315a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e315a.dat" right +[ 21, 10 ] = ascii (fp) : "./hrtfs/elev15/L15e310a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e310a.dat" right +[ 21, 11 ] = ascii (fp) : "./hrtfs/elev15/L15e305a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e305a.dat" right +[ 21, 12 ] = ascii (fp) : "./hrtfs/elev15/L15e300a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e300a.dat" right +[ 21, 13 ] = ascii (fp) : "./hrtfs/elev15/L15e295a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e295a.dat" right +[ 21, 14 ] = ascii (fp) : "./hrtfs/elev15/L15e290a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e290a.dat" right +[ 21, 15 ] = ascii (fp) : "./hrtfs/elev15/L15e285a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e285a.dat" right +[ 21, 16 ] = ascii (fp) : "./hrtfs/elev15/L15e280a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e280a.dat" right +[ 21, 17 ] = ascii (fp) : "./hrtfs/elev15/L15e275a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e275a.dat" right +[ 21, 18 ] = ascii (fp) : "./hrtfs/elev15/L15e270a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e270a.dat" right +[ 21, 19 ] = ascii (fp) : "./hrtfs/elev15/L15e265a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e265a.dat" right +[ 21, 20 ] = ascii (fp) : "./hrtfs/elev15/L15e260a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e260a.dat" right +[ 21, 21 ] = ascii (fp) : "./hrtfs/elev15/L15e255a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e255a.dat" right +[ 21, 22 ] = ascii (fp) : "./hrtfs/elev15/L15e250a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e250a.dat" right +[ 21, 23 ] = ascii (fp) : "./hrtfs/elev15/L15e245a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e245a.dat" right +[ 21, 24 ] = ascii (fp) : "./hrtfs/elev15/L15e240a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e240a.dat" right +[ 21, 25 ] = ascii (fp) : "./hrtfs/elev15/L15e235a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e235a.dat" right +[ 21, 26 ] = ascii (fp) : "./hrtfs/elev15/L15e230a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e230a.dat" right +[ 21, 27 ] = ascii (fp) : "./hrtfs/elev15/L15e225a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e225a.dat" right +[ 21, 28 ] = ascii (fp) : "./hrtfs/elev15/L15e220a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e220a.dat" right +[ 21, 29 ] = ascii (fp) : "./hrtfs/elev15/L15e215a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e215a.dat" right +[ 21, 30 ] = ascii (fp) : "./hrtfs/elev15/L15e210a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e210a.dat" right +[ 21, 31 ] = ascii (fp) : "./hrtfs/elev15/L15e205a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e205a.dat" right +[ 21, 32 ] = ascii (fp) : "./hrtfs/elev15/L15e200a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e200a.dat" right +[ 21, 33 ] = ascii (fp) : "./hrtfs/elev15/L15e195a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e195a.dat" right +[ 21, 34 ] = ascii (fp) : "./hrtfs/elev15/L15e190a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e190a.dat" right +[ 21, 35 ] = ascii (fp) : "./hrtfs/elev15/L15e185a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e185a.dat" right +[ 21, 36 ] = ascii (fp) : "./hrtfs/elev15/L15e180a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e180a.dat" right +[ 21, 37 ] = ascii (fp) : "./hrtfs/elev15/L15e175a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e175a.dat" right +[ 21, 38 ] = ascii (fp) : "./hrtfs/elev15/L15e170a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e170a.dat" right +[ 21, 39 ] = ascii (fp) : "./hrtfs/elev15/L15e165a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e165a.dat" right +[ 21, 40 ] = ascii (fp) : "./hrtfs/elev15/L15e160a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e160a.dat" right +[ 21, 41 ] = ascii (fp) : "./hrtfs/elev15/L15e155a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e155a.dat" right +[ 21, 42 ] = ascii (fp) : "./hrtfs/elev15/L15e150a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e150a.dat" right +[ 21, 43 ] = ascii (fp) : "./hrtfs/elev15/L15e145a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e145a.dat" right +[ 21, 44 ] = ascii (fp) : "./hrtfs/elev15/L15e140a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e140a.dat" right +[ 21, 45 ] = ascii (fp) : "./hrtfs/elev15/L15e135a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e135a.dat" right +[ 21, 46 ] = ascii (fp) : "./hrtfs/elev15/L15e130a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e130a.dat" right +[ 21, 47 ] = ascii (fp) : "./hrtfs/elev15/L15e125a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e125a.dat" right +[ 21, 48 ] = ascii (fp) : "./hrtfs/elev15/L15e120a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e120a.dat" right +[ 21, 49 ] = ascii (fp) : "./hrtfs/elev15/L15e115a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e115a.dat" right +[ 21, 50 ] = ascii (fp) : "./hrtfs/elev15/L15e110a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e110a.dat" right +[ 21, 51 ] = ascii (fp) : "./hrtfs/elev15/L15e105a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e105a.dat" right +[ 21, 52 ] = ascii (fp) : "./hrtfs/elev15/L15e100a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e100a.dat" right +[ 21, 53 ] = ascii (fp) : "./hrtfs/elev15/L15e095a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e095a.dat" right +[ 21, 54 ] = ascii (fp) : "./hrtfs/elev15/L15e090a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e090a.dat" right +[ 21, 55 ] = ascii (fp) : "./hrtfs/elev15/L15e085a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e085a.dat" right +[ 21, 56 ] = ascii (fp) : "./hrtfs/elev15/L15e080a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e080a.dat" right +[ 21, 57 ] = ascii (fp) : "./hrtfs/elev15/L15e075a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e075a.dat" right +[ 21, 58 ] = ascii (fp) : "./hrtfs/elev15/L15e070a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e070a.dat" right +[ 21, 59 ] = ascii (fp) : "./hrtfs/elev15/L15e065a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e065a.dat" right +[ 21, 60 ] = ascii (fp) : "./hrtfs/elev15/L15e060a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e060a.dat" right +[ 21, 61 ] = ascii (fp) : "./hrtfs/elev15/L15e055a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e055a.dat" right +[ 21, 62 ] = ascii (fp) : "./hrtfs/elev15/L15e050a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e050a.dat" right +[ 21, 63 ] = ascii (fp) : "./hrtfs/elev15/L15e045a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e045a.dat" right +[ 21, 64 ] = ascii (fp) : "./hrtfs/elev15/L15e040a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e040a.dat" right +[ 21, 65 ] = ascii (fp) : "./hrtfs/elev15/L15e035a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e035a.dat" right +[ 21, 66 ] = ascii (fp) : "./hrtfs/elev15/L15e030a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e030a.dat" right +[ 21, 67 ] = ascii (fp) : "./hrtfs/elev15/L15e025a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e025a.dat" right +[ 21, 68 ] = ascii (fp) : "./hrtfs/elev15/L15e020a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e020a.dat" right +[ 21, 69 ] = ascii (fp) : "./hrtfs/elev15/L15e015a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e015a.dat" right +[ 21, 70 ] = ascii (fp) : "./hrtfs/elev15/L15e010a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e010a.dat" right +[ 21, 71 ] = ascii (fp) : "./hrtfs/elev15/L15e005a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e005a.dat" right -[ 22, 0 ] = ascii (fp) : "./hrtfs/elev20/L20e000a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e000a.dat right -[ 22, 1 ] = ascii (fp) : "./hrtfs/elev20/L20e355a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e355a.dat right -[ 22, 2 ] = ascii (fp) : "./hrtfs/elev20/L20e350a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e350a.dat right -[ 22, 3 ] = ascii (fp) : "./hrtfs/elev20/L20e345a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e345a.dat right -[ 22, 4 ] = ascii (fp) : "./hrtfs/elev20/L20e340a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e340a.dat right -[ 22, 5 ] = ascii (fp) : "./hrtfs/elev20/L20e335a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e335a.dat right -[ 22, 6 ] = ascii (fp) : "./hrtfs/elev20/L20e330a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e330a.dat right -[ 22, 7 ] = ascii (fp) : "./hrtfs/elev20/L20e325a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e325a.dat right -[ 22, 8 ] = ascii (fp) : "./hrtfs/elev20/L20e320a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e320a.dat right -[ 22, 9 ] = ascii (fp) : "./hrtfs/elev20/L20e315a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e315a.dat right -[ 22, 10 ] = ascii (fp) : "./hrtfs/elev20/L20e310a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e310a.dat right -[ 22, 11 ] = ascii (fp) : "./hrtfs/elev20/L20e305a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e305a.dat right -[ 22, 12 ] = ascii (fp) : "./hrtfs/elev20/L20e300a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e300a.dat right -[ 22, 13 ] = ascii (fp) : "./hrtfs/elev20/L20e295a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e295a.dat right -[ 22, 14 ] = ascii (fp) : "./hrtfs/elev20/L20e290a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e290a.dat right -[ 22, 15 ] = ascii (fp) : "./hrtfs/elev20/L20e285a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e285a.dat right -[ 22, 16 ] = ascii (fp) : "./hrtfs/elev20/L20e280a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e280a.dat right -[ 22, 17 ] = ascii (fp) : "./hrtfs/elev20/L20e275a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e275a.dat right -[ 22, 18 ] = ascii (fp) : "./hrtfs/elev20/L20e270a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e270a.dat right -[ 22, 19 ] = ascii (fp) : "./hrtfs/elev20/L20e265a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e265a.dat right -[ 22, 20 ] = ascii (fp) : "./hrtfs/elev20/L20e260a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e260a.dat right -[ 22, 21 ] = ascii (fp) : "./hrtfs/elev20/L20e255a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e255a.dat right -[ 22, 22 ] = ascii (fp) : "./hrtfs/elev20/L20e250a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e250a.dat right -[ 22, 23 ] = ascii (fp) : "./hrtfs/elev20/L20e245a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e245a.dat right -[ 22, 24 ] = ascii (fp) : "./hrtfs/elev20/L20e240a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e240a.dat right -[ 22, 25 ] = ascii (fp) : "./hrtfs/elev20/L20e235a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e235a.dat right -[ 22, 26 ] = ascii (fp) : "./hrtfs/elev20/L20e230a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e230a.dat right -[ 22, 27 ] = ascii (fp) : "./hrtfs/elev20/L20e225a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e225a.dat right -[ 22, 28 ] = ascii (fp) : "./hrtfs/elev20/L20e220a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e220a.dat right -[ 22, 29 ] = ascii (fp) : "./hrtfs/elev20/L20e215a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e215a.dat right -[ 22, 30 ] = ascii (fp) : "./hrtfs/elev20/L20e210a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e210a.dat right -[ 22, 31 ] = ascii (fp) : "./hrtfs/elev20/L20e205a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e205a.dat right -[ 22, 32 ] = ascii (fp) : "./hrtfs/elev20/L20e200a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e200a.dat right -[ 22, 33 ] = ascii (fp) : "./hrtfs/elev20/L20e195a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e195a.dat right -[ 22, 34 ] = ascii (fp) : "./hrtfs/elev20/L20e190a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e190a.dat right -[ 22, 35 ] = ascii (fp) : "./hrtfs/elev20/L20e185a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e185a.dat right -[ 22, 36 ] = ascii (fp) : "./hrtfs/elev20/L20e180a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e180a.dat right -[ 22, 37 ] = ascii (fp) : "./hrtfs/elev20/L20e175a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e175a.dat right -[ 22, 38 ] = ascii (fp) : "./hrtfs/elev20/L20e170a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e170a.dat right -[ 22, 39 ] = ascii (fp) : "./hrtfs/elev20/L20e165a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e165a.dat right -[ 22, 40 ] = ascii (fp) : "./hrtfs/elev20/L20e160a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e160a.dat right -[ 22, 41 ] = ascii (fp) : "./hrtfs/elev20/L20e155a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e155a.dat right -[ 22, 42 ] = ascii (fp) : "./hrtfs/elev20/L20e150a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e150a.dat right -[ 22, 43 ] = ascii (fp) : "./hrtfs/elev20/L20e145a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e145a.dat right -[ 22, 44 ] = ascii (fp) : "./hrtfs/elev20/L20e140a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e140a.dat right -[ 22, 45 ] = ascii (fp) : "./hrtfs/elev20/L20e135a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e135a.dat right -[ 22, 46 ] = ascii (fp) : "./hrtfs/elev20/L20e130a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e130a.dat right -[ 22, 47 ] = ascii (fp) : "./hrtfs/elev20/L20e125a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e125a.dat right -[ 22, 48 ] = ascii (fp) : "./hrtfs/elev20/L20e120a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e120a.dat right -[ 22, 49 ] = ascii (fp) : "./hrtfs/elev20/L20e115a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e115a.dat right -[ 22, 50 ] = ascii (fp) : "./hrtfs/elev20/L20e110a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e110a.dat right -[ 22, 51 ] = ascii (fp) : "./hrtfs/elev20/L20e105a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e105a.dat right -[ 22, 52 ] = ascii (fp) : "./hrtfs/elev20/L20e100a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e100a.dat right -[ 22, 53 ] = ascii (fp) : "./hrtfs/elev20/L20e095a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e095a.dat right -[ 22, 54 ] = ascii (fp) : "./hrtfs/elev20/L20e090a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e090a.dat right -[ 22, 55 ] = ascii (fp) : "./hrtfs/elev20/L20e085a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e085a.dat right -[ 22, 56 ] = ascii (fp) : "./hrtfs/elev20/L20e080a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e080a.dat right -[ 22, 57 ] = ascii (fp) : "./hrtfs/elev20/L20e075a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e075a.dat right -[ 22, 58 ] = ascii (fp) : "./hrtfs/elev20/L20e070a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e070a.dat right -[ 22, 59 ] = ascii (fp) : "./hrtfs/elev20/L20e065a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e065a.dat right -[ 22, 60 ] = ascii (fp) : "./hrtfs/elev20/L20e060a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e060a.dat right -[ 22, 61 ] = ascii (fp) : "./hrtfs/elev20/L20e055a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e055a.dat right -[ 22, 62 ] = ascii (fp) : "./hrtfs/elev20/L20e050a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e050a.dat right -[ 22, 63 ] = ascii (fp) : "./hrtfs/elev20/L20e045a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e045a.dat right -[ 22, 64 ] = ascii (fp) : "./hrtfs/elev20/L20e040a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e040a.dat right -[ 22, 65 ] = ascii (fp) : "./hrtfs/elev20/L20e035a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e035a.dat right -[ 22, 66 ] = ascii (fp) : "./hrtfs/elev20/L20e030a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e030a.dat right -[ 22, 67 ] = ascii (fp) : "./hrtfs/elev20/L20e025a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e025a.dat right -[ 22, 68 ] = ascii (fp) : "./hrtfs/elev20/L20e020a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e020a.dat right -[ 22, 69 ] = ascii (fp) : "./hrtfs/elev20/L20e015a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e015a.dat right -[ 22, 70 ] = ascii (fp) : "./hrtfs/elev20/L20e010a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e010a.dat right -[ 22, 71 ] = ascii (fp) : "./hrtfs/elev20/L20e005a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e005a.dat right +[ 22, 0 ] = ascii (fp) : "./hrtfs/elev20/L20e000a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e000a.dat" right +[ 22, 1 ] = ascii (fp) : "./hrtfs/elev20/L20e355a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e355a.dat" right +[ 22, 2 ] = ascii (fp) : "./hrtfs/elev20/L20e350a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e350a.dat" right +[ 22, 3 ] = ascii (fp) : "./hrtfs/elev20/L20e345a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e345a.dat" right +[ 22, 4 ] = ascii (fp) : "./hrtfs/elev20/L20e340a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e340a.dat" right +[ 22, 5 ] = ascii (fp) : "./hrtfs/elev20/L20e335a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e335a.dat" right +[ 22, 6 ] = ascii (fp) : "./hrtfs/elev20/L20e330a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e330a.dat" right +[ 22, 7 ] = ascii (fp) : "./hrtfs/elev20/L20e325a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e325a.dat" right +[ 22, 8 ] = ascii (fp) : "./hrtfs/elev20/L20e320a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e320a.dat" right +[ 22, 9 ] = ascii (fp) : "./hrtfs/elev20/L20e315a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e315a.dat" right +[ 22, 10 ] = ascii (fp) : "./hrtfs/elev20/L20e310a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e310a.dat" right +[ 22, 11 ] = ascii (fp) : "./hrtfs/elev20/L20e305a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e305a.dat" right +[ 22, 12 ] = ascii (fp) : "./hrtfs/elev20/L20e300a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e300a.dat" right +[ 22, 13 ] = ascii (fp) : "./hrtfs/elev20/L20e295a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e295a.dat" right +[ 22, 14 ] = ascii (fp) : "./hrtfs/elev20/L20e290a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e290a.dat" right +[ 22, 15 ] = ascii (fp) : "./hrtfs/elev20/L20e285a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e285a.dat" right +[ 22, 16 ] = ascii (fp) : "./hrtfs/elev20/L20e280a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e280a.dat" right +[ 22, 17 ] = ascii (fp) : "./hrtfs/elev20/L20e275a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e275a.dat" right +[ 22, 18 ] = ascii (fp) : "./hrtfs/elev20/L20e270a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e270a.dat" right +[ 22, 19 ] = ascii (fp) : "./hrtfs/elev20/L20e265a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e265a.dat" right +[ 22, 20 ] = ascii (fp) : "./hrtfs/elev20/L20e260a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e260a.dat" right +[ 22, 21 ] = ascii (fp) : "./hrtfs/elev20/L20e255a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e255a.dat" right +[ 22, 22 ] = ascii (fp) : "./hrtfs/elev20/L20e250a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e250a.dat" right +[ 22, 23 ] = ascii (fp) : "./hrtfs/elev20/L20e245a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e245a.dat" right +[ 22, 24 ] = ascii (fp) : "./hrtfs/elev20/L20e240a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e240a.dat" right +[ 22, 25 ] = ascii (fp) : "./hrtfs/elev20/L20e235a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e235a.dat" right +[ 22, 26 ] = ascii (fp) : "./hrtfs/elev20/L20e230a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e230a.dat" right +[ 22, 27 ] = ascii (fp) : "./hrtfs/elev20/L20e225a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e225a.dat" right +[ 22, 28 ] = ascii (fp) : "./hrtfs/elev20/L20e220a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e220a.dat" right +[ 22, 29 ] = ascii (fp) : "./hrtfs/elev20/L20e215a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e215a.dat" right +[ 22, 30 ] = ascii (fp) : "./hrtfs/elev20/L20e210a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e210a.dat" right +[ 22, 31 ] = ascii (fp) : "./hrtfs/elev20/L20e205a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e205a.dat" right +[ 22, 32 ] = ascii (fp) : "./hrtfs/elev20/L20e200a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e200a.dat" right +[ 22, 33 ] = ascii (fp) : "./hrtfs/elev20/L20e195a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e195a.dat" right +[ 22, 34 ] = ascii (fp) : "./hrtfs/elev20/L20e190a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e190a.dat" right +[ 22, 35 ] = ascii (fp) : "./hrtfs/elev20/L20e185a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e185a.dat" right +[ 22, 36 ] = ascii (fp) : "./hrtfs/elev20/L20e180a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e180a.dat" right +[ 22, 37 ] = ascii (fp) : "./hrtfs/elev20/L20e175a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e175a.dat" right +[ 22, 38 ] = ascii (fp) : "./hrtfs/elev20/L20e170a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e170a.dat" right +[ 22, 39 ] = ascii (fp) : "./hrtfs/elev20/L20e165a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e165a.dat" right +[ 22, 40 ] = ascii (fp) : "./hrtfs/elev20/L20e160a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e160a.dat" right +[ 22, 41 ] = ascii (fp) : "./hrtfs/elev20/L20e155a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e155a.dat" right +[ 22, 42 ] = ascii (fp) : "./hrtfs/elev20/L20e150a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e150a.dat" right +[ 22, 43 ] = ascii (fp) : "./hrtfs/elev20/L20e145a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e145a.dat" right +[ 22, 44 ] = ascii (fp) : "./hrtfs/elev20/L20e140a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e140a.dat" right +[ 22, 45 ] = ascii (fp) : "./hrtfs/elev20/L20e135a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e135a.dat" right +[ 22, 46 ] = ascii (fp) : "./hrtfs/elev20/L20e130a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e130a.dat" right +[ 22, 47 ] = ascii (fp) : "./hrtfs/elev20/L20e125a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e125a.dat" right +[ 22, 48 ] = ascii (fp) : "./hrtfs/elev20/L20e120a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e120a.dat" right +[ 22, 49 ] = ascii (fp) : "./hrtfs/elev20/L20e115a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e115a.dat" right +[ 22, 50 ] = ascii (fp) : "./hrtfs/elev20/L20e110a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e110a.dat" right +[ 22, 51 ] = ascii (fp) : "./hrtfs/elev20/L20e105a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e105a.dat" right +[ 22, 52 ] = ascii (fp) : "./hrtfs/elev20/L20e100a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e100a.dat" right +[ 22, 53 ] = ascii (fp) : "./hrtfs/elev20/L20e095a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e095a.dat" right +[ 22, 54 ] = ascii (fp) : "./hrtfs/elev20/L20e090a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e090a.dat" right +[ 22, 55 ] = ascii (fp) : "./hrtfs/elev20/L20e085a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e085a.dat" right +[ 22, 56 ] = ascii (fp) : "./hrtfs/elev20/L20e080a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e080a.dat" right +[ 22, 57 ] = ascii (fp) : "./hrtfs/elev20/L20e075a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e075a.dat" right +[ 22, 58 ] = ascii (fp) : "./hrtfs/elev20/L20e070a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e070a.dat" right +[ 22, 59 ] = ascii (fp) : "./hrtfs/elev20/L20e065a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e065a.dat" right +[ 22, 60 ] = ascii (fp) : "./hrtfs/elev20/L20e060a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e060a.dat" right +[ 22, 61 ] = ascii (fp) : "./hrtfs/elev20/L20e055a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e055a.dat" right +[ 22, 62 ] = ascii (fp) : "./hrtfs/elev20/L20e050a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e050a.dat" right +[ 22, 63 ] = ascii (fp) : "./hrtfs/elev20/L20e045a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e045a.dat" right +[ 22, 64 ] = ascii (fp) : "./hrtfs/elev20/L20e040a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e040a.dat" right +[ 22, 65 ] = ascii (fp) : "./hrtfs/elev20/L20e035a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e035a.dat" right +[ 22, 66 ] = ascii (fp) : "./hrtfs/elev20/L20e030a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e030a.dat" right +[ 22, 67 ] = ascii (fp) : "./hrtfs/elev20/L20e025a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e025a.dat" right +[ 22, 68 ] = ascii (fp) : "./hrtfs/elev20/L20e020a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e020a.dat" right +[ 22, 69 ] = ascii (fp) : "./hrtfs/elev20/L20e015a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e015a.dat" right +[ 22, 70 ] = ascii (fp) : "./hrtfs/elev20/L20e010a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e010a.dat" right +[ 22, 71 ] = ascii (fp) : "./hrtfs/elev20/L20e005a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e005a.dat" right -[ 23, 0 ] = ascii (fp) : "./hrtfs/elev25/L25e000a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e000a.dat right -[ 23, 1 ] = ascii (fp) : "./hrtfs/elev25/L25e355a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e355a.dat right -[ 23, 2 ] = ascii (fp) : "./hrtfs/elev25/L25e350a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e350a.dat right -[ 23, 3 ] = ascii (fp) : "./hrtfs/elev25/L25e345a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e345a.dat right -[ 23, 4 ] = ascii (fp) : "./hrtfs/elev25/L25e340a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e340a.dat right -[ 23, 5 ] = ascii (fp) : "./hrtfs/elev25/L25e335a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e335a.dat right -[ 23, 6 ] = ascii (fp) : "./hrtfs/elev25/L25e330a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e330a.dat right -[ 23, 7 ] = ascii (fp) : "./hrtfs/elev25/L25e325a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e325a.dat right -[ 23, 8 ] = ascii (fp) : "./hrtfs/elev25/L25e320a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e320a.dat right -[ 23, 9 ] = ascii (fp) : "./hrtfs/elev25/L25e315a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e315a.dat right -[ 23, 10 ] = ascii (fp) : "./hrtfs/elev25/L25e310a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e310a.dat right -[ 23, 11 ] = ascii (fp) : "./hrtfs/elev25/L25e305a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e305a.dat right -[ 23, 12 ] = ascii (fp) : "./hrtfs/elev25/L25e300a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e300a.dat right -[ 23, 13 ] = ascii (fp) : "./hrtfs/elev25/L25e295a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e295a.dat right -[ 23, 14 ] = ascii (fp) : "./hrtfs/elev25/L25e290a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e290a.dat right -[ 23, 15 ] = ascii (fp) : "./hrtfs/elev25/L25e285a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e285a.dat right -[ 23, 16 ] = ascii (fp) : "./hrtfs/elev25/L25e280a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e280a.dat right -[ 23, 17 ] = ascii (fp) : "./hrtfs/elev25/L25e275a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e275a.dat right -[ 23, 18 ] = ascii (fp) : "./hrtfs/elev25/L25e270a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e270a.dat right -[ 23, 19 ] = ascii (fp) : "./hrtfs/elev25/L25e265a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e265a.dat right -[ 23, 20 ] = ascii (fp) : "./hrtfs/elev25/L25e260a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e260a.dat right -[ 23, 21 ] = ascii (fp) : "./hrtfs/elev25/L25e255a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e255a.dat right -[ 23, 22 ] = ascii (fp) : "./hrtfs/elev25/L25e250a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e250a.dat right -[ 23, 23 ] = ascii (fp) : "./hrtfs/elev25/L25e245a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e245a.dat right -[ 23, 24 ] = ascii (fp) : "./hrtfs/elev25/L25e240a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e240a.dat right -[ 23, 25 ] = ascii (fp) : "./hrtfs/elev25/L25e235a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e235a.dat right -[ 23, 26 ] = ascii (fp) : "./hrtfs/elev25/L25e230a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e230a.dat right -[ 23, 27 ] = ascii (fp) : "./hrtfs/elev25/L25e225a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e225a.dat right -[ 23, 28 ] = ascii (fp) : "./hrtfs/elev25/L25e220a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e220a.dat right -[ 23, 29 ] = ascii (fp) : "./hrtfs/elev25/L25e215a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e215a.dat right -[ 23, 30 ] = ascii (fp) : "./hrtfs/elev25/L25e210a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e210a.dat right -[ 23, 31 ] = ascii (fp) : "./hrtfs/elev25/L25e205a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e205a.dat right -[ 23, 32 ] = ascii (fp) : "./hrtfs/elev25/L25e200a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e200a.dat right -[ 23, 33 ] = ascii (fp) : "./hrtfs/elev25/L25e195a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e195a.dat right -[ 23, 34 ] = ascii (fp) : "./hrtfs/elev25/L25e190a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e190a.dat right -[ 23, 35 ] = ascii (fp) : "./hrtfs/elev25/L25e185a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e185a.dat right -[ 23, 36 ] = ascii (fp) : "./hrtfs/elev25/L25e180a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e180a.dat right -[ 23, 37 ] = ascii (fp) : "./hrtfs/elev25/L25e175a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e175a.dat right -[ 23, 38 ] = ascii (fp) : "./hrtfs/elev25/L25e170a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e170a.dat right -[ 23, 39 ] = ascii (fp) : "./hrtfs/elev25/L25e165a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e165a.dat right -[ 23, 40 ] = ascii (fp) : "./hrtfs/elev25/L25e160a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e160a.dat right -[ 23, 41 ] = ascii (fp) : "./hrtfs/elev25/L25e155a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e155a.dat right -[ 23, 42 ] = ascii (fp) : "./hrtfs/elev25/L25e150a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e150a.dat right -[ 23, 43 ] = ascii (fp) : "./hrtfs/elev25/L25e145a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e145a.dat right -[ 23, 44 ] = ascii (fp) : "./hrtfs/elev25/L25e140a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e140a.dat right -[ 23, 45 ] = ascii (fp) : "./hrtfs/elev25/L25e135a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e135a.dat right -[ 23, 46 ] = ascii (fp) : "./hrtfs/elev25/L25e130a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e130a.dat right -[ 23, 47 ] = ascii (fp) : "./hrtfs/elev25/L25e125a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e125a.dat right -[ 23, 48 ] = ascii (fp) : "./hrtfs/elev25/L25e120a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e120a.dat right -[ 23, 49 ] = ascii (fp) : "./hrtfs/elev25/L25e115a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e115a.dat right -[ 23, 50 ] = ascii (fp) : "./hrtfs/elev25/L25e110a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e110a.dat right -[ 23, 51 ] = ascii (fp) : "./hrtfs/elev25/L25e105a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e105a.dat right -[ 23, 52 ] = ascii (fp) : "./hrtfs/elev25/L25e100a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e100a.dat right -[ 23, 53 ] = ascii (fp) : "./hrtfs/elev25/L25e095a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e095a.dat right -[ 23, 54 ] = ascii (fp) : "./hrtfs/elev25/L25e090a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e090a.dat right -[ 23, 55 ] = ascii (fp) : "./hrtfs/elev25/L25e085a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e085a.dat right -[ 23, 56 ] = ascii (fp) : "./hrtfs/elev25/L25e080a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e080a.dat right -[ 23, 57 ] = ascii (fp) : "./hrtfs/elev25/L25e075a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e075a.dat right -[ 23, 58 ] = ascii (fp) : "./hrtfs/elev25/L25e070a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e070a.dat right -[ 23, 59 ] = ascii (fp) : "./hrtfs/elev25/L25e065a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e065a.dat right -[ 23, 60 ] = ascii (fp) : "./hrtfs/elev25/L25e060a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e060a.dat right -[ 23, 61 ] = ascii (fp) : "./hrtfs/elev25/L25e055a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e055a.dat right -[ 23, 62 ] = ascii (fp) : "./hrtfs/elev25/L25e050a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e050a.dat right -[ 23, 63 ] = ascii (fp) : "./hrtfs/elev25/L25e045a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e045a.dat right -[ 23, 64 ] = ascii (fp) : "./hrtfs/elev25/L25e040a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e040a.dat right -[ 23, 65 ] = ascii (fp) : "./hrtfs/elev25/L25e035a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e035a.dat right -[ 23, 66 ] = ascii (fp) : "./hrtfs/elev25/L25e030a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e030a.dat right -[ 23, 67 ] = ascii (fp) : "./hrtfs/elev25/L25e025a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e025a.dat right -[ 23, 68 ] = ascii (fp) : "./hrtfs/elev25/L25e020a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e020a.dat right -[ 23, 69 ] = ascii (fp) : "./hrtfs/elev25/L25e015a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e015a.dat right -[ 23, 70 ] = ascii (fp) : "./hrtfs/elev25/L25e010a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e010a.dat right -[ 23, 71 ] = ascii (fp) : "./hrtfs/elev25/L25e005a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e005a.dat right +[ 23, 0 ] = ascii (fp) : "./hrtfs/elev25/L25e000a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e000a.dat" right +[ 23, 1 ] = ascii (fp) : "./hrtfs/elev25/L25e355a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e355a.dat" right +[ 23, 2 ] = ascii (fp) : "./hrtfs/elev25/L25e350a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e350a.dat" right +[ 23, 3 ] = ascii (fp) : "./hrtfs/elev25/L25e345a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e345a.dat" right +[ 23, 4 ] = ascii (fp) : "./hrtfs/elev25/L25e340a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e340a.dat" right +[ 23, 5 ] = ascii (fp) : "./hrtfs/elev25/L25e335a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e335a.dat" right +[ 23, 6 ] = ascii (fp) : "./hrtfs/elev25/L25e330a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e330a.dat" right +[ 23, 7 ] = ascii (fp) : "./hrtfs/elev25/L25e325a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e325a.dat" right +[ 23, 8 ] = ascii (fp) : "./hrtfs/elev25/L25e320a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e320a.dat" right +[ 23, 9 ] = ascii (fp) : "./hrtfs/elev25/L25e315a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e315a.dat" right +[ 23, 10 ] = ascii (fp) : "./hrtfs/elev25/L25e310a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e310a.dat" right +[ 23, 11 ] = ascii (fp) : "./hrtfs/elev25/L25e305a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e305a.dat" right +[ 23, 12 ] = ascii (fp) : "./hrtfs/elev25/L25e300a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e300a.dat" right +[ 23, 13 ] = ascii (fp) : "./hrtfs/elev25/L25e295a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e295a.dat" right +[ 23, 14 ] = ascii (fp) : "./hrtfs/elev25/L25e290a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e290a.dat" right +[ 23, 15 ] = ascii (fp) : "./hrtfs/elev25/L25e285a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e285a.dat" right +[ 23, 16 ] = ascii (fp) : "./hrtfs/elev25/L25e280a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e280a.dat" right +[ 23, 17 ] = ascii (fp) : "./hrtfs/elev25/L25e275a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e275a.dat" right +[ 23, 18 ] = ascii (fp) : "./hrtfs/elev25/L25e270a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e270a.dat" right +[ 23, 19 ] = ascii (fp) : "./hrtfs/elev25/L25e265a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e265a.dat" right +[ 23, 20 ] = ascii (fp) : "./hrtfs/elev25/L25e260a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e260a.dat" right +[ 23, 21 ] = ascii (fp) : "./hrtfs/elev25/L25e255a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e255a.dat" right +[ 23, 22 ] = ascii (fp) : "./hrtfs/elev25/L25e250a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e250a.dat" right +[ 23, 23 ] = ascii (fp) : "./hrtfs/elev25/L25e245a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e245a.dat" right +[ 23, 24 ] = ascii (fp) : "./hrtfs/elev25/L25e240a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e240a.dat" right +[ 23, 25 ] = ascii (fp) : "./hrtfs/elev25/L25e235a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e235a.dat" right +[ 23, 26 ] = ascii (fp) : "./hrtfs/elev25/L25e230a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e230a.dat" right +[ 23, 27 ] = ascii (fp) : "./hrtfs/elev25/L25e225a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e225a.dat" right +[ 23, 28 ] = ascii (fp) : "./hrtfs/elev25/L25e220a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e220a.dat" right +[ 23, 29 ] = ascii (fp) : "./hrtfs/elev25/L25e215a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e215a.dat" right +[ 23, 30 ] = ascii (fp) : "./hrtfs/elev25/L25e210a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e210a.dat" right +[ 23, 31 ] = ascii (fp) : "./hrtfs/elev25/L25e205a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e205a.dat" right +[ 23, 32 ] = ascii (fp) : "./hrtfs/elev25/L25e200a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e200a.dat" right +[ 23, 33 ] = ascii (fp) : "./hrtfs/elev25/L25e195a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e195a.dat" right +[ 23, 34 ] = ascii (fp) : "./hrtfs/elev25/L25e190a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e190a.dat" right +[ 23, 35 ] = ascii (fp) : "./hrtfs/elev25/L25e185a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e185a.dat" right +[ 23, 36 ] = ascii (fp) : "./hrtfs/elev25/L25e180a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e180a.dat" right +[ 23, 37 ] = ascii (fp) : "./hrtfs/elev25/L25e175a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e175a.dat" right +[ 23, 38 ] = ascii (fp) : "./hrtfs/elev25/L25e170a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e170a.dat" right +[ 23, 39 ] = ascii (fp) : "./hrtfs/elev25/L25e165a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e165a.dat" right +[ 23, 40 ] = ascii (fp) : "./hrtfs/elev25/L25e160a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e160a.dat" right +[ 23, 41 ] = ascii (fp) : "./hrtfs/elev25/L25e155a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e155a.dat" right +[ 23, 42 ] = ascii (fp) : "./hrtfs/elev25/L25e150a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e150a.dat" right +[ 23, 43 ] = ascii (fp) : "./hrtfs/elev25/L25e145a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e145a.dat" right +[ 23, 44 ] = ascii (fp) : "./hrtfs/elev25/L25e140a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e140a.dat" right +[ 23, 45 ] = ascii (fp) : "./hrtfs/elev25/L25e135a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e135a.dat" right +[ 23, 46 ] = ascii (fp) : "./hrtfs/elev25/L25e130a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e130a.dat" right +[ 23, 47 ] = ascii (fp) : "./hrtfs/elev25/L25e125a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e125a.dat" right +[ 23, 48 ] = ascii (fp) : "./hrtfs/elev25/L25e120a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e120a.dat" right +[ 23, 49 ] = ascii (fp) : "./hrtfs/elev25/L25e115a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e115a.dat" right +[ 23, 50 ] = ascii (fp) : "./hrtfs/elev25/L25e110a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e110a.dat" right +[ 23, 51 ] = ascii (fp) : "./hrtfs/elev25/L25e105a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e105a.dat" right +[ 23, 52 ] = ascii (fp) : "./hrtfs/elev25/L25e100a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e100a.dat" right +[ 23, 53 ] = ascii (fp) : "./hrtfs/elev25/L25e095a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e095a.dat" right +[ 23, 54 ] = ascii (fp) : "./hrtfs/elev25/L25e090a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e090a.dat" right +[ 23, 55 ] = ascii (fp) : "./hrtfs/elev25/L25e085a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e085a.dat" right +[ 23, 56 ] = ascii (fp) : "./hrtfs/elev25/L25e080a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e080a.dat" right +[ 23, 57 ] = ascii (fp) : "./hrtfs/elev25/L25e075a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e075a.dat" right +[ 23, 58 ] = ascii (fp) : "./hrtfs/elev25/L25e070a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e070a.dat" right +[ 23, 59 ] = ascii (fp) : "./hrtfs/elev25/L25e065a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e065a.dat" right +[ 23, 60 ] = ascii (fp) : "./hrtfs/elev25/L25e060a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e060a.dat" right +[ 23, 61 ] = ascii (fp) : "./hrtfs/elev25/L25e055a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e055a.dat" right +[ 23, 62 ] = ascii (fp) : "./hrtfs/elev25/L25e050a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e050a.dat" right +[ 23, 63 ] = ascii (fp) : "./hrtfs/elev25/L25e045a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e045a.dat" right +[ 23, 64 ] = ascii (fp) : "./hrtfs/elev25/L25e040a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e040a.dat" right +[ 23, 65 ] = ascii (fp) : "./hrtfs/elev25/L25e035a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e035a.dat" right +[ 23, 66 ] = ascii (fp) : "./hrtfs/elev25/L25e030a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e030a.dat" right +[ 23, 67 ] = ascii (fp) : "./hrtfs/elev25/L25e025a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e025a.dat" right +[ 23, 68 ] = ascii (fp) : "./hrtfs/elev25/L25e020a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e020a.dat" right +[ 23, 69 ] = ascii (fp) : "./hrtfs/elev25/L25e015a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e015a.dat" right +[ 23, 70 ] = ascii (fp) : "./hrtfs/elev25/L25e010a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e010a.dat" right +[ 23, 71 ] = ascii (fp) : "./hrtfs/elev25/L25e005a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e005a.dat" right -[ 24, 0 ] = ascii (fp) : "./hrtfs/elev30/L30e000a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e000a.dat right -[ 24, 1 ] = ascii (fp) : "./hrtfs/elev30/L30e355a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e355a.dat right -[ 24, 2 ] = ascii (fp) : "./hrtfs/elev30/L30e350a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e350a.dat right -[ 24, 3 ] = ascii (fp) : "./hrtfs/elev30/L30e345a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e345a.dat right -[ 24, 4 ] = ascii (fp) : "./hrtfs/elev30/L30e340a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e340a.dat right -[ 24, 5 ] = ascii (fp) : "./hrtfs/elev30/L30e335a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e335a.dat right -[ 24, 6 ] = ascii (fp) : "./hrtfs/elev30/L30e330a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e330a.dat right -[ 24, 7 ] = ascii (fp) : "./hrtfs/elev30/L30e325a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e325a.dat right -[ 24, 8 ] = ascii (fp) : "./hrtfs/elev30/L30e320a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e320a.dat right -[ 24, 9 ] = ascii (fp) : "./hrtfs/elev30/L30e315a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e315a.dat right -[ 24, 10 ] = ascii (fp) : "./hrtfs/elev30/L30e310a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e310a.dat right -[ 24, 11 ] = ascii (fp) : "./hrtfs/elev30/L30e305a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e305a.dat right -[ 24, 12 ] = ascii (fp) : "./hrtfs/elev30/L30e300a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e300a.dat right -[ 24, 13 ] = ascii (fp) : "./hrtfs/elev30/L30e295a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e295a.dat right -[ 24, 14 ] = ascii (fp) : "./hrtfs/elev30/L30e290a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e290a.dat right -[ 24, 15 ] = ascii (fp) : "./hrtfs/elev30/L30e285a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e285a.dat right -[ 24, 16 ] = ascii (fp) : "./hrtfs/elev30/L30e280a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e280a.dat right -[ 24, 17 ] = ascii (fp) : "./hrtfs/elev30/L30e275a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e275a.dat right -[ 24, 18 ] = ascii (fp) : "./hrtfs/elev30/L30e270a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e270a.dat right -[ 24, 19 ] = ascii (fp) : "./hrtfs/elev30/L30e265a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e265a.dat right -[ 24, 20 ] = ascii (fp) : "./hrtfs/elev30/L30e260a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e260a.dat right -[ 24, 21 ] = ascii (fp) : "./hrtfs/elev30/L30e255a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e255a.dat right -[ 24, 22 ] = ascii (fp) : "./hrtfs/elev30/L30e250a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e250a.dat right -[ 24, 23 ] = ascii (fp) : "./hrtfs/elev30/L30e245a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e245a.dat right -[ 24, 24 ] = ascii (fp) : "./hrtfs/elev30/L30e240a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e240a.dat right -[ 24, 25 ] = ascii (fp) : "./hrtfs/elev30/L30e235a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e235a.dat right -[ 24, 26 ] = ascii (fp) : "./hrtfs/elev30/L30e230a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e230a.dat right -[ 24, 27 ] = ascii (fp) : "./hrtfs/elev30/L30e225a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e225a.dat right -[ 24, 28 ] = ascii (fp) : "./hrtfs/elev30/L30e220a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e220a.dat right -[ 24, 29 ] = ascii (fp) : "./hrtfs/elev30/L30e215a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e215a.dat right -[ 24, 30 ] = ascii (fp) : "./hrtfs/elev30/L30e210a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e210a.dat right -[ 24, 31 ] = ascii (fp) : "./hrtfs/elev30/L30e205a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e205a.dat right -[ 24, 32 ] = ascii (fp) : "./hrtfs/elev30/L30e200a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e200a.dat right -[ 24, 33 ] = ascii (fp) : "./hrtfs/elev30/L30e195a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e195a.dat right -[ 24, 34 ] = ascii (fp) : "./hrtfs/elev30/L30e190a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e190a.dat right -[ 24, 35 ] = ascii (fp) : "./hrtfs/elev30/L30e185a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e185a.dat right -[ 24, 36 ] = ascii (fp) : "./hrtfs/elev30/L30e180a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e180a.dat right -[ 24, 37 ] = ascii (fp) : "./hrtfs/elev30/L30e175a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e175a.dat right -[ 24, 38 ] = ascii (fp) : "./hrtfs/elev30/L30e170a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e170a.dat right -[ 24, 39 ] = ascii (fp) : "./hrtfs/elev30/L30e165a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e165a.dat right -[ 24, 40 ] = ascii (fp) : "./hrtfs/elev30/L30e160a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e160a.dat right -[ 24, 41 ] = ascii (fp) : "./hrtfs/elev30/L30e155a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e155a.dat right -[ 24, 42 ] = ascii (fp) : "./hrtfs/elev30/L30e150a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e150a.dat right -[ 24, 43 ] = ascii (fp) : "./hrtfs/elev30/L30e145a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e145a.dat right -[ 24, 44 ] = ascii (fp) : "./hrtfs/elev30/L30e140a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e140a.dat right -[ 24, 45 ] = ascii (fp) : "./hrtfs/elev30/L30e135a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e135a.dat right -[ 24, 46 ] = ascii (fp) : "./hrtfs/elev30/L30e130a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e130a.dat right -[ 24, 47 ] = ascii (fp) : "./hrtfs/elev30/L30e125a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e125a.dat right -[ 24, 48 ] = ascii (fp) : "./hrtfs/elev30/L30e120a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e120a.dat right -[ 24, 49 ] = ascii (fp) : "./hrtfs/elev30/L30e115a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e115a.dat right -[ 24, 50 ] = ascii (fp) : "./hrtfs/elev30/L30e110a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e110a.dat right -[ 24, 51 ] = ascii (fp) : "./hrtfs/elev30/L30e105a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e105a.dat right -[ 24, 52 ] = ascii (fp) : "./hrtfs/elev30/L30e100a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e100a.dat right -[ 24, 53 ] = ascii (fp) : "./hrtfs/elev30/L30e095a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e095a.dat right -[ 24, 54 ] = ascii (fp) : "./hrtfs/elev30/L30e090a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e090a.dat right -[ 24, 55 ] = ascii (fp) : "./hrtfs/elev30/L30e085a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e085a.dat right -[ 24, 56 ] = ascii (fp) : "./hrtfs/elev30/L30e080a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e080a.dat right -[ 24, 57 ] = ascii (fp) : "./hrtfs/elev30/L30e075a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e075a.dat right -[ 24, 58 ] = ascii (fp) : "./hrtfs/elev30/L30e070a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e070a.dat right -[ 24, 59 ] = ascii (fp) : "./hrtfs/elev30/L30e065a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e065a.dat right -[ 24, 60 ] = ascii (fp) : "./hrtfs/elev30/L30e060a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e060a.dat right -[ 24, 61 ] = ascii (fp) : "./hrtfs/elev30/L30e055a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e055a.dat right -[ 24, 62 ] = ascii (fp) : "./hrtfs/elev30/L30e050a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e050a.dat right -[ 24, 63 ] = ascii (fp) : "./hrtfs/elev30/L30e045a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e045a.dat right -[ 24, 64 ] = ascii (fp) : "./hrtfs/elev30/L30e040a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e040a.dat right -[ 24, 65 ] = ascii (fp) : "./hrtfs/elev30/L30e035a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e035a.dat right -[ 24, 66 ] = ascii (fp) : "./hrtfs/elev30/L30e030a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e030a.dat right -[ 24, 67 ] = ascii (fp) : "./hrtfs/elev30/L30e025a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e025a.dat right -[ 24, 68 ] = ascii (fp) : "./hrtfs/elev30/L30e020a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e020a.dat right -[ 24, 69 ] = ascii (fp) : "./hrtfs/elev30/L30e015a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e015a.dat right -[ 24, 70 ] = ascii (fp) : "./hrtfs/elev30/L30e010a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e010a.dat right -[ 24, 71 ] = ascii (fp) : "./hrtfs/elev30/L30e005a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e005a.dat right +[ 24, 0 ] = ascii (fp) : "./hrtfs/elev30/L30e000a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e000a.dat" right +[ 24, 1 ] = ascii (fp) : "./hrtfs/elev30/L30e355a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e355a.dat" right +[ 24, 2 ] = ascii (fp) : "./hrtfs/elev30/L30e350a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e350a.dat" right +[ 24, 3 ] = ascii (fp) : "./hrtfs/elev30/L30e345a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e345a.dat" right +[ 24, 4 ] = ascii (fp) : "./hrtfs/elev30/L30e340a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e340a.dat" right +[ 24, 5 ] = ascii (fp) : "./hrtfs/elev30/L30e335a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e335a.dat" right +[ 24, 6 ] = ascii (fp) : "./hrtfs/elev30/L30e330a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e330a.dat" right +[ 24, 7 ] = ascii (fp) : "./hrtfs/elev30/L30e325a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e325a.dat" right +[ 24, 8 ] = ascii (fp) : "./hrtfs/elev30/L30e320a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e320a.dat" right +[ 24, 9 ] = ascii (fp) : "./hrtfs/elev30/L30e315a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e315a.dat" right +[ 24, 10 ] = ascii (fp) : "./hrtfs/elev30/L30e310a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e310a.dat" right +[ 24, 11 ] = ascii (fp) : "./hrtfs/elev30/L30e305a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e305a.dat" right +[ 24, 12 ] = ascii (fp) : "./hrtfs/elev30/L30e300a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e300a.dat" right +[ 24, 13 ] = ascii (fp) : "./hrtfs/elev30/L30e295a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e295a.dat" right +[ 24, 14 ] = ascii (fp) : "./hrtfs/elev30/L30e290a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e290a.dat" right +[ 24, 15 ] = ascii (fp) : "./hrtfs/elev30/L30e285a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e285a.dat" right +[ 24, 16 ] = ascii (fp) : "./hrtfs/elev30/L30e280a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e280a.dat" right +[ 24, 17 ] = ascii (fp) : "./hrtfs/elev30/L30e275a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e275a.dat" right +[ 24, 18 ] = ascii (fp) : "./hrtfs/elev30/L30e270a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e270a.dat" right +[ 24, 19 ] = ascii (fp) : "./hrtfs/elev30/L30e265a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e265a.dat" right +[ 24, 20 ] = ascii (fp) : "./hrtfs/elev30/L30e260a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e260a.dat" right +[ 24, 21 ] = ascii (fp) : "./hrtfs/elev30/L30e255a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e255a.dat" right +[ 24, 22 ] = ascii (fp) : "./hrtfs/elev30/L30e250a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e250a.dat" right +[ 24, 23 ] = ascii (fp) : "./hrtfs/elev30/L30e245a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e245a.dat" right +[ 24, 24 ] = ascii (fp) : "./hrtfs/elev30/L30e240a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e240a.dat" right +[ 24, 25 ] = ascii (fp) : "./hrtfs/elev30/L30e235a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e235a.dat" right +[ 24, 26 ] = ascii (fp) : "./hrtfs/elev30/L30e230a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e230a.dat" right +[ 24, 27 ] = ascii (fp) : "./hrtfs/elev30/L30e225a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e225a.dat" right +[ 24, 28 ] = ascii (fp) : "./hrtfs/elev30/L30e220a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e220a.dat" right +[ 24, 29 ] = ascii (fp) : "./hrtfs/elev30/L30e215a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e215a.dat" right +[ 24, 30 ] = ascii (fp) : "./hrtfs/elev30/L30e210a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e210a.dat" right +[ 24, 31 ] = ascii (fp) : "./hrtfs/elev30/L30e205a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e205a.dat" right +[ 24, 32 ] = ascii (fp) : "./hrtfs/elev30/L30e200a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e200a.dat" right +[ 24, 33 ] = ascii (fp) : "./hrtfs/elev30/L30e195a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e195a.dat" right +[ 24, 34 ] = ascii (fp) : "./hrtfs/elev30/L30e190a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e190a.dat" right +[ 24, 35 ] = ascii (fp) : "./hrtfs/elev30/L30e185a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e185a.dat" right +[ 24, 36 ] = ascii (fp) : "./hrtfs/elev30/L30e180a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e180a.dat" right +[ 24, 37 ] = ascii (fp) : "./hrtfs/elev30/L30e175a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e175a.dat" right +[ 24, 38 ] = ascii (fp) : "./hrtfs/elev30/L30e170a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e170a.dat" right +[ 24, 39 ] = ascii (fp) : "./hrtfs/elev30/L30e165a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e165a.dat" right +[ 24, 40 ] = ascii (fp) : "./hrtfs/elev30/L30e160a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e160a.dat" right +[ 24, 41 ] = ascii (fp) : "./hrtfs/elev30/L30e155a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e155a.dat" right +[ 24, 42 ] = ascii (fp) : "./hrtfs/elev30/L30e150a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e150a.dat" right +[ 24, 43 ] = ascii (fp) : "./hrtfs/elev30/L30e145a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e145a.dat" right +[ 24, 44 ] = ascii (fp) : "./hrtfs/elev30/L30e140a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e140a.dat" right +[ 24, 45 ] = ascii (fp) : "./hrtfs/elev30/L30e135a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e135a.dat" right +[ 24, 46 ] = ascii (fp) : "./hrtfs/elev30/L30e130a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e130a.dat" right +[ 24, 47 ] = ascii (fp) : "./hrtfs/elev30/L30e125a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e125a.dat" right +[ 24, 48 ] = ascii (fp) : "./hrtfs/elev30/L30e120a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e120a.dat" right +[ 24, 49 ] = ascii (fp) : "./hrtfs/elev30/L30e115a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e115a.dat" right +[ 24, 50 ] = ascii (fp) : "./hrtfs/elev30/L30e110a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e110a.dat" right +[ 24, 51 ] = ascii (fp) : "./hrtfs/elev30/L30e105a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e105a.dat" right +[ 24, 52 ] = ascii (fp) : "./hrtfs/elev30/L30e100a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e100a.dat" right +[ 24, 53 ] = ascii (fp) : "./hrtfs/elev30/L30e095a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e095a.dat" right +[ 24, 54 ] = ascii (fp) : "./hrtfs/elev30/L30e090a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e090a.dat" right +[ 24, 55 ] = ascii (fp) : "./hrtfs/elev30/L30e085a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e085a.dat" right +[ 24, 56 ] = ascii (fp) : "./hrtfs/elev30/L30e080a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e080a.dat" right +[ 24, 57 ] = ascii (fp) : "./hrtfs/elev30/L30e075a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e075a.dat" right +[ 24, 58 ] = ascii (fp) : "./hrtfs/elev30/L30e070a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e070a.dat" right +[ 24, 59 ] = ascii (fp) : "./hrtfs/elev30/L30e065a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e065a.dat" right +[ 24, 60 ] = ascii (fp) : "./hrtfs/elev30/L30e060a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e060a.dat" right +[ 24, 61 ] = ascii (fp) : "./hrtfs/elev30/L30e055a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e055a.dat" right +[ 24, 62 ] = ascii (fp) : "./hrtfs/elev30/L30e050a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e050a.dat" right +[ 24, 63 ] = ascii (fp) : "./hrtfs/elev30/L30e045a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e045a.dat" right +[ 24, 64 ] = ascii (fp) : "./hrtfs/elev30/L30e040a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e040a.dat" right +[ 24, 65 ] = ascii (fp) : "./hrtfs/elev30/L30e035a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e035a.dat" right +[ 24, 66 ] = ascii (fp) : "./hrtfs/elev30/L30e030a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e030a.dat" right +[ 24, 67 ] = ascii (fp) : "./hrtfs/elev30/L30e025a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e025a.dat" right +[ 24, 68 ] = ascii (fp) : "./hrtfs/elev30/L30e020a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e020a.dat" right +[ 24, 69 ] = ascii (fp) : "./hrtfs/elev30/L30e015a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e015a.dat" right +[ 24, 70 ] = ascii (fp) : "./hrtfs/elev30/L30e010a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e010a.dat" right +[ 24, 71 ] = ascii (fp) : "./hrtfs/elev30/L30e005a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e005a.dat" right -[ 25, 0 ] = ascii (fp) : "./hrtfs/elev35/L35e000a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e000a.dat right -[ 25, 1 ] = ascii (fp) : "./hrtfs/elev35/L35e355a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e355a.dat right -[ 25, 2 ] = ascii (fp) : "./hrtfs/elev35/L35e350a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e350a.dat right -[ 25, 3 ] = ascii (fp) : "./hrtfs/elev35/L35e345a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e345a.dat right -[ 25, 4 ] = ascii (fp) : "./hrtfs/elev35/L35e340a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e340a.dat right -[ 25, 5 ] = ascii (fp) : "./hrtfs/elev35/L35e335a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e335a.dat right -[ 25, 6 ] = ascii (fp) : "./hrtfs/elev35/L35e330a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e330a.dat right -[ 25, 7 ] = ascii (fp) : "./hrtfs/elev35/L35e325a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e325a.dat right -[ 25, 8 ] = ascii (fp) : "./hrtfs/elev35/L35e320a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e320a.dat right -[ 25, 9 ] = ascii (fp) : "./hrtfs/elev35/L35e315a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e315a.dat right -[ 25, 10 ] = ascii (fp) : "./hrtfs/elev35/L35e310a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e310a.dat right -[ 25, 11 ] = ascii (fp) : "./hrtfs/elev35/L35e305a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e305a.dat right -[ 25, 12 ] = ascii (fp) : "./hrtfs/elev35/L35e300a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e300a.dat right -[ 25, 13 ] = ascii (fp) : "./hrtfs/elev35/L35e295a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e295a.dat right -[ 25, 14 ] = ascii (fp) : "./hrtfs/elev35/L35e290a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e290a.dat right -[ 25, 15 ] = ascii (fp) : "./hrtfs/elev35/L35e285a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e285a.dat right -[ 25, 16 ] = ascii (fp) : "./hrtfs/elev35/L35e280a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e280a.dat right -[ 25, 17 ] = ascii (fp) : "./hrtfs/elev35/L35e275a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e275a.dat right -[ 25, 18 ] = ascii (fp) : "./hrtfs/elev35/L35e270a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e270a.dat right -[ 25, 19 ] = ascii (fp) : "./hrtfs/elev35/L35e265a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e265a.dat right -[ 25, 20 ] = ascii (fp) : "./hrtfs/elev35/L35e260a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e260a.dat right -[ 25, 21 ] = ascii (fp) : "./hrtfs/elev35/L35e255a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e255a.dat right -[ 25, 22 ] = ascii (fp) : "./hrtfs/elev35/L35e250a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e250a.dat right -[ 25, 23 ] = ascii (fp) : "./hrtfs/elev35/L35e245a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e245a.dat right -[ 25, 24 ] = ascii (fp) : "./hrtfs/elev35/L35e240a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e240a.dat right -[ 25, 25 ] = ascii (fp) : "./hrtfs/elev35/L35e235a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e235a.dat right -[ 25, 26 ] = ascii (fp) : "./hrtfs/elev35/L35e230a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e230a.dat right -[ 25, 27 ] = ascii (fp) : "./hrtfs/elev35/L35e225a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e225a.dat right -[ 25, 28 ] = ascii (fp) : "./hrtfs/elev35/L35e220a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e220a.dat right -[ 25, 29 ] = ascii (fp) : "./hrtfs/elev35/L35e215a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e215a.dat right -[ 25, 30 ] = ascii (fp) : "./hrtfs/elev35/L35e210a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e210a.dat right -[ 25, 31 ] = ascii (fp) : "./hrtfs/elev35/L35e205a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e205a.dat right -[ 25, 32 ] = ascii (fp) : "./hrtfs/elev35/L35e200a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e200a.dat right -[ 25, 33 ] = ascii (fp) : "./hrtfs/elev35/L35e195a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e195a.dat right -[ 25, 34 ] = ascii (fp) : "./hrtfs/elev35/L35e190a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e190a.dat right -[ 25, 35 ] = ascii (fp) : "./hrtfs/elev35/L35e185a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e185a.dat right -[ 25, 36 ] = ascii (fp) : "./hrtfs/elev35/L35e180a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e180a.dat right -[ 25, 37 ] = ascii (fp) : "./hrtfs/elev35/L35e175a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e175a.dat right -[ 25, 38 ] = ascii (fp) : "./hrtfs/elev35/L35e170a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e170a.dat right -[ 25, 39 ] = ascii (fp) : "./hrtfs/elev35/L35e165a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e165a.dat right -[ 25, 40 ] = ascii (fp) : "./hrtfs/elev35/L35e160a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e160a.dat right -[ 25, 41 ] = ascii (fp) : "./hrtfs/elev35/L35e155a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e155a.dat right -[ 25, 42 ] = ascii (fp) : "./hrtfs/elev35/L35e150a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e150a.dat right -[ 25, 43 ] = ascii (fp) : "./hrtfs/elev35/L35e145a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e145a.dat right -[ 25, 44 ] = ascii (fp) : "./hrtfs/elev35/L35e140a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e140a.dat right -[ 25, 45 ] = ascii (fp) : "./hrtfs/elev35/L35e135a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e135a.dat right -[ 25, 46 ] = ascii (fp) : "./hrtfs/elev35/L35e130a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e130a.dat right -[ 25, 47 ] = ascii (fp) : "./hrtfs/elev35/L35e125a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e125a.dat right -[ 25, 48 ] = ascii (fp) : "./hrtfs/elev35/L35e120a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e120a.dat right -[ 25, 49 ] = ascii (fp) : "./hrtfs/elev35/L35e115a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e115a.dat right -[ 25, 50 ] = ascii (fp) : "./hrtfs/elev35/L35e110a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e110a.dat right -[ 25, 51 ] = ascii (fp) : "./hrtfs/elev35/L35e105a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e105a.dat right -[ 25, 52 ] = ascii (fp) : "./hrtfs/elev35/L35e100a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e100a.dat right -[ 25, 53 ] = ascii (fp) : "./hrtfs/elev35/L35e095a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e095a.dat right -[ 25, 54 ] = ascii (fp) : "./hrtfs/elev35/L35e090a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e090a.dat right -[ 25, 55 ] = ascii (fp) : "./hrtfs/elev35/L35e085a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e085a.dat right -[ 25, 56 ] = ascii (fp) : "./hrtfs/elev35/L35e080a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e080a.dat right -[ 25, 57 ] = ascii (fp) : "./hrtfs/elev35/L35e075a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e075a.dat right -[ 25, 58 ] = ascii (fp) : "./hrtfs/elev35/L35e070a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e070a.dat right -[ 25, 59 ] = ascii (fp) : "./hrtfs/elev35/L35e065a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e065a.dat right -[ 25, 60 ] = ascii (fp) : "./hrtfs/elev35/L35e060a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e060a.dat right -[ 25, 61 ] = ascii (fp) : "./hrtfs/elev35/L35e055a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e055a.dat right -[ 25, 62 ] = ascii (fp) : "./hrtfs/elev35/L35e050a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e050a.dat right -[ 25, 63 ] = ascii (fp) : "./hrtfs/elev35/L35e045a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e045a.dat right -[ 25, 64 ] = ascii (fp) : "./hrtfs/elev35/L35e040a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e040a.dat right -[ 25, 65 ] = ascii (fp) : "./hrtfs/elev35/L35e035a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e035a.dat right -[ 25, 66 ] = ascii (fp) : "./hrtfs/elev35/L35e030a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e030a.dat right -[ 25, 67 ] = ascii (fp) : "./hrtfs/elev35/L35e025a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e025a.dat right -[ 25, 68 ] = ascii (fp) : "./hrtfs/elev35/L35e020a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e020a.dat right -[ 25, 69 ] = ascii (fp) : "./hrtfs/elev35/L35e015a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e015a.dat right -[ 25, 70 ] = ascii (fp) : "./hrtfs/elev35/L35e010a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e010a.dat right -[ 25, 71 ] = ascii (fp) : "./hrtfs/elev35/L35e005a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e005a.dat right +[ 25, 0 ] = ascii (fp) : "./hrtfs/elev35/L35e000a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e000a.dat" right +[ 25, 1 ] = ascii (fp) : "./hrtfs/elev35/L35e355a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e355a.dat" right +[ 25, 2 ] = ascii (fp) : "./hrtfs/elev35/L35e350a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e350a.dat" right +[ 25, 3 ] = ascii (fp) : "./hrtfs/elev35/L35e345a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e345a.dat" right +[ 25, 4 ] = ascii (fp) : "./hrtfs/elev35/L35e340a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e340a.dat" right +[ 25, 5 ] = ascii (fp) : "./hrtfs/elev35/L35e335a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e335a.dat" right +[ 25, 6 ] = ascii (fp) : "./hrtfs/elev35/L35e330a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e330a.dat" right +[ 25, 7 ] = ascii (fp) : "./hrtfs/elev35/L35e325a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e325a.dat" right +[ 25, 8 ] = ascii (fp) : "./hrtfs/elev35/L35e320a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e320a.dat" right +[ 25, 9 ] = ascii (fp) : "./hrtfs/elev35/L35e315a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e315a.dat" right +[ 25, 10 ] = ascii (fp) : "./hrtfs/elev35/L35e310a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e310a.dat" right +[ 25, 11 ] = ascii (fp) : "./hrtfs/elev35/L35e305a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e305a.dat" right +[ 25, 12 ] = ascii (fp) : "./hrtfs/elev35/L35e300a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e300a.dat" right +[ 25, 13 ] = ascii (fp) : "./hrtfs/elev35/L35e295a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e295a.dat" right +[ 25, 14 ] = ascii (fp) : "./hrtfs/elev35/L35e290a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e290a.dat" right +[ 25, 15 ] = ascii (fp) : "./hrtfs/elev35/L35e285a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e285a.dat" right +[ 25, 16 ] = ascii (fp) : "./hrtfs/elev35/L35e280a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e280a.dat" right +[ 25, 17 ] = ascii (fp) : "./hrtfs/elev35/L35e275a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e275a.dat" right +[ 25, 18 ] = ascii (fp) : "./hrtfs/elev35/L35e270a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e270a.dat" right +[ 25, 19 ] = ascii (fp) : "./hrtfs/elev35/L35e265a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e265a.dat" right +[ 25, 20 ] = ascii (fp) : "./hrtfs/elev35/L35e260a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e260a.dat" right +[ 25, 21 ] = ascii (fp) : "./hrtfs/elev35/L35e255a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e255a.dat" right +[ 25, 22 ] = ascii (fp) : "./hrtfs/elev35/L35e250a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e250a.dat" right +[ 25, 23 ] = ascii (fp) : "./hrtfs/elev35/L35e245a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e245a.dat" right +[ 25, 24 ] = ascii (fp) : "./hrtfs/elev35/L35e240a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e240a.dat" right +[ 25, 25 ] = ascii (fp) : "./hrtfs/elev35/L35e235a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e235a.dat" right +[ 25, 26 ] = ascii (fp) : "./hrtfs/elev35/L35e230a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e230a.dat" right +[ 25, 27 ] = ascii (fp) : "./hrtfs/elev35/L35e225a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e225a.dat" right +[ 25, 28 ] = ascii (fp) : "./hrtfs/elev35/L35e220a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e220a.dat" right +[ 25, 29 ] = ascii (fp) : "./hrtfs/elev35/L35e215a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e215a.dat" right +[ 25, 30 ] = ascii (fp) : "./hrtfs/elev35/L35e210a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e210a.dat" right +[ 25, 31 ] = ascii (fp) : "./hrtfs/elev35/L35e205a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e205a.dat" right +[ 25, 32 ] = ascii (fp) : "./hrtfs/elev35/L35e200a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e200a.dat" right +[ 25, 33 ] = ascii (fp) : "./hrtfs/elev35/L35e195a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e195a.dat" right +[ 25, 34 ] = ascii (fp) : "./hrtfs/elev35/L35e190a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e190a.dat" right +[ 25, 35 ] = ascii (fp) : "./hrtfs/elev35/L35e185a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e185a.dat" right +[ 25, 36 ] = ascii (fp) : "./hrtfs/elev35/L35e180a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e180a.dat" right +[ 25, 37 ] = ascii (fp) : "./hrtfs/elev35/L35e175a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e175a.dat" right +[ 25, 38 ] = ascii (fp) : "./hrtfs/elev35/L35e170a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e170a.dat" right +[ 25, 39 ] = ascii (fp) : "./hrtfs/elev35/L35e165a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e165a.dat" right +[ 25, 40 ] = ascii (fp) : "./hrtfs/elev35/L35e160a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e160a.dat" right +[ 25, 41 ] = ascii (fp) : "./hrtfs/elev35/L35e155a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e155a.dat" right +[ 25, 42 ] = ascii (fp) : "./hrtfs/elev35/L35e150a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e150a.dat" right +[ 25, 43 ] = ascii (fp) : "./hrtfs/elev35/L35e145a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e145a.dat" right +[ 25, 44 ] = ascii (fp) : "./hrtfs/elev35/L35e140a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e140a.dat" right +[ 25, 45 ] = ascii (fp) : "./hrtfs/elev35/L35e135a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e135a.dat" right +[ 25, 46 ] = ascii (fp) : "./hrtfs/elev35/L35e130a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e130a.dat" right +[ 25, 47 ] = ascii (fp) : "./hrtfs/elev35/L35e125a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e125a.dat" right +[ 25, 48 ] = ascii (fp) : "./hrtfs/elev35/L35e120a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e120a.dat" right +[ 25, 49 ] = ascii (fp) : "./hrtfs/elev35/L35e115a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e115a.dat" right +[ 25, 50 ] = ascii (fp) : "./hrtfs/elev35/L35e110a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e110a.dat" right +[ 25, 51 ] = ascii (fp) : "./hrtfs/elev35/L35e105a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e105a.dat" right +[ 25, 52 ] = ascii (fp) : "./hrtfs/elev35/L35e100a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e100a.dat" right +[ 25, 53 ] = ascii (fp) : "./hrtfs/elev35/L35e095a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e095a.dat" right +[ 25, 54 ] = ascii (fp) : "./hrtfs/elev35/L35e090a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e090a.dat" right +[ 25, 55 ] = ascii (fp) : "./hrtfs/elev35/L35e085a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e085a.dat" right +[ 25, 56 ] = ascii (fp) : "./hrtfs/elev35/L35e080a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e080a.dat" right +[ 25, 57 ] = ascii (fp) : "./hrtfs/elev35/L35e075a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e075a.dat" right +[ 25, 58 ] = ascii (fp) : "./hrtfs/elev35/L35e070a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e070a.dat" right +[ 25, 59 ] = ascii (fp) : "./hrtfs/elev35/L35e065a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e065a.dat" right +[ 25, 60 ] = ascii (fp) : "./hrtfs/elev35/L35e060a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e060a.dat" right +[ 25, 61 ] = ascii (fp) : "./hrtfs/elev35/L35e055a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e055a.dat" right +[ 25, 62 ] = ascii (fp) : "./hrtfs/elev35/L35e050a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e050a.dat" right +[ 25, 63 ] = ascii (fp) : "./hrtfs/elev35/L35e045a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e045a.dat" right +[ 25, 64 ] = ascii (fp) : "./hrtfs/elev35/L35e040a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e040a.dat" right +[ 25, 65 ] = ascii (fp) : "./hrtfs/elev35/L35e035a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e035a.dat" right +[ 25, 66 ] = ascii (fp) : "./hrtfs/elev35/L35e030a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e030a.dat" right +[ 25, 67 ] = ascii (fp) : "./hrtfs/elev35/L35e025a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e025a.dat" right +[ 25, 68 ] = ascii (fp) : "./hrtfs/elev35/L35e020a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e020a.dat" right +[ 25, 69 ] = ascii (fp) : "./hrtfs/elev35/L35e015a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e015a.dat" right +[ 25, 70 ] = ascii (fp) : "./hrtfs/elev35/L35e010a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e010a.dat" right +[ 25, 71 ] = ascii (fp) : "./hrtfs/elev35/L35e005a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e005a.dat" right -[ 26, 0 ] = ascii (fp) : "./hrtfs/elev40/L40e000a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e000a.dat right -[ 26, 1 ] = ascii (fp) : "./hrtfs/elev40/L40e355a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e355a.dat right -[ 26, 2 ] = ascii (fp) : "./hrtfs/elev40/L40e350a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e350a.dat right -[ 26, 3 ] = ascii (fp) : "./hrtfs/elev40/L40e345a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e345a.dat right -[ 26, 4 ] = ascii (fp) : "./hrtfs/elev40/L40e340a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e340a.dat right -[ 26, 5 ] = ascii (fp) : "./hrtfs/elev40/L40e335a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e335a.dat right -[ 26, 6 ] = ascii (fp) : "./hrtfs/elev40/L40e330a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e330a.dat right -[ 26, 7 ] = ascii (fp) : "./hrtfs/elev40/L40e325a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e325a.dat right -[ 26, 8 ] = ascii (fp) : "./hrtfs/elev40/L40e320a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e320a.dat right -[ 26, 9 ] = ascii (fp) : "./hrtfs/elev40/L40e315a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e315a.dat right -[ 26, 10 ] = ascii (fp) : "./hrtfs/elev40/L40e310a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e310a.dat right -[ 26, 11 ] = ascii (fp) : "./hrtfs/elev40/L40e305a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e305a.dat right -[ 26, 12 ] = ascii (fp) : "./hrtfs/elev40/L40e300a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e300a.dat right -[ 26, 13 ] = ascii (fp) : "./hrtfs/elev40/L40e295a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e295a.dat right -[ 26, 14 ] = ascii (fp) : "./hrtfs/elev40/L40e290a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e290a.dat right -[ 26, 15 ] = ascii (fp) : "./hrtfs/elev40/L40e285a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e285a.dat right -[ 26, 16 ] = ascii (fp) : "./hrtfs/elev40/L40e280a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e280a.dat right -[ 26, 17 ] = ascii (fp) : "./hrtfs/elev40/L40e275a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e275a.dat right -[ 26, 18 ] = ascii (fp) : "./hrtfs/elev40/L40e270a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e270a.dat right -[ 26, 19 ] = ascii (fp) : "./hrtfs/elev40/L40e265a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e265a.dat right -[ 26, 20 ] = ascii (fp) : "./hrtfs/elev40/L40e260a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e260a.dat right -[ 26, 21 ] = ascii (fp) : "./hrtfs/elev40/L40e255a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e255a.dat right -[ 26, 22 ] = ascii (fp) : "./hrtfs/elev40/L40e250a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e250a.dat right -[ 26, 23 ] = ascii (fp) : "./hrtfs/elev40/L40e245a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e245a.dat right -[ 26, 24 ] = ascii (fp) : "./hrtfs/elev40/L40e240a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e240a.dat right -[ 26, 25 ] = ascii (fp) : "./hrtfs/elev40/L40e235a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e235a.dat right -[ 26, 26 ] = ascii (fp) : "./hrtfs/elev40/L40e230a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e230a.dat right -[ 26, 27 ] = ascii (fp) : "./hrtfs/elev40/L40e225a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e225a.dat right -[ 26, 28 ] = ascii (fp) : "./hrtfs/elev40/L40e220a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e220a.dat right -[ 26, 29 ] = ascii (fp) : "./hrtfs/elev40/L40e215a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e215a.dat right -[ 26, 30 ] = ascii (fp) : "./hrtfs/elev40/L40e210a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e210a.dat right -[ 26, 31 ] = ascii (fp) : "./hrtfs/elev40/L40e205a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e205a.dat right -[ 26, 32 ] = ascii (fp) : "./hrtfs/elev40/L40e200a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e200a.dat right -[ 26, 33 ] = ascii (fp) : "./hrtfs/elev40/L40e195a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e195a.dat right -[ 26, 34 ] = ascii (fp) : "./hrtfs/elev40/L40e190a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e190a.dat right -[ 26, 35 ] = ascii (fp) : "./hrtfs/elev40/L40e185a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e185a.dat right -[ 26, 36 ] = ascii (fp) : "./hrtfs/elev40/L40e180a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e180a.dat right -[ 26, 37 ] = ascii (fp) : "./hrtfs/elev40/L40e175a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e175a.dat right -[ 26, 38 ] = ascii (fp) : "./hrtfs/elev40/L40e170a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e170a.dat right -[ 26, 39 ] = ascii (fp) : "./hrtfs/elev40/L40e165a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e165a.dat right -[ 26, 40 ] = ascii (fp) : "./hrtfs/elev40/L40e160a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e160a.dat right -[ 26, 41 ] = ascii (fp) : "./hrtfs/elev40/L40e155a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e155a.dat right -[ 26, 42 ] = ascii (fp) : "./hrtfs/elev40/L40e150a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e150a.dat right -[ 26, 43 ] = ascii (fp) : "./hrtfs/elev40/L40e145a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e145a.dat right -[ 26, 44 ] = ascii (fp) : "./hrtfs/elev40/L40e140a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e140a.dat right -[ 26, 45 ] = ascii (fp) : "./hrtfs/elev40/L40e135a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e135a.dat right -[ 26, 46 ] = ascii (fp) : "./hrtfs/elev40/L40e130a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e130a.dat right -[ 26, 47 ] = ascii (fp) : "./hrtfs/elev40/L40e125a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e125a.dat right -[ 26, 48 ] = ascii (fp) : "./hrtfs/elev40/L40e120a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e120a.dat right -[ 26, 49 ] = ascii (fp) : "./hrtfs/elev40/L40e115a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e115a.dat right -[ 26, 50 ] = ascii (fp) : "./hrtfs/elev40/L40e110a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e110a.dat right -[ 26, 51 ] = ascii (fp) : "./hrtfs/elev40/L40e105a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e105a.dat right -[ 26, 52 ] = ascii (fp) : "./hrtfs/elev40/L40e100a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e100a.dat right -[ 26, 53 ] = ascii (fp) : "./hrtfs/elev40/L40e095a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e095a.dat right -[ 26, 54 ] = ascii (fp) : "./hrtfs/elev40/L40e090a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e090a.dat right -[ 26, 55 ] = ascii (fp) : "./hrtfs/elev40/L40e085a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e085a.dat right -[ 26, 56 ] = ascii (fp) : "./hrtfs/elev40/L40e080a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e080a.dat right -[ 26, 57 ] = ascii (fp) : "./hrtfs/elev40/L40e075a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e075a.dat right -[ 26, 58 ] = ascii (fp) : "./hrtfs/elev40/L40e070a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e070a.dat right -[ 26, 59 ] = ascii (fp) : "./hrtfs/elev40/L40e065a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e065a.dat right -[ 26, 60 ] = ascii (fp) : "./hrtfs/elev40/L40e060a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e060a.dat right -[ 26, 61 ] = ascii (fp) : "./hrtfs/elev40/L40e055a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e055a.dat right -[ 26, 62 ] = ascii (fp) : "./hrtfs/elev40/L40e050a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e050a.dat right -[ 26, 63 ] = ascii (fp) : "./hrtfs/elev40/L40e045a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e045a.dat right -[ 26, 64 ] = ascii (fp) : "./hrtfs/elev40/L40e040a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e040a.dat right -[ 26, 65 ] = ascii (fp) : "./hrtfs/elev40/L40e035a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e035a.dat right -[ 26, 66 ] = ascii (fp) : "./hrtfs/elev40/L40e030a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e030a.dat right -[ 26, 67 ] = ascii (fp) : "./hrtfs/elev40/L40e025a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e025a.dat right -[ 26, 68 ] = ascii (fp) : "./hrtfs/elev40/L40e020a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e020a.dat right -[ 26, 69 ] = ascii (fp) : "./hrtfs/elev40/L40e015a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e015a.dat right -[ 26, 70 ] = ascii (fp) : "./hrtfs/elev40/L40e010a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e010a.dat right -[ 26, 71 ] = ascii (fp) : "./hrtfs/elev40/L40e005a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e005a.dat right +[ 26, 0 ] = ascii (fp) : "./hrtfs/elev40/L40e000a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e000a.dat" right +[ 26, 1 ] = ascii (fp) : "./hrtfs/elev40/L40e355a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e355a.dat" right +[ 26, 2 ] = ascii (fp) : "./hrtfs/elev40/L40e350a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e350a.dat" right +[ 26, 3 ] = ascii (fp) : "./hrtfs/elev40/L40e345a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e345a.dat" right +[ 26, 4 ] = ascii (fp) : "./hrtfs/elev40/L40e340a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e340a.dat" right +[ 26, 5 ] = ascii (fp) : "./hrtfs/elev40/L40e335a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e335a.dat" right +[ 26, 6 ] = ascii (fp) : "./hrtfs/elev40/L40e330a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e330a.dat" right +[ 26, 7 ] = ascii (fp) : "./hrtfs/elev40/L40e325a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e325a.dat" right +[ 26, 8 ] = ascii (fp) : "./hrtfs/elev40/L40e320a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e320a.dat" right +[ 26, 9 ] = ascii (fp) : "./hrtfs/elev40/L40e315a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e315a.dat" right +[ 26, 10 ] = ascii (fp) : "./hrtfs/elev40/L40e310a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e310a.dat" right +[ 26, 11 ] = ascii (fp) : "./hrtfs/elev40/L40e305a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e305a.dat" right +[ 26, 12 ] = ascii (fp) : "./hrtfs/elev40/L40e300a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e300a.dat" right +[ 26, 13 ] = ascii (fp) : "./hrtfs/elev40/L40e295a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e295a.dat" right +[ 26, 14 ] = ascii (fp) : "./hrtfs/elev40/L40e290a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e290a.dat" right +[ 26, 15 ] = ascii (fp) : "./hrtfs/elev40/L40e285a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e285a.dat" right +[ 26, 16 ] = ascii (fp) : "./hrtfs/elev40/L40e280a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e280a.dat" right +[ 26, 17 ] = ascii (fp) : "./hrtfs/elev40/L40e275a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e275a.dat" right +[ 26, 18 ] = ascii (fp) : "./hrtfs/elev40/L40e270a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e270a.dat" right +[ 26, 19 ] = ascii (fp) : "./hrtfs/elev40/L40e265a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e265a.dat" right +[ 26, 20 ] = ascii (fp) : "./hrtfs/elev40/L40e260a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e260a.dat" right +[ 26, 21 ] = ascii (fp) : "./hrtfs/elev40/L40e255a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e255a.dat" right +[ 26, 22 ] = ascii (fp) : "./hrtfs/elev40/L40e250a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e250a.dat" right +[ 26, 23 ] = ascii (fp) : "./hrtfs/elev40/L40e245a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e245a.dat" right +[ 26, 24 ] = ascii (fp) : "./hrtfs/elev40/L40e240a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e240a.dat" right +[ 26, 25 ] = ascii (fp) : "./hrtfs/elev40/L40e235a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e235a.dat" right +[ 26, 26 ] = ascii (fp) : "./hrtfs/elev40/L40e230a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e230a.dat" right +[ 26, 27 ] = ascii (fp) : "./hrtfs/elev40/L40e225a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e225a.dat" right +[ 26, 28 ] = ascii (fp) : "./hrtfs/elev40/L40e220a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e220a.dat" right +[ 26, 29 ] = ascii (fp) : "./hrtfs/elev40/L40e215a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e215a.dat" right +[ 26, 30 ] = ascii (fp) : "./hrtfs/elev40/L40e210a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e210a.dat" right +[ 26, 31 ] = ascii (fp) : "./hrtfs/elev40/L40e205a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e205a.dat" right +[ 26, 32 ] = ascii (fp) : "./hrtfs/elev40/L40e200a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e200a.dat" right +[ 26, 33 ] = ascii (fp) : "./hrtfs/elev40/L40e195a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e195a.dat" right +[ 26, 34 ] = ascii (fp) : "./hrtfs/elev40/L40e190a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e190a.dat" right +[ 26, 35 ] = ascii (fp) : "./hrtfs/elev40/L40e185a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e185a.dat" right +[ 26, 36 ] = ascii (fp) : "./hrtfs/elev40/L40e180a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e180a.dat" right +[ 26, 37 ] = ascii (fp) : "./hrtfs/elev40/L40e175a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e175a.dat" right +[ 26, 38 ] = ascii (fp) : "./hrtfs/elev40/L40e170a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e170a.dat" right +[ 26, 39 ] = ascii (fp) : "./hrtfs/elev40/L40e165a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e165a.dat" right +[ 26, 40 ] = ascii (fp) : "./hrtfs/elev40/L40e160a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e160a.dat" right +[ 26, 41 ] = ascii (fp) : "./hrtfs/elev40/L40e155a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e155a.dat" right +[ 26, 42 ] = ascii (fp) : "./hrtfs/elev40/L40e150a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e150a.dat" right +[ 26, 43 ] = ascii (fp) : "./hrtfs/elev40/L40e145a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e145a.dat" right +[ 26, 44 ] = ascii (fp) : "./hrtfs/elev40/L40e140a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e140a.dat" right +[ 26, 45 ] = ascii (fp) : "./hrtfs/elev40/L40e135a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e135a.dat" right +[ 26, 46 ] = ascii (fp) : "./hrtfs/elev40/L40e130a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e130a.dat" right +[ 26, 47 ] = ascii (fp) : "./hrtfs/elev40/L40e125a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e125a.dat" right +[ 26, 48 ] = ascii (fp) : "./hrtfs/elev40/L40e120a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e120a.dat" right +[ 26, 49 ] = ascii (fp) : "./hrtfs/elev40/L40e115a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e115a.dat" right +[ 26, 50 ] = ascii (fp) : "./hrtfs/elev40/L40e110a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e110a.dat" right +[ 26, 51 ] = ascii (fp) : "./hrtfs/elev40/L40e105a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e105a.dat" right +[ 26, 52 ] = ascii (fp) : "./hrtfs/elev40/L40e100a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e100a.dat" right +[ 26, 53 ] = ascii (fp) : "./hrtfs/elev40/L40e095a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e095a.dat" right +[ 26, 54 ] = ascii (fp) : "./hrtfs/elev40/L40e090a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e090a.dat" right +[ 26, 55 ] = ascii (fp) : "./hrtfs/elev40/L40e085a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e085a.dat" right +[ 26, 56 ] = ascii (fp) : "./hrtfs/elev40/L40e080a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e080a.dat" right +[ 26, 57 ] = ascii (fp) : "./hrtfs/elev40/L40e075a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e075a.dat" right +[ 26, 58 ] = ascii (fp) : "./hrtfs/elev40/L40e070a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e070a.dat" right +[ 26, 59 ] = ascii (fp) : "./hrtfs/elev40/L40e065a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e065a.dat" right +[ 26, 60 ] = ascii (fp) : "./hrtfs/elev40/L40e060a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e060a.dat" right +[ 26, 61 ] = ascii (fp) : "./hrtfs/elev40/L40e055a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e055a.dat" right +[ 26, 62 ] = ascii (fp) : "./hrtfs/elev40/L40e050a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e050a.dat" right +[ 26, 63 ] = ascii (fp) : "./hrtfs/elev40/L40e045a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e045a.dat" right +[ 26, 64 ] = ascii (fp) : "./hrtfs/elev40/L40e040a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e040a.dat" right +[ 26, 65 ] = ascii (fp) : "./hrtfs/elev40/L40e035a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e035a.dat" right +[ 26, 66 ] = ascii (fp) : "./hrtfs/elev40/L40e030a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e030a.dat" right +[ 26, 67 ] = ascii (fp) : "./hrtfs/elev40/L40e025a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e025a.dat" right +[ 26, 68 ] = ascii (fp) : "./hrtfs/elev40/L40e020a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e020a.dat" right +[ 26, 69 ] = ascii (fp) : "./hrtfs/elev40/L40e015a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e015a.dat" right +[ 26, 70 ] = ascii (fp) : "./hrtfs/elev40/L40e010a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e010a.dat" right +[ 26, 71 ] = ascii (fp) : "./hrtfs/elev40/L40e005a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e005a.dat" right -[ 27, 0 ] = ascii (fp) : "./hrtfs/elev45/L45e000a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e000a.dat right -[ 27, 1 ] = ascii (fp) : "./hrtfs/elev45/L45e355a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e355a.dat right -[ 27, 2 ] = ascii (fp) : "./hrtfs/elev45/L45e350a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e350a.dat right -[ 27, 3 ] = ascii (fp) : "./hrtfs/elev45/L45e345a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e345a.dat right -[ 27, 4 ] = ascii (fp) : "./hrtfs/elev45/L45e340a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e340a.dat right -[ 27, 5 ] = ascii (fp) : "./hrtfs/elev45/L45e335a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e335a.dat right -[ 27, 6 ] = ascii (fp) : "./hrtfs/elev45/L45e330a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e330a.dat right -[ 27, 7 ] = ascii (fp) : "./hrtfs/elev45/L45e325a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e325a.dat right -[ 27, 8 ] = ascii (fp) : "./hrtfs/elev45/L45e320a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e320a.dat right -[ 27, 9 ] = ascii (fp) : "./hrtfs/elev45/L45e315a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e315a.dat right -[ 27, 10 ] = ascii (fp) : "./hrtfs/elev45/L45e310a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e310a.dat right -[ 27, 11 ] = ascii (fp) : "./hrtfs/elev45/L45e305a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e305a.dat right -[ 27, 12 ] = ascii (fp) : "./hrtfs/elev45/L45e300a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e300a.dat right -[ 27, 13 ] = ascii (fp) : "./hrtfs/elev45/L45e295a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e295a.dat right -[ 27, 14 ] = ascii (fp) : "./hrtfs/elev45/L45e290a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e290a.dat right -[ 27, 15 ] = ascii (fp) : "./hrtfs/elev45/L45e285a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e285a.dat right -[ 27, 16 ] = ascii (fp) : "./hrtfs/elev45/L45e280a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e280a.dat right -[ 27, 17 ] = ascii (fp) : "./hrtfs/elev45/L45e275a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e275a.dat right -[ 27, 18 ] = ascii (fp) : "./hrtfs/elev45/L45e270a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e270a.dat right -[ 27, 19 ] = ascii (fp) : "./hrtfs/elev45/L45e265a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e265a.dat right -[ 27, 20 ] = ascii (fp) : "./hrtfs/elev45/L45e260a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e260a.dat right -[ 27, 21 ] = ascii (fp) : "./hrtfs/elev45/L45e255a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e255a.dat right -[ 27, 22 ] = ascii (fp) : "./hrtfs/elev45/L45e250a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e250a.dat right -[ 27, 23 ] = ascii (fp) : "./hrtfs/elev45/L45e245a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e245a.dat right -[ 27, 24 ] = ascii (fp) : "./hrtfs/elev45/L45e240a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e240a.dat right -[ 27, 25 ] = ascii (fp) : "./hrtfs/elev45/L45e235a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e235a.dat right -[ 27, 26 ] = ascii (fp) : "./hrtfs/elev45/L45e230a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e230a.dat right -[ 27, 27 ] = ascii (fp) : "./hrtfs/elev45/L45e225a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e225a.dat right -[ 27, 28 ] = ascii (fp) : "./hrtfs/elev45/L45e220a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e220a.dat right -[ 27, 29 ] = ascii (fp) : "./hrtfs/elev45/L45e215a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e215a.dat right -[ 27, 30 ] = ascii (fp) : "./hrtfs/elev45/L45e210a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e210a.dat right -[ 27, 31 ] = ascii (fp) : "./hrtfs/elev45/L45e205a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e205a.dat right -[ 27, 32 ] = ascii (fp) : "./hrtfs/elev45/L45e200a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e200a.dat right -[ 27, 33 ] = ascii (fp) : "./hrtfs/elev45/L45e195a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e195a.dat right -[ 27, 34 ] = ascii (fp) : "./hrtfs/elev45/L45e190a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e190a.dat right -[ 27, 35 ] = ascii (fp) : "./hrtfs/elev45/L45e185a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e185a.dat right -[ 27, 36 ] = ascii (fp) : "./hrtfs/elev45/L45e180a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e180a.dat right -[ 27, 37 ] = ascii (fp) : "./hrtfs/elev45/L45e175a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e175a.dat right -[ 27, 38 ] = ascii (fp) : "./hrtfs/elev45/L45e170a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e170a.dat right -[ 27, 39 ] = ascii (fp) : "./hrtfs/elev45/L45e165a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e165a.dat right -[ 27, 40 ] = ascii (fp) : "./hrtfs/elev45/L45e160a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e160a.dat right -[ 27, 41 ] = ascii (fp) : "./hrtfs/elev45/L45e155a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e155a.dat right -[ 27, 42 ] = ascii (fp) : "./hrtfs/elev45/L45e150a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e150a.dat right -[ 27, 43 ] = ascii (fp) : "./hrtfs/elev45/L45e145a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e145a.dat right -[ 27, 44 ] = ascii (fp) : "./hrtfs/elev45/L45e140a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e140a.dat right -[ 27, 45 ] = ascii (fp) : "./hrtfs/elev45/L45e135a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e135a.dat right -[ 27, 46 ] = ascii (fp) : "./hrtfs/elev45/L45e130a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e130a.dat right -[ 27, 47 ] = ascii (fp) : "./hrtfs/elev45/L45e125a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e125a.dat right -[ 27, 48 ] = ascii (fp) : "./hrtfs/elev45/L45e120a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e120a.dat right -[ 27, 49 ] = ascii (fp) : "./hrtfs/elev45/L45e115a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e115a.dat right -[ 27, 50 ] = ascii (fp) : "./hrtfs/elev45/L45e110a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e110a.dat right -[ 27, 51 ] = ascii (fp) : "./hrtfs/elev45/L45e105a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e105a.dat right -[ 27, 52 ] = ascii (fp) : "./hrtfs/elev45/L45e100a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e100a.dat right -[ 27, 53 ] = ascii (fp) : "./hrtfs/elev45/L45e095a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e095a.dat right -[ 27, 54 ] = ascii (fp) : "./hrtfs/elev45/L45e090a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e090a.dat right -[ 27, 55 ] = ascii (fp) : "./hrtfs/elev45/L45e085a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e085a.dat right -[ 27, 56 ] = ascii (fp) : "./hrtfs/elev45/L45e080a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e080a.dat right -[ 27, 57 ] = ascii (fp) : "./hrtfs/elev45/L45e075a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e075a.dat right -[ 27, 58 ] = ascii (fp) : "./hrtfs/elev45/L45e070a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e070a.dat right -[ 27, 59 ] = ascii (fp) : "./hrtfs/elev45/L45e065a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e065a.dat right -[ 27, 60 ] = ascii (fp) : "./hrtfs/elev45/L45e060a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e060a.dat right -[ 27, 61 ] = ascii (fp) : "./hrtfs/elev45/L45e055a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e055a.dat right -[ 27, 62 ] = ascii (fp) : "./hrtfs/elev45/L45e050a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e050a.dat right -[ 27, 63 ] = ascii (fp) : "./hrtfs/elev45/L45e045a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e045a.dat right -[ 27, 64 ] = ascii (fp) : "./hrtfs/elev45/L45e040a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e040a.dat right -[ 27, 65 ] = ascii (fp) : "./hrtfs/elev45/L45e035a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e035a.dat right -[ 27, 66 ] = ascii (fp) : "./hrtfs/elev45/L45e030a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e030a.dat right -[ 27, 67 ] = ascii (fp) : "./hrtfs/elev45/L45e025a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e025a.dat right -[ 27, 68 ] = ascii (fp) : "./hrtfs/elev45/L45e020a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e020a.dat right -[ 27, 69 ] = ascii (fp) : "./hrtfs/elev45/L45e015a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e015a.dat right -[ 27, 70 ] = ascii (fp) : "./hrtfs/elev45/L45e010a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e010a.dat right -[ 27, 71 ] = ascii (fp) : "./hrtfs/elev45/L45e005a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e005a.dat right +[ 27, 0 ] = ascii (fp) : "./hrtfs/elev45/L45e000a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e000a.dat" right +[ 27, 1 ] = ascii (fp) : "./hrtfs/elev45/L45e355a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e355a.dat" right +[ 27, 2 ] = ascii (fp) : "./hrtfs/elev45/L45e350a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e350a.dat" right +[ 27, 3 ] = ascii (fp) : "./hrtfs/elev45/L45e345a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e345a.dat" right +[ 27, 4 ] = ascii (fp) : "./hrtfs/elev45/L45e340a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e340a.dat" right +[ 27, 5 ] = ascii (fp) : "./hrtfs/elev45/L45e335a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e335a.dat" right +[ 27, 6 ] = ascii (fp) : "./hrtfs/elev45/L45e330a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e330a.dat" right +[ 27, 7 ] = ascii (fp) : "./hrtfs/elev45/L45e325a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e325a.dat" right +[ 27, 8 ] = ascii (fp) : "./hrtfs/elev45/L45e320a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e320a.dat" right +[ 27, 9 ] = ascii (fp) : "./hrtfs/elev45/L45e315a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e315a.dat" right +[ 27, 10 ] = ascii (fp) : "./hrtfs/elev45/L45e310a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e310a.dat" right +[ 27, 11 ] = ascii (fp) : "./hrtfs/elev45/L45e305a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e305a.dat" right +[ 27, 12 ] = ascii (fp) : "./hrtfs/elev45/L45e300a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e300a.dat" right +[ 27, 13 ] = ascii (fp) : "./hrtfs/elev45/L45e295a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e295a.dat" right +[ 27, 14 ] = ascii (fp) : "./hrtfs/elev45/L45e290a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e290a.dat" right +[ 27, 15 ] = ascii (fp) : "./hrtfs/elev45/L45e285a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e285a.dat" right +[ 27, 16 ] = ascii (fp) : "./hrtfs/elev45/L45e280a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e280a.dat" right +[ 27, 17 ] = ascii (fp) : "./hrtfs/elev45/L45e275a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e275a.dat" right +[ 27, 18 ] = ascii (fp) : "./hrtfs/elev45/L45e270a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e270a.dat" right +[ 27, 19 ] = ascii (fp) : "./hrtfs/elev45/L45e265a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e265a.dat" right +[ 27, 20 ] = ascii (fp) : "./hrtfs/elev45/L45e260a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e260a.dat" right +[ 27, 21 ] = ascii (fp) : "./hrtfs/elev45/L45e255a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e255a.dat" right +[ 27, 22 ] = ascii (fp) : "./hrtfs/elev45/L45e250a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e250a.dat" right +[ 27, 23 ] = ascii (fp) : "./hrtfs/elev45/L45e245a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e245a.dat" right +[ 27, 24 ] = ascii (fp) : "./hrtfs/elev45/L45e240a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e240a.dat" right +[ 27, 25 ] = ascii (fp) : "./hrtfs/elev45/L45e235a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e235a.dat" right +[ 27, 26 ] = ascii (fp) : "./hrtfs/elev45/L45e230a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e230a.dat" right +[ 27, 27 ] = ascii (fp) : "./hrtfs/elev45/L45e225a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e225a.dat" right +[ 27, 28 ] = ascii (fp) : "./hrtfs/elev45/L45e220a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e220a.dat" right +[ 27, 29 ] = ascii (fp) : "./hrtfs/elev45/L45e215a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e215a.dat" right +[ 27, 30 ] = ascii (fp) : "./hrtfs/elev45/L45e210a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e210a.dat" right +[ 27, 31 ] = ascii (fp) : "./hrtfs/elev45/L45e205a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e205a.dat" right +[ 27, 32 ] = ascii (fp) : "./hrtfs/elev45/L45e200a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e200a.dat" right +[ 27, 33 ] = ascii (fp) : "./hrtfs/elev45/L45e195a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e195a.dat" right +[ 27, 34 ] = ascii (fp) : "./hrtfs/elev45/L45e190a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e190a.dat" right +[ 27, 35 ] = ascii (fp) : "./hrtfs/elev45/L45e185a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e185a.dat" right +[ 27, 36 ] = ascii (fp) : "./hrtfs/elev45/L45e180a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e180a.dat" right +[ 27, 37 ] = ascii (fp) : "./hrtfs/elev45/L45e175a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e175a.dat" right +[ 27, 38 ] = ascii (fp) : "./hrtfs/elev45/L45e170a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e170a.dat" right +[ 27, 39 ] = ascii (fp) : "./hrtfs/elev45/L45e165a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e165a.dat" right +[ 27, 40 ] = ascii (fp) : "./hrtfs/elev45/L45e160a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e160a.dat" right +[ 27, 41 ] = ascii (fp) : "./hrtfs/elev45/L45e155a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e155a.dat" right +[ 27, 42 ] = ascii (fp) : "./hrtfs/elev45/L45e150a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e150a.dat" right +[ 27, 43 ] = ascii (fp) : "./hrtfs/elev45/L45e145a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e145a.dat" right +[ 27, 44 ] = ascii (fp) : "./hrtfs/elev45/L45e140a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e140a.dat" right +[ 27, 45 ] = ascii (fp) : "./hrtfs/elev45/L45e135a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e135a.dat" right +[ 27, 46 ] = ascii (fp) : "./hrtfs/elev45/L45e130a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e130a.dat" right +[ 27, 47 ] = ascii (fp) : "./hrtfs/elev45/L45e125a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e125a.dat" right +[ 27, 48 ] = ascii (fp) : "./hrtfs/elev45/L45e120a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e120a.dat" right +[ 27, 49 ] = ascii (fp) : "./hrtfs/elev45/L45e115a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e115a.dat" right +[ 27, 50 ] = ascii (fp) : "./hrtfs/elev45/L45e110a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e110a.dat" right +[ 27, 51 ] = ascii (fp) : "./hrtfs/elev45/L45e105a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e105a.dat" right +[ 27, 52 ] = ascii (fp) : "./hrtfs/elev45/L45e100a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e100a.dat" right +[ 27, 53 ] = ascii (fp) : "./hrtfs/elev45/L45e095a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e095a.dat" right +[ 27, 54 ] = ascii (fp) : "./hrtfs/elev45/L45e090a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e090a.dat" right +[ 27, 55 ] = ascii (fp) : "./hrtfs/elev45/L45e085a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e085a.dat" right +[ 27, 56 ] = ascii (fp) : "./hrtfs/elev45/L45e080a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e080a.dat" right +[ 27, 57 ] = ascii (fp) : "./hrtfs/elev45/L45e075a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e075a.dat" right +[ 27, 58 ] = ascii (fp) : "./hrtfs/elev45/L45e070a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e070a.dat" right +[ 27, 59 ] = ascii (fp) : "./hrtfs/elev45/L45e065a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e065a.dat" right +[ 27, 60 ] = ascii (fp) : "./hrtfs/elev45/L45e060a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e060a.dat" right +[ 27, 61 ] = ascii (fp) : "./hrtfs/elev45/L45e055a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e055a.dat" right +[ 27, 62 ] = ascii (fp) : "./hrtfs/elev45/L45e050a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e050a.dat" right +[ 27, 63 ] = ascii (fp) : "./hrtfs/elev45/L45e045a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e045a.dat" right +[ 27, 64 ] = ascii (fp) : "./hrtfs/elev45/L45e040a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e040a.dat" right +[ 27, 65 ] = ascii (fp) : "./hrtfs/elev45/L45e035a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e035a.dat" right +[ 27, 66 ] = ascii (fp) : "./hrtfs/elev45/L45e030a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e030a.dat" right +[ 27, 67 ] = ascii (fp) : "./hrtfs/elev45/L45e025a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e025a.dat" right +[ 27, 68 ] = ascii (fp) : "./hrtfs/elev45/L45e020a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e020a.dat" right +[ 27, 69 ] = ascii (fp) : "./hrtfs/elev45/L45e015a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e015a.dat" right +[ 27, 70 ] = ascii (fp) : "./hrtfs/elev45/L45e010a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e010a.dat" right +[ 27, 71 ] = ascii (fp) : "./hrtfs/elev45/L45e005a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e005a.dat" right -[ 28, 0 ] = ascii (fp) : "./hrtfs/elev50/L50e000a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e000a.dat right -[ 28, 1 ] = ascii (fp) : "./hrtfs/elev50/L50e355a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e355a.dat right -[ 28, 2 ] = ascii (fp) : "./hrtfs/elev50/L50e350a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e350a.dat right -[ 28, 3 ] = ascii (fp) : "./hrtfs/elev50/L50e345a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e345a.dat right -[ 28, 4 ] = ascii (fp) : "./hrtfs/elev50/L50e340a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e340a.dat right -[ 28, 5 ] = ascii (fp) : "./hrtfs/elev50/L50e335a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e335a.dat right -[ 28, 6 ] = ascii (fp) : "./hrtfs/elev50/L50e330a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e330a.dat right -[ 28, 7 ] = ascii (fp) : "./hrtfs/elev50/L50e325a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e325a.dat right -[ 28, 8 ] = ascii (fp) : "./hrtfs/elev50/L50e320a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e320a.dat right -[ 28, 9 ] = ascii (fp) : "./hrtfs/elev50/L50e315a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e315a.dat right -[ 28, 10 ] = ascii (fp) : "./hrtfs/elev50/L50e310a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e310a.dat right -[ 28, 11 ] = ascii (fp) : "./hrtfs/elev50/L50e305a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e305a.dat right -[ 28, 12 ] = ascii (fp) : "./hrtfs/elev50/L50e300a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e300a.dat right -[ 28, 13 ] = ascii (fp) : "./hrtfs/elev50/L50e295a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e295a.dat right -[ 28, 14 ] = ascii (fp) : "./hrtfs/elev50/L50e290a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e290a.dat right -[ 28, 15 ] = ascii (fp) : "./hrtfs/elev50/L50e285a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e285a.dat right -[ 28, 16 ] = ascii (fp) : "./hrtfs/elev50/L50e280a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e280a.dat right -[ 28, 17 ] = ascii (fp) : "./hrtfs/elev50/L50e275a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e275a.dat right -[ 28, 18 ] = ascii (fp) : "./hrtfs/elev50/L50e270a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e270a.dat right -[ 28, 19 ] = ascii (fp) : "./hrtfs/elev50/L50e265a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e265a.dat right -[ 28, 20 ] = ascii (fp) : "./hrtfs/elev50/L50e260a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e260a.dat right -[ 28, 21 ] = ascii (fp) : "./hrtfs/elev50/L50e255a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e255a.dat right -[ 28, 22 ] = ascii (fp) : "./hrtfs/elev50/L50e250a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e250a.dat right -[ 28, 23 ] = ascii (fp) : "./hrtfs/elev50/L50e245a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e245a.dat right -[ 28, 24 ] = ascii (fp) : "./hrtfs/elev50/L50e240a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e240a.dat right -[ 28, 25 ] = ascii (fp) : "./hrtfs/elev50/L50e235a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e235a.dat right -[ 28, 26 ] = ascii (fp) : "./hrtfs/elev50/L50e230a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e230a.dat right -[ 28, 27 ] = ascii (fp) : "./hrtfs/elev50/L50e225a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e225a.dat right -[ 28, 28 ] = ascii (fp) : "./hrtfs/elev50/L50e220a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e220a.dat right -[ 28, 29 ] = ascii (fp) : "./hrtfs/elev50/L50e215a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e215a.dat right -[ 28, 30 ] = ascii (fp) : "./hrtfs/elev50/L50e210a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e210a.dat right -[ 28, 31 ] = ascii (fp) : "./hrtfs/elev50/L50e205a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e205a.dat right -[ 28, 32 ] = ascii (fp) : "./hrtfs/elev50/L50e200a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e200a.dat right -[ 28, 33 ] = ascii (fp) : "./hrtfs/elev50/L50e195a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e195a.dat right -[ 28, 34 ] = ascii (fp) : "./hrtfs/elev50/L50e190a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e190a.dat right -[ 28, 35 ] = ascii (fp) : "./hrtfs/elev50/L50e185a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e185a.dat right -[ 28, 36 ] = ascii (fp) : "./hrtfs/elev50/L50e180a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e180a.dat right -[ 28, 37 ] = ascii (fp) : "./hrtfs/elev50/L50e175a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e175a.dat right -[ 28, 38 ] = ascii (fp) : "./hrtfs/elev50/L50e170a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e170a.dat right -[ 28, 39 ] = ascii (fp) : "./hrtfs/elev50/L50e165a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e165a.dat right -[ 28, 40 ] = ascii (fp) : "./hrtfs/elev50/L50e160a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e160a.dat right -[ 28, 41 ] = ascii (fp) : "./hrtfs/elev50/L50e155a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e155a.dat right -[ 28, 42 ] = ascii (fp) : "./hrtfs/elev50/L50e150a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e150a.dat right -[ 28, 43 ] = ascii (fp) : "./hrtfs/elev50/L50e145a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e145a.dat right -[ 28, 44 ] = ascii (fp) : "./hrtfs/elev50/L50e140a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e140a.dat right -[ 28, 45 ] = ascii (fp) : "./hrtfs/elev50/L50e135a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e135a.dat right -[ 28, 46 ] = ascii (fp) : "./hrtfs/elev50/L50e130a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e130a.dat right -[ 28, 47 ] = ascii (fp) : "./hrtfs/elev50/L50e125a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e125a.dat right -[ 28, 48 ] = ascii (fp) : "./hrtfs/elev50/L50e120a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e120a.dat right -[ 28, 49 ] = ascii (fp) : "./hrtfs/elev50/L50e115a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e115a.dat right -[ 28, 50 ] = ascii (fp) : "./hrtfs/elev50/L50e110a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e110a.dat right -[ 28, 51 ] = ascii (fp) : "./hrtfs/elev50/L50e105a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e105a.dat right -[ 28, 52 ] = ascii (fp) : "./hrtfs/elev50/L50e100a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e100a.dat right -[ 28, 53 ] = ascii (fp) : "./hrtfs/elev50/L50e095a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e095a.dat right -[ 28, 54 ] = ascii (fp) : "./hrtfs/elev50/L50e090a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e090a.dat right -[ 28, 55 ] = ascii (fp) : "./hrtfs/elev50/L50e085a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e085a.dat right -[ 28, 56 ] = ascii (fp) : "./hrtfs/elev50/L50e080a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e080a.dat right -[ 28, 57 ] = ascii (fp) : "./hrtfs/elev50/L50e075a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e075a.dat right -[ 28, 58 ] = ascii (fp) : "./hrtfs/elev50/L50e070a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e070a.dat right -[ 28, 59 ] = ascii (fp) : "./hrtfs/elev50/L50e065a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e065a.dat right -[ 28, 60 ] = ascii (fp) : "./hrtfs/elev50/L50e060a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e060a.dat right -[ 28, 61 ] = ascii (fp) : "./hrtfs/elev50/L50e055a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e055a.dat right -[ 28, 62 ] = ascii (fp) : "./hrtfs/elev50/L50e050a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e050a.dat right -[ 28, 63 ] = ascii (fp) : "./hrtfs/elev50/L50e045a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e045a.dat right -[ 28, 64 ] = ascii (fp) : "./hrtfs/elev50/L50e040a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e040a.dat right -[ 28, 65 ] = ascii (fp) : "./hrtfs/elev50/L50e035a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e035a.dat right -[ 28, 66 ] = ascii (fp) : "./hrtfs/elev50/L50e030a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e030a.dat right -[ 28, 67 ] = ascii (fp) : "./hrtfs/elev50/L50e025a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e025a.dat right -[ 28, 68 ] = ascii (fp) : "./hrtfs/elev50/L50e020a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e020a.dat right -[ 28, 69 ] = ascii (fp) : "./hrtfs/elev50/L50e015a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e015a.dat right -[ 28, 70 ] = ascii (fp) : "./hrtfs/elev50/L50e010a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e010a.dat right -[ 28, 71 ] = ascii (fp) : "./hrtfs/elev50/L50e005a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e005a.dat right +[ 28, 0 ] = ascii (fp) : "./hrtfs/elev50/L50e000a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e000a.dat" right +[ 28, 1 ] = ascii (fp) : "./hrtfs/elev50/L50e355a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e355a.dat" right +[ 28, 2 ] = ascii (fp) : "./hrtfs/elev50/L50e350a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e350a.dat" right +[ 28, 3 ] = ascii (fp) : "./hrtfs/elev50/L50e345a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e345a.dat" right +[ 28, 4 ] = ascii (fp) : "./hrtfs/elev50/L50e340a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e340a.dat" right +[ 28, 5 ] = ascii (fp) : "./hrtfs/elev50/L50e335a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e335a.dat" right +[ 28, 6 ] = ascii (fp) : "./hrtfs/elev50/L50e330a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e330a.dat" right +[ 28, 7 ] = ascii (fp) : "./hrtfs/elev50/L50e325a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e325a.dat" right +[ 28, 8 ] = ascii (fp) : "./hrtfs/elev50/L50e320a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e320a.dat" right +[ 28, 9 ] = ascii (fp) : "./hrtfs/elev50/L50e315a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e315a.dat" right +[ 28, 10 ] = ascii (fp) : "./hrtfs/elev50/L50e310a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e310a.dat" right +[ 28, 11 ] = ascii (fp) : "./hrtfs/elev50/L50e305a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e305a.dat" right +[ 28, 12 ] = ascii (fp) : "./hrtfs/elev50/L50e300a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e300a.dat" right +[ 28, 13 ] = ascii (fp) : "./hrtfs/elev50/L50e295a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e295a.dat" right +[ 28, 14 ] = ascii (fp) : "./hrtfs/elev50/L50e290a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e290a.dat" right +[ 28, 15 ] = ascii (fp) : "./hrtfs/elev50/L50e285a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e285a.dat" right +[ 28, 16 ] = ascii (fp) : "./hrtfs/elev50/L50e280a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e280a.dat" right +[ 28, 17 ] = ascii (fp) : "./hrtfs/elev50/L50e275a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e275a.dat" right +[ 28, 18 ] = ascii (fp) : "./hrtfs/elev50/L50e270a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e270a.dat" right +[ 28, 19 ] = ascii (fp) : "./hrtfs/elev50/L50e265a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e265a.dat" right +[ 28, 20 ] = ascii (fp) : "./hrtfs/elev50/L50e260a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e260a.dat" right +[ 28, 21 ] = ascii (fp) : "./hrtfs/elev50/L50e255a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e255a.dat" right +[ 28, 22 ] = ascii (fp) : "./hrtfs/elev50/L50e250a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e250a.dat" right +[ 28, 23 ] = ascii (fp) : "./hrtfs/elev50/L50e245a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e245a.dat" right +[ 28, 24 ] = ascii (fp) : "./hrtfs/elev50/L50e240a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e240a.dat" right +[ 28, 25 ] = ascii (fp) : "./hrtfs/elev50/L50e235a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e235a.dat" right +[ 28, 26 ] = ascii (fp) : "./hrtfs/elev50/L50e230a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e230a.dat" right +[ 28, 27 ] = ascii (fp) : "./hrtfs/elev50/L50e225a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e225a.dat" right +[ 28, 28 ] = ascii (fp) : "./hrtfs/elev50/L50e220a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e220a.dat" right +[ 28, 29 ] = ascii (fp) : "./hrtfs/elev50/L50e215a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e215a.dat" right +[ 28, 30 ] = ascii (fp) : "./hrtfs/elev50/L50e210a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e210a.dat" right +[ 28, 31 ] = ascii (fp) : "./hrtfs/elev50/L50e205a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e205a.dat" right +[ 28, 32 ] = ascii (fp) : "./hrtfs/elev50/L50e200a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e200a.dat" right +[ 28, 33 ] = ascii (fp) : "./hrtfs/elev50/L50e195a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e195a.dat" right +[ 28, 34 ] = ascii (fp) : "./hrtfs/elev50/L50e190a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e190a.dat" right +[ 28, 35 ] = ascii (fp) : "./hrtfs/elev50/L50e185a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e185a.dat" right +[ 28, 36 ] = ascii (fp) : "./hrtfs/elev50/L50e180a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e180a.dat" right +[ 28, 37 ] = ascii (fp) : "./hrtfs/elev50/L50e175a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e175a.dat" right +[ 28, 38 ] = ascii (fp) : "./hrtfs/elev50/L50e170a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e170a.dat" right +[ 28, 39 ] = ascii (fp) : "./hrtfs/elev50/L50e165a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e165a.dat" right +[ 28, 40 ] = ascii (fp) : "./hrtfs/elev50/L50e160a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e160a.dat" right +[ 28, 41 ] = ascii (fp) : "./hrtfs/elev50/L50e155a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e155a.dat" right +[ 28, 42 ] = ascii (fp) : "./hrtfs/elev50/L50e150a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e150a.dat" right +[ 28, 43 ] = ascii (fp) : "./hrtfs/elev50/L50e145a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e145a.dat" right +[ 28, 44 ] = ascii (fp) : "./hrtfs/elev50/L50e140a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e140a.dat" right +[ 28, 45 ] = ascii (fp) : "./hrtfs/elev50/L50e135a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e135a.dat" right +[ 28, 46 ] = ascii (fp) : "./hrtfs/elev50/L50e130a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e130a.dat" right +[ 28, 47 ] = ascii (fp) : "./hrtfs/elev50/L50e125a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e125a.dat" right +[ 28, 48 ] = ascii (fp) : "./hrtfs/elev50/L50e120a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e120a.dat" right +[ 28, 49 ] = ascii (fp) : "./hrtfs/elev50/L50e115a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e115a.dat" right +[ 28, 50 ] = ascii (fp) : "./hrtfs/elev50/L50e110a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e110a.dat" right +[ 28, 51 ] = ascii (fp) : "./hrtfs/elev50/L50e105a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e105a.dat" right +[ 28, 52 ] = ascii (fp) : "./hrtfs/elev50/L50e100a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e100a.dat" right +[ 28, 53 ] = ascii (fp) : "./hrtfs/elev50/L50e095a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e095a.dat" right +[ 28, 54 ] = ascii (fp) : "./hrtfs/elev50/L50e090a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e090a.dat" right +[ 28, 55 ] = ascii (fp) : "./hrtfs/elev50/L50e085a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e085a.dat" right +[ 28, 56 ] = ascii (fp) : "./hrtfs/elev50/L50e080a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e080a.dat" right +[ 28, 57 ] = ascii (fp) : "./hrtfs/elev50/L50e075a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e075a.dat" right +[ 28, 58 ] = ascii (fp) : "./hrtfs/elev50/L50e070a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e070a.dat" right +[ 28, 59 ] = ascii (fp) : "./hrtfs/elev50/L50e065a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e065a.dat" right +[ 28, 60 ] = ascii (fp) : "./hrtfs/elev50/L50e060a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e060a.dat" right +[ 28, 61 ] = ascii (fp) : "./hrtfs/elev50/L50e055a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e055a.dat" right +[ 28, 62 ] = ascii (fp) : "./hrtfs/elev50/L50e050a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e050a.dat" right +[ 28, 63 ] = ascii (fp) : "./hrtfs/elev50/L50e045a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e045a.dat" right +[ 28, 64 ] = ascii (fp) : "./hrtfs/elev50/L50e040a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e040a.dat" right +[ 28, 65 ] = ascii (fp) : "./hrtfs/elev50/L50e035a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e035a.dat" right +[ 28, 66 ] = ascii (fp) : "./hrtfs/elev50/L50e030a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e030a.dat" right +[ 28, 67 ] = ascii (fp) : "./hrtfs/elev50/L50e025a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e025a.dat" right +[ 28, 68 ] = ascii (fp) : "./hrtfs/elev50/L50e020a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e020a.dat" right +[ 28, 69 ] = ascii (fp) : "./hrtfs/elev50/L50e015a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e015a.dat" right +[ 28, 70 ] = ascii (fp) : "./hrtfs/elev50/L50e010a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e010a.dat" right +[ 28, 71 ] = ascii (fp) : "./hrtfs/elev50/L50e005a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e005a.dat" right -[ 29, 0 ] = ascii (fp) : "./hrtfs/elev55/L55e000a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e000a.dat right -[ 29, 1 ] = ascii (fp) : "./hrtfs/elev55/L55e355a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e355a.dat right -[ 29, 2 ] = ascii (fp) : "./hrtfs/elev55/L55e350a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e350a.dat right -[ 29, 3 ] = ascii (fp) : "./hrtfs/elev55/L55e345a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e345a.dat right -[ 29, 4 ] = ascii (fp) : "./hrtfs/elev55/L55e340a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e340a.dat right -[ 29, 5 ] = ascii (fp) : "./hrtfs/elev55/L55e335a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e335a.dat right -[ 29, 6 ] = ascii (fp) : "./hrtfs/elev55/L55e330a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e330a.dat right -[ 29, 7 ] = ascii (fp) : "./hrtfs/elev55/L55e325a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e325a.dat right -[ 29, 8 ] = ascii (fp) : "./hrtfs/elev55/L55e320a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e320a.dat right -[ 29, 9 ] = ascii (fp) : "./hrtfs/elev55/L55e315a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e315a.dat right -[ 29, 10 ] = ascii (fp) : "./hrtfs/elev55/L55e310a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e310a.dat right -[ 29, 11 ] = ascii (fp) : "./hrtfs/elev55/L55e305a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e305a.dat right -[ 29, 12 ] = ascii (fp) : "./hrtfs/elev55/L55e300a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e300a.dat right -[ 29, 13 ] = ascii (fp) : "./hrtfs/elev55/L55e295a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e295a.dat right -[ 29, 14 ] = ascii (fp) : "./hrtfs/elev55/L55e290a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e290a.dat right -[ 29, 15 ] = ascii (fp) : "./hrtfs/elev55/L55e285a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e285a.dat right -[ 29, 16 ] = ascii (fp) : "./hrtfs/elev55/L55e280a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e280a.dat right -[ 29, 17 ] = ascii (fp) : "./hrtfs/elev55/L55e275a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e275a.dat right -[ 29, 18 ] = ascii (fp) : "./hrtfs/elev55/L55e270a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e270a.dat right -[ 29, 19 ] = ascii (fp) : "./hrtfs/elev55/L55e265a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e265a.dat right -[ 29, 20 ] = ascii (fp) : "./hrtfs/elev55/L55e260a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e260a.dat right -[ 29, 21 ] = ascii (fp) : "./hrtfs/elev55/L55e255a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e255a.dat right -[ 29, 22 ] = ascii (fp) : "./hrtfs/elev55/L55e250a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e250a.dat right -[ 29, 23 ] = ascii (fp) : "./hrtfs/elev55/L55e245a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e245a.dat right -[ 29, 24 ] = ascii (fp) : "./hrtfs/elev55/L55e240a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e240a.dat right -[ 29, 25 ] = ascii (fp) : "./hrtfs/elev55/L55e235a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e235a.dat right -[ 29, 26 ] = ascii (fp) : "./hrtfs/elev55/L55e230a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e230a.dat right -[ 29, 27 ] = ascii (fp) : "./hrtfs/elev55/L55e225a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e225a.dat right -[ 29, 28 ] = ascii (fp) : "./hrtfs/elev55/L55e220a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e220a.dat right -[ 29, 29 ] = ascii (fp) : "./hrtfs/elev55/L55e215a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e215a.dat right -[ 29, 30 ] = ascii (fp) : "./hrtfs/elev55/L55e210a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e210a.dat right -[ 29, 31 ] = ascii (fp) : "./hrtfs/elev55/L55e205a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e205a.dat right -[ 29, 32 ] = ascii (fp) : "./hrtfs/elev55/L55e200a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e200a.dat right -[ 29, 33 ] = ascii (fp) : "./hrtfs/elev55/L55e195a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e195a.dat right -[ 29, 34 ] = ascii (fp) : "./hrtfs/elev55/L55e190a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e190a.dat right -[ 29, 35 ] = ascii (fp) : "./hrtfs/elev55/L55e185a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e185a.dat right -[ 29, 36 ] = ascii (fp) : "./hrtfs/elev55/L55e180a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e180a.dat right -[ 29, 37 ] = ascii (fp) : "./hrtfs/elev55/L55e175a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e175a.dat right -[ 29, 38 ] = ascii (fp) : "./hrtfs/elev55/L55e170a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e170a.dat right -[ 29, 39 ] = ascii (fp) : "./hrtfs/elev55/L55e165a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e165a.dat right -[ 29, 40 ] = ascii (fp) : "./hrtfs/elev55/L55e160a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e160a.dat right -[ 29, 41 ] = ascii (fp) : "./hrtfs/elev55/L55e155a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e155a.dat right -[ 29, 42 ] = ascii (fp) : "./hrtfs/elev55/L55e150a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e150a.dat right -[ 29, 43 ] = ascii (fp) : "./hrtfs/elev55/L55e145a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e145a.dat right -[ 29, 44 ] = ascii (fp) : "./hrtfs/elev55/L55e140a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e140a.dat right -[ 29, 45 ] = ascii (fp) : "./hrtfs/elev55/L55e135a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e135a.dat right -[ 29, 46 ] = ascii (fp) : "./hrtfs/elev55/L55e130a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e130a.dat right -[ 29, 47 ] = ascii (fp) : "./hrtfs/elev55/L55e125a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e125a.dat right -[ 29, 48 ] = ascii (fp) : "./hrtfs/elev55/L55e120a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e120a.dat right -[ 29, 49 ] = ascii (fp) : "./hrtfs/elev55/L55e115a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e115a.dat right -[ 29, 50 ] = ascii (fp) : "./hrtfs/elev55/L55e110a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e110a.dat right -[ 29, 51 ] = ascii (fp) : "./hrtfs/elev55/L55e105a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e105a.dat right -[ 29, 52 ] = ascii (fp) : "./hrtfs/elev55/L55e100a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e100a.dat right -[ 29, 53 ] = ascii (fp) : "./hrtfs/elev55/L55e095a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e095a.dat right -[ 29, 54 ] = ascii (fp) : "./hrtfs/elev55/L55e090a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e090a.dat right -[ 29, 55 ] = ascii (fp) : "./hrtfs/elev55/L55e085a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e085a.dat right -[ 29, 56 ] = ascii (fp) : "./hrtfs/elev55/L55e080a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e080a.dat right -[ 29, 57 ] = ascii (fp) : "./hrtfs/elev55/L55e075a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e075a.dat right -[ 29, 58 ] = ascii (fp) : "./hrtfs/elev55/L55e070a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e070a.dat right -[ 29, 59 ] = ascii (fp) : "./hrtfs/elev55/L55e065a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e065a.dat right -[ 29, 60 ] = ascii (fp) : "./hrtfs/elev55/L55e060a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e060a.dat right -[ 29, 61 ] = ascii (fp) : "./hrtfs/elev55/L55e055a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e055a.dat right -[ 29, 62 ] = ascii (fp) : "./hrtfs/elev55/L55e050a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e050a.dat right -[ 29, 63 ] = ascii (fp) : "./hrtfs/elev55/L55e045a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e045a.dat right -[ 29, 64 ] = ascii (fp) : "./hrtfs/elev55/L55e040a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e040a.dat right -[ 29, 65 ] = ascii (fp) : "./hrtfs/elev55/L55e035a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e035a.dat right -[ 29, 66 ] = ascii (fp) : "./hrtfs/elev55/L55e030a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e030a.dat right -[ 29, 67 ] = ascii (fp) : "./hrtfs/elev55/L55e025a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e025a.dat right -[ 29, 68 ] = ascii (fp) : "./hrtfs/elev55/L55e020a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e020a.dat right -[ 29, 69 ] = ascii (fp) : "./hrtfs/elev55/L55e015a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e015a.dat right -[ 29, 70 ] = ascii (fp) : "./hrtfs/elev55/L55e010a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e010a.dat right -[ 29, 71 ] = ascii (fp) : "./hrtfs/elev55/L55e005a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e005a.dat right +[ 29, 0 ] = ascii (fp) : "./hrtfs/elev55/L55e000a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e000a.dat" right +[ 29, 1 ] = ascii (fp) : "./hrtfs/elev55/L55e355a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e355a.dat" right +[ 29, 2 ] = ascii (fp) : "./hrtfs/elev55/L55e350a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e350a.dat" right +[ 29, 3 ] = ascii (fp) : "./hrtfs/elev55/L55e345a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e345a.dat" right +[ 29, 4 ] = ascii (fp) : "./hrtfs/elev55/L55e340a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e340a.dat" right +[ 29, 5 ] = ascii (fp) : "./hrtfs/elev55/L55e335a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e335a.dat" right +[ 29, 6 ] = ascii (fp) : "./hrtfs/elev55/L55e330a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e330a.dat" right +[ 29, 7 ] = ascii (fp) : "./hrtfs/elev55/L55e325a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e325a.dat" right +[ 29, 8 ] = ascii (fp) : "./hrtfs/elev55/L55e320a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e320a.dat" right +[ 29, 9 ] = ascii (fp) : "./hrtfs/elev55/L55e315a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e315a.dat" right +[ 29, 10 ] = ascii (fp) : "./hrtfs/elev55/L55e310a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e310a.dat" right +[ 29, 11 ] = ascii (fp) : "./hrtfs/elev55/L55e305a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e305a.dat" right +[ 29, 12 ] = ascii (fp) : "./hrtfs/elev55/L55e300a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e300a.dat" right +[ 29, 13 ] = ascii (fp) : "./hrtfs/elev55/L55e295a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e295a.dat" right +[ 29, 14 ] = ascii (fp) : "./hrtfs/elev55/L55e290a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e290a.dat" right +[ 29, 15 ] = ascii (fp) : "./hrtfs/elev55/L55e285a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e285a.dat" right +[ 29, 16 ] = ascii (fp) : "./hrtfs/elev55/L55e280a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e280a.dat" right +[ 29, 17 ] = ascii (fp) : "./hrtfs/elev55/L55e275a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e275a.dat" right +[ 29, 18 ] = ascii (fp) : "./hrtfs/elev55/L55e270a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e270a.dat" right +[ 29, 19 ] = ascii (fp) : "./hrtfs/elev55/L55e265a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e265a.dat" right +[ 29, 20 ] = ascii (fp) : "./hrtfs/elev55/L55e260a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e260a.dat" right +[ 29, 21 ] = ascii (fp) : "./hrtfs/elev55/L55e255a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e255a.dat" right +[ 29, 22 ] = ascii (fp) : "./hrtfs/elev55/L55e250a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e250a.dat" right +[ 29, 23 ] = ascii (fp) : "./hrtfs/elev55/L55e245a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e245a.dat" right +[ 29, 24 ] = ascii (fp) : "./hrtfs/elev55/L55e240a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e240a.dat" right +[ 29, 25 ] = ascii (fp) : "./hrtfs/elev55/L55e235a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e235a.dat" right +[ 29, 26 ] = ascii (fp) : "./hrtfs/elev55/L55e230a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e230a.dat" right +[ 29, 27 ] = ascii (fp) : "./hrtfs/elev55/L55e225a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e225a.dat" right +[ 29, 28 ] = ascii (fp) : "./hrtfs/elev55/L55e220a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e220a.dat" right +[ 29, 29 ] = ascii (fp) : "./hrtfs/elev55/L55e215a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e215a.dat" right +[ 29, 30 ] = ascii (fp) : "./hrtfs/elev55/L55e210a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e210a.dat" right +[ 29, 31 ] = ascii (fp) : "./hrtfs/elev55/L55e205a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e205a.dat" right +[ 29, 32 ] = ascii (fp) : "./hrtfs/elev55/L55e200a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e200a.dat" right +[ 29, 33 ] = ascii (fp) : "./hrtfs/elev55/L55e195a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e195a.dat" right +[ 29, 34 ] = ascii (fp) : "./hrtfs/elev55/L55e190a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e190a.dat" right +[ 29, 35 ] = ascii (fp) : "./hrtfs/elev55/L55e185a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e185a.dat" right +[ 29, 36 ] = ascii (fp) : "./hrtfs/elev55/L55e180a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e180a.dat" right +[ 29, 37 ] = ascii (fp) : "./hrtfs/elev55/L55e175a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e175a.dat" right +[ 29, 38 ] = ascii (fp) : "./hrtfs/elev55/L55e170a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e170a.dat" right +[ 29, 39 ] = ascii (fp) : "./hrtfs/elev55/L55e165a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e165a.dat" right +[ 29, 40 ] = ascii (fp) : "./hrtfs/elev55/L55e160a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e160a.dat" right +[ 29, 41 ] = ascii (fp) : "./hrtfs/elev55/L55e155a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e155a.dat" right +[ 29, 42 ] = ascii (fp) : "./hrtfs/elev55/L55e150a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e150a.dat" right +[ 29, 43 ] = ascii (fp) : "./hrtfs/elev55/L55e145a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e145a.dat" right +[ 29, 44 ] = ascii (fp) : "./hrtfs/elev55/L55e140a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e140a.dat" right +[ 29, 45 ] = ascii (fp) : "./hrtfs/elev55/L55e135a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e135a.dat" right +[ 29, 46 ] = ascii (fp) : "./hrtfs/elev55/L55e130a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e130a.dat" right +[ 29, 47 ] = ascii (fp) : "./hrtfs/elev55/L55e125a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e125a.dat" right +[ 29, 48 ] = ascii (fp) : "./hrtfs/elev55/L55e120a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e120a.dat" right +[ 29, 49 ] = ascii (fp) : "./hrtfs/elev55/L55e115a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e115a.dat" right +[ 29, 50 ] = ascii (fp) : "./hrtfs/elev55/L55e110a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e110a.dat" right +[ 29, 51 ] = ascii (fp) : "./hrtfs/elev55/L55e105a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e105a.dat" right +[ 29, 52 ] = ascii (fp) : "./hrtfs/elev55/L55e100a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e100a.dat" right +[ 29, 53 ] = ascii (fp) : "./hrtfs/elev55/L55e095a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e095a.dat" right +[ 29, 54 ] = ascii (fp) : "./hrtfs/elev55/L55e090a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e090a.dat" right +[ 29, 55 ] = ascii (fp) : "./hrtfs/elev55/L55e085a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e085a.dat" right +[ 29, 56 ] = ascii (fp) : "./hrtfs/elev55/L55e080a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e080a.dat" right +[ 29, 57 ] = ascii (fp) : "./hrtfs/elev55/L55e075a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e075a.dat" right +[ 29, 58 ] = ascii (fp) : "./hrtfs/elev55/L55e070a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e070a.dat" right +[ 29, 59 ] = ascii (fp) : "./hrtfs/elev55/L55e065a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e065a.dat" right +[ 29, 60 ] = ascii (fp) : "./hrtfs/elev55/L55e060a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e060a.dat" right +[ 29, 61 ] = ascii (fp) : "./hrtfs/elev55/L55e055a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e055a.dat" right +[ 29, 62 ] = ascii (fp) : "./hrtfs/elev55/L55e050a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e050a.dat" right +[ 29, 63 ] = ascii (fp) : "./hrtfs/elev55/L55e045a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e045a.dat" right +[ 29, 64 ] = ascii (fp) : "./hrtfs/elev55/L55e040a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e040a.dat" right +[ 29, 65 ] = ascii (fp) : "./hrtfs/elev55/L55e035a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e035a.dat" right +[ 29, 66 ] = ascii (fp) : "./hrtfs/elev55/L55e030a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e030a.dat" right +[ 29, 67 ] = ascii (fp) : "./hrtfs/elev55/L55e025a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e025a.dat" right +[ 29, 68 ] = ascii (fp) : "./hrtfs/elev55/L55e020a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e020a.dat" right +[ 29, 69 ] = ascii (fp) : "./hrtfs/elev55/L55e015a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e015a.dat" right +[ 29, 70 ] = ascii (fp) : "./hrtfs/elev55/L55e010a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e010a.dat" right +[ 29, 71 ] = ascii (fp) : "./hrtfs/elev55/L55e005a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e005a.dat" right -[ 30, 0 ] = ascii (fp) : "./hrtfs/elev60/L60e000a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e000a.dat right -[ 30, 1 ] = ascii (fp) : "./hrtfs/elev60/L60e355a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e355a.dat right -[ 30, 2 ] = ascii (fp) : "./hrtfs/elev60/L60e350a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e350a.dat right -[ 30, 3 ] = ascii (fp) : "./hrtfs/elev60/L60e345a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e345a.dat right -[ 30, 4 ] = ascii (fp) : "./hrtfs/elev60/L60e340a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e340a.dat right -[ 30, 5 ] = ascii (fp) : "./hrtfs/elev60/L60e335a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e335a.dat right -[ 30, 6 ] = ascii (fp) : "./hrtfs/elev60/L60e330a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e330a.dat right -[ 30, 7 ] = ascii (fp) : "./hrtfs/elev60/L60e325a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e325a.dat right -[ 30, 8 ] = ascii (fp) : "./hrtfs/elev60/L60e320a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e320a.dat right -[ 30, 9 ] = ascii (fp) : "./hrtfs/elev60/L60e315a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e315a.dat right -[ 30, 10 ] = ascii (fp) : "./hrtfs/elev60/L60e310a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e310a.dat right -[ 30, 11 ] = ascii (fp) : "./hrtfs/elev60/L60e305a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e305a.dat right -[ 30, 12 ] = ascii (fp) : "./hrtfs/elev60/L60e300a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e300a.dat right -[ 30, 13 ] = ascii (fp) : "./hrtfs/elev60/L60e295a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e295a.dat right -[ 30, 14 ] = ascii (fp) : "./hrtfs/elev60/L60e290a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e290a.dat right -[ 30, 15 ] = ascii (fp) : "./hrtfs/elev60/L60e285a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e285a.dat right -[ 30, 16 ] = ascii (fp) : "./hrtfs/elev60/L60e280a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e280a.dat right -[ 30, 17 ] = ascii (fp) : "./hrtfs/elev60/L60e275a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e275a.dat right -[ 30, 18 ] = ascii (fp) : "./hrtfs/elev60/L60e270a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e270a.dat right -[ 30, 19 ] = ascii (fp) : "./hrtfs/elev60/L60e265a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e265a.dat right -[ 30, 20 ] = ascii (fp) : "./hrtfs/elev60/L60e260a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e260a.dat right -[ 30, 21 ] = ascii (fp) : "./hrtfs/elev60/L60e255a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e255a.dat right -[ 30, 22 ] = ascii (fp) : "./hrtfs/elev60/L60e250a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e250a.dat right -[ 30, 23 ] = ascii (fp) : "./hrtfs/elev60/L60e245a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e245a.dat right -[ 30, 24 ] = ascii (fp) : "./hrtfs/elev60/L60e240a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e240a.dat right -[ 30, 25 ] = ascii (fp) : "./hrtfs/elev60/L60e235a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e235a.dat right -[ 30, 26 ] = ascii (fp) : "./hrtfs/elev60/L60e230a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e230a.dat right -[ 30, 27 ] = ascii (fp) : "./hrtfs/elev60/L60e225a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e225a.dat right -[ 30, 28 ] = ascii (fp) : "./hrtfs/elev60/L60e220a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e220a.dat right -[ 30, 29 ] = ascii (fp) : "./hrtfs/elev60/L60e215a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e215a.dat right -[ 30, 30 ] = ascii (fp) : "./hrtfs/elev60/L60e210a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e210a.dat right -[ 30, 31 ] = ascii (fp) : "./hrtfs/elev60/L60e205a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e205a.dat right -[ 30, 32 ] = ascii (fp) : "./hrtfs/elev60/L60e200a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e200a.dat right -[ 30, 33 ] = ascii (fp) : "./hrtfs/elev60/L60e195a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e195a.dat right -[ 30, 34 ] = ascii (fp) : "./hrtfs/elev60/L60e190a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e190a.dat right -[ 30, 35 ] = ascii (fp) : "./hrtfs/elev60/L60e185a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e185a.dat right -[ 30, 36 ] = ascii (fp) : "./hrtfs/elev60/L60e180a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e180a.dat right -[ 30, 37 ] = ascii (fp) : "./hrtfs/elev60/L60e175a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e175a.dat right -[ 30, 38 ] = ascii (fp) : "./hrtfs/elev60/L60e170a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e170a.dat right -[ 30, 39 ] = ascii (fp) : "./hrtfs/elev60/L60e165a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e165a.dat right -[ 30, 40 ] = ascii (fp) : "./hrtfs/elev60/L60e160a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e160a.dat right -[ 30, 41 ] = ascii (fp) : "./hrtfs/elev60/L60e155a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e155a.dat right -[ 30, 42 ] = ascii (fp) : "./hrtfs/elev60/L60e150a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e150a.dat right -[ 30, 43 ] = ascii (fp) : "./hrtfs/elev60/L60e145a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e145a.dat right -[ 30, 44 ] = ascii (fp) : "./hrtfs/elev60/L60e140a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e140a.dat right -[ 30, 45 ] = ascii (fp) : "./hrtfs/elev60/L60e135a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e135a.dat right -[ 30, 46 ] = ascii (fp) : "./hrtfs/elev60/L60e130a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e130a.dat right -[ 30, 47 ] = ascii (fp) : "./hrtfs/elev60/L60e125a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e125a.dat right -[ 30, 48 ] = ascii (fp) : "./hrtfs/elev60/L60e120a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e120a.dat right -[ 30, 49 ] = ascii (fp) : "./hrtfs/elev60/L60e115a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e115a.dat right -[ 30, 50 ] = ascii (fp) : "./hrtfs/elev60/L60e110a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e110a.dat right -[ 30, 51 ] = ascii (fp) : "./hrtfs/elev60/L60e105a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e105a.dat right -[ 30, 52 ] = ascii (fp) : "./hrtfs/elev60/L60e100a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e100a.dat right -[ 30, 53 ] = ascii (fp) : "./hrtfs/elev60/L60e095a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e095a.dat right -[ 30, 54 ] = ascii (fp) : "./hrtfs/elev60/L60e090a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e090a.dat right -[ 30, 55 ] = ascii (fp) : "./hrtfs/elev60/L60e085a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e085a.dat right -[ 30, 56 ] = ascii (fp) : "./hrtfs/elev60/L60e080a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e080a.dat right -[ 30, 57 ] = ascii (fp) : "./hrtfs/elev60/L60e075a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e075a.dat right -[ 30, 58 ] = ascii (fp) : "./hrtfs/elev60/L60e070a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e070a.dat right -[ 30, 59 ] = ascii (fp) : "./hrtfs/elev60/L60e065a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e065a.dat right -[ 30, 60 ] = ascii (fp) : "./hrtfs/elev60/L60e060a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e060a.dat right -[ 30, 61 ] = ascii (fp) : "./hrtfs/elev60/L60e055a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e055a.dat right -[ 30, 62 ] = ascii (fp) : "./hrtfs/elev60/L60e050a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e050a.dat right -[ 30, 63 ] = ascii (fp) : "./hrtfs/elev60/L60e045a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e045a.dat right -[ 30, 64 ] = ascii (fp) : "./hrtfs/elev60/L60e040a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e040a.dat right -[ 30, 65 ] = ascii (fp) : "./hrtfs/elev60/L60e035a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e035a.dat right -[ 30, 66 ] = ascii (fp) : "./hrtfs/elev60/L60e030a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e030a.dat right -[ 30, 67 ] = ascii (fp) : "./hrtfs/elev60/L60e025a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e025a.dat right -[ 30, 68 ] = ascii (fp) : "./hrtfs/elev60/L60e020a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e020a.dat right -[ 30, 69 ] = ascii (fp) : "./hrtfs/elev60/L60e015a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e015a.dat right -[ 30, 70 ] = ascii (fp) : "./hrtfs/elev60/L60e010a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e010a.dat right -[ 30, 71 ] = ascii (fp) : "./hrtfs/elev60/L60e005a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e005a.dat right +[ 30, 0 ] = ascii (fp) : "./hrtfs/elev60/L60e000a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e000a.dat" right +[ 30, 1 ] = ascii (fp) : "./hrtfs/elev60/L60e355a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e355a.dat" right +[ 30, 2 ] = ascii (fp) : "./hrtfs/elev60/L60e350a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e350a.dat" right +[ 30, 3 ] = ascii (fp) : "./hrtfs/elev60/L60e345a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e345a.dat" right +[ 30, 4 ] = ascii (fp) : "./hrtfs/elev60/L60e340a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e340a.dat" right +[ 30, 5 ] = ascii (fp) : "./hrtfs/elev60/L60e335a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e335a.dat" right +[ 30, 6 ] = ascii (fp) : "./hrtfs/elev60/L60e330a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e330a.dat" right +[ 30, 7 ] = ascii (fp) : "./hrtfs/elev60/L60e325a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e325a.dat" right +[ 30, 8 ] = ascii (fp) : "./hrtfs/elev60/L60e320a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e320a.dat" right +[ 30, 9 ] = ascii (fp) : "./hrtfs/elev60/L60e315a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e315a.dat" right +[ 30, 10 ] = ascii (fp) : "./hrtfs/elev60/L60e310a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e310a.dat" right +[ 30, 11 ] = ascii (fp) : "./hrtfs/elev60/L60e305a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e305a.dat" right +[ 30, 12 ] = ascii (fp) : "./hrtfs/elev60/L60e300a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e300a.dat" right +[ 30, 13 ] = ascii (fp) : "./hrtfs/elev60/L60e295a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e295a.dat" right +[ 30, 14 ] = ascii (fp) : "./hrtfs/elev60/L60e290a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e290a.dat" right +[ 30, 15 ] = ascii (fp) : "./hrtfs/elev60/L60e285a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e285a.dat" right +[ 30, 16 ] = ascii (fp) : "./hrtfs/elev60/L60e280a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e280a.dat" right +[ 30, 17 ] = ascii (fp) : "./hrtfs/elev60/L60e275a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e275a.dat" right +[ 30, 18 ] = ascii (fp) : "./hrtfs/elev60/L60e270a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e270a.dat" right +[ 30, 19 ] = ascii (fp) : "./hrtfs/elev60/L60e265a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e265a.dat" right +[ 30, 20 ] = ascii (fp) : "./hrtfs/elev60/L60e260a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e260a.dat" right +[ 30, 21 ] = ascii (fp) : "./hrtfs/elev60/L60e255a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e255a.dat" right +[ 30, 22 ] = ascii (fp) : "./hrtfs/elev60/L60e250a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e250a.dat" right +[ 30, 23 ] = ascii (fp) : "./hrtfs/elev60/L60e245a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e245a.dat" right +[ 30, 24 ] = ascii (fp) : "./hrtfs/elev60/L60e240a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e240a.dat" right +[ 30, 25 ] = ascii (fp) : "./hrtfs/elev60/L60e235a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e235a.dat" right +[ 30, 26 ] = ascii (fp) : "./hrtfs/elev60/L60e230a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e230a.dat" right +[ 30, 27 ] = ascii (fp) : "./hrtfs/elev60/L60e225a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e225a.dat" right +[ 30, 28 ] = ascii (fp) : "./hrtfs/elev60/L60e220a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e220a.dat" right +[ 30, 29 ] = ascii (fp) : "./hrtfs/elev60/L60e215a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e215a.dat" right +[ 30, 30 ] = ascii (fp) : "./hrtfs/elev60/L60e210a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e210a.dat" right +[ 30, 31 ] = ascii (fp) : "./hrtfs/elev60/L60e205a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e205a.dat" right +[ 30, 32 ] = ascii (fp) : "./hrtfs/elev60/L60e200a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e200a.dat" right +[ 30, 33 ] = ascii (fp) : "./hrtfs/elev60/L60e195a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e195a.dat" right +[ 30, 34 ] = ascii (fp) : "./hrtfs/elev60/L60e190a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e190a.dat" right +[ 30, 35 ] = ascii (fp) : "./hrtfs/elev60/L60e185a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e185a.dat" right +[ 30, 36 ] = ascii (fp) : "./hrtfs/elev60/L60e180a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e180a.dat" right +[ 30, 37 ] = ascii (fp) : "./hrtfs/elev60/L60e175a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e175a.dat" right +[ 30, 38 ] = ascii (fp) : "./hrtfs/elev60/L60e170a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e170a.dat" right +[ 30, 39 ] = ascii (fp) : "./hrtfs/elev60/L60e165a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e165a.dat" right +[ 30, 40 ] = ascii (fp) : "./hrtfs/elev60/L60e160a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e160a.dat" right +[ 30, 41 ] = ascii (fp) : "./hrtfs/elev60/L60e155a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e155a.dat" right +[ 30, 42 ] = ascii (fp) : "./hrtfs/elev60/L60e150a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e150a.dat" right +[ 30, 43 ] = ascii (fp) : "./hrtfs/elev60/L60e145a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e145a.dat" right +[ 30, 44 ] = ascii (fp) : "./hrtfs/elev60/L60e140a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e140a.dat" right +[ 30, 45 ] = ascii (fp) : "./hrtfs/elev60/L60e135a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e135a.dat" right +[ 30, 46 ] = ascii (fp) : "./hrtfs/elev60/L60e130a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e130a.dat" right +[ 30, 47 ] = ascii (fp) : "./hrtfs/elev60/L60e125a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e125a.dat" right +[ 30, 48 ] = ascii (fp) : "./hrtfs/elev60/L60e120a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e120a.dat" right +[ 30, 49 ] = ascii (fp) : "./hrtfs/elev60/L60e115a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e115a.dat" right +[ 30, 50 ] = ascii (fp) : "./hrtfs/elev60/L60e110a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e110a.dat" right +[ 30, 51 ] = ascii (fp) : "./hrtfs/elev60/L60e105a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e105a.dat" right +[ 30, 52 ] = ascii (fp) : "./hrtfs/elev60/L60e100a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e100a.dat" right +[ 30, 53 ] = ascii (fp) : "./hrtfs/elev60/L60e095a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e095a.dat" right +[ 30, 54 ] = ascii (fp) : "./hrtfs/elev60/L60e090a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e090a.dat" right +[ 30, 55 ] = ascii (fp) : "./hrtfs/elev60/L60e085a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e085a.dat" right +[ 30, 56 ] = ascii (fp) : "./hrtfs/elev60/L60e080a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e080a.dat" right +[ 30, 57 ] = ascii (fp) : "./hrtfs/elev60/L60e075a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e075a.dat" right +[ 30, 58 ] = ascii (fp) : "./hrtfs/elev60/L60e070a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e070a.dat" right +[ 30, 59 ] = ascii (fp) : "./hrtfs/elev60/L60e065a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e065a.dat" right +[ 30, 60 ] = ascii (fp) : "./hrtfs/elev60/L60e060a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e060a.dat" right +[ 30, 61 ] = ascii (fp) : "./hrtfs/elev60/L60e055a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e055a.dat" right +[ 30, 62 ] = ascii (fp) : "./hrtfs/elev60/L60e050a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e050a.dat" right +[ 30, 63 ] = ascii (fp) : "./hrtfs/elev60/L60e045a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e045a.dat" right +[ 30, 64 ] = ascii (fp) : "./hrtfs/elev60/L60e040a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e040a.dat" right +[ 30, 65 ] = ascii (fp) : "./hrtfs/elev60/L60e035a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e035a.dat" right +[ 30, 66 ] = ascii (fp) : "./hrtfs/elev60/L60e030a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e030a.dat" right +[ 30, 67 ] = ascii (fp) : "./hrtfs/elev60/L60e025a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e025a.dat" right +[ 30, 68 ] = ascii (fp) : "./hrtfs/elev60/L60e020a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e020a.dat" right +[ 30, 69 ] = ascii (fp) : "./hrtfs/elev60/L60e015a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e015a.dat" right +[ 30, 70 ] = ascii (fp) : "./hrtfs/elev60/L60e010a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e010a.dat" right +[ 30, 71 ] = ascii (fp) : "./hrtfs/elev60/L60e005a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e005a.dat" right -[ 31, 0 ] = ascii (fp) : "./hrtfs/elev65/L65e000a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e000a.dat right -[ 31, 1 ] = ascii (fp) : "./hrtfs/elev65/L65e355a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e355a.dat right -[ 31, 2 ] = ascii (fp) : "./hrtfs/elev65/L65e350a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e350a.dat right -[ 31, 3 ] = ascii (fp) : "./hrtfs/elev65/L65e345a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e345a.dat right -[ 31, 4 ] = ascii (fp) : "./hrtfs/elev65/L65e340a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e340a.dat right -[ 31, 5 ] = ascii (fp) : "./hrtfs/elev65/L65e335a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e335a.dat right -[ 31, 6 ] = ascii (fp) : "./hrtfs/elev65/L65e330a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e330a.dat right -[ 31, 7 ] = ascii (fp) : "./hrtfs/elev65/L65e325a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e325a.dat right -[ 31, 8 ] = ascii (fp) : "./hrtfs/elev65/L65e320a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e320a.dat right -[ 31, 9 ] = ascii (fp) : "./hrtfs/elev65/L65e315a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e315a.dat right -[ 31, 10 ] = ascii (fp) : "./hrtfs/elev65/L65e310a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e310a.dat right -[ 31, 11 ] = ascii (fp) : "./hrtfs/elev65/L65e305a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e305a.dat right -[ 31, 12 ] = ascii (fp) : "./hrtfs/elev65/L65e300a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e300a.dat right -[ 31, 13 ] = ascii (fp) : "./hrtfs/elev65/L65e295a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e295a.dat right -[ 31, 14 ] = ascii (fp) : "./hrtfs/elev65/L65e290a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e290a.dat right -[ 31, 15 ] = ascii (fp) : "./hrtfs/elev65/L65e285a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e285a.dat right -[ 31, 16 ] = ascii (fp) : "./hrtfs/elev65/L65e280a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e280a.dat right -[ 31, 17 ] = ascii (fp) : "./hrtfs/elev65/L65e275a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e275a.dat right -[ 31, 18 ] = ascii (fp) : "./hrtfs/elev65/L65e270a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e270a.dat right -[ 31, 19 ] = ascii (fp) : "./hrtfs/elev65/L65e265a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e265a.dat right -[ 31, 20 ] = ascii (fp) : "./hrtfs/elev65/L65e260a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e260a.dat right -[ 31, 21 ] = ascii (fp) : "./hrtfs/elev65/L65e255a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e255a.dat right -[ 31, 22 ] = ascii (fp) : "./hrtfs/elev65/L65e250a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e250a.dat right -[ 31, 23 ] = ascii (fp) : "./hrtfs/elev65/L65e245a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e245a.dat right -[ 31, 24 ] = ascii (fp) : "./hrtfs/elev65/L65e240a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e240a.dat right -[ 31, 25 ] = ascii (fp) : "./hrtfs/elev65/L65e235a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e235a.dat right -[ 31, 26 ] = ascii (fp) : "./hrtfs/elev65/L65e230a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e230a.dat right -[ 31, 27 ] = ascii (fp) : "./hrtfs/elev65/L65e225a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e225a.dat right -[ 31, 28 ] = ascii (fp) : "./hrtfs/elev65/L65e220a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e220a.dat right -[ 31, 29 ] = ascii (fp) : "./hrtfs/elev65/L65e215a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e215a.dat right -[ 31, 30 ] = ascii (fp) : "./hrtfs/elev65/L65e210a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e210a.dat right -[ 31, 31 ] = ascii (fp) : "./hrtfs/elev65/L65e205a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e205a.dat right -[ 31, 32 ] = ascii (fp) : "./hrtfs/elev65/L65e200a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e200a.dat right -[ 31, 33 ] = ascii (fp) : "./hrtfs/elev65/L65e195a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e195a.dat right -[ 31, 34 ] = ascii (fp) : "./hrtfs/elev65/L65e190a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e190a.dat right -[ 31, 35 ] = ascii (fp) : "./hrtfs/elev65/L65e185a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e185a.dat right -[ 31, 36 ] = ascii (fp) : "./hrtfs/elev65/L65e180a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e180a.dat right -[ 31, 37 ] = ascii (fp) : "./hrtfs/elev65/L65e175a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e175a.dat right -[ 31, 38 ] = ascii (fp) : "./hrtfs/elev65/L65e170a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e170a.dat right -[ 31, 39 ] = ascii (fp) : "./hrtfs/elev65/L65e165a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e165a.dat right -[ 31, 40 ] = ascii (fp) : "./hrtfs/elev65/L65e160a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e160a.dat right -[ 31, 41 ] = ascii (fp) : "./hrtfs/elev65/L65e155a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e155a.dat right -[ 31, 42 ] = ascii (fp) : "./hrtfs/elev65/L65e150a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e150a.dat right -[ 31, 43 ] = ascii (fp) : "./hrtfs/elev65/L65e145a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e145a.dat right -[ 31, 44 ] = ascii (fp) : "./hrtfs/elev65/L65e140a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e140a.dat right -[ 31, 45 ] = ascii (fp) : "./hrtfs/elev65/L65e135a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e135a.dat right -[ 31, 46 ] = ascii (fp) : "./hrtfs/elev65/L65e130a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e130a.dat right -[ 31, 47 ] = ascii (fp) : "./hrtfs/elev65/L65e125a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e125a.dat right -[ 31, 48 ] = ascii (fp) : "./hrtfs/elev65/L65e120a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e120a.dat right -[ 31, 49 ] = ascii (fp) : "./hrtfs/elev65/L65e115a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e115a.dat right -[ 31, 50 ] = ascii (fp) : "./hrtfs/elev65/L65e110a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e110a.dat right -[ 31, 51 ] = ascii (fp) : "./hrtfs/elev65/L65e105a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e105a.dat right -[ 31, 52 ] = ascii (fp) : "./hrtfs/elev65/L65e100a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e100a.dat right -[ 31, 53 ] = ascii (fp) : "./hrtfs/elev65/L65e095a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e095a.dat right -[ 31, 54 ] = ascii (fp) : "./hrtfs/elev65/L65e090a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e090a.dat right -[ 31, 55 ] = ascii (fp) : "./hrtfs/elev65/L65e085a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e085a.dat right -[ 31, 56 ] = ascii (fp) : "./hrtfs/elev65/L65e080a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e080a.dat right -[ 31, 57 ] = ascii (fp) : "./hrtfs/elev65/L65e075a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e075a.dat right -[ 31, 58 ] = ascii (fp) : "./hrtfs/elev65/L65e070a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e070a.dat right -[ 31, 59 ] = ascii (fp) : "./hrtfs/elev65/L65e065a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e065a.dat right -[ 31, 60 ] = ascii (fp) : "./hrtfs/elev65/L65e060a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e060a.dat right -[ 31, 61 ] = ascii (fp) : "./hrtfs/elev65/L65e055a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e055a.dat right -[ 31, 62 ] = ascii (fp) : "./hrtfs/elev65/L65e050a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e050a.dat right -[ 31, 63 ] = ascii (fp) : "./hrtfs/elev65/L65e045a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e045a.dat right -[ 31, 64 ] = ascii (fp) : "./hrtfs/elev65/L65e040a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e040a.dat right -[ 31, 65 ] = ascii (fp) : "./hrtfs/elev65/L65e035a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e035a.dat right -[ 31, 66 ] = ascii (fp) : "./hrtfs/elev65/L65e030a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e030a.dat right -[ 31, 67 ] = ascii (fp) : "./hrtfs/elev65/L65e025a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e025a.dat right -[ 31, 68 ] = ascii (fp) : "./hrtfs/elev65/L65e020a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e020a.dat right -[ 31, 69 ] = ascii (fp) : "./hrtfs/elev65/L65e015a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e015a.dat right -[ 31, 70 ] = ascii (fp) : "./hrtfs/elev65/L65e010a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e010a.dat right -[ 31, 71 ] = ascii (fp) : "./hrtfs/elev65/L65e005a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e005a.dat right +[ 31, 0 ] = ascii (fp) : "./hrtfs/elev65/L65e000a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e000a.dat" right +[ 31, 1 ] = ascii (fp) : "./hrtfs/elev65/L65e355a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e355a.dat" right +[ 31, 2 ] = ascii (fp) : "./hrtfs/elev65/L65e350a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e350a.dat" right +[ 31, 3 ] = ascii (fp) : "./hrtfs/elev65/L65e345a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e345a.dat" right +[ 31, 4 ] = ascii (fp) : "./hrtfs/elev65/L65e340a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e340a.dat" right +[ 31, 5 ] = ascii (fp) : "./hrtfs/elev65/L65e335a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e335a.dat" right +[ 31, 6 ] = ascii (fp) : "./hrtfs/elev65/L65e330a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e330a.dat" right +[ 31, 7 ] = ascii (fp) : "./hrtfs/elev65/L65e325a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e325a.dat" right +[ 31, 8 ] = ascii (fp) : "./hrtfs/elev65/L65e320a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e320a.dat" right +[ 31, 9 ] = ascii (fp) : "./hrtfs/elev65/L65e315a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e315a.dat" right +[ 31, 10 ] = ascii (fp) : "./hrtfs/elev65/L65e310a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e310a.dat" right +[ 31, 11 ] = ascii (fp) : "./hrtfs/elev65/L65e305a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e305a.dat" right +[ 31, 12 ] = ascii (fp) : "./hrtfs/elev65/L65e300a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e300a.dat" right +[ 31, 13 ] = ascii (fp) : "./hrtfs/elev65/L65e295a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e295a.dat" right +[ 31, 14 ] = ascii (fp) : "./hrtfs/elev65/L65e290a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e290a.dat" right +[ 31, 15 ] = ascii (fp) : "./hrtfs/elev65/L65e285a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e285a.dat" right +[ 31, 16 ] = ascii (fp) : "./hrtfs/elev65/L65e280a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e280a.dat" right +[ 31, 17 ] = ascii (fp) : "./hrtfs/elev65/L65e275a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e275a.dat" right +[ 31, 18 ] = ascii (fp) : "./hrtfs/elev65/L65e270a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e270a.dat" right +[ 31, 19 ] = ascii (fp) : "./hrtfs/elev65/L65e265a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e265a.dat" right +[ 31, 20 ] = ascii (fp) : "./hrtfs/elev65/L65e260a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e260a.dat" right +[ 31, 21 ] = ascii (fp) : "./hrtfs/elev65/L65e255a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e255a.dat" right +[ 31, 22 ] = ascii (fp) : "./hrtfs/elev65/L65e250a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e250a.dat" right +[ 31, 23 ] = ascii (fp) : "./hrtfs/elev65/L65e245a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e245a.dat" right +[ 31, 24 ] = ascii (fp) : "./hrtfs/elev65/L65e240a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e240a.dat" right +[ 31, 25 ] = ascii (fp) : "./hrtfs/elev65/L65e235a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e235a.dat" right +[ 31, 26 ] = ascii (fp) : "./hrtfs/elev65/L65e230a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e230a.dat" right +[ 31, 27 ] = ascii (fp) : "./hrtfs/elev65/L65e225a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e225a.dat" right +[ 31, 28 ] = ascii (fp) : "./hrtfs/elev65/L65e220a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e220a.dat" right +[ 31, 29 ] = ascii (fp) : "./hrtfs/elev65/L65e215a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e215a.dat" right +[ 31, 30 ] = ascii (fp) : "./hrtfs/elev65/L65e210a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e210a.dat" right +[ 31, 31 ] = ascii (fp) : "./hrtfs/elev65/L65e205a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e205a.dat" right +[ 31, 32 ] = ascii (fp) : "./hrtfs/elev65/L65e200a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e200a.dat" right +[ 31, 33 ] = ascii (fp) : "./hrtfs/elev65/L65e195a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e195a.dat" right +[ 31, 34 ] = ascii (fp) : "./hrtfs/elev65/L65e190a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e190a.dat" right +[ 31, 35 ] = ascii (fp) : "./hrtfs/elev65/L65e185a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e185a.dat" right +[ 31, 36 ] = ascii (fp) : "./hrtfs/elev65/L65e180a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e180a.dat" right +[ 31, 37 ] = ascii (fp) : "./hrtfs/elev65/L65e175a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e175a.dat" right +[ 31, 38 ] = ascii (fp) : "./hrtfs/elev65/L65e170a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e170a.dat" right +[ 31, 39 ] = ascii (fp) : "./hrtfs/elev65/L65e165a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e165a.dat" right +[ 31, 40 ] = ascii (fp) : "./hrtfs/elev65/L65e160a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e160a.dat" right +[ 31, 41 ] = ascii (fp) : "./hrtfs/elev65/L65e155a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e155a.dat" right +[ 31, 42 ] = ascii (fp) : "./hrtfs/elev65/L65e150a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e150a.dat" right +[ 31, 43 ] = ascii (fp) : "./hrtfs/elev65/L65e145a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e145a.dat" right +[ 31, 44 ] = ascii (fp) : "./hrtfs/elev65/L65e140a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e140a.dat" right +[ 31, 45 ] = ascii (fp) : "./hrtfs/elev65/L65e135a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e135a.dat" right +[ 31, 46 ] = ascii (fp) : "./hrtfs/elev65/L65e130a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e130a.dat" right +[ 31, 47 ] = ascii (fp) : "./hrtfs/elev65/L65e125a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e125a.dat" right +[ 31, 48 ] = ascii (fp) : "./hrtfs/elev65/L65e120a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e120a.dat" right +[ 31, 49 ] = ascii (fp) : "./hrtfs/elev65/L65e115a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e115a.dat" right +[ 31, 50 ] = ascii (fp) : "./hrtfs/elev65/L65e110a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e110a.dat" right +[ 31, 51 ] = ascii (fp) : "./hrtfs/elev65/L65e105a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e105a.dat" right +[ 31, 52 ] = ascii (fp) : "./hrtfs/elev65/L65e100a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e100a.dat" right +[ 31, 53 ] = ascii (fp) : "./hrtfs/elev65/L65e095a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e095a.dat" right +[ 31, 54 ] = ascii (fp) : "./hrtfs/elev65/L65e090a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e090a.dat" right +[ 31, 55 ] = ascii (fp) : "./hrtfs/elev65/L65e085a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e085a.dat" right +[ 31, 56 ] = ascii (fp) : "./hrtfs/elev65/L65e080a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e080a.dat" right +[ 31, 57 ] = ascii (fp) : "./hrtfs/elev65/L65e075a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e075a.dat" right +[ 31, 58 ] = ascii (fp) : "./hrtfs/elev65/L65e070a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e070a.dat" right +[ 31, 59 ] = ascii (fp) : "./hrtfs/elev65/L65e065a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e065a.dat" right +[ 31, 60 ] = ascii (fp) : "./hrtfs/elev65/L65e060a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e060a.dat" right +[ 31, 61 ] = ascii (fp) : "./hrtfs/elev65/L65e055a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e055a.dat" right +[ 31, 62 ] = ascii (fp) : "./hrtfs/elev65/L65e050a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e050a.dat" right +[ 31, 63 ] = ascii (fp) : "./hrtfs/elev65/L65e045a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e045a.dat" right +[ 31, 64 ] = ascii (fp) : "./hrtfs/elev65/L65e040a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e040a.dat" right +[ 31, 65 ] = ascii (fp) : "./hrtfs/elev65/L65e035a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e035a.dat" right +[ 31, 66 ] = ascii (fp) : "./hrtfs/elev65/L65e030a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e030a.dat" right +[ 31, 67 ] = ascii (fp) : "./hrtfs/elev65/L65e025a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e025a.dat" right +[ 31, 68 ] = ascii (fp) : "./hrtfs/elev65/L65e020a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e020a.dat" right +[ 31, 69 ] = ascii (fp) : "./hrtfs/elev65/L65e015a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e015a.dat" right +[ 31, 70 ] = ascii (fp) : "./hrtfs/elev65/L65e010a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e010a.dat" right +[ 31, 71 ] = ascii (fp) : "./hrtfs/elev65/L65e005a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e005a.dat" right -[ 32, 0 ] = ascii (fp) : "./hrtfs/elev70/L70e000a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e000a.dat right -[ 32, 1 ] = ascii (fp) : "./hrtfs/elev70/L70e355a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e355a.dat right -[ 32, 2 ] = ascii (fp) : "./hrtfs/elev70/L70e350a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e350a.dat right -[ 32, 3 ] = ascii (fp) : "./hrtfs/elev70/L70e345a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e345a.dat right -[ 32, 4 ] = ascii (fp) : "./hrtfs/elev70/L70e340a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e340a.dat right -[ 32, 5 ] = ascii (fp) : "./hrtfs/elev70/L70e335a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e335a.dat right -[ 32, 6 ] = ascii (fp) : "./hrtfs/elev70/L70e330a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e330a.dat right -[ 32, 7 ] = ascii (fp) : "./hrtfs/elev70/L70e325a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e325a.dat right -[ 32, 8 ] = ascii (fp) : "./hrtfs/elev70/L70e320a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e320a.dat right -[ 32, 9 ] = ascii (fp) : "./hrtfs/elev70/L70e315a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e315a.dat right -[ 32, 10 ] = ascii (fp) : "./hrtfs/elev70/L70e310a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e310a.dat right -[ 32, 11 ] = ascii (fp) : "./hrtfs/elev70/L70e305a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e305a.dat right -[ 32, 12 ] = ascii (fp) : "./hrtfs/elev70/L70e300a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e300a.dat right -[ 32, 13 ] = ascii (fp) : "./hrtfs/elev70/L70e295a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e295a.dat right -[ 32, 14 ] = ascii (fp) : "./hrtfs/elev70/L70e290a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e290a.dat right -[ 32, 15 ] = ascii (fp) : "./hrtfs/elev70/L70e285a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e285a.dat right -[ 32, 16 ] = ascii (fp) : "./hrtfs/elev70/L70e280a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e280a.dat right -[ 32, 17 ] = ascii (fp) : "./hrtfs/elev70/L70e275a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e275a.dat right -[ 32, 18 ] = ascii (fp) : "./hrtfs/elev70/L70e270a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e270a.dat right -[ 32, 19 ] = ascii (fp) : "./hrtfs/elev70/L70e265a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e265a.dat right -[ 32, 20 ] = ascii (fp) : "./hrtfs/elev70/L70e260a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e260a.dat right -[ 32, 21 ] = ascii (fp) : "./hrtfs/elev70/L70e255a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e255a.dat right -[ 32, 22 ] = ascii (fp) : "./hrtfs/elev70/L70e250a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e250a.dat right -[ 32, 23 ] = ascii (fp) : "./hrtfs/elev70/L70e245a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e245a.dat right -[ 32, 24 ] = ascii (fp) : "./hrtfs/elev70/L70e240a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e240a.dat right -[ 32, 25 ] = ascii (fp) : "./hrtfs/elev70/L70e235a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e235a.dat right -[ 32, 26 ] = ascii (fp) : "./hrtfs/elev70/L70e230a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e230a.dat right -[ 32, 27 ] = ascii (fp) : "./hrtfs/elev70/L70e225a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e225a.dat right -[ 32, 28 ] = ascii (fp) : "./hrtfs/elev70/L70e220a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e220a.dat right -[ 32, 29 ] = ascii (fp) : "./hrtfs/elev70/L70e215a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e215a.dat right -[ 32, 30 ] = ascii (fp) : "./hrtfs/elev70/L70e210a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e210a.dat right -[ 32, 31 ] = ascii (fp) : "./hrtfs/elev70/L70e205a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e205a.dat right -[ 32, 32 ] = ascii (fp) : "./hrtfs/elev70/L70e200a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e200a.dat right -[ 32, 33 ] = ascii (fp) : "./hrtfs/elev70/L70e195a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e195a.dat right -[ 32, 34 ] = ascii (fp) : "./hrtfs/elev70/L70e190a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e190a.dat right -[ 32, 35 ] = ascii (fp) : "./hrtfs/elev70/L70e185a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e185a.dat right -[ 32, 36 ] = ascii (fp) : "./hrtfs/elev70/L70e180a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e180a.dat right -[ 32, 37 ] = ascii (fp) : "./hrtfs/elev70/L70e175a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e175a.dat right -[ 32, 38 ] = ascii (fp) : "./hrtfs/elev70/L70e170a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e170a.dat right -[ 32, 39 ] = ascii (fp) : "./hrtfs/elev70/L70e165a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e165a.dat right -[ 32, 40 ] = ascii (fp) : "./hrtfs/elev70/L70e160a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e160a.dat right -[ 32, 41 ] = ascii (fp) : "./hrtfs/elev70/L70e155a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e155a.dat right -[ 32, 42 ] = ascii (fp) : "./hrtfs/elev70/L70e150a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e150a.dat right -[ 32, 43 ] = ascii (fp) : "./hrtfs/elev70/L70e145a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e145a.dat right -[ 32, 44 ] = ascii (fp) : "./hrtfs/elev70/L70e140a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e140a.dat right -[ 32, 45 ] = ascii (fp) : "./hrtfs/elev70/L70e135a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e135a.dat right -[ 32, 46 ] = ascii (fp) : "./hrtfs/elev70/L70e130a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e130a.dat right -[ 32, 47 ] = ascii (fp) : "./hrtfs/elev70/L70e125a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e125a.dat right -[ 32, 48 ] = ascii (fp) : "./hrtfs/elev70/L70e120a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e120a.dat right -[ 32, 49 ] = ascii (fp) : "./hrtfs/elev70/L70e115a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e115a.dat right -[ 32, 50 ] = ascii (fp) : "./hrtfs/elev70/L70e110a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e110a.dat right -[ 32, 51 ] = ascii (fp) : "./hrtfs/elev70/L70e105a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e105a.dat right -[ 32, 52 ] = ascii (fp) : "./hrtfs/elev70/L70e100a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e100a.dat right -[ 32, 53 ] = ascii (fp) : "./hrtfs/elev70/L70e095a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e095a.dat right -[ 32, 54 ] = ascii (fp) : "./hrtfs/elev70/L70e090a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e090a.dat right -[ 32, 55 ] = ascii (fp) : "./hrtfs/elev70/L70e085a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e085a.dat right -[ 32, 56 ] = ascii (fp) : "./hrtfs/elev70/L70e080a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e080a.dat right -[ 32, 57 ] = ascii (fp) : "./hrtfs/elev70/L70e075a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e075a.dat right -[ 32, 58 ] = ascii (fp) : "./hrtfs/elev70/L70e070a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e070a.dat right -[ 32, 59 ] = ascii (fp) : "./hrtfs/elev70/L70e065a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e065a.dat right -[ 32, 60 ] = ascii (fp) : "./hrtfs/elev70/L70e060a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e060a.dat right -[ 32, 61 ] = ascii (fp) : "./hrtfs/elev70/L70e055a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e055a.dat right -[ 32, 62 ] = ascii (fp) : "./hrtfs/elev70/L70e050a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e050a.dat right -[ 32, 63 ] = ascii (fp) : "./hrtfs/elev70/L70e045a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e045a.dat right -[ 32, 64 ] = ascii (fp) : "./hrtfs/elev70/L70e040a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e040a.dat right -[ 32, 65 ] = ascii (fp) : "./hrtfs/elev70/L70e035a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e035a.dat right -[ 32, 66 ] = ascii (fp) : "./hrtfs/elev70/L70e030a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e030a.dat right -[ 32, 67 ] = ascii (fp) : "./hrtfs/elev70/L70e025a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e025a.dat right -[ 32, 68 ] = ascii (fp) : "./hrtfs/elev70/L70e020a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e020a.dat right -[ 32, 69 ] = ascii (fp) : "./hrtfs/elev70/L70e015a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e015a.dat right -[ 32, 70 ] = ascii (fp) : "./hrtfs/elev70/L70e010a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e010a.dat right -[ 32, 71 ] = ascii (fp) : "./hrtfs/elev70/L70e005a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e005a.dat right +[ 32, 0 ] = ascii (fp) : "./hrtfs/elev70/L70e000a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e000a.dat" right +[ 32, 1 ] = ascii (fp) : "./hrtfs/elev70/L70e355a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e355a.dat" right +[ 32, 2 ] = ascii (fp) : "./hrtfs/elev70/L70e350a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e350a.dat" right +[ 32, 3 ] = ascii (fp) : "./hrtfs/elev70/L70e345a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e345a.dat" right +[ 32, 4 ] = ascii (fp) : "./hrtfs/elev70/L70e340a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e340a.dat" right +[ 32, 5 ] = ascii (fp) : "./hrtfs/elev70/L70e335a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e335a.dat" right +[ 32, 6 ] = ascii (fp) : "./hrtfs/elev70/L70e330a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e330a.dat" right +[ 32, 7 ] = ascii (fp) : "./hrtfs/elev70/L70e325a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e325a.dat" right +[ 32, 8 ] = ascii (fp) : "./hrtfs/elev70/L70e320a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e320a.dat" right +[ 32, 9 ] = ascii (fp) : "./hrtfs/elev70/L70e315a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e315a.dat" right +[ 32, 10 ] = ascii (fp) : "./hrtfs/elev70/L70e310a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e310a.dat" right +[ 32, 11 ] = ascii (fp) : "./hrtfs/elev70/L70e305a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e305a.dat" right +[ 32, 12 ] = ascii (fp) : "./hrtfs/elev70/L70e300a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e300a.dat" right +[ 32, 13 ] = ascii (fp) : "./hrtfs/elev70/L70e295a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e295a.dat" right +[ 32, 14 ] = ascii (fp) : "./hrtfs/elev70/L70e290a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e290a.dat" right +[ 32, 15 ] = ascii (fp) : "./hrtfs/elev70/L70e285a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e285a.dat" right +[ 32, 16 ] = ascii (fp) : "./hrtfs/elev70/L70e280a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e280a.dat" right +[ 32, 17 ] = ascii (fp) : "./hrtfs/elev70/L70e275a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e275a.dat" right +[ 32, 18 ] = ascii (fp) : "./hrtfs/elev70/L70e270a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e270a.dat" right +[ 32, 19 ] = ascii (fp) : "./hrtfs/elev70/L70e265a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e265a.dat" right +[ 32, 20 ] = ascii (fp) : "./hrtfs/elev70/L70e260a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e260a.dat" right +[ 32, 21 ] = ascii (fp) : "./hrtfs/elev70/L70e255a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e255a.dat" right +[ 32, 22 ] = ascii (fp) : "./hrtfs/elev70/L70e250a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e250a.dat" right +[ 32, 23 ] = ascii (fp) : "./hrtfs/elev70/L70e245a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e245a.dat" right +[ 32, 24 ] = ascii (fp) : "./hrtfs/elev70/L70e240a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e240a.dat" right +[ 32, 25 ] = ascii (fp) : "./hrtfs/elev70/L70e235a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e235a.dat" right +[ 32, 26 ] = ascii (fp) : "./hrtfs/elev70/L70e230a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e230a.dat" right +[ 32, 27 ] = ascii (fp) : "./hrtfs/elev70/L70e225a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e225a.dat" right +[ 32, 28 ] = ascii (fp) : "./hrtfs/elev70/L70e220a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e220a.dat" right +[ 32, 29 ] = ascii (fp) : "./hrtfs/elev70/L70e215a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e215a.dat" right +[ 32, 30 ] = ascii (fp) : "./hrtfs/elev70/L70e210a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e210a.dat" right +[ 32, 31 ] = ascii (fp) : "./hrtfs/elev70/L70e205a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e205a.dat" right +[ 32, 32 ] = ascii (fp) : "./hrtfs/elev70/L70e200a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e200a.dat" right +[ 32, 33 ] = ascii (fp) : "./hrtfs/elev70/L70e195a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e195a.dat" right +[ 32, 34 ] = ascii (fp) : "./hrtfs/elev70/L70e190a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e190a.dat" right +[ 32, 35 ] = ascii (fp) : "./hrtfs/elev70/L70e185a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e185a.dat" right +[ 32, 36 ] = ascii (fp) : "./hrtfs/elev70/L70e180a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e180a.dat" right +[ 32, 37 ] = ascii (fp) : "./hrtfs/elev70/L70e175a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e175a.dat" right +[ 32, 38 ] = ascii (fp) : "./hrtfs/elev70/L70e170a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e170a.dat" right +[ 32, 39 ] = ascii (fp) : "./hrtfs/elev70/L70e165a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e165a.dat" right +[ 32, 40 ] = ascii (fp) : "./hrtfs/elev70/L70e160a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e160a.dat" right +[ 32, 41 ] = ascii (fp) : "./hrtfs/elev70/L70e155a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e155a.dat" right +[ 32, 42 ] = ascii (fp) : "./hrtfs/elev70/L70e150a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e150a.dat" right +[ 32, 43 ] = ascii (fp) : "./hrtfs/elev70/L70e145a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e145a.dat" right +[ 32, 44 ] = ascii (fp) : "./hrtfs/elev70/L70e140a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e140a.dat" right +[ 32, 45 ] = ascii (fp) : "./hrtfs/elev70/L70e135a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e135a.dat" right +[ 32, 46 ] = ascii (fp) : "./hrtfs/elev70/L70e130a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e130a.dat" right +[ 32, 47 ] = ascii (fp) : "./hrtfs/elev70/L70e125a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e125a.dat" right +[ 32, 48 ] = ascii (fp) : "./hrtfs/elev70/L70e120a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e120a.dat" right +[ 32, 49 ] = ascii (fp) : "./hrtfs/elev70/L70e115a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e115a.dat" right +[ 32, 50 ] = ascii (fp) : "./hrtfs/elev70/L70e110a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e110a.dat" right +[ 32, 51 ] = ascii (fp) : "./hrtfs/elev70/L70e105a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e105a.dat" right +[ 32, 52 ] = ascii (fp) : "./hrtfs/elev70/L70e100a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e100a.dat" right +[ 32, 53 ] = ascii (fp) : "./hrtfs/elev70/L70e095a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e095a.dat" right +[ 32, 54 ] = ascii (fp) : "./hrtfs/elev70/L70e090a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e090a.dat" right +[ 32, 55 ] = ascii (fp) : "./hrtfs/elev70/L70e085a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e085a.dat" right +[ 32, 56 ] = ascii (fp) : "./hrtfs/elev70/L70e080a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e080a.dat" right +[ 32, 57 ] = ascii (fp) : "./hrtfs/elev70/L70e075a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e075a.dat" right +[ 32, 58 ] = ascii (fp) : "./hrtfs/elev70/L70e070a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e070a.dat" right +[ 32, 59 ] = ascii (fp) : "./hrtfs/elev70/L70e065a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e065a.dat" right +[ 32, 60 ] = ascii (fp) : "./hrtfs/elev70/L70e060a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e060a.dat" right +[ 32, 61 ] = ascii (fp) : "./hrtfs/elev70/L70e055a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e055a.dat" right +[ 32, 62 ] = ascii (fp) : "./hrtfs/elev70/L70e050a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e050a.dat" right +[ 32, 63 ] = ascii (fp) : "./hrtfs/elev70/L70e045a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e045a.dat" right +[ 32, 64 ] = ascii (fp) : "./hrtfs/elev70/L70e040a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e040a.dat" right +[ 32, 65 ] = ascii (fp) : "./hrtfs/elev70/L70e035a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e035a.dat" right +[ 32, 66 ] = ascii (fp) : "./hrtfs/elev70/L70e030a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e030a.dat" right +[ 32, 67 ] = ascii (fp) : "./hrtfs/elev70/L70e025a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e025a.dat" right +[ 32, 68 ] = ascii (fp) : "./hrtfs/elev70/L70e020a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e020a.dat" right +[ 32, 69 ] = ascii (fp) : "./hrtfs/elev70/L70e015a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e015a.dat" right +[ 32, 70 ] = ascii (fp) : "./hrtfs/elev70/L70e010a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e010a.dat" right +[ 32, 71 ] = ascii (fp) : "./hrtfs/elev70/L70e005a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e005a.dat" right -[ 33, 0 ] = ascii (fp) : "./hrtfs/elev75/L75e000a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e000a.dat right -[ 33, 1 ] = ascii (fp) : "./hrtfs/elev75/L75e355a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e355a.dat right -[ 33, 2 ] = ascii (fp) : "./hrtfs/elev75/L75e350a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e350a.dat right -[ 33, 3 ] = ascii (fp) : "./hrtfs/elev75/L75e345a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e345a.dat right -[ 33, 4 ] = ascii (fp) : "./hrtfs/elev75/L75e340a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e340a.dat right -[ 33, 5 ] = ascii (fp) : "./hrtfs/elev75/L75e335a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e335a.dat right -[ 33, 6 ] = ascii (fp) : "./hrtfs/elev75/L75e330a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e330a.dat right -[ 33, 7 ] = ascii (fp) : "./hrtfs/elev75/L75e325a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e325a.dat right -[ 33, 8 ] = ascii (fp) : "./hrtfs/elev75/L75e320a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e320a.dat right -[ 33, 9 ] = ascii (fp) : "./hrtfs/elev75/L75e315a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e315a.dat right -[ 33, 10 ] = ascii (fp) : "./hrtfs/elev75/L75e310a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e310a.dat right -[ 33, 11 ] = ascii (fp) : "./hrtfs/elev75/L75e305a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e305a.dat right -[ 33, 12 ] = ascii (fp) : "./hrtfs/elev75/L75e300a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e300a.dat right -[ 33, 13 ] = ascii (fp) : "./hrtfs/elev75/L75e295a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e295a.dat right -[ 33, 14 ] = ascii (fp) : "./hrtfs/elev75/L75e290a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e290a.dat right -[ 33, 15 ] = ascii (fp) : "./hrtfs/elev75/L75e285a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e285a.dat right -[ 33, 16 ] = ascii (fp) : "./hrtfs/elev75/L75e280a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e280a.dat right -[ 33, 17 ] = ascii (fp) : "./hrtfs/elev75/L75e275a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e275a.dat right -[ 33, 18 ] = ascii (fp) : "./hrtfs/elev75/L75e270a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e270a.dat right -[ 33, 19 ] = ascii (fp) : "./hrtfs/elev75/L75e265a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e265a.dat right -[ 33, 20 ] = ascii (fp) : "./hrtfs/elev75/L75e260a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e260a.dat right -[ 33, 21 ] = ascii (fp) : "./hrtfs/elev75/L75e255a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e255a.dat right -[ 33, 22 ] = ascii (fp) : "./hrtfs/elev75/L75e250a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e250a.dat right -[ 33, 23 ] = ascii (fp) : "./hrtfs/elev75/L75e245a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e245a.dat right -[ 33, 24 ] = ascii (fp) : "./hrtfs/elev75/L75e240a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e240a.dat right -[ 33, 25 ] = ascii (fp) : "./hrtfs/elev75/L75e235a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e235a.dat right -[ 33, 26 ] = ascii (fp) : "./hrtfs/elev75/L75e230a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e230a.dat right -[ 33, 27 ] = ascii (fp) : "./hrtfs/elev75/L75e225a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e225a.dat right -[ 33, 28 ] = ascii (fp) : "./hrtfs/elev75/L75e220a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e220a.dat right -[ 33, 29 ] = ascii (fp) : "./hrtfs/elev75/L75e215a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e215a.dat right -[ 33, 30 ] = ascii (fp) : "./hrtfs/elev75/L75e210a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e210a.dat right -[ 33, 31 ] = ascii (fp) : "./hrtfs/elev75/L75e205a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e205a.dat right -[ 33, 32 ] = ascii (fp) : "./hrtfs/elev75/L75e200a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e200a.dat right -[ 33, 33 ] = ascii (fp) : "./hrtfs/elev75/L75e195a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e195a.dat right -[ 33, 34 ] = ascii (fp) : "./hrtfs/elev75/L75e190a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e190a.dat right -[ 33, 35 ] = ascii (fp) : "./hrtfs/elev75/L75e185a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e185a.dat right -[ 33, 36 ] = ascii (fp) : "./hrtfs/elev75/L75e180a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e180a.dat right -[ 33, 37 ] = ascii (fp) : "./hrtfs/elev75/L75e175a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e175a.dat right -[ 33, 38 ] = ascii (fp) : "./hrtfs/elev75/L75e170a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e170a.dat right -[ 33, 39 ] = ascii (fp) : "./hrtfs/elev75/L75e165a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e165a.dat right -[ 33, 40 ] = ascii (fp) : "./hrtfs/elev75/L75e160a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e160a.dat right -[ 33, 41 ] = ascii (fp) : "./hrtfs/elev75/L75e155a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e155a.dat right -[ 33, 42 ] = ascii (fp) : "./hrtfs/elev75/L75e150a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e150a.dat right -[ 33, 43 ] = ascii (fp) : "./hrtfs/elev75/L75e145a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e145a.dat right -[ 33, 44 ] = ascii (fp) : "./hrtfs/elev75/L75e140a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e140a.dat right -[ 33, 45 ] = ascii (fp) : "./hrtfs/elev75/L75e135a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e135a.dat right -[ 33, 46 ] = ascii (fp) : "./hrtfs/elev75/L75e130a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e130a.dat right -[ 33, 47 ] = ascii (fp) : "./hrtfs/elev75/L75e125a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e125a.dat right -[ 33, 48 ] = ascii (fp) : "./hrtfs/elev75/L75e120a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e120a.dat right -[ 33, 49 ] = ascii (fp) : "./hrtfs/elev75/L75e115a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e115a.dat right -[ 33, 50 ] = ascii (fp) : "./hrtfs/elev75/L75e110a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e110a.dat right -[ 33, 51 ] = ascii (fp) : "./hrtfs/elev75/L75e105a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e105a.dat right -[ 33, 52 ] = ascii (fp) : "./hrtfs/elev75/L75e100a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e100a.dat right -[ 33, 53 ] = ascii (fp) : "./hrtfs/elev75/L75e095a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e095a.dat right -[ 33, 54 ] = ascii (fp) : "./hrtfs/elev75/L75e090a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e090a.dat right -[ 33, 55 ] = ascii (fp) : "./hrtfs/elev75/L75e085a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e085a.dat right -[ 33, 56 ] = ascii (fp) : "./hrtfs/elev75/L75e080a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e080a.dat right -[ 33, 57 ] = ascii (fp) : "./hrtfs/elev75/L75e075a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e075a.dat right -[ 33, 58 ] = ascii (fp) : "./hrtfs/elev75/L75e070a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e070a.dat right -[ 33, 59 ] = ascii (fp) : "./hrtfs/elev75/L75e065a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e065a.dat right -[ 33, 60 ] = ascii (fp) : "./hrtfs/elev75/L75e060a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e060a.dat right -[ 33, 61 ] = ascii (fp) : "./hrtfs/elev75/L75e055a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e055a.dat right -[ 33, 62 ] = ascii (fp) : "./hrtfs/elev75/L75e050a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e050a.dat right -[ 33, 63 ] = ascii (fp) : "./hrtfs/elev75/L75e045a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e045a.dat right -[ 33, 64 ] = ascii (fp) : "./hrtfs/elev75/L75e040a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e040a.dat right -[ 33, 65 ] = ascii (fp) : "./hrtfs/elev75/L75e035a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e035a.dat right -[ 33, 66 ] = ascii (fp) : "./hrtfs/elev75/L75e030a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e030a.dat right -[ 33, 67 ] = ascii (fp) : "./hrtfs/elev75/L75e025a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e025a.dat right -[ 33, 68 ] = ascii (fp) : "./hrtfs/elev75/L75e020a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e020a.dat right -[ 33, 69 ] = ascii (fp) : "./hrtfs/elev75/L75e015a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e015a.dat right -[ 33, 70 ] = ascii (fp) : "./hrtfs/elev75/L75e010a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e010a.dat right -[ 33, 71 ] = ascii (fp) : "./hrtfs/elev75/L75e005a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e005a.dat right +[ 33, 0 ] = ascii (fp) : "./hrtfs/elev75/L75e000a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e000a.dat" right +[ 33, 1 ] = ascii (fp) : "./hrtfs/elev75/L75e355a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e355a.dat" right +[ 33, 2 ] = ascii (fp) : "./hrtfs/elev75/L75e350a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e350a.dat" right +[ 33, 3 ] = ascii (fp) : "./hrtfs/elev75/L75e345a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e345a.dat" right +[ 33, 4 ] = ascii (fp) : "./hrtfs/elev75/L75e340a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e340a.dat" right +[ 33, 5 ] = ascii (fp) : "./hrtfs/elev75/L75e335a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e335a.dat" right +[ 33, 6 ] = ascii (fp) : "./hrtfs/elev75/L75e330a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e330a.dat" right +[ 33, 7 ] = ascii (fp) : "./hrtfs/elev75/L75e325a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e325a.dat" right +[ 33, 8 ] = ascii (fp) : "./hrtfs/elev75/L75e320a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e320a.dat" right +[ 33, 9 ] = ascii (fp) : "./hrtfs/elev75/L75e315a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e315a.dat" right +[ 33, 10 ] = ascii (fp) : "./hrtfs/elev75/L75e310a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e310a.dat" right +[ 33, 11 ] = ascii (fp) : "./hrtfs/elev75/L75e305a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e305a.dat" right +[ 33, 12 ] = ascii (fp) : "./hrtfs/elev75/L75e300a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e300a.dat" right +[ 33, 13 ] = ascii (fp) : "./hrtfs/elev75/L75e295a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e295a.dat" right +[ 33, 14 ] = ascii (fp) : "./hrtfs/elev75/L75e290a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e290a.dat" right +[ 33, 15 ] = ascii (fp) : "./hrtfs/elev75/L75e285a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e285a.dat" right +[ 33, 16 ] = ascii (fp) : "./hrtfs/elev75/L75e280a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e280a.dat" right +[ 33, 17 ] = ascii (fp) : "./hrtfs/elev75/L75e275a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e275a.dat" right +[ 33, 18 ] = ascii (fp) : "./hrtfs/elev75/L75e270a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e270a.dat" right +[ 33, 19 ] = ascii (fp) : "./hrtfs/elev75/L75e265a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e265a.dat" right +[ 33, 20 ] = ascii (fp) : "./hrtfs/elev75/L75e260a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e260a.dat" right +[ 33, 21 ] = ascii (fp) : "./hrtfs/elev75/L75e255a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e255a.dat" right +[ 33, 22 ] = ascii (fp) : "./hrtfs/elev75/L75e250a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e250a.dat" right +[ 33, 23 ] = ascii (fp) : "./hrtfs/elev75/L75e245a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e245a.dat" right +[ 33, 24 ] = ascii (fp) : "./hrtfs/elev75/L75e240a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e240a.dat" right +[ 33, 25 ] = ascii (fp) : "./hrtfs/elev75/L75e235a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e235a.dat" right +[ 33, 26 ] = ascii (fp) : "./hrtfs/elev75/L75e230a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e230a.dat" right +[ 33, 27 ] = ascii (fp) : "./hrtfs/elev75/L75e225a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e225a.dat" right +[ 33, 28 ] = ascii (fp) : "./hrtfs/elev75/L75e220a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e220a.dat" right +[ 33, 29 ] = ascii (fp) : "./hrtfs/elev75/L75e215a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e215a.dat" right +[ 33, 30 ] = ascii (fp) : "./hrtfs/elev75/L75e210a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e210a.dat" right +[ 33, 31 ] = ascii (fp) : "./hrtfs/elev75/L75e205a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e205a.dat" right +[ 33, 32 ] = ascii (fp) : "./hrtfs/elev75/L75e200a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e200a.dat" right +[ 33, 33 ] = ascii (fp) : "./hrtfs/elev75/L75e195a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e195a.dat" right +[ 33, 34 ] = ascii (fp) : "./hrtfs/elev75/L75e190a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e190a.dat" right +[ 33, 35 ] = ascii (fp) : "./hrtfs/elev75/L75e185a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e185a.dat" right +[ 33, 36 ] = ascii (fp) : "./hrtfs/elev75/L75e180a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e180a.dat" right +[ 33, 37 ] = ascii (fp) : "./hrtfs/elev75/L75e175a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e175a.dat" right +[ 33, 38 ] = ascii (fp) : "./hrtfs/elev75/L75e170a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e170a.dat" right +[ 33, 39 ] = ascii (fp) : "./hrtfs/elev75/L75e165a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e165a.dat" right +[ 33, 40 ] = ascii (fp) : "./hrtfs/elev75/L75e160a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e160a.dat" right +[ 33, 41 ] = ascii (fp) : "./hrtfs/elev75/L75e155a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e155a.dat" right +[ 33, 42 ] = ascii (fp) : "./hrtfs/elev75/L75e150a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e150a.dat" right +[ 33, 43 ] = ascii (fp) : "./hrtfs/elev75/L75e145a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e145a.dat" right +[ 33, 44 ] = ascii (fp) : "./hrtfs/elev75/L75e140a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e140a.dat" right +[ 33, 45 ] = ascii (fp) : "./hrtfs/elev75/L75e135a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e135a.dat" right +[ 33, 46 ] = ascii (fp) : "./hrtfs/elev75/L75e130a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e130a.dat" right +[ 33, 47 ] = ascii (fp) : "./hrtfs/elev75/L75e125a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e125a.dat" right +[ 33, 48 ] = ascii (fp) : "./hrtfs/elev75/L75e120a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e120a.dat" right +[ 33, 49 ] = ascii (fp) : "./hrtfs/elev75/L75e115a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e115a.dat" right +[ 33, 50 ] = ascii (fp) : "./hrtfs/elev75/L75e110a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e110a.dat" right +[ 33, 51 ] = ascii (fp) : "./hrtfs/elev75/L75e105a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e105a.dat" right +[ 33, 52 ] = ascii (fp) : "./hrtfs/elev75/L75e100a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e100a.dat" right +[ 33, 53 ] = ascii (fp) : "./hrtfs/elev75/L75e095a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e095a.dat" right +[ 33, 54 ] = ascii (fp) : "./hrtfs/elev75/L75e090a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e090a.dat" right +[ 33, 55 ] = ascii (fp) : "./hrtfs/elev75/L75e085a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e085a.dat" right +[ 33, 56 ] = ascii (fp) : "./hrtfs/elev75/L75e080a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e080a.dat" right +[ 33, 57 ] = ascii (fp) : "./hrtfs/elev75/L75e075a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e075a.dat" right +[ 33, 58 ] = ascii (fp) : "./hrtfs/elev75/L75e070a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e070a.dat" right +[ 33, 59 ] = ascii (fp) : "./hrtfs/elev75/L75e065a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e065a.dat" right +[ 33, 60 ] = ascii (fp) : "./hrtfs/elev75/L75e060a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e060a.dat" right +[ 33, 61 ] = ascii (fp) : "./hrtfs/elev75/L75e055a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e055a.dat" right +[ 33, 62 ] = ascii (fp) : "./hrtfs/elev75/L75e050a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e050a.dat" right +[ 33, 63 ] = ascii (fp) : "./hrtfs/elev75/L75e045a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e045a.dat" right +[ 33, 64 ] = ascii (fp) : "./hrtfs/elev75/L75e040a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e040a.dat" right +[ 33, 65 ] = ascii (fp) : "./hrtfs/elev75/L75e035a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e035a.dat" right +[ 33, 66 ] = ascii (fp) : "./hrtfs/elev75/L75e030a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e030a.dat" right +[ 33, 67 ] = ascii (fp) : "./hrtfs/elev75/L75e025a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e025a.dat" right +[ 33, 68 ] = ascii (fp) : "./hrtfs/elev75/L75e020a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e020a.dat" right +[ 33, 69 ] = ascii (fp) : "./hrtfs/elev75/L75e015a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e015a.dat" right +[ 33, 70 ] = ascii (fp) : "./hrtfs/elev75/L75e010a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e010a.dat" right +[ 33, 71 ] = ascii (fp) : "./hrtfs/elev75/L75e005a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e005a.dat" right -[ 34, 0 ] = ascii (fp) : "./hrtfs/elev80/L80e000a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e000a.dat right -[ 34, 1 ] = ascii (fp) : "./hrtfs/elev80/L80e355a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e355a.dat right -[ 34, 2 ] = ascii (fp) : "./hrtfs/elev80/L80e350a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e350a.dat right -[ 34, 3 ] = ascii (fp) : "./hrtfs/elev80/L80e345a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e345a.dat right -[ 34, 4 ] = ascii (fp) : "./hrtfs/elev80/L80e340a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e340a.dat right -[ 34, 5 ] = ascii (fp) : "./hrtfs/elev80/L80e335a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e335a.dat right -[ 34, 6 ] = ascii (fp) : "./hrtfs/elev80/L80e330a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e330a.dat right -[ 34, 7 ] = ascii (fp) : "./hrtfs/elev80/L80e325a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e325a.dat right -[ 34, 8 ] = ascii (fp) : "./hrtfs/elev80/L80e320a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e320a.dat right -[ 34, 9 ] = ascii (fp) : "./hrtfs/elev80/L80e315a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e315a.dat right -[ 34, 10 ] = ascii (fp) : "./hrtfs/elev80/L80e310a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e310a.dat right -[ 34, 11 ] = ascii (fp) : "./hrtfs/elev80/L80e305a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e305a.dat right -[ 34, 12 ] = ascii (fp) : "./hrtfs/elev80/L80e300a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e300a.dat right -[ 34, 13 ] = ascii (fp) : "./hrtfs/elev80/L80e295a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e295a.dat right -[ 34, 14 ] = ascii (fp) : "./hrtfs/elev80/L80e290a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e290a.dat right -[ 34, 15 ] = ascii (fp) : "./hrtfs/elev80/L80e285a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e285a.dat right -[ 34, 16 ] = ascii (fp) : "./hrtfs/elev80/L80e280a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e280a.dat right -[ 34, 17 ] = ascii (fp) : "./hrtfs/elev80/L80e275a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e275a.dat right -[ 34, 18 ] = ascii (fp) : "./hrtfs/elev80/L80e270a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e270a.dat right -[ 34, 19 ] = ascii (fp) : "./hrtfs/elev80/L80e265a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e265a.dat right -[ 34, 20 ] = ascii (fp) : "./hrtfs/elev80/L80e260a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e260a.dat right -[ 34, 21 ] = ascii (fp) : "./hrtfs/elev80/L80e255a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e255a.dat right -[ 34, 22 ] = ascii (fp) : "./hrtfs/elev80/L80e250a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e250a.dat right -[ 34, 23 ] = ascii (fp) : "./hrtfs/elev80/L80e245a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e245a.dat right -[ 34, 24 ] = ascii (fp) : "./hrtfs/elev80/L80e240a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e240a.dat right -[ 34, 25 ] = ascii (fp) : "./hrtfs/elev80/L80e235a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e235a.dat right -[ 34, 26 ] = ascii (fp) : "./hrtfs/elev80/L80e230a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e230a.dat right -[ 34, 27 ] = ascii (fp) : "./hrtfs/elev80/L80e225a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e225a.dat right -[ 34, 28 ] = ascii (fp) : "./hrtfs/elev80/L80e220a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e220a.dat right -[ 34, 29 ] = ascii (fp) : "./hrtfs/elev80/L80e215a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e215a.dat right -[ 34, 30 ] = ascii (fp) : "./hrtfs/elev80/L80e210a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e210a.dat right -[ 34, 31 ] = ascii (fp) : "./hrtfs/elev80/L80e205a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e205a.dat right -[ 34, 32 ] = ascii (fp) : "./hrtfs/elev80/L80e200a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e200a.dat right -[ 34, 33 ] = ascii (fp) : "./hrtfs/elev80/L80e195a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e195a.dat right -[ 34, 34 ] = ascii (fp) : "./hrtfs/elev80/L80e190a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e190a.dat right -[ 34, 35 ] = ascii (fp) : "./hrtfs/elev80/L80e185a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e185a.dat right -[ 34, 36 ] = ascii (fp) : "./hrtfs/elev80/L80e180a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e180a.dat right -[ 34, 37 ] = ascii (fp) : "./hrtfs/elev80/L80e175a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e175a.dat right -[ 34, 38 ] = ascii (fp) : "./hrtfs/elev80/L80e170a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e170a.dat right -[ 34, 39 ] = ascii (fp) : "./hrtfs/elev80/L80e165a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e165a.dat right -[ 34, 40 ] = ascii (fp) : "./hrtfs/elev80/L80e160a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e160a.dat right -[ 34, 41 ] = ascii (fp) : "./hrtfs/elev80/L80e155a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e155a.dat right -[ 34, 42 ] = ascii (fp) : "./hrtfs/elev80/L80e150a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e150a.dat right -[ 34, 43 ] = ascii (fp) : "./hrtfs/elev80/L80e145a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e145a.dat right -[ 34, 44 ] = ascii (fp) : "./hrtfs/elev80/L80e140a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e140a.dat right -[ 34, 45 ] = ascii (fp) : "./hrtfs/elev80/L80e135a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e135a.dat right -[ 34, 46 ] = ascii (fp) : "./hrtfs/elev80/L80e130a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e130a.dat right -[ 34, 47 ] = ascii (fp) : "./hrtfs/elev80/L80e125a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e125a.dat right -[ 34, 48 ] = ascii (fp) : "./hrtfs/elev80/L80e120a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e120a.dat right -[ 34, 49 ] = ascii (fp) : "./hrtfs/elev80/L80e115a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e115a.dat right -[ 34, 50 ] = ascii (fp) : "./hrtfs/elev80/L80e110a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e110a.dat right -[ 34, 51 ] = ascii (fp) : "./hrtfs/elev80/L80e105a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e105a.dat right -[ 34, 52 ] = ascii (fp) : "./hrtfs/elev80/L80e100a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e100a.dat right -[ 34, 53 ] = ascii (fp) : "./hrtfs/elev80/L80e095a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e095a.dat right -[ 34, 54 ] = ascii (fp) : "./hrtfs/elev80/L80e090a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e090a.dat right -[ 34, 55 ] = ascii (fp) : "./hrtfs/elev80/L80e085a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e085a.dat right -[ 34, 56 ] = ascii (fp) : "./hrtfs/elev80/L80e080a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e080a.dat right -[ 34, 57 ] = ascii (fp) : "./hrtfs/elev80/L80e075a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e075a.dat right -[ 34, 58 ] = ascii (fp) : "./hrtfs/elev80/L80e070a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e070a.dat right -[ 34, 59 ] = ascii (fp) : "./hrtfs/elev80/L80e065a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e065a.dat right -[ 34, 60 ] = ascii (fp) : "./hrtfs/elev80/L80e060a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e060a.dat right -[ 34, 61 ] = ascii (fp) : "./hrtfs/elev80/L80e055a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e055a.dat right -[ 34, 62 ] = ascii (fp) : "./hrtfs/elev80/L80e050a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e050a.dat right -[ 34, 63 ] = ascii (fp) : "./hrtfs/elev80/L80e045a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e045a.dat right -[ 34, 64 ] = ascii (fp) : "./hrtfs/elev80/L80e040a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e040a.dat right -[ 34, 65 ] = ascii (fp) : "./hrtfs/elev80/L80e035a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e035a.dat right -[ 34, 66 ] = ascii (fp) : "./hrtfs/elev80/L80e030a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e030a.dat right -[ 34, 67 ] = ascii (fp) : "./hrtfs/elev80/L80e025a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e025a.dat right -[ 34, 68 ] = ascii (fp) : "./hrtfs/elev80/L80e020a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e020a.dat right -[ 34, 69 ] = ascii (fp) : "./hrtfs/elev80/L80e015a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e015a.dat right -[ 34, 70 ] = ascii (fp) : "./hrtfs/elev80/L80e010a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e010a.dat right -[ 34, 71 ] = ascii (fp) : "./hrtfs/elev80/L80e005a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e005a.dat right +[ 34, 0 ] = ascii (fp) : "./hrtfs/elev80/L80e000a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e000a.dat" right +[ 34, 1 ] = ascii (fp) : "./hrtfs/elev80/L80e355a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e355a.dat" right +[ 34, 2 ] = ascii (fp) : "./hrtfs/elev80/L80e350a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e350a.dat" right +[ 34, 3 ] = ascii (fp) : "./hrtfs/elev80/L80e345a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e345a.dat" right +[ 34, 4 ] = ascii (fp) : "./hrtfs/elev80/L80e340a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e340a.dat" right +[ 34, 5 ] = ascii (fp) : "./hrtfs/elev80/L80e335a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e335a.dat" right +[ 34, 6 ] = ascii (fp) : "./hrtfs/elev80/L80e330a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e330a.dat" right +[ 34, 7 ] = ascii (fp) : "./hrtfs/elev80/L80e325a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e325a.dat" right +[ 34, 8 ] = ascii (fp) : "./hrtfs/elev80/L80e320a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e320a.dat" right +[ 34, 9 ] = ascii (fp) : "./hrtfs/elev80/L80e315a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e315a.dat" right +[ 34, 10 ] = ascii (fp) : "./hrtfs/elev80/L80e310a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e310a.dat" right +[ 34, 11 ] = ascii (fp) : "./hrtfs/elev80/L80e305a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e305a.dat" right +[ 34, 12 ] = ascii (fp) : "./hrtfs/elev80/L80e300a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e300a.dat" right +[ 34, 13 ] = ascii (fp) : "./hrtfs/elev80/L80e295a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e295a.dat" right +[ 34, 14 ] = ascii (fp) : "./hrtfs/elev80/L80e290a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e290a.dat" right +[ 34, 15 ] = ascii (fp) : "./hrtfs/elev80/L80e285a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e285a.dat" right +[ 34, 16 ] = ascii (fp) : "./hrtfs/elev80/L80e280a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e280a.dat" right +[ 34, 17 ] = ascii (fp) : "./hrtfs/elev80/L80e275a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e275a.dat" right +[ 34, 18 ] = ascii (fp) : "./hrtfs/elev80/L80e270a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e270a.dat" right +[ 34, 19 ] = ascii (fp) : "./hrtfs/elev80/L80e265a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e265a.dat" right +[ 34, 20 ] = ascii (fp) : "./hrtfs/elev80/L80e260a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e260a.dat" right +[ 34, 21 ] = ascii (fp) : "./hrtfs/elev80/L80e255a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e255a.dat" right +[ 34, 22 ] = ascii (fp) : "./hrtfs/elev80/L80e250a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e250a.dat" right +[ 34, 23 ] = ascii (fp) : "./hrtfs/elev80/L80e245a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e245a.dat" right +[ 34, 24 ] = ascii (fp) : "./hrtfs/elev80/L80e240a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e240a.dat" right +[ 34, 25 ] = ascii (fp) : "./hrtfs/elev80/L80e235a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e235a.dat" right +[ 34, 26 ] = ascii (fp) : "./hrtfs/elev80/L80e230a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e230a.dat" right +[ 34, 27 ] = ascii (fp) : "./hrtfs/elev80/L80e225a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e225a.dat" right +[ 34, 28 ] = ascii (fp) : "./hrtfs/elev80/L80e220a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e220a.dat" right +[ 34, 29 ] = ascii (fp) : "./hrtfs/elev80/L80e215a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e215a.dat" right +[ 34, 30 ] = ascii (fp) : "./hrtfs/elev80/L80e210a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e210a.dat" right +[ 34, 31 ] = ascii (fp) : "./hrtfs/elev80/L80e205a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e205a.dat" right +[ 34, 32 ] = ascii (fp) : "./hrtfs/elev80/L80e200a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e200a.dat" right +[ 34, 33 ] = ascii (fp) : "./hrtfs/elev80/L80e195a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e195a.dat" right +[ 34, 34 ] = ascii (fp) : "./hrtfs/elev80/L80e190a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e190a.dat" right +[ 34, 35 ] = ascii (fp) : "./hrtfs/elev80/L80e185a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e185a.dat" right +[ 34, 36 ] = ascii (fp) : "./hrtfs/elev80/L80e180a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e180a.dat" right +[ 34, 37 ] = ascii (fp) : "./hrtfs/elev80/L80e175a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e175a.dat" right +[ 34, 38 ] = ascii (fp) : "./hrtfs/elev80/L80e170a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e170a.dat" right +[ 34, 39 ] = ascii (fp) : "./hrtfs/elev80/L80e165a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e165a.dat" right +[ 34, 40 ] = ascii (fp) : "./hrtfs/elev80/L80e160a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e160a.dat" right +[ 34, 41 ] = ascii (fp) : "./hrtfs/elev80/L80e155a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e155a.dat" right +[ 34, 42 ] = ascii (fp) : "./hrtfs/elev80/L80e150a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e150a.dat" right +[ 34, 43 ] = ascii (fp) : "./hrtfs/elev80/L80e145a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e145a.dat" right +[ 34, 44 ] = ascii (fp) : "./hrtfs/elev80/L80e140a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e140a.dat" right +[ 34, 45 ] = ascii (fp) : "./hrtfs/elev80/L80e135a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e135a.dat" right +[ 34, 46 ] = ascii (fp) : "./hrtfs/elev80/L80e130a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e130a.dat" right +[ 34, 47 ] = ascii (fp) : "./hrtfs/elev80/L80e125a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e125a.dat" right +[ 34, 48 ] = ascii (fp) : "./hrtfs/elev80/L80e120a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e120a.dat" right +[ 34, 49 ] = ascii (fp) : "./hrtfs/elev80/L80e115a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e115a.dat" right +[ 34, 50 ] = ascii (fp) : "./hrtfs/elev80/L80e110a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e110a.dat" right +[ 34, 51 ] = ascii (fp) : "./hrtfs/elev80/L80e105a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e105a.dat" right +[ 34, 52 ] = ascii (fp) : "./hrtfs/elev80/L80e100a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e100a.dat" right +[ 34, 53 ] = ascii (fp) : "./hrtfs/elev80/L80e095a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e095a.dat" right +[ 34, 54 ] = ascii (fp) : "./hrtfs/elev80/L80e090a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e090a.dat" right +[ 34, 55 ] = ascii (fp) : "./hrtfs/elev80/L80e085a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e085a.dat" right +[ 34, 56 ] = ascii (fp) : "./hrtfs/elev80/L80e080a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e080a.dat" right +[ 34, 57 ] = ascii (fp) : "./hrtfs/elev80/L80e075a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e075a.dat" right +[ 34, 58 ] = ascii (fp) : "./hrtfs/elev80/L80e070a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e070a.dat" right +[ 34, 59 ] = ascii (fp) : "./hrtfs/elev80/L80e065a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e065a.dat" right +[ 34, 60 ] = ascii (fp) : "./hrtfs/elev80/L80e060a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e060a.dat" right +[ 34, 61 ] = ascii (fp) : "./hrtfs/elev80/L80e055a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e055a.dat" right +[ 34, 62 ] = ascii (fp) : "./hrtfs/elev80/L80e050a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e050a.dat" right +[ 34, 63 ] = ascii (fp) : "./hrtfs/elev80/L80e045a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e045a.dat" right +[ 34, 64 ] = ascii (fp) : "./hrtfs/elev80/L80e040a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e040a.dat" right +[ 34, 65 ] = ascii (fp) : "./hrtfs/elev80/L80e035a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e035a.dat" right +[ 34, 66 ] = ascii (fp) : "./hrtfs/elev80/L80e030a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e030a.dat" right +[ 34, 67 ] = ascii (fp) : "./hrtfs/elev80/L80e025a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e025a.dat" right +[ 34, 68 ] = ascii (fp) : "./hrtfs/elev80/L80e020a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e020a.dat" right +[ 34, 69 ] = ascii (fp) : "./hrtfs/elev80/L80e015a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e015a.dat" right +[ 34, 70 ] = ascii (fp) : "./hrtfs/elev80/L80e010a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e010a.dat" right +[ 34, 71 ] = ascii (fp) : "./hrtfs/elev80/L80e005a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e005a.dat" right -[ 35, 0 ] = ascii (fp) : "./hrtfs/elev85/L85e000a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e000a.dat right -[ 35, 1 ] = ascii (fp) : "./hrtfs/elev85/L85e355a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e355a.dat right -[ 35, 2 ] = ascii (fp) : "./hrtfs/elev85/L85e350a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e350a.dat right -[ 35, 3 ] = ascii (fp) : "./hrtfs/elev85/L85e345a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e345a.dat right -[ 35, 4 ] = ascii (fp) : "./hrtfs/elev85/L85e340a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e340a.dat right -[ 35, 5 ] = ascii (fp) : "./hrtfs/elev85/L85e335a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e335a.dat right -[ 35, 6 ] = ascii (fp) : "./hrtfs/elev85/L85e330a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e330a.dat right -[ 35, 7 ] = ascii (fp) : "./hrtfs/elev85/L85e325a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e325a.dat right -[ 35, 8 ] = ascii (fp) : "./hrtfs/elev85/L85e320a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e320a.dat right -[ 35, 9 ] = ascii (fp) : "./hrtfs/elev85/L85e315a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e315a.dat right -[ 35, 10 ] = ascii (fp) : "./hrtfs/elev85/L85e310a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e310a.dat right -[ 35, 11 ] = ascii (fp) : "./hrtfs/elev85/L85e305a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e305a.dat right -[ 35, 12 ] = ascii (fp) : "./hrtfs/elev85/L85e300a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e300a.dat right -[ 35, 13 ] = ascii (fp) : "./hrtfs/elev85/L85e295a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e295a.dat right -[ 35, 14 ] = ascii (fp) : "./hrtfs/elev85/L85e290a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e290a.dat right -[ 35, 15 ] = ascii (fp) : "./hrtfs/elev85/L85e285a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e285a.dat right -[ 35, 16 ] = ascii (fp) : "./hrtfs/elev85/L85e280a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e280a.dat right -[ 35, 17 ] = ascii (fp) : "./hrtfs/elev85/L85e275a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e275a.dat right -[ 35, 18 ] = ascii (fp) : "./hrtfs/elev85/L85e270a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e270a.dat right -[ 35, 19 ] = ascii (fp) : "./hrtfs/elev85/L85e265a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e265a.dat right -[ 35, 20 ] = ascii (fp) : "./hrtfs/elev85/L85e260a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e260a.dat right -[ 35, 21 ] = ascii (fp) : "./hrtfs/elev85/L85e255a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e255a.dat right -[ 35, 22 ] = ascii (fp) : "./hrtfs/elev85/L85e250a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e250a.dat right -[ 35, 23 ] = ascii (fp) : "./hrtfs/elev85/L85e245a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e245a.dat right -[ 35, 24 ] = ascii (fp) : "./hrtfs/elev85/L85e240a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e240a.dat right -[ 35, 25 ] = ascii (fp) : "./hrtfs/elev85/L85e235a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e235a.dat right -[ 35, 26 ] = ascii (fp) : "./hrtfs/elev85/L85e230a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e230a.dat right -[ 35, 27 ] = ascii (fp) : "./hrtfs/elev85/L85e225a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e225a.dat right -[ 35, 28 ] = ascii (fp) : "./hrtfs/elev85/L85e220a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e220a.dat right -[ 35, 29 ] = ascii (fp) : "./hrtfs/elev85/L85e215a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e215a.dat right -[ 35, 30 ] = ascii (fp) : "./hrtfs/elev85/L85e210a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e210a.dat right -[ 35, 31 ] = ascii (fp) : "./hrtfs/elev85/L85e205a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e205a.dat right -[ 35, 32 ] = ascii (fp) : "./hrtfs/elev85/L85e200a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e200a.dat right -[ 35, 33 ] = ascii (fp) : "./hrtfs/elev85/L85e195a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e195a.dat right -[ 35, 34 ] = ascii (fp) : "./hrtfs/elev85/L85e190a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e190a.dat right -[ 35, 35 ] = ascii (fp) : "./hrtfs/elev85/L85e185a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e185a.dat right -[ 35, 36 ] = ascii (fp) : "./hrtfs/elev85/L85e180a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e180a.dat right -[ 35, 37 ] = ascii (fp) : "./hrtfs/elev85/L85e175a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e175a.dat right -[ 35, 38 ] = ascii (fp) : "./hrtfs/elev85/L85e170a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e170a.dat right -[ 35, 39 ] = ascii (fp) : "./hrtfs/elev85/L85e165a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e165a.dat right -[ 35, 40 ] = ascii (fp) : "./hrtfs/elev85/L85e160a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e160a.dat right -[ 35, 41 ] = ascii (fp) : "./hrtfs/elev85/L85e155a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e155a.dat right -[ 35, 42 ] = ascii (fp) : "./hrtfs/elev85/L85e150a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e150a.dat right -[ 35, 43 ] = ascii (fp) : "./hrtfs/elev85/L85e145a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e145a.dat right -[ 35, 44 ] = ascii (fp) : "./hrtfs/elev85/L85e140a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e140a.dat right -[ 35, 45 ] = ascii (fp) : "./hrtfs/elev85/L85e135a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e135a.dat right -[ 35, 46 ] = ascii (fp) : "./hrtfs/elev85/L85e130a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e130a.dat right -[ 35, 47 ] = ascii (fp) : "./hrtfs/elev85/L85e125a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e125a.dat right -[ 35, 48 ] = ascii (fp) : "./hrtfs/elev85/L85e120a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e120a.dat right -[ 35, 49 ] = ascii (fp) : "./hrtfs/elev85/L85e115a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e115a.dat right -[ 35, 50 ] = ascii (fp) : "./hrtfs/elev85/L85e110a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e110a.dat right -[ 35, 51 ] = ascii (fp) : "./hrtfs/elev85/L85e105a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e105a.dat right -[ 35, 52 ] = ascii (fp) : "./hrtfs/elev85/L85e100a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e100a.dat right -[ 35, 53 ] = ascii (fp) : "./hrtfs/elev85/L85e095a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e095a.dat right -[ 35, 54 ] = ascii (fp) : "./hrtfs/elev85/L85e090a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e090a.dat right -[ 35, 55 ] = ascii (fp) : "./hrtfs/elev85/L85e085a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e085a.dat right -[ 35, 56 ] = ascii (fp) : "./hrtfs/elev85/L85e080a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e080a.dat right -[ 35, 57 ] = ascii (fp) : "./hrtfs/elev85/L85e075a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e075a.dat right -[ 35, 58 ] = ascii (fp) : "./hrtfs/elev85/L85e070a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e070a.dat right -[ 35, 59 ] = ascii (fp) : "./hrtfs/elev85/L85e065a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e065a.dat right -[ 35, 60 ] = ascii (fp) : "./hrtfs/elev85/L85e060a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e060a.dat right -[ 35, 61 ] = ascii (fp) : "./hrtfs/elev85/L85e055a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e055a.dat right -[ 35, 62 ] = ascii (fp) : "./hrtfs/elev85/L85e050a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e050a.dat right -[ 35, 63 ] = ascii (fp) : "./hrtfs/elev85/L85e045a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e045a.dat right -[ 35, 64 ] = ascii (fp) : "./hrtfs/elev85/L85e040a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e040a.dat right -[ 35, 65 ] = ascii (fp) : "./hrtfs/elev85/L85e035a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e035a.dat right -[ 35, 66 ] = ascii (fp) : "./hrtfs/elev85/L85e030a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e030a.dat right -[ 35, 67 ] = ascii (fp) : "./hrtfs/elev85/L85e025a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e025a.dat right -[ 35, 68 ] = ascii (fp) : "./hrtfs/elev85/L85e020a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e020a.dat right -[ 35, 69 ] = ascii (fp) : "./hrtfs/elev85/L85e015a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e015a.dat right -[ 35, 70 ] = ascii (fp) : "./hrtfs/elev85/L85e010a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e010a.dat right -[ 35, 71 ] = ascii (fp) : "./hrtfs/elev85/L85e005a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e005a.dat right +[ 35, 0 ] = ascii (fp) : "./hrtfs/elev85/L85e000a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e000a.dat" right +[ 35, 1 ] = ascii (fp) : "./hrtfs/elev85/L85e355a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e355a.dat" right +[ 35, 2 ] = ascii (fp) : "./hrtfs/elev85/L85e350a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e350a.dat" right +[ 35, 3 ] = ascii (fp) : "./hrtfs/elev85/L85e345a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e345a.dat" right +[ 35, 4 ] = ascii (fp) : "./hrtfs/elev85/L85e340a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e340a.dat" right +[ 35, 5 ] = ascii (fp) : "./hrtfs/elev85/L85e335a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e335a.dat" right +[ 35, 6 ] = ascii (fp) : "./hrtfs/elev85/L85e330a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e330a.dat" right +[ 35, 7 ] = ascii (fp) : "./hrtfs/elev85/L85e325a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e325a.dat" right +[ 35, 8 ] = ascii (fp) : "./hrtfs/elev85/L85e320a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e320a.dat" right +[ 35, 9 ] = ascii (fp) : "./hrtfs/elev85/L85e315a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e315a.dat" right +[ 35, 10 ] = ascii (fp) : "./hrtfs/elev85/L85e310a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e310a.dat" right +[ 35, 11 ] = ascii (fp) : "./hrtfs/elev85/L85e305a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e305a.dat" right +[ 35, 12 ] = ascii (fp) : "./hrtfs/elev85/L85e300a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e300a.dat" right +[ 35, 13 ] = ascii (fp) : "./hrtfs/elev85/L85e295a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e295a.dat" right +[ 35, 14 ] = ascii (fp) : "./hrtfs/elev85/L85e290a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e290a.dat" right +[ 35, 15 ] = ascii (fp) : "./hrtfs/elev85/L85e285a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e285a.dat" right +[ 35, 16 ] = ascii (fp) : "./hrtfs/elev85/L85e280a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e280a.dat" right +[ 35, 17 ] = ascii (fp) : "./hrtfs/elev85/L85e275a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e275a.dat" right +[ 35, 18 ] = ascii (fp) : "./hrtfs/elev85/L85e270a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e270a.dat" right +[ 35, 19 ] = ascii (fp) : "./hrtfs/elev85/L85e265a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e265a.dat" right +[ 35, 20 ] = ascii (fp) : "./hrtfs/elev85/L85e260a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e260a.dat" right +[ 35, 21 ] = ascii (fp) : "./hrtfs/elev85/L85e255a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e255a.dat" right +[ 35, 22 ] = ascii (fp) : "./hrtfs/elev85/L85e250a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e250a.dat" right +[ 35, 23 ] = ascii (fp) : "./hrtfs/elev85/L85e245a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e245a.dat" right +[ 35, 24 ] = ascii (fp) : "./hrtfs/elev85/L85e240a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e240a.dat" right +[ 35, 25 ] = ascii (fp) : "./hrtfs/elev85/L85e235a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e235a.dat" right +[ 35, 26 ] = ascii (fp) : "./hrtfs/elev85/L85e230a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e230a.dat" right +[ 35, 27 ] = ascii (fp) : "./hrtfs/elev85/L85e225a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e225a.dat" right +[ 35, 28 ] = ascii (fp) : "./hrtfs/elev85/L85e220a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e220a.dat" right +[ 35, 29 ] = ascii (fp) : "./hrtfs/elev85/L85e215a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e215a.dat" right +[ 35, 30 ] = ascii (fp) : "./hrtfs/elev85/L85e210a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e210a.dat" right +[ 35, 31 ] = ascii (fp) : "./hrtfs/elev85/L85e205a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e205a.dat" right +[ 35, 32 ] = ascii (fp) : "./hrtfs/elev85/L85e200a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e200a.dat" right +[ 35, 33 ] = ascii (fp) : "./hrtfs/elev85/L85e195a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e195a.dat" right +[ 35, 34 ] = ascii (fp) : "./hrtfs/elev85/L85e190a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e190a.dat" right +[ 35, 35 ] = ascii (fp) : "./hrtfs/elev85/L85e185a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e185a.dat" right +[ 35, 36 ] = ascii (fp) : "./hrtfs/elev85/L85e180a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e180a.dat" right +[ 35, 37 ] = ascii (fp) : "./hrtfs/elev85/L85e175a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e175a.dat" right +[ 35, 38 ] = ascii (fp) : "./hrtfs/elev85/L85e170a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e170a.dat" right +[ 35, 39 ] = ascii (fp) : "./hrtfs/elev85/L85e165a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e165a.dat" right +[ 35, 40 ] = ascii (fp) : "./hrtfs/elev85/L85e160a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e160a.dat" right +[ 35, 41 ] = ascii (fp) : "./hrtfs/elev85/L85e155a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e155a.dat" right +[ 35, 42 ] = ascii (fp) : "./hrtfs/elev85/L85e150a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e150a.dat" right +[ 35, 43 ] = ascii (fp) : "./hrtfs/elev85/L85e145a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e145a.dat" right +[ 35, 44 ] = ascii (fp) : "./hrtfs/elev85/L85e140a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e140a.dat" right +[ 35, 45 ] = ascii (fp) : "./hrtfs/elev85/L85e135a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e135a.dat" right +[ 35, 46 ] = ascii (fp) : "./hrtfs/elev85/L85e130a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e130a.dat" right +[ 35, 47 ] = ascii (fp) : "./hrtfs/elev85/L85e125a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e125a.dat" right +[ 35, 48 ] = ascii (fp) : "./hrtfs/elev85/L85e120a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e120a.dat" right +[ 35, 49 ] = ascii (fp) : "./hrtfs/elev85/L85e115a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e115a.dat" right +[ 35, 50 ] = ascii (fp) : "./hrtfs/elev85/L85e110a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e110a.dat" right +[ 35, 51 ] = ascii (fp) : "./hrtfs/elev85/L85e105a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e105a.dat" right +[ 35, 52 ] = ascii (fp) : "./hrtfs/elev85/L85e100a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e100a.dat" right +[ 35, 53 ] = ascii (fp) : "./hrtfs/elev85/L85e095a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e095a.dat" right +[ 35, 54 ] = ascii (fp) : "./hrtfs/elev85/L85e090a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e090a.dat" right +[ 35, 55 ] = ascii (fp) : "./hrtfs/elev85/L85e085a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e085a.dat" right +[ 35, 56 ] = ascii (fp) : "./hrtfs/elev85/L85e080a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e080a.dat" right +[ 35, 57 ] = ascii (fp) : "./hrtfs/elev85/L85e075a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e075a.dat" right +[ 35, 58 ] = ascii (fp) : "./hrtfs/elev85/L85e070a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e070a.dat" right +[ 35, 59 ] = ascii (fp) : "./hrtfs/elev85/L85e065a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e065a.dat" right +[ 35, 60 ] = ascii (fp) : "./hrtfs/elev85/L85e060a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e060a.dat" right +[ 35, 61 ] = ascii (fp) : "./hrtfs/elev85/L85e055a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e055a.dat" right +[ 35, 62 ] = ascii (fp) : "./hrtfs/elev85/L85e050a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e050a.dat" right +[ 35, 63 ] = ascii (fp) : "./hrtfs/elev85/L85e045a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e045a.dat" right +[ 35, 64 ] = ascii (fp) : "./hrtfs/elev85/L85e040a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e040a.dat" right +[ 35, 65 ] = ascii (fp) : "./hrtfs/elev85/L85e035a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e035a.dat" right +[ 35, 66 ] = ascii (fp) : "./hrtfs/elev85/L85e030a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e030a.dat" right +[ 35, 67 ] = ascii (fp) : "./hrtfs/elev85/L85e025a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e025a.dat" right +[ 35, 68 ] = ascii (fp) : "./hrtfs/elev85/L85e020a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e020a.dat" right +[ 35, 69 ] = ascii (fp) : "./hrtfs/elev85/L85e015a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e015a.dat" right +[ 35, 70 ] = ascii (fp) : "./hrtfs/elev85/L85e010a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e010a.dat" right +[ 35, 71 ] = ascii (fp) : "./hrtfs/elev85/L85e005a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e005a.dat" right -[ 36, 0 ] = ascii (fp) : "./hrtfs/elev90/L90e000a.dat left - + ascii (fp) : "./hrtfs/elev90/R90e000a.dat right +[ 36, 0 ] = ascii (fp) : "./hrtfs/elev90/L90e000a.dat" left + + ascii (fp) : "./hrtfs/elev90/R90e000a.dat" right diff --git a/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt b/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt index c6a460759..cb896382c 100644 --- a/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt +++ b/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt @@ -12,9 +12,10 @@ if(Qt5Widgets_FOUND) verstr.cpp verstr.h ${UIS} ${RSCS} ${TRS} ${MOCS}) - target_link_libraries(alsoft-config Qt5::Widgets) + target_link_libraries(alsoft-config PUBLIC Qt5::Widgets PRIVATE alcommon) target_include_directories(alsoft-config PRIVATE "${alsoft-config_BINARY_DIR}" "${OpenAL_BINARY_DIR}") + target_compile_definitions(alsoft-config PRIVATE QT_NO_KEYWORDS) set_target_properties(alsoft-config PROPERTIES ${DEFAULT_TARGET_PROPS} RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR}) if(TARGET build_version) diff --git a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp index bee7022f3..672c3d87d 100644 --- a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp +++ b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp @@ -3,8 +3,9 @@ #include "mainwindow.h" -#include +#include #include +#include #include #include @@ -19,148 +20,149 @@ #include #endif +#include "almalloc.h" +#include "alspan.h" + namespace { -const struct { +struct BackendNamePair { + /* NOLINTBEGIN(*-avoid-c-arrays) */ char backend_name[16]; char full_string[32]; -} backendList[] = { -#ifdef HAVE_JACK - { "jack", "JACK" }, -#endif + /* NOLINTEND(*-avoid-c-arrays) */ +}; +constexpr std::array backendList{ #ifdef HAVE_PIPEWIRE - { "pipewire", "PipeWire" }, + BackendNamePair{ "pipewire", "PipeWire" }, #endif #ifdef HAVE_PULSEAUDIO - { "pulse", "PulseAudio" }, + BackendNamePair{ "pulse", "PulseAudio" }, #endif #ifdef HAVE_ALSA - { "alsa", "ALSA" }, + BackendNamePair{ "alsa", "ALSA" }, +#endif +#ifdef HAVE_JACK + BackendNamePair{ "jack", "JACK" }, #endif #ifdef HAVE_COREAUDIO - { "core", "CoreAudio" }, + BackendNamePair{ "core", "CoreAudio" }, #endif #ifdef HAVE_OSS - { "oss", "OSS" }, + BackendNamePair{ "oss", "OSS" }, #endif #ifdef HAVE_SOLARIS - { "solaris", "Solaris" }, + BackendNamePair{ "solaris", "Solaris" }, #endif #ifdef HAVE_SNDIO - { "sndio", "SoundIO" }, -#endif -#ifdef HAVE_QSA - { "qsa", "QSA" }, + BackendNamePair{ "sndio", "SndIO" }, #endif #ifdef HAVE_WASAPI - { "wasapi", "WASAPI" }, + BackendNamePair{ "wasapi", "WASAPI" }, #endif #ifdef HAVE_DSOUND - { "dsound", "DirectSound" }, + BackendNamePair{ "dsound", "DirectSound" }, #endif #ifdef HAVE_WINMM - { "winmm", "Windows Multimedia" }, + BackendNamePair{ "winmm", "Windows Multimedia" }, #endif #ifdef HAVE_PORTAUDIO - { "port", "PortAudio" }, + BackendNamePair{ "port", "PortAudio" }, #endif #ifdef HAVE_OPENSL - { "opensl", "OpenSL" }, + BackendNamePair{ "opensl", "OpenSL" }, #endif - { "null", "Null Output" }, + BackendNamePair{ "null", "Null Output" }, #ifdef HAVE_WAVE - { "wave", "Wave Writer" }, + BackendNamePair{ "wave", "Wave Writer" }, #endif - { "", "" } }; -const struct NameValuePair { +struct NameValuePair { + /* NOLINTBEGIN(*-avoid-c-arrays) */ const char name[64]; const char value[16]; -} speakerModeList[] = { - { "Autodetect", "" }, - { "Mono", "mono" }, - { "Stereo", "stereo" }, - { "Quadraphonic", "quad" }, - { "5.1 Surround", "surround51" }, - { "6.1 Surround", "surround61" }, - { "7.1 Surround", "surround71" }, - { "3D7.1 Surround", "surround3d71" }, + /* NOLINTEND(*-avoid-c-arrays) */ +}; +constexpr std::array speakerModeList{ + NameValuePair{ "Autodetect", "" }, + NameValuePair{ "Mono", "mono" }, + NameValuePair{ "Stereo", "stereo" }, + NameValuePair{ "Quadraphonic", "quad" }, + NameValuePair{ "5.1 Surround", "surround51" }, + NameValuePair{ "6.1 Surround", "surround61" }, + NameValuePair{ "7.1 Surround", "surround71" }, + NameValuePair{ "3D7.1 Surround", "surround3d71" }, - { "Ambisonic, 1st Order", "ambi1" }, - { "Ambisonic, 2nd Order", "ambi2" }, - { "Ambisonic, 3rd Order", "ambi3" }, - - { "", "" } -}, sampleTypeList[] = { - { "Autodetect", "" }, - { "8-bit int", "int8" }, - { "8-bit uint", "uint8" }, - { "16-bit int", "int16" }, - { "16-bit uint", "uint16" }, - { "32-bit int", "int32" }, - { "32-bit uint", "uint32" }, - { "32-bit float", "float32" }, - - { "", "" } -}, resamplerList[] = { - { "Point", "point" }, - { "Linear", "linear" }, - { "Cubic Spline", "cubic" }, - { "Default (Cubic Spline)", "" }, - { "11th order Sinc (fast)", "fast_bsinc12" }, - { "11th order Sinc", "bsinc12" }, - { "23rd order Sinc (fast)", "fast_bsinc24" }, - { "23rd order Sinc", "bsinc24" }, - - { "", "" } -}, stereoModeList[] = { - { "Autodetect", "" }, - { "Speakers", "speakers" }, - { "Headphones", "headphones" }, - - { "", "" } -}, stereoEncList[] = { - { "Default", "" }, - { "Basic", "panpot" }, - { "UHJ", "uhj" }, - { "Binaural", "hrtf" }, - - { "", "" } -}, ambiFormatList[] = { - { "Default", "" }, - { "AmbiX (ACN, SN3D)", "ambix" }, - { "Furse-Malham", "fuma" }, - { "ACN, N3D", "acn+n3d" }, - { "ACN, FuMa", "acn+fuma" }, - - { "", "" } -}, hrtfModeList[] = { - { "1st Order Ambisonic", "ambi1" }, - { "2nd Order Ambisonic", "ambi2" }, - { "3rd Order Ambisonic", "ambi3" }, - { "Default (Full)", "" }, - { "Full", "full" }, - - { "", "" } + NameValuePair{ "Ambisonic, 1st Order", "ambi1" }, + NameValuePair{ "Ambisonic, 2nd Order", "ambi2" }, + NameValuePair{ "Ambisonic, 3rd Order", "ambi3" }, +}; +constexpr std::array sampleTypeList{ + NameValuePair{ "Autodetect", "" }, + NameValuePair{ "8-bit int", "int8" }, + NameValuePair{ "8-bit uint", "uint8" }, + NameValuePair{ "16-bit int", "int16" }, + NameValuePair{ "16-bit uint", "uint16" }, + NameValuePair{ "32-bit int", "int32" }, + NameValuePair{ "32-bit uint", "uint32" }, + NameValuePair{ "32-bit float", "float32" }, +}; +constexpr std::array resamplerList{ + NameValuePair{ "Point", "point" }, + NameValuePair{ "Linear", "linear" }, + NameValuePair{ "Cubic Spline", "spline" }, + NameValuePair{ "4-point Gaussian", "gaussian" }, + NameValuePair{ "Default (4-point Gaussian)", "" }, + NameValuePair{ "11th order Sinc (fast)", "fast_bsinc12" }, + NameValuePair{ "11th order Sinc", "bsinc12" }, + NameValuePair{ "23rd order Sinc (fast)", "fast_bsinc24" }, + NameValuePair{ "23rd order Sinc", "bsinc24" }, +}; +constexpr std::array stereoModeList{ + NameValuePair{ "Autodetect", "" }, + NameValuePair{ "Speakers", "speakers" }, + NameValuePair{ "Headphones", "headphones" }, +}; +constexpr std::array stereoEncList{ + NameValuePair{ "Default", "" }, + NameValuePair{ "Basic", "panpot" }, + NameValuePair{ "UHJ", "uhj" }, + NameValuePair{ "Binaural", "hrtf" }, +}; +constexpr std::array ambiFormatList{ + NameValuePair{ "Default", "" }, + NameValuePair{ "AmbiX (ACN, SN3D)", "ambix" }, + NameValuePair{ "Furse-Malham", "fuma" }, + NameValuePair{ "ACN, N3D", "acn+n3d" }, + NameValuePair{ "ACN, FuMa", "acn+fuma" }, +}; +constexpr std::array hrtfModeList{ + NameValuePair{ "1st Order Ambisonic", "ambi1" }, + NameValuePair{ "2nd Order Ambisonic", "ambi2" }, + NameValuePair{ "3rd Order Ambisonic", "ambi3" }, + NameValuePair{ "Default (Full)", "" }, + NameValuePair{ "Full", "full" }, }; QString getDefaultConfigName() { #ifdef Q_OS_WIN32 - static const char fname[] = "alsoft.ini"; + const char *fname{"alsoft.ini"}; 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 ret; + WCHAR *buffer{}; + if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, + nullptr, &buffer)}; SUCCEEDED(hr)) + ret = QString::fromWCharArray(buffer); + CoTaskMemFree(buffer); + return ret; }; QString base = get_appdata_path(); #else - static const char fname[] = "alsoft.conf"; - QByteArray base = qgetenv("XDG_CONFIG_HOME"); + const char *fname{"alsoft.conf"}; + QString base = qgetenv("XDG_CONFIG_HOME"); if(base.isEmpty()) { base = qgetenv("HOME"); @@ -178,14 +180,17 @@ QString getBaseDataPath() #ifdef Q_OS_WIN32 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 ret; + WCHAR *buffer{}; + if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, + nullptr, &buffer)}; SUCCEEDED(hr)) + ret = QString::fromWCharArray(buffer); + CoTaskMemFree(buffer); + return ret; }; QString base = get_appdata_path(); #else - QByteArray base = qgetenv("XDG_DATA_HOME"); + QString base = qgetenv("XDG_DATA_HOME"); if(base.isEmpty()) { base = qgetenv("HOME"); @@ -226,24 +231,22 @@ QStringList getAllDataPaths(const QString &append) return list; } -template -QString getValueFromName(const NameValuePair (&list)[N], const QString &str) +QString getValueFromName(const al::span list, const QString &str) { - for(size_t i = 0;i < N-1;i++) + for(size_t i{0};i < list.size();++i) { - if(str == list[i].name) - return list[i].value; + if(str == std::data(list[i].name)) + return std::data(list[i].value); } return QString{}; } -template -QString getNameFromValue(const NameValuePair (&list)[N], const QString &str) +QString getNameFromValue(const al::span list, const QString &str) { - for(size_t i = 0;i < N-1;i++) + for(size_t i{0};i < list.size();++i) { - if(str == list[i].value) - return list[i].name; + if(str == std::data(list[i].value)) + return std::data(list[i].name); } return QString{}; } @@ -262,52 +265,37 @@ QString getCheckValue(const QCheckBox *checkbox) { const Qt::CheckState state{checkbox->checkState()}; if(state == Qt::Checked) - return QString{"true"}; + return QStringLiteral("true"); if(state == Qt::Unchecked) - return QString{"false"}; + return QStringLiteral("false"); return QString{}; } } -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow), - mPeriodSizeValidator(nullptr), - mPeriodCountValidator(nullptr), - mSourceCountValidator(nullptr), - mEffectSlotValidator(nullptr), - mSourceSendValidator(nullptr), - mSampleRateValidator(nullptr), - mJackBufferValidator(nullptr), - mNeedsSave(false) +MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} + , ui{std::make_unique()} { ui->setupUi(this); - for(int i = 0;speakerModeList[i].name[0];i++) - ui->channelConfigCombo->addItem(speakerModeList[i].name); + for(auto &item : speakerModeList) + ui->channelConfigCombo->addItem(std::data(item.name)); ui->channelConfigCombo->adjustSize(); - for(int i = 0;sampleTypeList[i].name[0];i++) - ui->sampleFormatCombo->addItem(sampleTypeList[i].name); + for(auto &item : sampleTypeList) + ui->sampleFormatCombo->addItem(std::data(item.name)); ui->sampleFormatCombo->adjustSize(); - for(int i = 0;stereoModeList[i].name[0];i++) - ui->stereoModeCombo->addItem(stereoModeList[i].name); + for(auto &item : stereoModeList) + ui->stereoModeCombo->addItem(std::data(item.name)); ui->stereoModeCombo->adjustSize(); - for(int i = 0;stereoEncList[i].name[0];i++) - ui->stereoEncodingComboBox->addItem(stereoEncList[i].name); + for(auto &item : stereoEncList) + ui->stereoEncodingComboBox->addItem(std::data(item.name)); ui->stereoEncodingComboBox->adjustSize(); - for(int i = 0;ambiFormatList[i].name[0];i++) - ui->ambiFormatComboBox->addItem(ambiFormatList[i].name); + for(auto &item : ambiFormatList) + ui->ambiFormatComboBox->addItem(std::data(item.name)); ui->ambiFormatComboBox->adjustSize(); - int count; - for(count = 0;resamplerList[count].name[0];count++) { - } - ui->resamplerSlider->setRange(0, count-1); - - for(count = 0;hrtfModeList[count].name[0];count++) { - } - ui->hrtfmodeSlider->setRange(0, count-1); + ui->resamplerSlider->setRange(0, resamplerList.size()-1); + ui->hrtfmodeSlider->setRange(0, hrtfModeList.size()-1); #if !defined(HAVE_NEON) && !defined(HAVE_SSE) ui->cpuExtDisabledLabel->move(ui->cpuExtDisabledLabel->x(), ui->cpuExtDisabledLabel->y() - 60); @@ -355,22 +343,22 @@ MainWindow::MainWindow(QWidget *parent) : ui->enableEaxCheck->setVisible(false); #endif - mPeriodSizeValidator = new QIntValidator{64, 8192, this}; - ui->periodSizeEdit->setValidator(mPeriodSizeValidator); - mPeriodCountValidator = new QIntValidator{2, 16, this}; - ui->periodCountEdit->setValidator(mPeriodCountValidator); + mPeriodSizeValidator = std::make_unique(64, 8192, this); + ui->periodSizeEdit->setValidator(mPeriodSizeValidator.get()); + mPeriodCountValidator = std::make_unique(2, 16, this); + ui->periodCountEdit->setValidator(mPeriodCountValidator.get()); - mSourceCountValidator = new QIntValidator{0, 4096, this}; - ui->srcCountLineEdit->setValidator(mSourceCountValidator); - mEffectSlotValidator = new QIntValidator{0, 64, this}; - ui->effectSlotLineEdit->setValidator(mEffectSlotValidator); - mSourceSendValidator = new QIntValidator{0, 16, this}; - ui->srcSendLineEdit->setValidator(mSourceSendValidator); - mSampleRateValidator = new QIntValidator{8000, 192000, this}; - ui->sampleRateCombo->lineEdit()->setValidator(mSampleRateValidator); + mSourceCountValidator = std::make_unique(0, 4096, this); + ui->srcCountLineEdit->setValidator(mSourceCountValidator.get()); + mEffectSlotValidator = std::make_unique(0, 64, this); + ui->effectSlotLineEdit->setValidator(mEffectSlotValidator.get()); + mSourceSendValidator = std::make_unique(0, 16, this); + ui->srcSendLineEdit->setValidator(mSourceSendValidator.get()); + mSampleRateValidator = std::make_unique(8000, 192000, this); + ui->sampleRateCombo->lineEdit()->setValidator(mSampleRateValidator.get()); - mJackBufferValidator = new QIntValidator{0, 8192, this}; - ui->jackBufferSizeLine->setValidator(mJackBufferValidator); + mJackBufferValidator = std::make_unique(0, 8192, this); + ui->jackBufferSizeLine->setValidator(mJackBufferValidator.get()); connect(ui->actionLoad, &QAction::triggered, this, &MainWindow::loadConfigFromFile); connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::saveConfigAsFile); @@ -495,28 +483,18 @@ MainWindow::MainWindow(QWidget *parent) : for(int i = 1;i < ui->backendListWidget->count();i++) ui->backendListWidget->setRowHidden(i, true); - for(int i = 0;backendList[i].backend_name[0];i++) + for(size_t i{0};i < backendList.size();++i) { QList items = ui->backendListWidget->findItems( - backendList[i].full_string, Qt::MatchFixedString); - foreach(QListWidgetItem *item, items) + std::data(backendList[i].full_string), Qt::MatchFixedString); + Q_FOREACH(QListWidgetItem *item, items) item->setHidden(false); } loadConfig(getDefaultConfigName()); } -MainWindow::~MainWindow() -{ - delete ui; - delete mPeriodSizeValidator; - delete mPeriodCountValidator; - delete mSourceCountValidator; - delete mEffectSlotValidator; - delete mSourceSendValidator; - delete mSampleRateValidator; - delete mJackBufferValidator; -} +MainWindow::~MainWindow() = default; void MainWindow::closeEvent(QCloseEvent *event) { @@ -560,9 +538,9 @@ QStringList MainWindow::collectHrtfs() { QDir dir(ui->hrtfFileList->item(i)->text()); QStringList fnames = dir.entryList(QDir::Files | QDir::Readable, QDir::Name); - foreach(const QString &fname, fnames) + Q_FOREACH(const QString &fname, fnames) { - if(!fname.endsWith(".mhr", Qt::CaseInsensitive)) + if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive)) continue; QString fullname{dir.absoluteFilePath(fname)}; if(processed.contains(fullname)) @@ -583,21 +561,21 @@ QStringList MainWindow::collectHrtfs() break; } ++i; - } while(1); + } while(true); } } } if(ui->defaultHrtfPathsCheckBox->isChecked()) { - QStringList paths = getAllDataPaths("/openal/hrtf"); - foreach(const QString &name, paths) + QStringList paths = getAllDataPaths(QStringLiteral("/openal/hrtf")); + Q_FOREACH(const QString &name, paths) { QDir dir{name}; QStringList fnames{dir.entryList(QDir::Files | QDir::Readable, QDir::Name)}; - foreach(const QString &fname, fnames) + Q_FOREACH(const QString &fname, fnames) { - if(!fname.endsWith(".mhr", Qt::CaseInsensitive)) + if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive)) continue; QString fullname{dir.absoluteFilePath(fname)}; if(processed.contains(fullname)) @@ -618,13 +596,13 @@ QStringList MainWindow::collectHrtfs() break; } ++i; - } while(1); + } while(true); } } } #ifdef ALSOFT_EMBED_HRTF_DATA - ret.push_back("Built-In HRTF"); + ret.push_back(QStringLiteral("Built-In HRTF")); #endif } return ret; @@ -642,7 +620,7 @@ void MainWindow::loadConfig(const QString &fname) { QSettings settings{fname, QSettings::IniFormat}; - QString sampletype = settings.value("sample-type").toString(); + QString sampletype{settings.value(QStringLiteral("sample-type")).toString()}; ui->sampleFormatCombo->setCurrentIndex(0); if(sampletype.isEmpty() == false) { @@ -654,12 +632,12 @@ void MainWindow::loadConfig(const QString &fname) } } - QString channelconfig{settings.value("channels").toString()}; + QString channelconfig{settings.value(QStringLiteral("channels")).toString()}; ui->channelConfigCombo->setCurrentIndex(0); if(channelconfig.isEmpty() == false) { - if(channelconfig == "surround51rear") - channelconfig = "surround51"; + if(channelconfig == QStringLiteral("surround51rear")) + channelconfig = QStringLiteral("surround51"); QString str{getNameFromValue(speakerModeList, channelconfig)}; if(!str.isEmpty()) { @@ -668,7 +646,7 @@ void MainWindow::loadConfig(const QString &fname) } } - QString srate{settings.value("frequency").toString()}; + QString srate{settings.value(QStringLiteral("frequency")).toString()}; if(srate.isEmpty()) ui->sampleRateCombo->setCurrentIndex(0); else @@ -678,34 +656,35 @@ void MainWindow::loadConfig(const QString &fname) } ui->srcCountLineEdit->clear(); - ui->srcCountLineEdit->insert(settings.value("sources").toString()); + ui->srcCountLineEdit->insert(settings.value(QStringLiteral("sources")).toString()); ui->effectSlotLineEdit->clear(); - ui->effectSlotLineEdit->insert(settings.value("slots").toString()); + ui->effectSlotLineEdit->insert(settings.value(QStringLiteral("slots")).toString()); ui->srcSendLineEdit->clear(); - ui->srcSendLineEdit->insert(settings.value("sends").toString()); + ui->srcSendLineEdit->insert(settings.value(QStringLiteral("sends")).toString()); - QString resampler = settings.value("resampler").toString().trimmed(); + QString resampler = settings.value(QStringLiteral("resampler")).toString().trimmed(); ui->resamplerSlider->setValue(2); - ui->resamplerLabel->setText(resamplerList[2].name); - /* The "sinc4" and "sinc8" resamplers are no longer supported. Use "cubic" - * as a fallback. + ui->resamplerLabel->setText(std::data(resamplerList[2].name)); + /* "Cubic" is an alias for the 4-point gaussian resampler. The "sinc4" and + * "sinc8" resamplers are unsupported, use "gaussian" as a fallback. */ - if(resampler == "sinc4" || resampler == "sinc8") - resampler = "cubic"; + if(resampler == QLatin1String{"cubic"} || resampler == QLatin1String{"sinc4"} + || resampler == QLatin1String{"sinc8"}) + resampler = QStringLiteral("gaussian"); /* The "bsinc" resampler name is an alias for "bsinc12". */ - else if(resampler == "bsinc") - resampler = "bsinc12"; + else if(resampler == QLatin1String{"bsinc"}) + resampler = QStringLiteral("bsinc12"); for(int i = 0;resamplerList[i].name[0];i++) { - if(resampler == resamplerList[i].value) + if(resampler == std::data(resamplerList[i].value)) { ui->resamplerSlider->setValue(i); - ui->resamplerLabel->setText(resamplerList[i].name); + ui->resamplerLabel->setText(std::data(resamplerList[i].name)); break; } } - QString stereomode = settings.value("stereo-mode").toString().trimmed(); + QString stereomode{settings.value(QStringLiteral("stereo-mode")).toString().trimmed()}; ui->stereoModeCombo->setCurrentIndex(0); if(stereomode.isEmpty() == false) { @@ -733,10 +712,10 @@ void MainWindow::loadConfig(const QString &fname) updatePeriodCountSlider(); } - ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value("output-limiter"))); - ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value("dither"))); + ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("output-limiter")))); + ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("dither")))); - QString stereopan{settings.value("stereo-encoding").toString()}; + QString stereopan{settings.value(QStringLiteral("stereo-encoding")).toString()}; ui->stereoEncodingComboBox->setCurrentIndex(0); if(stereopan.isEmpty() == false) { @@ -748,7 +727,7 @@ void MainWindow::loadConfig(const QString &fname) } } - QString ambiformat{settings.value("ambi-format").toString()}; + QString ambiformat{settings.value(QStringLiteral("ambi-format")).toString()}; ui->ambiFormatComboBox->setCurrentIndex(0); if(ambiformat.isEmpty() == false) { @@ -760,46 +739,46 @@ void MainWindow::loadConfig(const QString &fname) } } - ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value("decoder/hq-mode"))); - ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value("decoder/distance-comp"))); - ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value("decoder/nfc"))); - double speakerdist{settings.value("decoder/speaker-dist", 1.0).toDouble()}; + ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value(QStringLiteral("decoder/hq-mode")))); + ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("decoder/distance-comp")))); + ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("decoder/nfc")))); + double speakerdist{settings.value(QStringLiteral("decoder/speaker-dist"), 1.0).toDouble()}; ui->decoderSpeakerDistSpinBox->setValue(speakerdist); - ui->decoderQuadLineEdit->setText(settings.value("decoder/quad").toString()); - ui->decoder51LineEdit->setText(settings.value("decoder/surround51").toString()); - ui->decoder61LineEdit->setText(settings.value("decoder/surround61").toString()); - ui->decoder71LineEdit->setText(settings.value("decoder/surround71").toString()); - ui->decoder3D71LineEdit->setText(settings.value("decoder/surround3d71").toString()); + ui->decoderQuadLineEdit->setText(settings.value(QStringLiteral("decoder/quad")).toString()); + ui->decoder51LineEdit->setText(settings.value(QStringLiteral("decoder/surround51")).toString()); + ui->decoder61LineEdit->setText(settings.value(QStringLiteral("decoder/surround61")).toString()); + ui->decoder71LineEdit->setText(settings.value(QStringLiteral("decoder/surround71")).toString()); + ui->decoder3D71LineEdit->setText(settings.value(QStringLiteral("decoder/surround3d71")).toString()); - QStringList disabledCpuExts{settings.value("disable-cpu-exts").toStringList()}; + QStringList disabledCpuExts{settings.value(QStringLiteral("disable-cpu-exts")).toStringList()}; if(disabledCpuExts.size() == 1) disabledCpuExts = disabledCpuExts[0].split(QChar(',')); 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)); + ui->enableSSECheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse"), Qt::CaseInsensitive)); + ui->enableSSE2CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse2"), Qt::CaseInsensitive)); + ui->enableSSE3CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse3"), Qt::CaseInsensitive)); + ui->enableSSE41CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse4.1"), Qt::CaseInsensitive)); + ui->enableNeonCheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("neon"), Qt::CaseInsensitive)); - QString hrtfmode{settings.value("hrtf-mode").toString().trimmed()}; + QString hrtfmode{settings.value(QStringLiteral("hrtf-mode")).toString().trimmed()}; ui->hrtfmodeSlider->setValue(2); - ui->hrtfmodeLabel->setText(hrtfModeList[3].name); + ui->hrtfmodeLabel->setText(std::data(hrtfModeList[3].name)); /* The "basic" mode name is no longer supported. Use "ambi2" instead. */ - if(hrtfmode == "basic") - hrtfmode = "ambi2"; - for(int i = 0;hrtfModeList[i].name[0];i++) + if(hrtfmode == QLatin1String{"basic"}) + hrtfmode = QStringLiteral("ambi2"); + for(size_t i{0};i < hrtfModeList.size();++i) { - if(hrtfmode == hrtfModeList[i].value) + if(hrtfmode == std::data(hrtfModeList[i].value)) { - ui->hrtfmodeSlider->setValue(i); - ui->hrtfmodeLabel->setText(hrtfModeList[i].name); + ui->hrtfmodeSlider->setValue(static_cast(i)); + ui->hrtfmodeLabel->setText(std::data(hrtfModeList[i].name)); break; } } - QStringList hrtf_paths{settings.value("hrtf-paths").toStringList()}; + QStringList hrtf_paths{settings.value(QStringLiteral("hrtf-paths")).toStringList()}; if(hrtf_paths.size() == 1) hrtf_paths = hrtf_paths[0].split(QChar(',')); for(QString &name : hrtf_paths) @@ -817,15 +796,15 @@ void MainWindow::loadConfig(const QString &fname) updateHrtfRemoveButton(); ui->preferredHrtfComboBox->clear(); - ui->preferredHrtfComboBox->addItem("- Any -"); + ui->preferredHrtfComboBox->addItem(QStringLiteral("- Any -")); if(ui->defaultHrtfPathsCheckBox->isChecked()) { QStringList hrtfs{collectHrtfs()}; - foreach(const QString &name, hrtfs) + Q_FOREACH(const QString &name, hrtfs) ui->preferredHrtfComboBox->addItem(name); } - QString defaulthrtf{settings.value("default-hrtf").toString()}; + QString defaulthrtf{settings.value(QStringLiteral("default-hrtf")).toString()}; ui->preferredHrtfComboBox->setCurrentIndex(0); if(defaulthrtf.isEmpty() == false) { @@ -843,7 +822,7 @@ void MainWindow::loadConfig(const QString &fname) ui->enabledBackendList->clear(); ui->disabledBackendList->clear(); - QStringList drivers{settings.value("drivers").toStringList()}; + QStringList drivers{settings.value(QStringLiteral("drivers")).toStringList()}; if(drivers.empty()) ui->backendCheckBox->setChecked(true); else @@ -856,35 +835,37 @@ void MainWindow::loadConfig(const QString &fname) /* Convert "mmdevapi" references to "wasapi" for backwards * compatibility. */ - if(name == "-mmdevapi") - name = "-wasapi"; - else if(name == "mmdevapi") - name = "wasapi"; + if(name == QLatin1String{"-mmdevapi"}) + name = QStringLiteral("-wasapi"); + else if(name == QLatin1String{"mmdevapi"}) + name = QStringLiteral("wasapi"); } - bool lastWasEmpty = false; - foreach(const QString &backend, drivers) + bool lastWasEmpty{false}; + Q_FOREACH(const QString &backend, drivers) { lastWasEmpty = backend.isEmpty(); if(lastWasEmpty) continue; if(!backend.startsWith(QChar('-'))) - for(int j = 0;backendList[j].backend_name[0];j++) + { + for(size_t j{0};j < backendList.size();++j) { - if(backend == backendList[j].backend_name) + if(backend == std::data(backendList[j].backend_name)) { - ui->enabledBackendList->addItem(backendList[j].full_string); + ui->enabledBackendList->addItem(std::data(backendList[j].full_string)); break; } } + } else if(backend.size() > 1) { QStringRef backendref{backend.rightRef(backend.size()-1)}; - for(int j = 0;backendList[j].backend_name[0];j++) + for(size_t j{0};j < backendList.size();++j) { - if(backendref == backendList[j].backend_name) + if(backendref == std::data(backendList[j].backend_name)) { - ui->disabledBackendList->addItem(backendList[j].full_string); + ui->disabledBackendList->addItem(std::data(backendList[j].full_string)); break; } } @@ -893,7 +874,7 @@ void MainWindow::loadConfig(const QString &fname) ui->backendCheckBox->setChecked(lastWasEmpty); } - QString defaultreverb{settings.value("default-reverb").toString().toLower()}; + QString defaultreverb{settings.value(QStringLiteral("default-reverb")).toString().toLower()}; ui->defaultReverbComboBox->setCurrentIndex(0); if(defaultreverb.isEmpty() == false) { @@ -907,56 +888,56 @@ void MainWindow::loadConfig(const QString &fname) } } - QStringList excludefx{settings.value("excludefx").toStringList()}; + QStringList excludefx{settings.value(QStringLiteral("excludefx")).toStringList()}; if(excludefx.size() == 1) excludefx = excludefx[0].split(QChar(',')); 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)); - ui->enableChorusCheck->setChecked(!excludefx.contains("chorus", Qt::CaseInsensitive)); - ui->enableCompressorCheck->setChecked(!excludefx.contains("compressor", Qt::CaseInsensitive)); - ui->enableDistortionCheck->setChecked(!excludefx.contains("distortion", Qt::CaseInsensitive)); - ui->enableEchoCheck->setChecked(!excludefx.contains("echo", Qt::CaseInsensitive)); - ui->enableEqualizerCheck->setChecked(!excludefx.contains("equalizer", Qt::CaseInsensitive)); - ui->enableFlangerCheck->setChecked(!excludefx.contains("flanger", Qt::CaseInsensitive)); - ui->enableFrequencyShifterCheck->setChecked(!excludefx.contains("fshifter", Qt::CaseInsensitive)); - 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->enableEaxReverbCheck->setChecked(!excludefx.contains(QStringLiteral("eaxreverb"), Qt::CaseInsensitive)); + ui->enableStdReverbCheck->setChecked(!excludefx.contains(QStringLiteral("reverb"), Qt::CaseInsensitive)); + ui->enableAutowahCheck->setChecked(!excludefx.contains(QStringLiteral("autowah"), Qt::CaseInsensitive)); + ui->enableChorusCheck->setChecked(!excludefx.contains(QStringLiteral("chorus"), Qt::CaseInsensitive)); + ui->enableCompressorCheck->setChecked(!excludefx.contains(QStringLiteral("compressor"), Qt::CaseInsensitive)); + ui->enableDistortionCheck->setChecked(!excludefx.contains(QStringLiteral("distortion"), Qt::CaseInsensitive)); + ui->enableEchoCheck->setChecked(!excludefx.contains(QStringLiteral("echo"), Qt::CaseInsensitive)); + ui->enableEqualizerCheck->setChecked(!excludefx.contains(QStringLiteral("equalizer"), Qt::CaseInsensitive)); + ui->enableFlangerCheck->setChecked(!excludefx.contains(QStringLiteral("flanger"), Qt::CaseInsensitive)); + ui->enableFrequencyShifterCheck->setChecked(!excludefx.contains(QStringLiteral("fshifter"), Qt::CaseInsensitive)); + ui->enableModulatorCheck->setChecked(!excludefx.contains(QStringLiteral("modulator"), Qt::CaseInsensitive)); + ui->enableDedicatedCheck->setChecked(!excludefx.contains(QStringLiteral("dedicated"), Qt::CaseInsensitive)); + ui->enablePitchShifterCheck->setChecked(!excludefx.contains(QStringLiteral("pshifter"), Qt::CaseInsensitive)); + ui->enableVocalMorpherCheck->setChecked(!excludefx.contains(QStringLiteral("vmorpher"), Qt::CaseInsensitive)); if(ui->enableEaxCheck->isEnabled()) - ui->enableEaxCheck->setChecked(getCheckState(settings.value("eax/enable")) != Qt::Unchecked); + ui->enableEaxCheck->setChecked(getCheckState(settings.value(QStringLiteral("eax/enable"))) != Qt::Unchecked); - 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->pulseAutospawnCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/spawn-server")))); + ui->pulseAllowMovesCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/allow-moves")))); + ui->pulseFixRateCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/fix-rate")))); + ui->pulseAdjLatencyCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/adjust-latency")))); - ui->pwireAssumeAudioCheckBox->setCheckState(getCheckState(settings.value("pipewire/assume-audio"))); - ui->pwireRtMixCheckBox->setCheckState(getCheckState(settings.value("pipewire/rt-mix"))); + ui->pwireAssumeAudioCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pipewire/assume-audio")))); + ui->pwireRtMixCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pipewire/rt-mix")))); - ui->wasapiResamplerCheckBox->setCheckState(getCheckState(settings.value("wasapi/allow-resampler"))); + ui->wasapiResamplerCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("wasapi/allow-resampler")))); - ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value("jack/spawn-server"))); - ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value("jack/connect-ports"))); - ui->jackRtMixCheckBox->setCheckState(getCheckState(settings.value("jack/rt-mix"))); - ui->jackBufferSizeLine->setText(settings.value("jack/buffer-size", QString()).toString()); + ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/spawn-server")))); + ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/connect-ports")))); + ui->jackRtMixCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/rt-mix")))); + ui->jackBufferSizeLine->setText(settings.value(QStringLiteral("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->setCheckState(getCheckState(settings.value("alsa/allow-resampler"))); - ui->alsaMmapCheckBox->setCheckState(getCheckState(settings.value("alsa/mmap"))); + ui->alsaDefaultDeviceLine->setText(settings.value(QStringLiteral("alsa/device"), QString()).toString()); + ui->alsaDefaultCaptureLine->setText(settings.value(QStringLiteral("alsa/capture"), QString()).toString()); + ui->alsaResamplerCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("alsa/allow-resampler")))); + ui->alsaMmapCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("alsa/mmap")))); - ui->ossDefaultDeviceLine->setText(settings.value("oss/device", QString()).toString()); - ui->ossDefaultCaptureLine->setText(settings.value("oss/capture", QString()).toString()); + ui->ossDefaultDeviceLine->setText(settings.value(QStringLiteral("oss/device"), QString()).toString()); + ui->ossDefaultCaptureLine->setText(settings.value(QStringLiteral("oss/capture"), QString()).toString()); - ui->solarisDefaultDeviceLine->setText(settings.value("solaris/device", QString()).toString()); + ui->solarisDefaultDeviceLine->setText(settings.value(QStringLiteral("solaris/device"), QString()).toString()); - ui->waveOutputLine->setText(settings.value("wave/file", QString()).toString()); - ui->waveBFormatCheckBox->setChecked(settings.value("wave/bformat", false).toBool()); + ui->waveOutputLine->setText(settings.value(QStringLiteral("wave/file"), QString()).toString()); + ui->waveBFormatCheckBox->setChecked(settings.value(QStringLiteral("wave/bformat"), false).toBool()); ui->applyButton->setEnabled(false); ui->closeCancelButton->setText(tr("Close")); @@ -990,72 +971,72 @@ void MainWindow::saveConfig(const QString &fname) const /* HACK: Compound any stringlist values into a comma-separated string. */ QStringList allkeys{settings.allKeys()}; - foreach(const QString &key, allkeys) + Q_FOREACH(const QString &key, allkeys) { QStringList vals{settings.value(key).toStringList()}; if(vals.size() > 1) settings.setValue(key, vals.join(QChar(','))); } - settings.setValue("sample-type", getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText())); - settings.setValue("channels", getValueFromName(speakerModeList, ui->channelConfigCombo->currentText())); + settings.setValue(QStringLiteral("sample-type"), getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText())); + settings.setValue(QStringLiteral("channels"), getValueFromName(speakerModeList, ui->channelConfigCombo->currentText())); uint rate{ui->sampleRateCombo->currentText().toUInt()}; if(rate <= 0) - settings.setValue("frequency", QString{}); + settings.setValue(QStringLiteral("frequency"), QString{}); else - settings.setValue("frequency", rate); + settings.setValue(QStringLiteral("frequency"), rate); - settings.setValue("period_size", ui->periodSizeEdit->text()); - settings.setValue("periods", ui->periodCountEdit->text()); + settings.setValue(QStringLiteral("period_size"), ui->periodSizeEdit->text()); + settings.setValue(QStringLiteral("periods"), ui->periodCountEdit->text()); - settings.setValue("sources", ui->srcCountLineEdit->text()); - settings.setValue("slots", ui->effectSlotLineEdit->text()); + settings.setValue(QStringLiteral("sources"), ui->srcCountLineEdit->text()); + settings.setValue(QStringLiteral("slots"), ui->effectSlotLineEdit->text()); - settings.setValue("resampler", resamplerList[ui->resamplerSlider->value()].value); + settings.setValue(QStringLiteral("resampler"), std::data(resamplerList[ui->resamplerSlider->value()].value)); - settings.setValue("stereo-mode", getValueFromName(stereoModeList, ui->stereoModeCombo->currentText())); - settings.setValue("stereo-encoding", getValueFromName(stereoEncList, ui->stereoEncodingComboBox->currentText())); - settings.setValue("ambi-format", getValueFromName(ambiFormatList, ui->ambiFormatComboBox->currentText())); + settings.setValue(QStringLiteral("stereo-mode"), getValueFromName(stereoModeList, ui->stereoModeCombo->currentText())); + settings.setValue(QStringLiteral("stereo-encoding"), getValueFromName(stereoEncList, ui->stereoEncodingComboBox->currentText())); + settings.setValue(QStringLiteral("ambi-format"), getValueFromName(ambiFormatList, ui->ambiFormatComboBox->currentText())); - settings.setValue("output-limiter", getCheckValue(ui->outputLimiterCheckBox)); - settings.setValue("dither", getCheckValue(ui->outputDitherCheckBox)); + settings.setValue(QStringLiteral("output-limiter"), getCheckValue(ui->outputLimiterCheckBox)); + settings.setValue(QStringLiteral("dither"), getCheckValue(ui->outputDitherCheckBox)); - settings.setValue("decoder/hq-mode", getCheckValue(ui->decoderHQModeCheckBox)); - settings.setValue("decoder/distance-comp", getCheckValue(ui->decoderDistCompCheckBox)); - settings.setValue("decoder/nfc", getCheckValue(ui->decoderNFEffectsCheckBox)); + settings.setValue(QStringLiteral("decoder/hq-mode"), getCheckValue(ui->decoderHQModeCheckBox)); + settings.setValue(QStringLiteral("decoder/distance-comp"), getCheckValue(ui->decoderDistCompCheckBox)); + settings.setValue(QStringLiteral("decoder/nfc"), getCheckValue(ui->decoderNFEffectsCheckBox)); double speakerdist{ui->decoderSpeakerDistSpinBox->value()}; - settings.setValue("decoder/speaker-dist", + settings.setValue(QStringLiteral("decoder/speaker-dist"), (speakerdist != 1.0) ? QString::number(speakerdist) : QString{} ); - settings.setValue("decoder/quad", ui->decoderQuadLineEdit->text()); - settings.setValue("decoder/surround51", ui->decoder51LineEdit->text()); - settings.setValue("decoder/surround61", ui->decoder61LineEdit->text()); - settings.setValue("decoder/surround71", ui->decoder71LineEdit->text()); - settings.setValue("decoder/surround3d71", ui->decoder3D71LineEdit->text()); + settings.setValue(QStringLiteral("decoder/quad"), ui->decoderQuadLineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround51"), ui->decoder51LineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround61"), ui->decoder61LineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround71"), ui->decoder71LineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround3d71"), ui->decoder3D71LineEdit->text()); QStringList strlist; if(!ui->enableSSECheckBox->isChecked()) - strlist.append("sse"); + strlist.append(QStringLiteral("sse")); if(!ui->enableSSE2CheckBox->isChecked()) - strlist.append("sse2"); + strlist.append(QStringLiteral("sse2")); if(!ui->enableSSE3CheckBox->isChecked()) - strlist.append("sse3"); + strlist.append(QStringLiteral("sse3")); if(!ui->enableSSE41CheckBox->isChecked()) - strlist.append("sse4.1"); + strlist.append(QStringLiteral("sse4.1")); if(!ui->enableNeonCheckBox->isChecked()) - strlist.append("neon"); - settings.setValue("disable-cpu-exts", strlist.join(QChar(','))); + strlist.append(QStringLiteral("neon")); + settings.setValue(QStringLiteral("disable-cpu-exts"), strlist.join(QChar(','))); - settings.setValue("hrtf-mode", hrtfModeList[ui->hrtfmodeSlider->value()].value); + settings.setValue(QStringLiteral("hrtf-mode"), std::data(hrtfModeList[ui->hrtfmodeSlider->value()].value)); if(ui->preferredHrtfComboBox->currentIndex() == 0) - settings.setValue("default-hrtf", QString{}); + settings.setValue(QStringLiteral("default-hrtf"), QString{}); else { QString str{ui->preferredHrtfComboBox->currentText()}; - settings.setValue("default-hrtf", str); + settings.setValue(QStringLiteral("default-hrtf"), str); } strlist.clear(); @@ -1064,17 +1045,17 @@ void MainWindow::saveConfig(const QString &fname) const strlist.append(ui->hrtfFileList->item(i)->text()); if(!strlist.empty() && ui->defaultHrtfPathsCheckBox->isChecked()) strlist.append(QString{}); - settings.setValue("hrtf-paths", strlist.join(QChar{','})); + settings.setValue(QStringLiteral("hrtf-paths"), strlist.join(QChar{','})); strlist.clear(); for(int i = 0;i < ui->enabledBackendList->count();i++) { QString label{ui->enabledBackendList->item(i)->text()}; - for(int j = 0;backendList[j].backend_name[0];j++) + for(size_t j{0};j < backendList.size();++j) { - if(label == backendList[j].full_string) + if(label == std::data(backendList[j].full_string)) { - strlist.append(backendList[j].backend_name); + strlist.append(std::data(backendList[j].backend_name)); break; } } @@ -1082,102 +1063,102 @@ void MainWindow::saveConfig(const QString &fname) const for(int i = 0;i < ui->disabledBackendList->count();i++) { QString label{ui->disabledBackendList->item(i)->text()}; - for(int j = 0;backendList[j].backend_name[0];j++) + for(size_t j{0};j < backendList.size();++j) { - if(label == backendList[j].full_string) + if(label == std::data(backendList[j].full_string)) { - strlist.append(QChar{'-'}+QString{backendList[j].backend_name}); + strlist.append(QChar{'-'}+QString{std::data(backendList[j].backend_name)}); break; } } } if(strlist.empty() && !ui->backendCheckBox->isChecked()) - strlist.append("-all"); + strlist.append(QStringLiteral("-all")); else if(ui->backendCheckBox->isChecked()) strlist.append(QString{}); - settings.setValue("drivers", strlist.join(QChar(','))); + settings.setValue(QStringLiteral("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(QStringLiteral("default-reverb"), QString{}); else { QString str{ui->defaultReverbComboBox->currentText().toLower()}; - settings.setValue("default-reverb", str); + settings.setValue(QStringLiteral("default-reverb"), str); } strlist.clear(); if(!ui->enableEaxReverbCheck->isChecked()) - strlist.append("eaxreverb"); + strlist.append(QStringLiteral("eaxreverb")); if(!ui->enableStdReverbCheck->isChecked()) - strlist.append("reverb"); + strlist.append(QStringLiteral("reverb")); if(!ui->enableAutowahCheck->isChecked()) - strlist.append("autowah"); + strlist.append(QStringLiteral("autowah")); if(!ui->enableChorusCheck->isChecked()) - strlist.append("chorus"); + strlist.append(QStringLiteral("chorus")); if(!ui->enableDistortionCheck->isChecked()) - strlist.append("distortion"); + strlist.append(QStringLiteral("distortion")); if(!ui->enableCompressorCheck->isChecked()) - strlist.append("compressor"); + strlist.append(QStringLiteral("compressor")); if(!ui->enableEchoCheck->isChecked()) - strlist.append("echo"); + strlist.append(QStringLiteral("echo")); if(!ui->enableEqualizerCheck->isChecked()) - strlist.append("equalizer"); + strlist.append(QStringLiteral("equalizer")); if(!ui->enableFlangerCheck->isChecked()) - strlist.append("flanger"); + strlist.append(QStringLiteral("flanger")); if(!ui->enableFrequencyShifterCheck->isChecked()) - strlist.append("fshifter"); + strlist.append(QStringLiteral("fshifter")); if(!ui->enableModulatorCheck->isChecked()) - strlist.append("modulator"); + strlist.append(QStringLiteral("modulator")); if(!ui->enableDedicatedCheck->isChecked()) - strlist.append("dedicated"); + strlist.append(QStringLiteral("dedicated")); if(!ui->enablePitchShifterCheck->isChecked()) - strlist.append("pshifter"); + strlist.append(QStringLiteral("pshifter")); if(!ui->enableVocalMorpherCheck->isChecked()) - strlist.append("vmorpher"); - settings.setValue("excludefx", strlist.join(QChar{','})); - settings.setValue("eax/enable", + strlist.append(QStringLiteral("vmorpher")); + settings.setValue(QStringLiteral("excludefx"), strlist.join(QChar{','})); + settings.setValue(QStringLiteral("eax/enable"), (!ui->enableEaxCheck->isEnabled() || ui->enableEaxCheck->isChecked()) - ? QString{/*"true"*/} : QString{"false"}); + ? QString{/*"true"*/} : QStringLiteral("false")); - settings.setValue("pipewire/assume-audio", getCheckValue(ui->pwireAssumeAudioCheckBox)); - settings.setValue("pipewire/rt-mix", getCheckValue(ui->pwireRtMixCheckBox)); + settings.setValue(QStringLiteral("pipewire/assume-audio"), getCheckValue(ui->pwireAssumeAudioCheckBox)); + settings.setValue(QStringLiteral("pipewire/rt-mix"), getCheckValue(ui->pwireRtMixCheckBox)); - settings.setValue("wasapi/allow-resampler", getCheckValue(ui->wasapiResamplerCheckBox)); + settings.setValue(QStringLiteral("wasapi/allow-resampler"), getCheckValue(ui->wasapiResamplerCheckBox)); - 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(QStringLiteral("pulse/spawn-server"), getCheckValue(ui->pulseAutospawnCheckBox)); + settings.setValue(QStringLiteral("pulse/allow-moves"), getCheckValue(ui->pulseAllowMovesCheckBox)); + settings.setValue(QStringLiteral("pulse/fix-rate"), getCheckValue(ui->pulseFixRateCheckBox)); + settings.setValue(QStringLiteral("pulse/adjust-latency"), getCheckValue(ui->pulseAdjLatencyCheckBox)); - settings.setValue("jack/spawn-server", getCheckValue(ui->jackAutospawnCheckBox)); - settings.setValue("jack/connect-ports", getCheckValue(ui->jackConnectPortsCheckBox)); - settings.setValue("jack/rt-mix", getCheckValue(ui->jackRtMixCheckBox)); - settings.setValue("jack/buffer-size", ui->jackBufferSizeLine->text()); + settings.setValue(QStringLiteral("jack/spawn-server"), getCheckValue(ui->jackAutospawnCheckBox)); + settings.setValue(QStringLiteral("jack/connect-ports"), getCheckValue(ui->jackConnectPortsCheckBox)); + settings.setValue(QStringLiteral("jack/rt-mix"), getCheckValue(ui->jackRtMixCheckBox)); + settings.setValue(QStringLiteral("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", getCheckValue(ui->alsaResamplerCheckBox)); - settings.setValue("alsa/mmap", getCheckValue(ui->alsaMmapCheckBox)); + settings.setValue(QStringLiteral("alsa/device"), ui->alsaDefaultDeviceLine->text()); + settings.setValue(QStringLiteral("alsa/capture"), ui->alsaDefaultCaptureLine->text()); + settings.setValue(QStringLiteral("alsa/allow-resampler"), getCheckValue(ui->alsaResamplerCheckBox)); + settings.setValue(QStringLiteral("alsa/mmap"), getCheckValue(ui->alsaMmapCheckBox)); - settings.setValue("oss/device", ui->ossDefaultDeviceLine->text()); - settings.setValue("oss/capture", ui->ossDefaultCaptureLine->text()); + settings.setValue(QStringLiteral("oss/device"), ui->ossDefaultDeviceLine->text()); + settings.setValue(QStringLiteral("oss/capture"), ui->ossDefaultCaptureLine->text()); - settings.setValue("solaris/device", ui->solarisDefaultDeviceLine->text()); + settings.setValue(QStringLiteral("solaris/device"), ui->solarisDefaultDeviceLine->text()); - settings.setValue("wave/file", ui->waveOutputLine->text()); - settings.setValue("wave/bformat", - ui->waveBFormatCheckBox->isChecked() ? QString{"true"} : QString{/*"false"*/} + settings.setValue(QStringLiteral("wave/file"), ui->waveOutputLine->text()); + settings.setValue(QStringLiteral("wave/bformat"), + ui->waveBFormatCheckBox->isChecked() ? QStringLiteral("true") : QString{/*"false"*/} ); /* Remove empty keys * FIXME: Should only remove keys whose value matches the globally-specified value. */ allkeys = settings.allKeys(); - foreach(const QString &key, allkeys) + Q_FOREACH(const QString &key, allkeys) { QString str{settings.value(key).toString()}; - if(str == QString{}) + if(str.isEmpty()) settings.remove(key); } } @@ -1194,7 +1175,7 @@ void MainWindow::enableApplyButton() void MainWindow::updateResamplerLabel(int num) { - ui->resamplerLabel->setText(resamplerList[num].name); + ui->resamplerLabel->setText(std::data(resamplerList[num].name)); enableApplyButton(); } @@ -1294,7 +1275,7 @@ void MainWindow::updateJackBufferSizeSlider() void MainWindow::updateHrtfModeLabel(int num) { - ui->hrtfmodeLabel->setText(hrtfModeList[num].name); + ui->hrtfmodeLabel->setText(std::data(hrtfModeList[static_cast(num)].name)); enableApplyButton(); } @@ -1302,7 +1283,7 @@ void MainWindow::updateHrtfModeLabel(int num) void MainWindow::addHrtfFile() { QString path{QFileDialog::getExistingDirectory(this, tr("Select HRTF Path"))}; - if(path.isEmpty() == false && !getAllDataPaths("/openal/hrtf").contains(path)) + if(path.isEmpty() == false && !getAllDataPaths(QStringLiteral("/openal/hrtf")).contains(path)) { ui->hrtfFileList->addItem(path); enableApplyButton(); @@ -1311,11 +1292,10 @@ void MainWindow::addHrtfFile() void MainWindow::removeHrtfFile() { - QList selected{ui->hrtfFileList->selectedItems()}; + QList> selected{ui->hrtfFileList->selectedItems()}; if(!selected.isEmpty()) { - foreach(QListWidgetItem *item, selected) - delete item; + std::for_each(selected.begin(), selected.end(), std::default_delete{}); enableApplyButton(); } } @@ -1336,9 +1316,9 @@ void MainWindow::showEnabledBackendMenu(QPoint pt) if(ui->enabledBackendList->selectedItems().empty()) removeAction->setEnabled(false); ctxmenu.addSeparator(); - for(size_t i = 0;backendList[i].backend_name[0];i++) + for(size_t i{0};i < backendList.size();++i) { - QString backend{backendList[i].full_string}; + QString backend{std::data(backendList[i].full_string)}; QAction *action{ctxmenu.addAction(QString("Add ")+backend)}; actionMap[action] = backend; if(!ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).empty() || @@ -1349,9 +1329,8 @@ void MainWindow::showEnabledBackendMenu(QPoint pt) QAction *gotAction{ctxmenu.exec(pt)}; if(gotAction == removeAction) { - QList selected{ui->enabledBackendList->selectedItems()}; - foreach(QListWidgetItem *item, selected) - delete item; + QList> selected{ui->enabledBackendList->selectedItems()}; + std::for_each(selected.begin(), selected.end(), std::default_delete{}); enableApplyButton(); } else if(gotAction != nullptr) @@ -1374,9 +1353,9 @@ void MainWindow::showDisabledBackendMenu(QPoint pt) if(ui->disabledBackendList->selectedItems().empty()) removeAction->setEnabled(false); ctxmenu.addSeparator(); - for(size_t i = 0;backendList[i].backend_name[0];i++) + for(size_t i{0};i < backendList.size();++i) { - QString backend{backendList[i].full_string}; + QString backend{std::data(backendList[i].full_string)}; QAction *action{ctxmenu.addAction(QString("Add ")+backend)}; actionMap[action] = backend; if(!ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).empty() || @@ -1387,9 +1366,8 @@ void MainWindow::showDisabledBackendMenu(QPoint pt) QAction *gotAction{ctxmenu.exec(pt)}; if(gotAction == removeAction) { - QList selected{ui->disabledBackendList->selectedItems()}; - foreach(QListWidgetItem *item, selected) - delete item; + QList> selected{ui->disabledBackendList->selectedItems()}; + std::for_each(selected.begin(), selected.end(), std::default_delete{}); enableApplyButton(); } else if(gotAction != nullptr) diff --git a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.h b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.h index f7af8eace..9596793eb 100644 --- a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.h +++ b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.h @@ -1,6 +1,8 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include + #include #include @@ -8,15 +10,10 @@ namespace Ui { class MainWindow; } -class MainWindow : public QMainWindow -{ +class MainWindow : public QMainWindow { Q_OBJECT -public: - explicit MainWindow(QWidget *parent = 0); - ~MainWindow(); - -private slots: +private Q_SLOTS: void cancelCloseAction(); void saveCurrentConfig(); @@ -32,7 +29,7 @@ private slots: void updatePeriodSizeEdit(int size); void updatePeriodSizeSlider(); - void updatePeriodCountEdit(int size); + void updatePeriodCountEdit(int count); void updatePeriodCountSlider(); void selectQuadDecoderFile(); @@ -60,22 +57,26 @@ private slots: void selectWaveOutput(); +public: + explicit MainWindow(QWidget *parent=nullptr); + ~MainWindow() override; + private: - Ui::MainWindow *ui; + std::unique_ptr mPeriodSizeValidator; + std::unique_ptr mPeriodCountValidator; + std::unique_ptr mSourceCountValidator; + std::unique_ptr mEffectSlotValidator; + std::unique_ptr mSourceSendValidator; + std::unique_ptr mSampleRateValidator; + std::unique_ptr mJackBufferValidator; - QValidator *mPeriodSizeValidator; - QValidator *mPeriodCountValidator; - QValidator *mSourceCountValidator; - QValidator *mEffectSlotValidator; - QValidator *mSourceSendValidator; - QValidator *mSampleRateValidator; - QValidator *mJackBufferValidator; + std::unique_ptr ui; - bool mNeedsSave; + bool mNeedsSave{}; - void closeEvent(QCloseEvent *event); + void closeEvent(QCloseEvent *event) override; - void selectDecoderFile(QLineEdit *line, const char *name); + void selectDecoderFile(QLineEdit *line, const char *caption); QStringList collectHrtfs(); diff --git a/Engine/lib/openal-soft/utils/getopt.c b/Engine/lib/openal-soft/utils/getopt.c deleted file mode 100644 index ab1a246e3..000000000 --- a/Engine/lib/openal-soft/utils/getopt.c +++ /dev/null @@ -1,137 +0,0 @@ -/* $NetBSD: getopt.c,v 1.26 2003/08/07 16:43:40 agc Exp $ */ - -/* - * Copyright (c) 1987, 1993, 1994 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. 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. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. - */ - -#if defined(LIBC_SCCS) && !defined(lint) -static char sccsid[] = "@(#)getopt.c 8.3 (Berkeley) 4/27/95"; -#endif /* LIBC_SCCS and not lint */ -#include -#include -#include -#include "getopt.h" - -int opterr = 1, /* if error message should be printed */ - optind = 1, /* index into parent argv vector */ - optopt, /* character checked for validity */ - optreset; /* reset getopt */ -char *optarg; /* argument associated with option */ - -#define BADCH (int)'?' -#define BADARG (int)':' -#define EMSG "" - -/* - * Get program name in Windows - */ -const char * _getprogname(void); - -/* - * getopt -- - * Parse argc/argv argument vector. - */ -int -getopt(int nargc, char * const nargv[], const char *ostr) -{ - static char *place = EMSG; /* option letter processing */ - char *oli; /* option letter list index */ - - if (optreset || *place == 0) { /* update scanning pointer */ - optreset = 0; - place = nargv[optind]; - if (optind >= nargc || *place++ != '-') { - /* Argument is absent or is not an option */ - place = EMSG; - return (-1); - } - optopt = *place++; - if (optopt == '-' && *place == 0) { - /* "--" => end of options */ - ++optind; - place = EMSG; - return (-1); - } - if (optopt == 0) { - /* Solitary '-', treat as a '-' option - if the program (eg su) is looking for it. */ - place = EMSG; - if (strchr(ostr, '-') == NULL) - return (-1); - optopt = '-'; - } - } else - optopt = *place++; - - /* See if option letter is one the caller wanted... */ - if (optopt == ':' || (oli = strchr(ostr, optopt)) == NULL) { - if (*place == 0) - ++optind; - if (opterr && *ostr != ':') - (void)fprintf(stderr, - "%s: illegal option -- %c\n", _getprogname(), - optopt); - return (BADCH); - } - - /* Does this option need an argument? */ - if (oli[1] != ':') { - /* don't need argument */ - optarg = NULL; - if (*place == 0) - ++optind; - } else { - /* Option-argument is either the rest of this argument or the - entire next argument. */ - if (*place) - optarg = place; - else if (nargc > ++optind) - optarg = nargv[optind]; - else { - /* option-argument absent */ - place = EMSG; - if (*ostr == ':') - return (BADARG); - if (opterr) - (void)fprintf(stderr, - "%s: option requires an argument -- %c\n", - _getprogname(), optopt); - return (BADCH); - } - place = EMSG; - ++optind; - } - return (optopt); /* return option letter */ -} - -const char * _getprogname() { - char *pgmptr = NULL; - _get_pgmptr(&pgmptr); - return strrchr(pgmptr,'\\')+1; -} - diff --git a/Engine/lib/openal-soft/utils/getopt.h b/Engine/lib/openal-soft/utils/getopt.h deleted file mode 100644 index 0218c42dd..000000000 --- a/Engine/lib/openal-soft/utils/getopt.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef GETOPT_H -#define GETOPT_H - -#ifndef _WIN32 - -#include - -#else /* _WIN32 */ - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -extern char *optarg; -extern int optind, opterr, optopt, optreset; - -int getopt(int nargc, char * const nargv[], const char *ostr); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* !_WIN32 */ - -#endif /* !GETOPT_H */ - diff --git a/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp b/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp index e8092363a..5938cfed8 100644 --- a/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp +++ b/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp @@ -30,14 +30,19 @@ #include #include #include +#include +#include #include #include #include -#include +#include +#include +#include #include -#include "alfstream.h" -#include "aloptional.h" +#include "albit.h" +#include "almalloc.h" +#include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "makemhr.h" @@ -45,21 +50,23 @@ #include "mysofa.h" +namespace { + // 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) +constexpr uint TRRingBits{16}; +constexpr uint TRRingSize{1 << TRRingBits}; +constexpr uint TRRingMask{TRRingSize - 1}; // The token reader's load interval in bytes. -#define TR_LOAD_SIZE (TR_RING_SIZE >> 2) +constexpr uint TRLoadSize{TRRingSize >> 2}; // Token reader state for parsing the data set definition. struct TokenReaderT { std::istream &mIStream; - const char *mName{}; + std::string mName{}; uint mLine{}; uint mColumn{}; - char mRing[TR_RING_SIZE]{}; + std::array mRing{}; std::streamsize mIn{}; std::streamsize mOut{}; @@ -70,44 +77,48 @@ struct TokenReaderT { // The maximum identifier length used when processing the data set // definition. -#define MAX_IDENT_LEN (16) +constexpr uint MaxIdentLen{16}; // The limits for the listener's head 'radius' in the data set definition. -#define MIN_RADIUS (0.05) -#define MAX_RADIUS (0.15) +constexpr double MinRadius{0.05}; +constexpr double MaxRadius{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) +constexpr uint MaxWaveChannels{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) +enum : uint { + MinBinSize = 2, + MaxBinSize = 4 +}; // 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) +enum : uint { + MinASCIIBits = 16, + MaxASCIIBits = 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' +enum : uint { + FOURCC_RIFF = 0x46464952, // 'RIFF' + FOURCC_RIFX = 0x58464952, // 'RIFX' + FOURCC_WAVE = 0x45564157, // 'WAVE' + FOURCC_FMT = 0x20746D66, // 'fmt ' + FOURCC_DATA = 0x61746164, // 'data' + FOURCC_LIST = 0x5453494C, // 'LIST' + FOURCC_WAVL = 0x6C766177, // 'wavl' + 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 : uint { + WAVE_FORMAT_PCM = 0x0001, + WAVE_FORMAT_IEEE_FLOAT = 0x0003, + WAVE_FORMAT_EXTENSIBLE = 0xFFFE, +}; enum ByteOrderT { @@ -145,7 +156,7 @@ struct SourceRefT { double mRadius; uint mSkip; uint mOffset; - char mPath[MAX_PATH_LEN+1]; + std::array mPath; }; @@ -156,71 +167,67 @@ struct SourceRefT { // 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, +void TrSetup(const al::span startbytes, const std::string_view filename, TokenReaderT *tr) { - const char *name = nullptr; + std::string_view namepart; - if(filename) + if(!filename.empty()) { - 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; - } + const auto fslashpos = filename.rfind('/'); + const auto bslashpos = filename.rfind('\\'); + const auto slashpos = (bslashpos >= filename.size()) ? fslashpos : + (fslashpos >= filename.size()) ? bslashpos : + std::max(fslashpos, bslashpos); + if(slashpos < filename.size()) + namepart = filename.substr(slashpos+1); } - tr->mName = name; + tr->mName = namepart; tr->mLine = 1; tr->mColumn = 1; tr->mIn = 0; tr->mOut = 0; - if(startbytecount > 0) + if(!startbytes.empty()) { - std::copy_n(startbytes, startbytecount, std::begin(tr->mRing)); - tr->mIn += startbytecount; + assert(startbytes.size() <= tr->mRing.size()); + std::copy(startbytes.cbegin(), startbytes.cend(), tr->mRing.begin()); + tr->mIn += std::streamsize(startbytes.size()); } } // Prime the reader's ring buffer, and return a result indicating that there // is text to process. -static int TrLoad(TokenReaderT *tr) +auto TrLoad(TokenReaderT *tr) -> int { std::istream &istream = tr->mIStream; - std::streamsize toLoad{TR_RING_SIZE - static_cast(tr->mIn - tr->mOut)}; - if(toLoad >= TR_LOAD_SIZE && istream.good()) + std::streamsize toLoad{TRRingSize - static_cast(tr->mIn - tr->mOut)}; + if(toLoad >= TRLoadSize && 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}; + // Load TRLoadSize (or less if at the end of the file) per read. + toLoad = TRLoadSize; + + const auto in = tr->mIn&TRRingMask; + std::streamsize count{TRRingSize - in}; if(count < toLoad) { - istream.read(&tr->mRing[in], count); + istream.read(al::to_address(tr->mRing.begin() + in), count); tr->mIn += istream.gcount(); - istream.read(&tr->mRing[0], toLoad-count); + istream.read(tr->mRing.data(), toLoad-count); tr->mIn += istream.gcount(); } else { - istream.read(&tr->mRing[in], toLoad); + istream.read(al::to_address(tr->mRing.begin() + in), toLoad); tr->mIn += istream.gcount(); } - if(tr->mOut >= TR_RING_SIZE) + if(tr->mOut >= TRRingSize) { - tr->mOut -= TR_RING_SIZE; - tr->mIn -= TR_RING_SIZE; + tr->mOut -= TRRingSize; + tr->mIn -= TRRingSize; } } if(tr->mIn > tr->mOut) @@ -229,42 +236,44 @@ static int TrLoad(TokenReaderT *tr) } // 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) +void TrErrorVA(const TokenReaderT *tr, uint line, uint column, const char *format, va_list argPtr) { - if(!tr->mName) + if(tr->mName.empty()) return; - fprintf(stderr, "\nError (%s:%u:%u): ", tr->mName, line, column); + fprintf(stderr, "\nError (%s:%u:%u): ", tr->mName.c_str(), 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, ...) +void TrErrorAt(const TokenReaderT *tr, uint line, uint column, const char *format, ...) { + /* NOLINTBEGIN(*-array-to-pointer-decay) */ va_list argPtr; - va_start(argPtr, format); TrErrorVA(tr, line, column, format, argPtr); va_end(argPtr); + /* NOLINTEND(*-array-to-pointer-decay) */ } // Used to display an error at the current line/column. -static void TrError(const TokenReaderT *tr, const char *format, ...) +void TrError(const TokenReaderT *tr, const char *format, ...) { + /* NOLINTBEGIN(*-array-to-pointer-decay) */ va_list argPtr; - va_start(argPtr, format); TrErrorVA(tr, tr->mLine, tr->mColumn, format, argPtr); va_end(argPtr); + /* NOLINTEND(*-array-to-pointer-decay) */ } // Skips to the next line. -static void TrSkipLine(TokenReaderT *tr) +void TrSkipLine(TokenReaderT *tr) { char ch; while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; tr->mOut++; if(ch == '\n') { @@ -277,11 +286,11 @@ static void TrSkipLine(TokenReaderT *tr) } // Skips to the next token. -static int TrSkipWhitespace(TokenReaderT *tr) +auto TrSkipWhitespace(TokenReaderT *tr) -> int { while(TrLoad(tr)) { - char ch{tr->mRing[tr->mOut&TR_RING_MASK]}; + char ch{tr->mRing[tr->mOut&TRRingMask]}; if(isspace(ch)) { tr->mOut++; @@ -302,7 +311,7 @@ static int TrSkipWhitespace(TokenReaderT *tr) } // Get the line and/or column of the next token (or the end of input). -static void TrIndication(TokenReaderT *tr, uint *line, uint *column) +void TrIndication(TokenReaderT *tr, uint *line, uint *column) { TrSkipWhitespace(tr); if(line) *line = tr->mLine; @@ -311,35 +320,31 @@ static void TrIndication(TokenReaderT *tr, uint *line, uint *column) // 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) +auto TrIsIdent(TokenReaderT *tr) -> int { if(!TrSkipWhitespace(tr)) return 0; - char ch{tr->mRing[tr->mOut&TR_RING_MASK]}; + char ch{tr->mRing[tr->mOut&TRRingMask]}; 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) +auto TrIsOperator(TokenReaderT *tr, const std::string_view op) -> int { - 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) + auto out = tr->mOut; + size_t len{0}; + while(len < op.size() && out < tr->mIn) { - ch = tr->mRing[out&TR_RING_MASK]; - if(ch != op[len]) break; - len++; - out++; + if(tr->mRing[out&TRRingMask] != op[len]) + break; + ++len; + ++out; } - if(op[len] == '\0') + if(len == op.size()) return 1; return 0; } @@ -350,30 +355,29 @@ static int TrIsOperator(TokenReaderT *tr, const char *op) */ // Reads and validates an identifier token. -static int TrReadIdent(TokenReaderT *tr, const uint maxLen, char *ident) +auto TrReadIdent(TokenReaderT *tr, const al::span ident) -> int { - uint col, len; - char ch; - - col = tr->mColumn; + assert(!ident.empty()); + const size_t maxLen{ident.size()-1}; + uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + char ch{tr->mRing[tr->mOut&TRRingMask]}; if(ch == '_' || isalpha(ch)) { - len = 0; + size_t len{0}; do { if(len < maxLen) ident[len] = ch; - len++; + ++len; tr->mOut++; if(!TrLoad(tr)) break; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; } while(ch == '_' || isdigit(ch) || isalpha(ch)); - tr->mColumn += len; + tr->mColumn += static_cast(len); if(len < maxLen) { ident[len] = '\0'; @@ -388,27 +392,25 @@ static int TrReadIdent(TokenReaderT *tr, const uint maxLen, char *ident) } // Reads and validates (including bounds) an integer token. -static int TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int *value) +auto TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int *value) -> int { - uint col, digis, len; - char ch, temp[64+1]; - - col = tr->mColumn; + uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - len = 0; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + uint len{0}; + std::array temp{}; + char ch{tr->mRing[tr->mOut&TRRingMask]}; if(ch == '+' || ch == '-') { temp[len] = ch; len++; tr->mOut++; } - digis = 0; + uint digis{0}; while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; @@ -425,7 +427,7 @@ static int TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int return 0; } temp[len] = '\0'; - *value = static_cast(strtol(temp, nullptr, 10)); + *value = static_cast(strtol(temp.data(), nullptr, 10)); if(*value < loBound || *value > hiBound) { TrErrorAt(tr, tr->mLine, col, "Expected a value from %d to %d.\n", loBound, hiBound); @@ -439,17 +441,15 @@ static int TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int } // Reads and validates (including bounds) a float token. -static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, double *value) +auto TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, double *value) -> int { - uint col, digis, len; - char ch, temp[64+1]; - - col = tr->mColumn; + uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - len = 0; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + std::array temp{}; + uint len{0}; + char ch{tr->mRing[tr->mOut&TRRingMask]}; if(ch == '+' || ch == '-') { temp[len] = ch; @@ -457,10 +457,10 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo tr->mOut++; } - digis = 0; + uint digis{0}; while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; @@ -477,7 +477,7 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo } while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; @@ -503,7 +503,7 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo } while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; @@ -521,7 +521,7 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo return 0; } temp[len] = '\0'; - *value = strtod(temp, nullptr); + *value = strtod(temp.data(), nullptr); if(*value < loBound || *value > hiBound) { TrErrorAt(tr, tr->mLine, col, "Expected a value from %f to %f.\n", loBound, hiBound); @@ -538,23 +538,22 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo } // Reads and validates a string token. -static int TrReadString(TokenReaderT *tr, const uint maxLen, char *text) +auto TrReadString(TokenReaderT *tr, const al::span text) -> int { - uint col, len; - char ch; + assert(!text.empty()); + const size_t maxLen{text.size()-1}; - col = tr->mColumn; + uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; - if(ch == '\"') + if(char ch{tr->mRing[tr->mOut&TRRingMask]}; ch == '\"') { tr->mOut++; - len = 0; + size_t len{0}; while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; tr->mOut++; if(ch == '\"') break; @@ -569,11 +568,11 @@ static int TrReadString(TokenReaderT *tr, const uint maxLen, char *text) } if(ch != '\"') { - tr->mColumn += 1 + len; + tr->mColumn += static_cast(1 + len); TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of input.\n"); return 0; } - tr->mColumn += 2 + len; + tr->mColumn += static_cast(2 + len); if(len > maxLen) { TrErrorAt(tr, tr->mLine, col, "String is too long.\n"); @@ -588,25 +587,22 @@ static int TrReadString(TokenReaderT *tr, const uint maxLen, char *text) } // Reads and validates the given operator. -static int TrReadOperator(TokenReaderT *tr, const char *op) +auto TrReadOperator(TokenReaderT *tr, const std::string_view op) -> int { - uint col, len; - char ch; - - col = tr->mColumn; + uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - len = 0; - while(op[len] != '\0' && TrLoad(tr)) + size_t len{0}; + while(len < op.size() && TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; - if(ch != op[len]) break; - len++; - tr->mOut++; + if(tr->mRing[tr->mOut&TRRingMask] != op[len]) + break; + ++len; + tr->mOut += 1; } - tr->mColumn += len; - if(op[len] == '\0') + tr->mColumn += static_cast(len); + if(len == op.size()) return 1; } TrErrorAt(tr, tr->mLine, col, "Expected '%s' operator.\n", op); @@ -620,10 +616,11 @@ static int TrReadOperator(TokenReaderT *tr, const char *op) // 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) +auto ReadBin4(std::istream &istream, const char *filename, const ByteOrderT order, + const uint bytes, uint32_t *out) -> int { - uint8_t in[4]; - istream.read(reinterpret_cast(in), static_cast(bytes)); + std::array in{}; + istream.read(reinterpret_cast(in.data()), static_cast(bytes)); if(istream.gcount() != bytes) { fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); @@ -649,31 +646,29 @@ static int ReadBin4(std::istream &istream, const char *filename, const ByteOrder // 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) +auto ReadBin8(std::istream &istream, const char *filename, const ByteOrderT order, uint64_t *out) -> int { - uint8_t in[8]; - uint64_t accum; - uint i; - - istream.read(reinterpret_cast(in), 8); + std::array in{}; + istream.read(reinterpret_cast(in.data()), 8); if(istream.gcount() != 8) { fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); return 0; } - accum = 0; + + uint64_t accum{}; 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; + case BO_LITTLE: + for(uint i{0};i < 8;++i) + accum = (accum<<8) | in[8 - i - 1]; + break; + case BO_BIG: + for(uint i{0};i < 8;++i) + accum = (accum<<8) | in[i]; + break; + default: + break; } *out = accum; return 1; @@ -685,43 +680,35 @@ static int ReadBin8(std::istream &istream, const char *filename, const ByteOrder * 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) +auto ReadBinAsDouble(std::istream &istream, const char *filename, const ByteOrderT order, + const ElementTypeT type, const uint bytes, const int bits, double *out) -> int { - 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)) + uint64_t val{}; + if(!ReadBin8(istream, filename, order, &val)) return 0; if(type == ET_FP) - *out = v8.f; + *out = al::bit_cast(val); } else { - if(!ReadBin4(istream, filename, order, bytes, &v4.ui)) + uint32_t val{}; + if(!ReadBin4(istream, filename, order, bytes, &val)) return 0; if(type == ET_FP) - *out = v4.f; + *out = al::bit_cast(val); else { if(bits > 0) - v4.ui >>= (8*bytes) - (static_cast(bits)); + val >>= (8*bytes) - (static_cast(bits)); else - v4.ui &= (0xFFFFFFFF >> (32+bits)); + val &= (0xFFFFFFFF >> (32+bits)); - if(v4.ui&static_cast(1<<(std::abs(bits)-1))) - v4.ui |= (0xFFFFFFFF << std::abs(bits)); - *out = v4.i / static_cast(1<<(std::abs(bits)-1)); + if(val&static_cast(1<<(std::abs(bits)-1))) + val |= (0xFFFFFFFF << std::abs(bits)); + *out = static_cast(val) / static_cast(1<<(std::abs(bits)-1)); } } return 1; @@ -732,7 +719,8 @@ static int ReadBinAsDouble(std::istream &istream, const char *filename, const By * 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) +auto ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const ElementTypeT type, + const uint bits, double *out) -> int { if(TrIsOperator(tr, ",")) TrReadOperator(tr, ","); @@ -767,8 +755,8 @@ static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const Eleme // 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) +auto ReadWaveFormat(std::istream &istream, const ByteOrderT order, const uint hrirRate, + SourceRefT *src) -> int { uint32_t fourCC, chunkSize; uint32_t format, channels, rate, dummy, block, size, bits; @@ -777,20 +765,20 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u do { if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath.data(), 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)) + if(!ReadBin4(istream, src->mPath.data(), order, 2, &format) + || !ReadBin4(istream, src->mPath.data(), order, 2, &channels) + || !ReadBin4(istream, src->mPath.data(), order, 4, &rate) + || !ReadBin4(istream, src->mPath.data(), order, 4, &dummy) + || !ReadBin4(istream, src->mPath.data(), order, 2, &block)) return 0; block /= channels; if(chunkSize > 14) { - if(!ReadBin4(istream, src->mPath, order, 2, &size)) + if(!ReadBin4(istream, src->mPath.data(), order, 2, &size)) return 0; size /= 8; if(block > size) @@ -801,12 +789,12 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u if(format == WAVE_FORMAT_EXTENSIBLE) { istream.seekg(2, std::ios::cur); - if(!ReadBin4(istream, src->mPath, order, 2, &bits)) + if(!ReadBin4(istream, src->mPath.data(), 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)) + if(!ReadBin4(istream, src->mPath.data(), order, 2, &format)) return 0; istream.seekg(static_cast(chunkSize - 26), std::ios::cur); } @@ -820,29 +808,32 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u } if(format != WAVE_FORMAT_PCM && format != WAVE_FORMAT_IEEE_FLOAT) { - fprintf(stderr, "\nError: Unsupported WAVE format in file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Unsupported WAVE format in file '%s'.\n", src->mPath.data()); return 0; } if(src->mChannel >= channels) { - fprintf(stderr, "\nError: Missing source channel in WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Missing source channel in WAVE file '%s'.\n", src->mPath.data()); return 0; } if(rate != hrirRate) { - fprintf(stderr, "\nError: Mismatched source sample rate in WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Mismatched source sample rate in WAVE file '%s'.\n", + src->mPath.data()); 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); + fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", + src->mPath.data()); return 0; } if(bits < 16 || bits > (8*size)) { - fprintf(stderr, "\nError: Bad significant bits in WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Bad significant bits in WAVE file '%s'.\n", + src->mPath.data()); return 0; } src->mType = ET_INT; @@ -851,7 +842,8 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u { if(size != 4 && size != 8) { - fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", + src->mPath.data()); return 0; } src->mType = ET_FP; @@ -863,21 +855,19 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u } // 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) +auto ReadWaveData(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const al::span hrir) -> int { - int pre, post, skip; - uint i; - - pre = static_cast(src->mSize * src->mChannel); - post = static_cast(src->mSize * (src->mSkip - src->mChannel - 1)); - skip = 0; - for(i = 0;i < n;i++) + auto pre = static_cast(src->mSize * src->mChannel); + auto post = static_cast(src->mSize * (src->mSkip - src->mChannel - 1)); + auto skip = int{0}; + for(size_t i{0};i < hrir.size();++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])) + if(!ReadBinAsDouble(istream, src->mPath.data(), order, src->mType, src->mSize, src->mBits, + &hrir[i])) return 0; skip = post; } @@ -888,8 +878,8 @@ static int ReadWaveData(std::istream &istream, const SourceRefT *src, const Byte // 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) +auto ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const al::span hrir) -> int { uint32_t fourCC, chunkSize, listSize, count; uint block, skip, offset, i; @@ -897,27 +887,28 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte for(;;) { - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath.data(), order, 4, &chunkSize)) return 0; if(fourCC == FOURCC_DATA) { block = src->mSize * src->mSkip; count = chunkSize / block; - if(count < (src->mOffset + n)) + if(count < (src->mOffset + hrir.size())) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath.data()); return 0; } - istream.seekg(static_cast(src->mOffset * block), std::ios::cur); - if(!ReadWaveData(istream, src, order, n, &hrir[0])) + using off_type = std::istream::off_type; + istream.seekg(off_type(src->mOffset) * off_type(block), std::ios::cur); + if(!ReadWaveData(istream, src, order, hrir)) return 0; return 1; } - else if(fourCC == FOURCC_LIST) + if(fourCC == FOURCC_LIST) { - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC)) return 0; chunkSize -= 4; if(fourCC == FOURCC_WAVL) @@ -931,10 +922,10 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte skip = src->mOffset; offset = 0; lastSample = 0.0; - while(offset < n && listSize > 8) + while(offset < hrir.size() && listSize > 8) { - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath.data(), order, 4, &chunkSize)) return 0; listSize -= 8 + chunkSize; if(fourCC == FOURCC_DATA) @@ -942,13 +933,14 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte count = chunkSize / block; if(count > skip) { - istream.seekg(static_cast(skip * block), std::ios::cur); + using off_type = std::istream::off_type; + istream.seekg(off_type(skip) * off_type(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])) + if(count > (hrir.size() - offset)) + count = static_cast(hrir.size() - offset); + if(!ReadWaveData(istream, src, order, hrir.subspan(offset, count))) return 0; chunkSize -= count * block; offset += count; @@ -962,15 +954,15 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte } else if(fourCC == FOURCC_SLNT) { - if(!ReadBin4(istream, src->mPath, order, 4, &count)) + if(!ReadBin4(istream, src->mPath.data(), order, 4, &count)) return 0; chunkSize -= 4; if(count > skip) { count -= skip; skip = 0; - if(count > (n - offset)) - count = n - offset; + if(count > (hrir.size() - offset)) + count = static_cast(hrir.size() - offset); for(i = 0; i < count; i ++) hrir[offset + i] = lastSample; offset += count; @@ -984,9 +976,9 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); } - if(offset < n) + if(offset < hrir.size()) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath.data()); return 0; } return 1; @@ -994,26 +986,28 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte // 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) +auto LoadAsciiSource(std::istream &istream, const SourceRefT *src, const al::span hrir) -> int { TokenReaderT tr{istream}; - uint i, j; - double dummy; - TrSetup(nullptr, 0, nullptr, &tr); - for(i = 0;i < src->mOffset;i++) + TrSetup({}, {}, &tr); + for(uint i{0};i < src->mOffset;++i) { - if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &dummy)) + double dummy{}; + if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, static_cast(src->mBits), + &dummy)) return 0; } - for(i = 0;i < n;i++) + for(size_t i{0};i < hrir.size();++i) { - if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &hrir[i])) + if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, static_cast(src->mBits), + &hrir[i])) return 0; - for(j = 0;j < src->mSkip;j++) + for(uint j{0};j < src->mSkip;++j) { - if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &dummy)) + double dummy{}; + if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, + static_cast(src->mBits), &dummy)) return 0; } } @@ -1021,13 +1015,14 @@ static int LoadAsciiSource(std::istream &istream, const SourceRefT *src, } // 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) +auto LoadBinarySource(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const al::span hrir) -> int { istream.seekg(static_cast(src->mOffset), std::ios::beg); - for(uint i{0};i < n;i++) + for(size_t i{0};i < hrir.size();++i) { - if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) + if(!ReadBinAsDouble(istream, src->mPath.data(), order, src->mType, src->mSize, src->mBits, + &hrir[i])) return 0; if(src->mSkip > 0) istream.seekg(static_cast(src->mSkip), std::ios::cur); @@ -1036,14 +1031,14 @@ static int LoadBinarySource(std::istream &istream, const SourceRefT *src, const } // 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) +auto LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hrirRate, + const al::span hrir) -> int { uint32_t fourCC, dummy; ByteOrderT order; - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath, BO_LITTLE, 4, &dummy)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &dummy)) return 0; if(fourCC == FOURCC_RIFF) order = BO_LITTLE; @@ -1051,34 +1046,54 @@ static int LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hri order = BO_BIG; else { - fprintf(stderr, "\nError: No RIFF/RIFX chunk in file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: No RIFF/RIFX chunk in file '%s'.\n", src->mPath.data()); return 0; } - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC)) return 0; if(fourCC != FOURCC_WAVE) { - fprintf(stderr, "\nError: Not a RIFF/RIFX WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Not a RIFF/RIFX WAVE file '%s'.\n", src->mPath.data()); return 0; } if(!ReadWaveFormat(istream, order, hrirRate, src)) return 0; - if(!ReadWaveList(istream, src, order, n, hrir)) + if(!ReadWaveList(istream, src, order, hrir)) return 0; return 1; } +struct SofaEasyDeleter { + void operator()(gsl::owner sofa) + { + if(sofa->neighborhood) mysofa_neighborhood_free(sofa->neighborhood); + if(sofa->lookup) mysofa_lookup_free(sofa->lookup); + if(sofa->hrtf) mysofa_free(sofa->hrtf); + delete sofa; + } +}; +using SofaEasyPtr = std::unique_ptr; + +struct SofaCacheEntry { + std::string mName; + uint mSampleRate{}; + SofaEasyPtr mSofa; +}; +std::vector gSofaCache; // Load a Spatially Oriented Format for Accoustics (SOFA) file. -static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) +auto LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) -> MYSOFA_EASY* { - struct MYSOFA_EASY *sofa{mysofa_cache_lookup(src->mPath, static_cast(hrirRate))}; - if(sofa) return sofa; + const std::string_view srcname{src->mPath.data()}; + auto iter = std::find_if(gSofaCache.begin(), gSofaCache.end(), + [srcname,hrirRate](SofaCacheEntry &entry) -> bool + { return entry.mName == srcname && entry.mSampleRate == hrirRate; }); + if(iter != gSofaCache.end()) return iter->mSofa.get(); - sofa = static_cast(calloc(1, sizeof(*sofa))); - if(sofa == nullptr) + SofaEasyPtr sofa{new(std::nothrow) MYSOFA_EASY{}}; + if(!sofa) { fprintf(stderr, "\nError: Out of memory.\n"); return nullptr; @@ -1087,136 +1102,127 @@ static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uin sofa->neighborhood = nullptr; int err; - sofa->hrtf = mysofa_load(src->mPath, &err); + sofa->hrtf = mysofa_load(src->mPath.data(), &err); if(!sofa->hrtf) { - mysofa_close(sofa); - fprintf(stderr, "\nError: Could not load source file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Could not load source file '%s' (error: %d).\n", + src->mPath.data(), err); 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); + fprintf(stderr, "\nWarning: Supposedly malformed source file '%s' (error: %d).\n", + src->mPath.data(), err); if((src->mOffset + n) > sofa->hrtf->N) { - mysofa_close(sofa); - fprintf(stderr, "\nError: Not enough samples in SOFA file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Not enough samples in SOFA file '%s'.\n", src->mPath.data()); return nullptr; } if(src->mChannel >= sofa->hrtf->R) { - mysofa_close(sofa); - fprintf(stderr, "\nError: Missing source receiver in SOFA file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Missing source receiver in SOFA file '%s'.\n",src->mPath.data()); 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(hrirRate)); + gSofaCache.emplace_back(SofaCacheEntry{std::string{srcname}, hrirRate, std::move(sofa)}); + return gSofaCache.back().mSofa.get(); } // 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) +void ExtractSofaHrir(const MYSOFA_HRTF *hrtf, const size_t index, const size_t channel, + const size_t offset, const al::span hrir) { - for(uint i{0u};i < n;i++) - hrir[i] = sofa->hrtf->DataIR.values[(index*sofa->hrtf->R + channel)*sofa->hrtf->N + offset + i]; + const auto irValues = al::span{hrtf->DataIR.values, hrtf->DataIR.elements} + .subspan((index*hrtf->R + channel)*hrtf->N + offset); + std::copy_n(irValues.cbegin(), hrir.size(), hrir.begin()); } // 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) +auto LoadSofaSource(SourceRefT *src, const uint hrirRate, const al::span hrir) -> int { - struct MYSOFA_EASY *sofa; - float target[3]; - int nearest; - float *coords; + MYSOFA_EASY *sofa{LoadSofaFile(src, hrirRate, static_cast(hrir.size()))}; + if(sofa == nullptr) return 0; - sofa = LoadSofaFile(src, hrirRate, n); - if(sofa == nullptr) - return 0; - - /* NOTE: At some point it may be benficial or necessary to consider the + /* NOTE: At some point it may be beneficial or necessary to consider the various coordinate systems, listener/source orientations, and - direciontal vectors defined in the SOFA file. + directional vectors defined in the SOFA file. */ - target[0] = static_cast(src->mAzimuth); - target[1] = static_cast(src->mElevation); - target[2] = static_cast(src->mRadius); - mysofa_s2c(target); + std::array target{ + static_cast(src->mAzimuth), + static_cast(src->mElevation), + static_cast(src->mRadius) + }; + mysofa_s2c(target.data()); - nearest = mysofa_lookup(sofa->lookup, target); + int nearest{mysofa_lookup(sofa->lookup, target.data())}; if(nearest < 0) { - fprintf(stderr, "\nError: Lookup failed in source file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Lookup failed in source file '%s'.\n", src->mPath.data()); 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) + al::span coords = al::span{sofa->hrtf->SourcePosition.values, sofa->hrtf->M*3_uz} + .subspan(static_cast(nearest)*3_uz).first<3>(); + 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); + fprintf(stderr, "\nError: No impulse response at coordinates (%.3fr, %.1fev, %.1faz) in file '%s'.\n", + src->mRadius, src->mElevation, src->mAzimuth, src->mPath.data()); 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]); + mysofa_c2s(target.data()); + fprintf(stderr, " Nearest candidate at (%.3fr, %.1fev, %.1faz).\n", target[2], + target[1], target[0]); return 0; } - ExtractSofaHrir(sofa, static_cast(nearest), src->mChannel, src->mOffset, n, hrir); + ExtractSofaHrir(sofa->hrtf, static_cast(nearest), src->mChannel, src->mOffset, 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) +auto LoadSource(SourceRefT *src, const uint hrirRate, const al::span hrir) -> int { - std::unique_ptr istream; + std::unique_ptr istream; if(src->mFormat != SF_SOFA) { if(src->mFormat == SF_ASCII) - istream.reset(new al::ifstream{src->mPath}); + istream = std::make_unique(std::filesystem::u8path(src->mPath.data())); else - istream.reset(new al::ifstream{src->mPath, std::ios::binary}); + istream = std::make_unique(std::filesystem::u8path(src->mPath.data()), + std::ios::binary); if(!istream->good()) { - fprintf(stderr, "\nError: Could not open source file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Could not open source file '%s'.\n", src->mPath.data()); 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; + case SF_ASCII: return LoadAsciiSource(*istream, src, hrir); + case SF_BIN_LE: return LoadBinarySource(*istream, src, BO_LITTLE, hrir); + case SF_BIN_BE: return LoadBinarySource(*istream, src, BO_BIG, hrir); + case SF_WAVE: return LoadWaveSource(*istream, src, hrirRate, hrir); + case SF_SOFA: return LoadSofaSource(src, hrirRate, hrir); + case SF_NONE: break; } - return result; + return 0; } // Match the channel type from a given identifier. -static ChannelTypeT MatchChannelType(const char *ident) +auto MatchChannelType(const char *ident) -> ChannelTypeT { if(al::strcasecmp(ident, "mono") == 0) return CT_MONO; @@ -1227,18 +1233,19 @@ static ChannelTypeT MatchChannelType(const char *ident) // 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) +auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, + const ChannelModeT chanMode, HrirDataT *hData) -> int { int hasRate = 0, hasType = 0, hasPoints = 0, hasRadius = 0; int hasDistance = 0, hasAzimuths = 0; - char ident[MAX_IDENT_LEN+1]; + std::array ident{}; uint line, col; double fpVal; uint points; int intVal; - double distances[MAX_FD_COUNT]; + std::array distances{}; uint fdCount = 0; - uint evCounts[MAX_FD_COUNT]; + std::array evCounts{}; auto azCounts = std::vector>(MAX_FD_COUNT); for(auto &azs : azCounts) azs.fill(0u); @@ -1246,9 +1253,9 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc while(TrIsIdent(tr)) { TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + if(!TrReadIdent(tr, ident)) return 0; - if(al::strcasecmp(ident, "rate") == 0) + if(al::strcasecmp(ident.data(), "rate") == 0) { if(hasRate) { @@ -1262,9 +1269,9 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc hData->mIrRate = static_cast(intVal); hasRate = 1; } - else if(al::strcasecmp(ident, "type") == 0) + else if(al::strcasecmp(ident.data(), "type") == 0) { - char type[MAX_IDENT_LEN+1]; + std::array type{}; if(hasType) { @@ -1274,22 +1281,22 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc if(!TrReadOperator(tr, "=")) return 0; - if(!TrReadIdent(tr, MAX_IDENT_LEN, type)) + if(!TrReadIdent(tr, type)) return 0; - hData->mChannelType = MatchChannelType(type); + hData->mChannelType = MatchChannelType(type.data()); if(hData->mChannelType == CT_NONE) { TrErrorAt(tr, line, col, "Expected a channel type.\n"); return 0; } - else if(hData->mChannelType == CT_STEREO) + if(hData->mChannelType == CT_STEREO) { if(chanMode == CM_ForceMono) hData->mChannelType = CT_MONO; } hasType = 1; } - else if(al::strcasecmp(ident, "points") == 0) + else if(al::strcasecmp(ident.data(), "points") == 0) { if(hasPoints) { @@ -1319,7 +1326,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc hData->mIrSize = points; hasPoints = 1; } - else if(al::strcasecmp(ident, "radius") == 0) + else if(al::strcasecmp(ident.data(), "radius") == 0) { if(hasRadius) { @@ -1328,12 +1335,12 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc } if(!TrReadOperator(tr, "=")) return 0; - if(!TrReadFloat(tr, MIN_RADIUS, MAX_RADIUS, &fpVal)) + if(!TrReadFloat(tr, MinRadius, MaxRadius, &fpVal)) return 0; hData->mRadius = fpVal; hasRadius = 1; } - else if(al::strcasecmp(ident, "distance") == 0) + else if(al::strcasecmp(ident.data(), "distance") == 0) { uint count = 0; @@ -1372,7 +1379,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc fdCount = count; hasDistance = 1; } - else if(al::strcasecmp(ident, "azimuths") == 0) + else if(al::strcasecmp(ident.data(), "azimuths") == 0) { uint count = 0; @@ -1451,8 +1458,8 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc } if(hData->mChannelType == CT_NONE) hData->mChannelType = CT_MONO; - const auto azs = al::as_span(azCounts).first(); - if(!PrepareHrirData({distances, fdCount}, evCounts, azs, hData)) + const auto azs = al::span{azCounts}.first(); + if(!PrepareHrirData(al::span{distances}.first(fdCount), evCounts, azs, hData)) { fprintf(stderr, "Error: Out of memory.\n"); exit(-1); @@ -1461,7 +1468,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc } // Parse an index triplet from the data set definition. -static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, uint *ei, uint *ai) +auto ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, uint *ei, uint *ai)->int { int intVal; @@ -1489,7 +1496,7 @@ static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, } // Match the source format from a given identifier. -static SourceFormatT MatchSourceFormat(const char *ident) +auto MatchSourceFormat(const char *ident) -> SourceFormatT { if(al::strcasecmp(ident, "ascii") == 0) return SF_ASCII; @@ -1505,7 +1512,7 @@ static SourceFormatT MatchSourceFormat(const char *ident) } // Match the source element type from a given identifier. -static ElementTypeT MatchElementType(const char *ident) +auto MatchElementType(const char *ident) -> ElementTypeT { if(al::strcasecmp(ident, "int") == 0) return ET_INT; @@ -1515,17 +1522,17 @@ static ElementTypeT MatchElementType(const char *ident) } // Parse and validate a source reference from the data set definition. -static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) +auto ReadSourceRef(TokenReaderT *tr, SourceRefT *src) -> int { - char ident[MAX_IDENT_LEN+1]; + std::array ident{}; uint line, col; double fpVal; int intVal; TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + if(!TrReadIdent(tr, ident)) return 0; - src->mFormat = MatchSourceFormat(ident); + src->mFormat = MatchSourceFormat(ident.data()); if(src->mFormat == SF_NONE) { TrErrorAt(tr, line, col, "Expected a source format.\n"); @@ -1550,7 +1557,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) src->mAzimuth = fpVal; if(!TrReadOperator(tr, ":")) return 0; - if(!TrReadInt(tr, 0, MAX_WAVE_CHANNELS, &intVal)) + if(!TrReadInt(tr, 0, MaxWaveChannels, &intVal)) return 0; src->mType = ET_NONE; src->mSize = 0; @@ -1560,7 +1567,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) } else if(src->mFormat == SF_WAVE) { - if(!TrReadInt(tr, 0, MAX_WAVE_CHANNELS, &intVal)) + if(!TrReadInt(tr, 0, MaxWaveChannels, &intVal)) return 0; src->mType = ET_NONE; src->mSize = 0; @@ -1571,9 +1578,9 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) else { TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + if(!TrReadIdent(tr, ident)) return 0; - src->mType = MatchElementType(ident); + src->mType = MatchElementType(ident.data()); if(src->mType == ET_NONE) { TrErrorAt(tr, line, col, "Expected a source element type.\n"); @@ -1585,7 +1592,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) return 0; if(src->mType == ET_INT) { - if(!TrReadInt(tr, MIN_BIN_SIZE, MAX_BIN_SIZE, &intVal)) + if(!TrReadInt(tr, MinBinSize, MaxBinSize, &intVal)) return 0; src->mSize = static_cast(intVal); if(!TrIsOperator(tr, ",")) @@ -1596,9 +1603,9 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) TrIndication(tr, &line, &col); if(!TrReadInt(tr, -2147483647-1, 2147483647, &intVal)) return 0; - if(std::abs(intVal) < MIN_BIN_BITS || static_cast(std::abs(intVal)) > (8*src->mSize)) + if(std::abs(intVal) < int{MinBinSize}*8 || static_cast(std::abs(intVal)) > (8*src->mSize)) { - TrErrorAt(tr, line, col, "Expected a value of (+/-) %d to %d.\n", MIN_BIN_BITS, 8*src->mSize); + TrErrorAt(tr, line, col, "Expected a value of (+/-) %d to %d.\n", MinBinSize*8, 8*src->mSize); return 0; } src->mBits = intVal; @@ -1622,7 +1629,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) { if(!TrReadOperator(tr, ",")) return 0; - if(!TrReadInt(tr, MIN_ASCII_BITS, MAX_ASCII_BITS, &intVal)) + if(!TrReadInt(tr, MinASCIIBits, MaxASCIIBits, &intVal)) return 0; src->mSize = 0; src->mBits = intVal; @@ -1656,22 +1663,22 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) src->mOffset = 0; if(!TrReadOperator(tr, ":")) return 0; - if(!TrReadString(tr, MAX_PATH_LEN, src->mPath)) + if(!TrReadString(tr, 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) +auto ReadSofaRef(TokenReaderT *tr, SourceRefT *src) -> int { - char ident[MAX_IDENT_LEN+1]; + std::array ident{}; uint line, col; int intVal; TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + if(!TrReadIdent(tr, ident)) return 0; - src->mFormat = MatchSourceFormat(ident); + src->mFormat = MatchSourceFormat(ident.data()); if(src->mFormat != SF_SOFA) { TrErrorAt(tr, line, col, "Expected the SOFA source format.\n"); @@ -1695,13 +1702,13 @@ static int ReadSofaRef(TokenReaderT *tr, SourceRefT *src) src->mOffset = 0; if(!TrReadOperator(tr, ":")) return 0; - if(!TrReadString(tr, MAX_PATH_LEN, src->mPath)) + if(!TrReadString(tr, src->mPath)) return 0; return 1; } // Match the target ear (index) from a given identifier. -static int MatchTargetEar(const char *ident) +auto MatchTargetEar(const char *ident) -> int { if(al::strcasecmp(ident, "left") == 0) return 0; @@ -1712,13 +1719,13 @@ static int MatchTargetEar(const char *ident) // Calculate the onset time of an HRIR and average it with any existing // timing for its field, elevation, azimuth, and ear. -static constexpr int OnsetRateMultiple{10}; -static double AverageHrirOnset(PPhaseResampler &rs, al::span upsampled, const uint rate, - const uint n, const double *hrir, const double f, const double onset) +constexpr int OnsetRateMultiple{10}; +auto AverageHrirOnset(PPhaseResampler &rs, al::span upsampled, const uint rate, + const al::span hrir, const double f, const double onset) -> double { - rs.process(n, hrir, static_cast(upsampled.size()), upsampled.data()); + rs.process(hrir, upsampled); - auto abs_lt = [](const double &lhs, const double &rhs) -> bool + auto abs_lt = [](const double lhs, const double rhs) -> bool { return std::abs(lhs) < std::abs(rhs); }; auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt); return Lerp(onset, static_cast(std::distance(upsampled.cbegin(), iter))/(10*rate), f); @@ -1726,36 +1733,35 @@ static double AverageHrirOnset(PPhaseResampler &rs, al::span upsampled, // 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) +void AverageHrirMagnitude(const uint fftSize, const al::span hrir, const double f, + const al::span mag) { - uint m = 1 + (n / 2), i; - std::vector h(n); - std::vector r(n); + const uint m{1 + (fftSize/2)}; + std::vector h(fftSize); + std::vector r(m); - for(i = 0;i < points;i++) - h[i] = hrir[i]; - for(;i < n;i++) - h[i] = 0.0; - FftForward(n, h.data()); - MagnitudeResponse(n, h.data(), r.data()); - for(i = 0;i < m;i++) + auto hiter = std::copy(hrir.cbegin(), hrir.cend(), h.begin()); + std::fill(hiter, h.end(), 0.0); + forward_fft(h); + MagnitudeResponse(h, r); + for(uint 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, const uint outRate) +auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> int { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; - hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize); - double *hrirs = hData->mHrirsBase.data(); - auto hrir = std::make_unique(hData->mIrSize); + hData->mHrirsBase.resize(size_t{channels} * hData->mIrCount * hData->mIrSize); + const auto hrirs = al::span{hData->mHrirsBase}; + auto hrir = std::vector(hData->mIrSize); uint line, col, fi, ei, ai; - std::vector onsetSamples(OnsetRateMultiple * hData->mIrPoints); + std::vector onsetSamples(size_t{OnsetRateMultiple} * hData->mIrPoints); PPhaseResampler onsetResampler; onsetResampler.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate); - al::optional resampler; + std::optional resampler; if(outRate && outRate != hData->mIrRate) resampler.emplace().init(hData->mIrRate, outRate); const double rateScale{outRate ? static_cast(outRate) / hData->mIrRate : 1.0}; @@ -1768,57 +1774,50 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate int count{0}; while(TrIsOperator(tr, "[")) { - double factor[2]{ 1.0, 1.0 }; + std::array factor{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); + SourceRefT src{}; if(!ReadSofaRef(tr, &src)) return 0; if(hData->mChannelType == CT_STEREO) { - char type[MAX_IDENT_LEN+1]; - ChannelTypeT channelType; + std::array type{}; - if(!TrReadIdent(tr, MAX_IDENT_LEN, type)) + if(!TrReadIdent(tr, type)) return 0; - channelType = MatchChannelType(type); - + const ChannelTypeT channelType{MatchChannelType(type.data())}; 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; + 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)) + std::array type{}; + if(!TrReadIdent(tr, type)) return 0; - channelType = MatchChannelType(type); + ChannelTypeT channelType{MatchChannelType(type.data())}; if(channelType != CT_MONO) { TrErrorAt(tr, line, col, "Expected a mono channel type.\n"); @@ -1827,20 +1826,19 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate src.mChannel = 0; } - sofa = LoadSofaFile(&src, hData->mIrRate, hData->mIrPoints); + MYSOFA_EASY *sofa{LoadSofaFile(&src, hData->mIrRate, hData->mIrPoints)}; if(!sofa) return 0; - for(si = 0;si < sofa->hrtf->M;si++) + const auto srcPosValues = al::span{sofa->hrtf->SourcePosition.values, + sofa->hrtf->M*3_uz}; + for(uint 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); + std::array aer{srcPosValues[3_uz*si], srcPosValues[3_uz*si + 1], + srcPosValues[3_uz*si + 2]}; + mysofa_c2s(aer.data()); if(std::fabs(aer[1]) >= 89.999f) aer[0] = 0.0f; @@ -1870,30 +1868,33 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate continue; HrirAzT *azd = &field->mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) + if(!azd->mIrs[0].empty()) { 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.get()); - azd->mIrs[0] = &hrirs[hData->mIrSize * azd->mIndex]; + const auto hrirPoints = al::span{hrir}.first(hData->mIrPoints); + ExtractSofaHrir(sofa->hrtf, si, 0, src.mOffset, hrirPoints); + azd->mIrs[0] = hrirs.subspan(size_t{hData->mIrSize}*azd->mIndex, hData->mIrSize); azd->mDelays[0] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate, - hData->mIrPoints, hrir.get(), 1.0, azd->mDelays[0]); + hrirPoints, 1.0, azd->mDelays[0]); if(resampler) - resampler->process(hData->mIrPoints, hrir.get(), hData->mIrSize, hrir.get()); - AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.get(), 1.0, azd->mIrs[0]); + resampler->process(hrirPoints, hrir); + AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.first(irPoints), 1.0, + azd->mIrs[0]); if(src.mChannel == 1) { - ExtractSofaHrir(sofa, si, 1, src.mOffset, hData->mIrPoints, hrir.get()); - azd->mIrs[1] = &hrirs[hData->mIrSize * (hData->mIrCount + azd->mIndex)]; + ExtractSofaHrir(sofa->hrtf, si, 1, src.mOffset, hrirPoints); + azd->mIrs[1] = hrirs.subspan( + (size_t{hData->mIrCount}+azd->mIndex) * hData->mIrSize, hData->mIrSize); azd->mDelays[1] = AverageHrirOnset(onsetResampler, onsetSamples, - hData->mIrRate, hData->mIrPoints, hrir.get(), 1.0, azd->mDelays[1]); + hData->mIrRate, hrirPoints, 1.0, azd->mDelays[1]); if(resampler) - resampler->process(hData->mIrPoints, hrir.get(), hData->mIrSize, - hrir.get()); - AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.get(), 1.0, azd->mIrs[1]); + resampler->process(hrirPoints, hrir); + AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.first(irPoints), 1.0, + azd->mIrs[1]); } // TODO: Since some SOFA files contain minimum phase HRIRs, @@ -1910,7 +1911,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate return 0; HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) + if(!azd->mIrs[0].empty()) { TrErrorAt(tr, line, col, "Redefinition of source.\n"); return 0; @@ -1918,10 +1919,9 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate if(!TrReadOperator(tr, "=")) return 0; - for(;;) + while(true) { - SourceRefT src; - + SourceRefT src{}; if(!ReadSourceRef(tr, &src)) return 0; @@ -1932,29 +1932,30 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate printf("\rLoading sources... %d file%s", count, (count==1)?"":"s"); fflush(stdout); - if(!LoadSource(&src, hData->mIrRate, hData->mIrPoints, hrir.get())) + if(!LoadSource(&src, hData->mIrRate, al::span{hrir}.first(hData->mIrPoints))) return 0; uint ti{0}; if(hData->mChannelType == CT_STEREO) { - char ident[MAX_IDENT_LEN+1]; - - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + std::array ident{}; + if(!TrReadIdent(tr, ident)) return 0; - ti = static_cast(MatchTargetEar(ident)); + ti = static_cast(MatchTargetEar(ident.data())); if(static_cast(ti) < 0) { TrErrorAt(tr, line, col, "Expected a target ear.\n"); return 0; } } - azd->mIrs[ti] = &hrirs[hData->mIrSize * (ti * hData->mIrCount + azd->mIndex)]; + const auto hrirPoints = al::span{hrir}.first(hData->mIrPoints); + azd->mIrs[ti] = hrirs.subspan((ti*size_t{hData->mIrCount}+azd->mIndex)*hData->mIrSize, + hData->mIrSize); azd->mDelays[ti] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate, - hData->mIrPoints, hrir.get(), 1.0 / factor[ti], azd->mDelays[ti]); + hrirPoints, 1.0/factor[ti], azd->mDelays[ti]); if(resampler) - resampler->process(hData->mIrPoints, hrir.get(), hData->mIrSize, hrir.get()); - AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.get(), 1.0 / factor[ti], + resampler->process(hrirPoints, hrir); + AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.subspan(irPoints), 1.0/factor[ti], azd->mIrs[ti]); factor[ti] += 1.0; if(!TrIsOperator(tr, "+")) @@ -1963,12 +1964,12 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate } if(hData->mChannelType == CT_STEREO) { - if(azd->mIrs[0] == nullptr) + if(azd->mIrs[0].empty()) { TrErrorAt(tr, line, col, "Missing left ear source reference(s).\n"); return 0; } - else if(azd->mIrs[1] == nullptr) + if(azd->mIrs[1].empty()) { TrErrorAt(tr, line, col, "Missing right ear source reference(s).\n"); return 0; @@ -1976,7 +1977,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate } } printf("\n"); - hrir = nullptr; + hrir.clear(); if(resampler) { hData->mIrRate = outRate; @@ -1990,7 +1991,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) + if(!azd->mIrs[0].empty()) break; } if(ai < hData->mFds[fi].mEvs[ei].mAzs.size()) @@ -2008,7 +2009,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] == nullptr) + if(azd->mIrs[0].empty()) { TrError(tr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai); return 0; @@ -2025,31 +2026,33 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - - azd->mIrs[ti] = &hrirs[hData->mIrSize * (ti * hData->mIrCount + azd->mIndex)]; + azd->mIrs[ti] = hrirs.subspan( + (ti*size_t{hData->mIrCount} + azd->mIndex) * hData->mIrSize, + hData->mIrSize); } } } } if(!TrLoad(tr)) { - mysofa_cache_release_all(); + gSofaCache.clear(); return 1; } TrError(tr, "Errant data at end of source list.\n"); - mysofa_cache_release_all(); + gSofaCache.clear(); return 0; } +} /* namespace */ -bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount, - const char *filename, const uint fftSize, const uint truncSize, const uint outRate, +bool LoadDefInput(std::istream &istream, const al::span startbytes, + const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData) { TokenReaderT tr{istream}; - TrSetup(startbytes, startbytecount, filename, &tr); + TrSetup(startbytes, filename, &tr); if(!ProcessMetrics(&tr, fftSize, truncSize, chanMode, hData) || !ProcessSources(&tr, hData, outRate)) return false; diff --git a/Engine/lib/openal-soft/utils/makemhr/loaddef.h b/Engine/lib/openal-soft/utils/makemhr/loaddef.h index 63600dcd3..3eafc8254 100644 --- a/Engine/lib/openal-soft/utils/makemhr/loaddef.h +++ b/Engine/lib/openal-soft/utils/makemhr/loaddef.h @@ -2,12 +2,15 @@ #define LOADDEF_H #include +#include + +#include "alspan.h" #include "makemhr.h" -bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount, - const char *filename, const uint fftSize, const uint truncSize, const uint outRate, +bool LoadDefInput(std::istream &istream, const al::span startbytes, + const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData); #endif /* LOADDEF_H */ diff --git a/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp b/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp index dcb0a35ed..903019d86 100644 --- a/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp +++ b/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp @@ -33,12 +33,15 @@ #include #include #include +#include #include +#include #include #include -#include "aloptional.h" #include "alspan.h" +#include "alstring.h" +#include "alnumeric.h" #include "makemhr.h" #include "polyphase_resampler.h" #include "sofa-support.h" @@ -46,6 +49,9 @@ #include "mysofa.h" +namespace { + +using namespace std::string_view_literals; using uint = unsigned int; /* Attempts to produce a compatible layout. Most data sets tend to be @@ -54,19 +60,19 @@ using uint = unsigned int; * 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 PrepareLayout(const al::span xyzs, HrirDataT *hData) -> bool { fprintf(stdout, "Detecting compatible layout...\n"); - auto fds = GetCompatibleLayout(m, xyzs); + auto fds = GetCompatibleLayout(xyzs); if(fds.size() > MAX_FD_COUNT) { fprintf(stdout, "Incompatible layout (inumerable radii).\n"); return false; } - double distances[MAX_FD_COUNT]{}; - uint evCounts[MAX_FD_COUNT]{}; + std::array distances{}; + std::array evCounts{}; auto azCounts = std::vector>(MAX_FD_COUNT); for(auto &azs : azCounts) azs.fill(0u); @@ -86,12 +92,11 @@ static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData) ++fi; } - fprintf(stdout, "Using %u of %u IRs.\n", ir_total, m); - const auto azs = al::as_span(azCounts).first(); - return PrepareHrirData({distances, fi}, evCounts, azs, hData); + fprintf(stdout, "Using %u of %zu IRs.\n", ir_total, xyzs.size()/3); + const auto azs = al::span{azCounts}.first(); + return PrepareHrirData(al::span{distances}.first(fi), evCounts, azs, hData); } - float GetSampleRate(MYSOFA_HRTF *sofaHrtf) { const char *srate_dim{nullptr}; @@ -100,7 +105,7 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) MYSOFA_ATTRIBUTE *srate_attrs{srate_array->attributes}; while(srate_attrs) { - if(std::string{"DIMENSION_LIST"} == srate_attrs->name) + if("DIMENSION_LIST"sv == srate_attrs->name) { if(srate_dim) { @@ -109,7 +114,7 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) } srate_dim = srate_attrs->value; } - else if(std::string{"Units"} == srate_attrs->name) + else if("Units"sv == srate_attrs->name) { if(srate_units) { @@ -128,7 +133,7 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) fprintf(stderr, "Missing sample rate dimensions\n"); return 0.0f; } - if(srate_dim != std::string{"I"}) + if(srate_dim != "I"sv) { fprintf(stderr, "Unsupported sample rate dimensions: %s\n", srate_dim); return 0.0f; @@ -138,40 +143,40 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) fprintf(stderr, "Missing sample rate unit type\n"); return 0.0f; } - if(srate_units != std::string{"hertz"}) + if(srate_units != "hertz"sv) { fprintf(stderr, "Unsupported sample rate unit type: %s\n", srate_units); return 0.0f; } /* I dimensions guarantees 1 element, so just extract it. */ - if(srate_array->values[0] < MIN_RATE || srate_array->values[0] > MAX_RATE) + const auto values = al::span{srate_array->values, sofaHrtf->I}; + if(values[0] < float{MIN_RATE} || values[0] > float{MAX_RATE}) { - fprintf(stderr, "Sample rate out of range: %f (expected %u to %u)", srate_array->values[0], - MIN_RATE, MAX_RATE); + fprintf(stderr, "Sample rate out of range: %f (expected %u to %u)", values[0], MIN_RATE, + MAX_RATE); return 0.0f; } - return srate_array->values[0]; + return values[0]; } enum class DelayType : uint8_t { None, I_R, /* [1][Channels] */ M_R, /* [HRIRs][Channels] */ - Invalid, }; -DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf) +auto PrepareDelay(MYSOFA_HRTF *sofaHrtf) -> std::optional { 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("DIMENSION_LIST"sv == delay_attrs->name) { if(delay_dim) { fprintf(stderr, "Duplicate Delay.DIMENSION_LIST\n"); - return DelayType::Invalid; + return std::nullopt; } delay_dim = delay_attrs->value; } @@ -185,13 +190,13 @@ DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf) fprintf(stderr, "Missing delay dimensions\n"); return DelayType::None; } - if(delay_dim == std::string{"I,R"}) + if(delay_dim == "I,R"sv) return DelayType::I_R; - else if(delay_dim == std::string{"M,R"}) + if(delay_dim == "M,R"sv) return DelayType::M_R; fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim); - return DelayType::Invalid; + return std::nullopt; } bool CheckIrData(MYSOFA_HRTF *sofaHrtf) @@ -201,7 +206,7 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf) MYSOFA_ATTRIBUTE *ir_attrs{ir_array->attributes}; while(ir_attrs) { - if(std::string{"DIMENSION_LIST"} == ir_attrs->name) + if("DIMENSION_LIST"sv == ir_attrs->name) { if(ir_dim) { @@ -220,7 +225,7 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf) fprintf(stderr, "Missing IR dimensions\n"); return false; } - if(ir_dim != std::string{"M,R,N"}) + if(ir_dim != "M,R,N"sv) { fprintf(stderr, "Unsupported IR dimensions: %s\n", ir_dim); return false; @@ -230,13 +235,13 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf) /* Calculate the onset time of a HRIR. */ -static constexpr int OnsetRateMultiple{10}; -static double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n, - al::span upsampled, const double *hrir) +constexpr int OnsetRateMultiple{10}; +auto CalcHrirOnset(PPhaseResampler &rs, const uint rate, al::span upsampled, + const al::span hrir) -> double { - rs.process(n, hrir, static_cast(upsampled.size()), upsampled.data()); + rs.process(hrir, upsampled); - auto abs_lt = [](const double &lhs, const double &rhs) -> bool + auto abs_lt = [](const double lhs, const double rhs) -> bool { return std::abs(lhs) < std::abs(rhs); }; auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt); return static_cast(std::distance(upsampled.cbegin(), iter)) / @@ -244,16 +249,16 @@ static double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n, } /* Calculate the magnitude response of a HRIR. */ -static void CalcHrirMagnitude(const uint points, const uint n, al::span h, double *hrir) +void CalcHrirMagnitude(const uint points, al::span h, const al::span hrir) { - auto iter = std::copy_n(hrir, points, h.begin()); + auto iter = std::copy_n(hrir.cbegin(), points, h.begin()); std::fill(iter, h.end(), complex_d{0.0, 0.0}); - FftForward(n, h.data()); - MagnitudeResponse(n, h.data(), hrir); + forward_fft(h); + MagnitudeResponse(h, hrir.first((h.size()/2) + 1)); } -static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType delayType, +bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType delayType, const uint outRate) { std::atomic loaded_count{0u}; @@ -261,27 +266,27 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayTy auto load_proc = [sofaHrtf,hData,delayType,outRate,&loaded_count]() -> bool { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; - hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize, 0.0); - double *hrirs = hData->mHrirsBase.data(); + hData->mHrirsBase.resize(channels * size_t{hData->mIrCount} * hData->mIrSize, 0.0); + const auto hrirs = al::span{hData->mHrirsBase}; - std::unique_ptr restmp; - al::optional resampler; + std::vector restmp; + std::optional resampler; if(outRate && outRate != hData->mIrRate) { resampler.emplace().init(hData->mIrRate, outRate); - restmp = std::make_unique(sofaHrtf->N); + restmp.resize(sofaHrtf->N); } + const auto srcPosValues = al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz}; + const auto irValues = al::span{sofaHrtf->DataIR.values, + size_t{sofaHrtf->M}*sofaHrtf->R*sofaHrtf->N}; for(uint si{0u};si < sofaHrtf->M;++si) { loaded_count.fetch_add(1u); - float aer[3]{ - sofaHrtf->SourcePosition.values[3*si], - sofaHrtf->SourcePosition.values[3*si + 1], - sofaHrtf->SourcePosition.values[3*si + 2] - }; - mysofa_c2s(aer); + std::array aer{srcPosValues[3_uz*si], srcPosValues[3_uz*si + 1], + srcPosValues[3_uz*si + 2]}; + mysofa_c2s(aer.data()); if(std::abs(aer[1]) >= 89.999f) aer[0] = 0.0f; @@ -307,8 +312,8 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayTy ai %= static_cast(field->mEvs[ei].mAzs.size()); if(std::abs(af) >= 0.1) continue; - HrirAzT *azd = &field->mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) + HrirAzT &azd = field->mEvs[ei].mAzs[ai]; + if(!azd.mIrs[0].empty()) { fprintf(stderr, "\nMultiple measurements near [ a=%f, e=%f, r=%f ].\n", aer[0], aer[1], aer[2]); @@ -317,30 +322,33 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayTy for(uint ti{0u};ti < channels;++ti) { - azd->mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd->mIndex)]; + azd.mIrs[ti] = hrirs.subspan( + (size_t{hData->mIrCount}*ti + azd.mIndex) * hData->mIrSize, hData->mIrSize); + const auto ir = irValues.subspan((size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N, + sofaHrtf->N); if(!resampler) - std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N], - sofaHrtf->N, azd->mIrs[ti]); + std::copy_n(ir.cbegin(), ir.size(), azd.mIrs[ti].begin()); else { - std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N], - sofaHrtf->N, restmp.get()); - resampler->process(sofaHrtf->N, restmp.get(), hData->mIrSize, azd->mIrs[ti]); + std::copy_n(ir.cbegin(), ir.size(), restmp.begin()); + resampler->process(restmp, azd.mIrs[ti]); } } /* Include any per-channel or per-HRIR delays. */ if(delayType == DelayType::I_R) { - const float *delayValues{sofaHrtf->DataDelay.values}; + const auto delayValues = al::span{sofaHrtf->DataDelay.values, + size_t{sofaHrtf->I}*sofaHrtf->R}; for(uint ti{0u};ti < channels;++ti) - azd->mDelays[ti] = delayValues[ti] / static_cast(hData->mIrRate); + azd.mDelays[ti] = delayValues[ti] / static_cast(hData->mIrRate); } else if(delayType == DelayType::M_R) { - const float *delayValues{sofaHrtf->DataDelay.values}; + const auto delayValues = al::span{sofaHrtf->DataDelay.values, + size_t{sofaHrtf->M}*sofaHrtf->R}; for(uint ti{0u};ti < channels;++ti) - azd->mDelays[ti] = delayValues[si*sofaHrtf->R + ti] / + azd.mDelays[ti] = delayValues[si*sofaHrtf->R + ti] / static_cast(hData->mIrRate); } } @@ -374,7 +382,7 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayTy struct MagCalculator { const uint mFftSize{}; const uint mIrPoints{}; - std::vector mIrs{}; + std::vector> mIrs{}; std::atomic mCurrent{}; std::atomic mDone{}; @@ -382,7 +390,7 @@ struct MagCalculator { { auto htemp = std::vector(mFftSize); - while(1) + while(true) { /* Load the current index to process. */ size_t idx{mCurrent.load()}; @@ -397,7 +405,7 @@ struct MagCalculator { */ } while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed)); - CalcHrirMagnitude(mIrPoints, mFftSize, htemp, mIrs[idx]); + CalcHrirMagnitude(mIrPoints, htemp, mIrs[idx]); /* Increment the number of IRs done. */ mDone.fetch_add(1); @@ -405,22 +413,25 @@ struct MagCalculator { } }; -bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize, +} // namespace + +bool LoadSofaFile(const std::string_view filename, const uint numThreads, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData) { int err; - MySofaHrtfPtr sofaHrtf{mysofa_load(filename, &err)}; + MySofaHrtfPtr sofaHrtf{mysofa_load(std::string{filename}.c_str(), &err)}; if(!sofaHrtf) { - fprintf(stdout, "Error: Could not load %s: %s\n", filename, SofaErrorStr(err)); + fprintf(stdout, "Error: Could not load %.*s: %s\n", al::sizei(filename), filename.data(), + 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)); + fprintf(stderr, "Warning: Supposedly malformed source file '%.*s' (%s).\n", + al::sizei(filename), filename.data(), SofaErrorStr(err)); mysofa_tocartesian(sofaHrtf.get()); @@ -459,19 +470,19 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz /* Assume a default head radius of 9cm. */ hData->mRadius = 0.09; - hData->mIrRate = static_cast(GetSampleRate(sofaHrtf.get()) + 0.5f); + hData->mIrRate = static_cast(std::lround(GetSampleRate(sofaHrtf.get()))); if(!hData->mIrRate) return false; - DelayType delayType = PrepareDelay(sofaHrtf.get()); - if(delayType == DelayType::Invalid) + const auto delayType = PrepareDelay(sofaHrtf.get()); + if(!delayType) return false; if(!CheckIrData(sofaHrtf.get())) return false; - if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData)) + if(!PrepareLayout(al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz}, hData)) return false; - if(!LoadResponses(sofaHrtf.get(), hData, delayType, outRate)) + if(!LoadResponses(sofaHrtf.get(), hData, *delayType, outRate)) return false; sofaHrtf = nullptr; @@ -484,7 +495,7 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz for(;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd.mIrs[0] != nullptr) break; + if(!azd.mIrs[0].empty()) break; } if(ai < hData->mFds[fi].mEvs[ei].mAzs.size()) break; @@ -500,7 +511,7 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd.mIrs[0] == nullptr) + if(azd.mIrs[0].empty()) { fprintf(stderr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai); return false; @@ -512,7 +523,7 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz size_t hrir_total{0}; const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; - double *hrirs = hData->mHrirsBase.data(); + const auto hrirs = al::span{hData->mHrirsBase}; for(uint fi{0u};fi < hData->mFds.size();fi++) { for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++) @@ -520,8 +531,9 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzs.size();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)]; + for(size_t ti{0u};ti < channels;ti++) + azd.mIrs[ti] = hrirs.subspan((hData->mIrCount*ti + azd.mIndex)*hData->mIrSize, + hData->mIrSize); } } @@ -533,7 +545,7 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz auto onset_proc = [hData,channels,&hrir_done]() -> bool { /* Temporary buffer used to calculate the IR's onset. */ - auto upsampled = std::vector(OnsetRateMultiple * hData->mIrPoints); + auto upsampled = std::vector(size_t{OnsetRateMultiple} * hData->mIrPoints); /* This resampler is used to help detect the response onset. */ PPhaseResampler rs; rs.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate); @@ -547,8 +559,8 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz for(uint ti{0};ti < channels;ti++) { hrir_done.fetch_add(1u, std::memory_order_acq_rel); - azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, hData->mIrPoints, - upsampled, azd.mIrs[ti]); + azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, upsampled, + azd.mIrs[ti].first(hData->mIrPoints)); } } } diff --git a/Engine/lib/openal-soft/utils/makemhr/loadsofa.h b/Engine/lib/openal-soft/utils/makemhr/loadsofa.h index 82dce85a0..90d77a0f8 100644 --- a/Engine/lib/openal-soft/utils/makemhr/loadsofa.h +++ b/Engine/lib/openal-soft/utils/makemhr/loadsofa.h @@ -1,10 +1,12 @@ #ifndef LOADSOFA_H #define LOADSOFA_H +#include + #include "makemhr.h" -bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize, +bool LoadSofaFile(const std::string_view filename, const uint numThreads, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData); #endif /* LOADSOFA_H */ diff --git a/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp b/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp index ae301dc33..b6bafba93 100644 --- a/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp +++ b/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp @@ -59,7 +59,7 @@ * 1999 */ -#define _UNICODE +#define _UNICODE /* NOLINT(bugprone-reserved-identifier) */ #include "config.h" #include "makemhr.h" @@ -73,23 +73,21 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include -#ifdef HAVE_GETOPT -#include -#else -#include "../getopt.h" -#endif - #include "alcomplex.h" -#include "alfstream.h" +#include "alnumbers.h" +#include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "loaddef.h" @@ -98,61 +96,61 @@ #include "win_main_utf8.h" -namespace { - -using namespace std::placeholders; - -} // namespace - -#ifndef M_PI -#define M_PI (3.14159265358979323846) -#endif - - HrirDataT::~HrirDataT() = default; -// 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. -}; +namespace { +using namespace std::string_view_literals; + +struct FileDeleter { + void operator()(gsl::owner f) { fclose(f); } +}; +using FilePtr = std::unique_ptr; // The epsilon used to maintain signal stability. -#define EPSILON (1e-9) +constexpr double Epsilon{1e-9}; // The limits to the FFT window size override on the command line. -#define MIN_FFTSIZE (65536) -#define MAX_FFTSIZE (131072) +constexpr uint MinFftSize{65536}; +constexpr uint MaxFftSize{131072}; // The limits to the equalization range limit on the command line. -#define MIN_LIMIT (2.0) -#define MAX_LIMIT (120.0) +constexpr double MinLimit{2.0}; +constexpr double MaxLimit{120.0}; // The limits to the truncation window size on the command line. -#define MIN_TRUNCSIZE (16) -#define MAX_TRUNCSIZE (128) +constexpr uint MinTruncSize{16}; +constexpr uint MaxTruncSize{128}; // The limits to the custom head radius on the command line. -#define MIN_CUSTOM_RADIUS (0.05) -#define MAX_CUSTOM_RADIUS (0.15) - -// 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 (64) -#define DEFAULT_HEAD_MODEL (HM_DATASET) -#define DEFAULT_CUSTOM_RADIUS (0.0) +constexpr double MinCustomRadius{0.05}; +constexpr double MaxCustomRadius{0.15}; // The maximum propagation delay value supported by OpenAL Soft. -#define MAX_HRTD (63.0) +constexpr double MaxHrtd{63.0}; // The OpenAL Soft HRTF format marker. It stands for minimum-phase head // response protocol 03. -#define MHR_FORMAT ("MinPHR03") +constexpr auto GetMHRMarker() noexcept { return "MinPHR03"sv; } + + +// 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. + + HM_Default = HM_Dataset +}; + + +// The defaults for the command line options. +constexpr uint DefaultFftSize{65536}; +constexpr bool DefaultEqualize{true}; +constexpr bool DefaultSurface{true}; +constexpr double DefaultLimit{24.0}; +constexpr uint DefaultTruncSize{64}; +constexpr double DefaultCustomRadius{0.0}; /* Channel index enums. Mono uses LeftChannel only. */ enum ChannelIndex : uint { @@ -165,29 +163,28 @@ enum ChannelIndex : uint { * pattern string are replaced with the replacement string. The result is * truncated if necessary. */ -static std::string StrSubst(al::span in, const al::span pat, - const al::span rep) +auto StrSubst(std::string_view in, const std::string_view pat, const std::string_view rep) -> std::string { std::string ret; ret.reserve(in.size() + pat.size()); while(in.size() >= pat.size()) { - if(al::strncasecmp(in.data(), pat.data(), pat.size()) == 0) + if(al::starts_with(in, pat)) { - in = in.subspan(pat.size()); - ret.append(rep.data(), rep.size()); + in = in.substr(pat.size()); + ret += rep; } else { size_t endpos{1}; - while(endpos < in.size() && in[endpos] != pat.front()) + while(endpos < in.size() && std::toupper(in[endpos]) != std::toupper(pat.front())) ++endpos; - ret.append(in.data(), endpos); - in = in.subspan(endpos); + ret += in.substr(0, endpos); + in = in.substr(endpos); } } - ret.append(in.data(), in.size()); + ret += in; return ret; } @@ -198,12 +195,12 @@ static std::string StrSubst(al::span in, const al::span *********************/ // Simple clamp routine. -static double Clamp(const double val, const double lower, const double upper) +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) +inline uint dither_rng(uint *seed) { *seed = *seed * 96314165 + 907633515; return *seed; @@ -211,69 +208,44 @@ static inline uint dither_rng(uint *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) +void TpdfDither(const al::span out, const al::span in, const double scale, + const size_t channel, const size_t step, uint *seed) { static constexpr double PRNG_SCALE = 1.0 / std::numeric_limits::max(); + assert(channel < step); - for(uint i{0};i < count;i++) + for(size_t i{0};i < in.size();++i) { uint prn0{dither_rng(seed)}; uint prn1{dither_rng(seed)}; - *out = std::round(*(in++)*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE)); - out += step; + out[i*step + channel] = std::round(in[i]*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE)); } } - -/* 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. - */ -inline static void Hilbert(const uint n, complex_d *inout) -{ complex_hilbert({inout, n}); } - -/* 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) +void LimitMagnitudeResponse(const uint n, const uint m, const double limit, + const al::span inout) { - double halfLim; - uint i, lower, upper; - double ave; - - halfLim = limit / 2.0; + const double halfLim{limit / 2.0}; // Convert the response to dB. - for(i = 0;i < m;i++) - out[i] = 20.0 * std::log10(in[i]); + for(uint i{0};i < m;++i) + inout[i] = 20.0 * std::log10(inout[i]); // Use six octaves to calculate the average magnitude of the signal. - lower = (static_cast(std::ceil(n / std::pow(2.0, 8.0)))) - 1; - upper = (static_cast(std::floor(n / std::pow(2.0, 2.0)))) - 1; - ave = 0.0; - for(i = lower;i <= upper;i++) - ave += out[i]; + const auto lower = (static_cast(std::ceil(n / std::pow(2.0, 8.0)))) - 1; + const auto upper = (static_cast(std::floor(n / std::pow(2.0, 2.0)))) - 1; + double ave{0.0}; + for(uint i{lower};i <= upper;++i) + ave += inout[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); + for(uint i{0};i < m;++i) + inout[i] = Clamp(inout[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); + for(uint i{0};i < m;++i) + inout[i] = std::pow(10.0, inout[i] / 20.0); } /* Reconstructs the minimum-phase component for the given magnitude response @@ -281,26 +253,24 @@ static void LimitMagnitudeResponse(const uint n, const uint m, const double limi * residuals (which were discarded). The mirrored half of the response is * reconstructed. */ -static void MinimumPhase(const uint n, double *mags, complex_d *out) +void MinimumPhase(const al::span mags, const al::span out) { - const uint m{(n/2) + 1}; + assert(mags.size() == out.size()); + const size_t m{(mags.size()/2) + 1}; - uint i; + size_t i; for(i = 0;i < m;i++) out[i] = std::log(mags[i]); - for(;i < n;i++) + for(;i < mags.size();++i) { - mags[i] = mags[n - i]; - out[i] = out[n - i]; + mags[i] = mags[mags.size() - i]; + out[i] = out[mags.size() - i]; } - Hilbert(n, out); + complex_hilbert(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] = a * mags[i]; - } + mags[0] = Epsilon; + for(i = 0;i < mags.size();++i) + out[i] = std::polar(mags[i], out[i].imag()); } @@ -309,15 +279,12 @@ static void MinimumPhase(const uint n, double *mags, complex_d *out) ***************************/ // Write an ASCII string to a file. -static int WriteAscii(const char *out, FILE *fp, const char *filename) +auto WriteAscii(const std::string_view out, std::ostream &ostream, const std::string_view filename) -> int { - size_t len; - - len = strlen(out); - if(fwrite(out, 1, len, fp) != len) + if(!ostream.write(out.data(), std::streamsize(out.size())) || ostream.bad()) { - fclose(fp); - fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename); + fprintf(stderr, "\nError: Bad write to file '%.*s'.\n", al::sizei(filename), + filename.data()); return 0; } return 1; @@ -325,105 +292,104 @@ static int WriteAscii(const char *out, FILE *fp, const char *filename) // 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) +auto WriteBin4(const uint bytes, const uint32_t in, std::ostream &ostream, + const std::string_view filename) -> int { - uint8_t out[4]; - uint i; + std::array out{}; + for(uint i{0};i < bytes;i++) + out[i] = static_cast((in>>(i*8)) & 0x000000FF); - for(i = 0;i < bytes;i++) - out[i] = (in>>(i*8)) & 0x000000FF; - - if(fwrite(out, 1, bytes, fp) != bytes) + if(!ostream.write(out.data(), std::streamsize(bytes)) || ostream.bad()) { - fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename); + fprintf(stderr, "\nError: Bad write to file '%.*s'.\n", al::sizei(filename), + filename.data()); return 0; } return 1; } // Store the OpenAL Soft HRTF data set. -static int StoreMhr(const HrirDataT *hData, const char *filename) +auto StoreMhr(const HrirDataT *hData, const std::string_view filename) -> bool { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; const uint n{hData->mIrPoints}; uint dither_seed{22222}; - uint fi, ei, ai, i; - FILE *fp; - if((fp=fopen(filename, "wb")) == nullptr) + std::ofstream ostream{std::filesystem::u8path(filename)}; + if(!ostream.is_open()) { - fprintf(stderr, "\nError: Could not open MHR file '%s'.\n", filename); - return 0; + fprintf(stderr, "\nError: Could not open MHR file '%.*s'.\n", al::sizei(filename), + filename.data()); + return false; } - if(!WriteAscii(MHR_FORMAT, fp, filename)) - return 0; - if(!WriteBin4(4, hData->mIrRate, fp, filename)) - return 0; - if(!WriteBin4(1, static_cast(hData->mChannelType), fp, filename)) - return 0; - if(!WriteBin4(1, hData->mIrPoints, fp, filename)) - return 0; - if(!WriteBin4(1, static_cast(hData->mFds.size()), fp, filename)) - return 0; - for(fi = static_cast(hData->mFds.size()-1);fi < hData->mFds.size();fi--) + if(!WriteAscii(GetMHRMarker(), ostream, filename)) + return false; + if(!WriteBin4(4, hData->mIrRate, ostream, filename)) + return false; + if(!WriteBin4(1, static_cast(hData->mChannelType), ostream, filename)) + return false; + if(!WriteBin4(1, hData->mIrPoints, ostream, filename)) + return false; + if(!WriteBin4(1, static_cast(hData->mFds.size()), ostream, filename)) + return false; + for(size_t fi{hData->mFds.size()-1};fi < hData->mFds.size();--fi) { auto fdist = static_cast(std::round(1000.0 * hData->mFds[fi].mDistance)); - if(!WriteBin4(2, fdist, fp, filename)) - return 0; - if(!WriteBin4(1, static_cast(hData->mFds[fi].mEvs.size()), fp, filename)) - return 0; - for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++) + if(!WriteBin4(2, fdist, ostream, filename)) + return false; + if(!WriteBin4(1, static_cast(hData->mFds[fi].mEvs.size()), ostream, filename)) + return false; + for(size_t ei{0};ei < hData->mFds[fi].mEvs.size();++ei) { const auto &elev = hData->mFds[fi].mEvs[ei]; - if(!WriteBin4(1, static_cast(elev.mAzs.size()), fp, filename)) - return 0; + if(!WriteBin4(1, static_cast(elev.mAzs.size()), ostream, filename)) + return false; } } - for(fi = static_cast(hData->mFds.size()-1);fi < hData->mFds.size();fi--) + for(size_t fi{hData->mFds.size()-1};fi < hData->mFds.size();--fi) { - constexpr double scale{8388607.0}; - constexpr uint bps{3u}; + static constexpr double scale{8388607.0}; + static constexpr uint bps{3u}; - for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++) + for(const auto &evd : hData->mFds[fi].mEvs) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) + for(const auto &azd : evd.mAzs) { - HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - double out[2 * MAX_TRUNCSIZE]; + std::array out{}; - TpdfDither(out, azd->mIrs[0], scale, n, channels, &dither_seed); + TpdfDither(out, azd.mIrs[0].first(n), scale, 0, 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++) + TpdfDither(out, azd.mIrs[1].first(n), scale, 1, channels, &dither_seed); + const size_t numsamples{size_t{channels} * n}; + for(size_t i{0};i < numsamples;i++) { const auto v = static_cast(Clamp(out[i], -scale-1.0, scale)); - if(!WriteBin4(bps, static_cast(v), fp, filename)) - return 0; + if(!WriteBin4(bps, static_cast(v), ostream, filename)) + return false; } } } } - for(fi = static_cast(hData->mFds.size()-1);fi < hData->mFds.size();fi--) + for(size_t fi{hData->mFds.size()-1};fi < hData->mFds.size();--fi) { /* Delay storage has 2 bits of extra precision. */ - constexpr double DelayPrecScale{4.0}; - for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++) + static constexpr double DelayPrecScale{4.0}; + for(const auto &evd : hData->mFds[fi].mEvs) { - for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs) + for(const auto &azd : evd.mAzs) { auto v = static_cast(std::round(azd.mDelays[0]*DelayPrecScale)); - if(!WriteBin4(1, v, fp, filename)) return 0; + if(!WriteBin4(1, v, ostream, filename)) return false; if(hData->mChannelType == CT_STEREO) { v = static_cast(std::round(azd.mDelays[1]*DelayPrecScale)); - if(!WriteBin4(1, v, fp, filename)) return 0; + if(!WriteBin4(1, v, ostream, filename)) return false; } } } } - fclose(fp); - return 1; + return true; } @@ -435,23 +401,20 @@ static int StoreMhr(const HrirDataT *hData, const char *filename) * 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) +void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, const uint m) { - double maxMags[MAX_FD_COUNT]; - uint fi, ei, ti, i; - + std::array maxMags{}; double maxMag{0.0}; - for(fi = 0;fi < hData->mFds.size();fi++) - { - maxMags[fi] = 0.0; - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) + for(size_t fi{0};fi < hData->mFds.size();++fi) + { + for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { - for(ti = 0;ti < channels;ti++) + for(size_t ti{0};ti < channels;++ti) { - for(i = 0;i < m;i++) + for(size_t i{0};i < m;++i) maxMags[fi] = std::max(azd.mIrs[ti][i], maxMags[fi]); } } @@ -460,17 +423,17 @@ static void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, maxMag = std::max(maxMags[fi], maxMag); } - for(fi = 0;fi < hData->mFds.size();fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { const double magFactor{maxMag / maxMags[fi]}; - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) + for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { - for(ti = 0;ti < channels;ti++) + for(size_t ti{0};ti < channels;++ti) { - for(i = 0;i < m;i++) + for(size_t i{0};i < m;++i) azd.mIrs[ti][i] *= magFactor; } } @@ -482,7 +445,7 @@ static void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, * 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) +void CalculateDfWeights(const HrirDataT *hData, const al::span weights) { double sum, innerRa, outerRa, evs, ev, upperEv, lowerEv; double solidAngle, solidVolume; @@ -502,17 +465,17 @@ static void CalculateDfWeights(const HrirDataT *hData, double *weights) outerRa = 10.0f; const double raPowDiff{std::pow(outerRa, 3.0) - std::pow(innerRa, 3.0)}; - evs = M_PI / 2.0 / static_cast(hData->mFds[fi].mEvs.size() - 1); + evs = al::numbers::pi / 2.0 / static_cast(hData->mFds[fi].mEvs.size() - 1); for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) { const auto &elev = hData->mFds[fi].mEvs[ei]; // For each elevation, calculate the upper and lower limits of // the patch band. ev = elev.mElevation; - lowerEv = std::max(-M_PI / 2.0, ev - evs); - upperEv = std::min(M_PI / 2.0, ev + evs); + lowerEv = std::max(-al::numbers::pi / 2.0, ev - evs); + upperEv = std::min(al::numbers::pi / 2.0, ev + evs); // Calculate the surface area of the patch band. - solidAngle = 2.0 * M_PI * (std::sin(upperEv) - std::sin(lowerEv)); + solidAngle = 2.0 * al::numbers::pi * (std::sin(upperEv) - std::sin(lowerEv)); // Then the volume of the extruded patch band. solidVolume = solidAngle * raPowDiff / 3.0; // Each weight is the volume of one extruded patch. @@ -538,16 +501,16 @@ static void CalculateDfWeights(const HrirDataT *hData, double *weights) * 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) +void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, const uint m, + const bool weighted, const double limit, const al::span dfa) { std::vector weights(hData->mFds.size() * MAX_EV_COUNT); - uint count, ti, fi, ei, i, ai; + uint count; if(weighted) { // Use coverage weighting to calculate the average. - CalculateDfWeights(hData, weights.data()); + CalculateDfWeights(hData, weights); } else { @@ -556,64 +519,63 @@ static void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint chan // 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->mFds.size();fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = 0;ei < hData->mFds[fi].mEvStart;ei++) + for(size_t ei{0};ei < hData->mFds[fi].mEvStart;++ei) count -= static_cast(hData->mFds[fi].mEvs[ei].mAzs.size()); } weight = 1.0 / count; - for(fi = 0;fi < hData->mFds.size();fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) + for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) weights[(fi * MAX_EV_COUNT) + ei] = weight; } } - for(ti = 0;ti < channels;ti++) + for(size_t ti{0};ti < channels;++ti) { - for(i = 0;i < m;i++) + for(size_t i{0};i < m;++i) dfa[(ti * m) + i] = 0.0; - for(fi = 0;fi < hData->mFds.size();fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) + for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) + for(size_t ai{0};ai < hData->mFds[fi].mEvs[ei].mAzs.size();++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++) + for(size_t 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); + for(size_t 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]); + LimitMagnitudeResponse(hData->mFftSize, m, limit, dfa.subspan(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) +void DiffuseFieldEqualize(const uint channels, const uint m, const al::span dfa, + const HrirDataT *hData) { - uint ti, fi, ei, i; - - for(fi = 0;fi < hData->mFds.size();fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) + for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { for(auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { - for(ti = 0;ti < channels;ti++) + for(size_t ti{0};ti < channels;++ti) { - for(i = 0;i < m;i++) + for(size_t i{0};i < m;++i) azd.mIrs[ti][i] /= dfa[(ti * m) + i]; } } @@ -625,9 +587,10 @@ static void DiffuseFieldEqualize(const uint channels, const uint m, const double * 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) +void CalcAzIndices(const HrirFdT &field, const uint ei, const double az, uint *a0, uint *a1, double *af) { - double f{(2.0*M_PI + az) * static_cast(field.mEvs[ei].mAzs.size()) / (2.0*M_PI)}; + double f{(2.0*al::numbers::pi + az) * static_cast(field.mEvs[ei].mAzs.size()) / + (2.0*al::numbers::pi)}; const uint i{static_cast(f) % static_cast(field.mEvs[ei].mAzs.size())}; f -= std::floor(f); @@ -640,7 +603,7 @@ static void CalcAzIndices(const HrirFdT &field, const uint ei, const double az, * This just mirrors some top elevations for the bottom, and blends the * remaining elevations (not an accurate model). */ -static void SynthesizeOnsets(HrirDataT *hData) +void SynthesizeOnsets(HrirDataT *hData) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; @@ -677,7 +640,7 @@ static void SynthesizeOnsets(HrirDataT *hData) * 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}; + const double az{field.mEvs[ei].mAzs[ai].mAzimuth + al::numbers::pi}; CalcAzIndices(field, topElev, az, &a0, &a1, &af); /* Blend the delays, and again, swap the ears. */ @@ -709,8 +672,8 @@ static void SynthesizeOnsets(HrirDataT *hData) * 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; + if(az <= al::numbers::pi) az = al::numbers::pi - az; + else az = (al::numbers::pi*2.0)-az + al::numbers::pi; CalcAzIndices(field, topElev, az, &a0, &a1, &af); field.mEvs[ei].mAzs[ai].mDelays[0] = Lerp( @@ -738,12 +701,12 @@ static void SynthesizeOnsets(HrirDataT *hData) 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]{ + std::array blend{{ (1.0-ef) * (1.0-af0), (1.0-ef) * ( af0), ( ef) * (1.0-af1), ( ef) * ( af1) - }; + }}; for(uint ti{0u};ti < channels;ti++) { @@ -764,7 +727,7 @@ static void SynthesizeOnsets(HrirDataT *hData) * applies a low-pass filter to simulate body occlusion. It is a simple, if * inaccurate model. */ -static void SynthesizeHrirs(HrirDataT *hData) +void SynthesizeHrirs(HrirDataT *hData) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; auto htemp = std::vector(hData->mFftSize); @@ -788,7 +751,7 @@ static void SynthesizeHrirs(HrirDataT *hData) * and vice-versa, this produces a decent phantom-center response * underneath the head. */ - CalcAzIndices(field, oi, ((ti==0) ? -M_PI : M_PI) / 2.0, &a0, &a1, &af); + CalcAzIndices(field, oi, al::numbers::pi / ((ti==0) ? -2.0 : 2.0), &a0, &a1, &af); for(uint i{0u};i < m;i++) { field.mEvs[0].mAzs[0].mIrs[ti][i] = Lerp(field.mEvs[oi].mAzs[a0].mIrs[ti][i], @@ -800,7 +763,7 @@ static void SynthesizeHrirs(HrirDataT *hData) { const double of{static_cast(ei) / field.mEvStart}; const double b{(1.0 - of) * beta}; - double lp[4]{}; + std::array lp{}; /* Calculate a low-pass filter to simulate body occlusion. */ lp[0] = Lerp(1.0, lp[0], b); @@ -821,7 +784,7 @@ static void SynthesizeHrirs(HrirDataT *hData) */ FftForward(static_cast(htemp.size()), htemp.data()); std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(), - [](const complex_d &c) -> double { return std::abs(c); }); + [](const complex_d c) -> double { return std::abs(c); }); for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++) { @@ -845,7 +808,7 @@ static void SynthesizeHrirs(HrirDataT *hData) } } const double b{beta}; - double lp[4]{}; + std::array lp{}; lp[0] = Lerp(1.0, lp[0], b); lp[1] = Lerp(lp[0], lp[1], b); lp[2] = Lerp(lp[1], lp[2], b); @@ -861,7 +824,7 @@ static void SynthesizeHrirs(HrirDataT *hData) } FftForward(static_cast(htemp.size()), htemp.data()); std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(), - [](const complex_d &c) -> double { return std::abs(c); }); + [](const complex_d c) -> double { return std::abs(c); }); for(uint ti{0u};ti < channels;ti++) { @@ -879,11 +842,11 @@ static void SynthesizeHrirs(HrirDataT *hData) * or more threads (sharing the same reconstructor object). */ struct HrirReconstructor { - std::vector mIrs; - std::atomic mCurrent; - std::atomic mDone; - uint mFftSize; - uint mIrPoints; + std::vector> mIrs; + std::atomic mCurrent{}; + std::atomic mDone{}; + uint mFftSize{}; + uint mIrPoints{}; void Worker() { @@ -891,7 +854,7 @@ struct HrirReconstructor { auto mags = std::vector(mFftSize); size_t m{(mFftSize/2) + 1}; - while(1) + while(true) { /* Load the current index to process. */ size_t idx{mCurrent.load()}; @@ -910,8 +873,8 @@ struct HrirReconstructor { * time-domain response. */ for(size_t i{0};i < m;++i) - mags[i] = std::max(mIrs[idx][i], EPSILON); - MinimumPhase(mFftSize, mags.data(), h.data()); + mags[i] = std::max(mIrs[idx][i], Epsilon); + MinimumPhase(mags, h); FftInverse(mFftSize, h.data()); for(uint i{0u};i < mIrPoints;++i) mIrs[idx][i] = h[i].real(); @@ -922,7 +885,7 @@ struct HrirReconstructor { } }; -static void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) +void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; @@ -973,17 +936,18 @@ static void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) } // Normalize the HRIR set and slightly attenuate the result. -static void NormalizeHrirs(HrirDataT *hData) +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 mesasure_channel = [irSize](const LevelPair levels, const double *ir) + auto mesasure_channel = [irSize](const LevelPair levels, al::span ir) { /* Calculate the peak amplitude and RMS of this IR. */ - auto current = std::accumulate(ir, ir+irSize, LevelPair{0.0, 0.0}, + ir = ir.first(irSize); + auto current = std::accumulate(ir.cbegin(), ir.cend(), LevelPair{0.0, 0.0}, [](const LevelPair cur, const double impulse) { return LevelPair{std::max(std::abs(impulse), cur.amp), cur.rms + impulse*impulse}; @@ -994,7 +958,7 @@ static void NormalizeHrirs(HrirDataT *hData) return LevelPair{std::max(current.amp, levels.amp), std::max(current.rms, levels.rms)}; }; auto measure_azi = [channels,mesasure_channel](const LevelPair levels, const HrirAzT &azi) - { return std::accumulate(azi.mIrs, azi.mIrs+channels, levels, mesasure_channel); }; + { return std::accumulate(azi.mIrs.begin(), azi.mIrs.begin()+channels, levels, mesasure_channel); }; auto measure_elev = [measure_azi](const LevelPair levels, const HrirEvT &elev) { return std::accumulate(elev.mAzs.cbegin(), elev.mAzs.cend(), levels, measure_azi); }; auto measure_field = [measure_elev](const LevelPair levels, const HrirFdT &field) @@ -1018,10 +982,14 @@ static void NormalizeHrirs(HrirDataT *hData) factor = std::min(factor, 0.99/maxlev.amp); /* Now scale all IRs by the given factor. */ - auto proc_channel = [irSize,factor](double *ir) - { std::transform(ir, ir+irSize, ir, [factor](double s){ return s * factor; }); }; + auto proc_channel = [irSize,factor](al::span ir) + { + ir = ir.first(irSize); + std::transform(ir.cbegin(), ir.cend(), ir.begin(), + [factor](double s) { return s * factor; }); + }; auto proc_azi = [channels,proc_channel](HrirAzT &azi) - { std::for_each(azi.mIrs, azi.mIrs+channels, proc_channel); }; + { std::for_each(azi.mIrs.begin(), azi.mIrs.begin()+channels, proc_channel); }; auto proc_elev = [proc_azi](HrirEvT &elev) { std::for_each(elev.mAzs.begin(), elev.mAzs.end(), proc_azi); }; auto proc1_field = [proc_elev](HrirFdT &field) @@ -1031,14 +999,14 @@ static void NormalizeHrirs(HrirDataT *hData) } // 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 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; + al = (0.5 * al::numbers::pi) + azp; if(dlp > l) dlp = l + (rad * (al - std::acos(rad / dist))); return dlp / 343.3; @@ -1046,13 +1014,13 @@ static double CalcLTD(const double ev, const double az, const double rad, const // 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) +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; - if(model == HM_SPHERE) + if(model == HM_Sphere) { for(auto &field : hData->mFds) { @@ -1106,10 +1074,10 @@ static void CalculateHrtds(const HeadModelT model, const double radius, HrirData } } } - if(maxHrtd > MAX_HRTD) + if(maxHrtd > MaxHrtd) { - fprintf(stdout, " Scaling for max delay of %f samples to %f\n...\n", maxHrtd, MAX_HRTD); - const double scale{MAX_HRTD / maxHrtd}; + fprintf(stdout, " Scaling for max delay of %f samples to %f\n...\n", maxHrtd, MaxHrtd); + const double scale{MaxHrtd / maxHrtd}; for(auto &field : hData->mFds) { for(auto &elev : field.mEvs) @@ -1124,6 +1092,8 @@ static void CalculateHrtds(const HeadModelT model, const double radius, HrirData } } +} // namespace + // Allocate and configure dynamic HRIR structures. bool PrepareHrirData(const al::span distances, const al::span evCounts, @@ -1150,22 +1120,23 @@ bool PrepareHrirData(const al::span distances, { hData->mFds[fi].mDistance = distances[fi]; hData->mFds[fi].mEvStart = 0; - hData->mFds[fi].mEvs = {&hData->mEvsBase[evTotal], evCounts[fi]}; + hData->mFds[fi].mEvs = al::span{hData->mEvsBase}.subspan(evTotal, evCounts[fi]); evTotal += evCounts[fi]; for(uint ei{0};ei < evCounts[fi];++ei) { uint azCount = azCounts[fi][ei]; - hData->mFds[fi].mEvs[ei].mElevation = -M_PI / 2.0 + M_PI * ei / (evCounts[fi] - 1); - hData->mFds[fi].mEvs[ei].mAzs = {&hData->mAzsBase[azTotal], azCount}; + hData->mFds[fi].mEvs[ei].mElevation = -al::numbers::pi / 2.0 + al::numbers::pi * ei / + (evCounts[fi] - 1); + hData->mFds[fi].mEvs[ei].mAzs = al::span{hData->mAzsBase}.subspan(azTotal, azCount); for(uint 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].mAzimuth = 2.0 * al::numbers::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; + hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = {}; + hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = {}; } azTotal += azCount; } @@ -1174,56 +1145,63 @@ bool PrepareHrirData(const al::span distances, } +namespace { + /* 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 bool farfield, const uint numThreads, const uint fftSize, const int equalize, - const int surface, const double limit, const uint truncSize, const HeadModelT model, - const double radius, const char *outName) +bool ProcessDefinition(std::string_view inName, const uint outRate, const ChannelModeT chanMode, + const bool farfield, const uint numThreads, const uint fftSize, const bool equalize, + const bool surface, const double limit, const uint truncSize, const HeadModelT model, + const double radius, const std::string_view outName) { HrirDataT hData; fprintf(stdout, "Using %u thread%s.\n", numThreads, (numThreads==1)?"":"s"); - if(!inName) + if(inName.empty() || inName == "-"sv) { - inName = "stdin"; - fprintf(stdout, "Reading HRIR definition from %s...\n", inName); - if(!LoadDefInput(std::cin, nullptr, 0, inName, fftSize, truncSize, outRate, chanMode, &hData)) - return 0; + inName = "stdin"sv; + fprintf(stdout, "Reading HRIR definition from %.*s...\n", al::sizei(inName), + inName.data()); + if(!LoadDefInput(std::cin, {}, inName, fftSize, truncSize, outRate, chanMode, &hData)) + return false; } else { - std::unique_ptr input{new al::ifstream{inName}}; + auto input = std::make_unique(std::filesystem::u8path(inName)); if(!input->is_open()) { - fprintf(stderr, "Error: Could not open input file '%s'\n", inName); - return 0; + fprintf(stderr, "Error: Could not open input file '%.*s'\n", al::sizei(inName), + inName.data()); + return false; } - char startbytes[4]{}; - input->read(startbytes, sizeof(startbytes)); - std::streamsize startbytecount{input->gcount()}; - if(startbytecount != sizeof(startbytes) || !input->good()) + std::array startbytes{}; + input->read(startbytes.data(), startbytes.size()); + if(input->gcount() != startbytes.size() || !input->good()) { - fprintf(stderr, "Error: Could not read input file '%s'\n", inName); - return 0; + fprintf(stderr, "Error: Could not read input file '%.*s'\n", al::sizei(inName), + inName.data()); + return false; } if(startbytes[0] == '\x89' && startbytes[1] == 'H' && startbytes[2] == 'D' && startbytes[3] == 'F') { input = nullptr; - fprintf(stdout, "Reading HRTF data from %s...\n", inName); + fprintf(stdout, "Reading HRTF data from %.*s...\n", al::sizei(inName), + inName.data()); if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, outRate, chanMode, &hData)) - return 0; + return false; } else { - fprintf(stdout, "Reading HRIR definition from %s...\n", inName); - if(!LoadDefInput(*input, startbytes, startbytecount, inName, fftSize, truncSize, outRate, chanMode, &hData)) - return 0; + fprintf(stdout, "Reading HRIR definition from %.*s...\n", al::sizei(inName), + inName.data()); + if(!LoadDefInput(*input, startbytes, inName, fftSize, truncSize, outRate, chanMode, + &hData)) + return false; } } @@ -1231,7 +1209,7 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann { uint c{(hData.mChannelType == CT_STEREO) ? 2u : 1u}; uint m{hData.mFftSize/2u + 1u}; - auto dfa = std::vector(c * m); + auto dfa = std::vector(size_t{c} * m); if(hData.mFds.size() > 1) { @@ -1239,9 +1217,9 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann BalanceFieldMagnitudes(&hData, c, m); } fprintf(stdout, "Calculating diffuse-field average...\n"); - CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa.data()); + CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa); fprintf(stdout, "Performing diffuse-field equalization...\n"); - DiffuseFieldEqualize(c, m, dfa.data(), &hData); + DiffuseFieldEqualize(c, m, dfa, &hData); } if(hData.mFds.size() > 1) { @@ -1257,7 +1235,7 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann } } fprintf(stdout, "Synthesizing missing elevations...\n"); - if(model == HM_DATASET) + if(model == HM_Dataset) SynthesizeOnsets(&hData); SynthesizeHrirs(&hData); fprintf(stdout, "Performing minimum phase reconstruction...\n"); @@ -1267,18 +1245,17 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann fprintf(stdout, "Normalizing final HRIRs...\n"); NormalizeHrirs(&hData); fprintf(stdout, "Calculating impulse delays...\n"); - CalculateHrtds(model, (radius > DEFAULT_CUSTOM_RADIUS) ? radius : hData.mRadius, &hData); + CalculateHrtds(model, (radius > DefaultCustomRadius) ? radius : hData.mRadius, &hData); const auto rateStr = std::to_string(hData.mIrRate); - const auto expName = StrSubst({outName, strlen(outName)}, {"%r", 2}, - {rateStr.data(), rateStr.size()}); + const auto expName = StrSubst(outName, "%r"sv, rateStr); fprintf(stdout, "Creating MHR data set %s...\n", expName.c_str()); - return StoreMhr(&hData, expName.c_str()); + return StoreMhr(&hData, expName); } -static void PrintHelp(const char *argv0, FILE *ofile) +void PrintHelp(const std::string_view argv0, FILE *ofile) { - fprintf(ofile, "Usage: %s [