From 73a6fc9196eeb443f7647c8abacc99c3c9325a64 Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Wed, 20 Mar 2024 11:06:03 +0800 Subject: [PATCH] Update OpenAL-soft to 1.23.1-bc7cb17. --- libs/openal-soft/.github/workflows/ci.yml | 152 + .../openal-soft/.github/workflows/makemhr.yml | 76 + libs/openal-soft/.gitignore | 1 + libs/openal-soft/CMakeLists.txt | 948 +- libs/openal-soft/ChangeLog | 112 + libs/openal-soft/LICENSE-pffft | 38 + libs/openal-soft/OpenALConfig.cmake.in | 2 +- libs/openal-soft/README.md | 62 +- libs/openal-soft/al/auxeffectslot.cpp | 1888 ++-- libs/openal-soft/al/auxeffectslot.h | 464 +- libs/openal-soft/al/buffer.cpp | 2480 +++--- libs/openal-soft/al/buffer.h | 69 +- libs/openal-soft/al/debug.cpp | 620 ++ libs/openal-soft/al/debug.h | 70 + libs/openal-soft/al/direct_defs.h | 127 + .../al/{eax_api.cpp => eax/api.cpp} | 722 +- libs/openal-soft/al/{eax_api.h => eax/api.h} | 493 +- libs/openal-soft/al/eax/call.cpp | 218 + libs/openal-soft/al/eax/call.h | 97 + libs/openal-soft/al/eax/effect.h | 457 + libs/openal-soft/al/eax/exception.cpp | 32 + libs/openal-soft/al/eax/exception.h | 18 + .../fx_slot_index.cpp} | 4 +- .../fx_slot_index.h} | 9 +- .../al/{eax_fx_slots.cpp => eax/fx_slots.cpp} | 19 +- .../al/{eax_fx_slots.h => eax/fx_slots.h} | 24 +- libs/openal-soft/al/eax/globals.h | 6 + libs/openal-soft/al/eax/utils.cpp | 26 + .../al/{eax_utils.h => eax/utils.h} | 79 +- .../al/{eax_x_ram.h => eax/x_ram.h} | 17 +- libs/openal-soft/al/eax_eax_call.cpp | 324 - libs/openal-soft/al/eax_eax_call.h | 117 - libs/openal-soft/al/eax_effect.cpp | 3 - libs/openal-soft/al/eax_effect.h | 44 - libs/openal-soft/al/eax_exception.cpp | 63 - libs/openal-soft/al/eax_exception.h | 25 - libs/openal-soft/al/eax_globals.cpp | 21 - libs/openal-soft/al/eax_globals.h | 22 - libs/openal-soft/al/eax_utils.cpp | 36 - libs/openal-soft/al/eax_x_ram.cpp | 3 - libs/openal-soft/al/effect.cpp | 746 +- libs/openal-soft/al/effect.h | 45 +- libs/openal-soft/al/effects/autowah.cpp | 606 +- libs/openal-soft/al/effects/chorus.cpp | 1517 +--- libs/openal-soft/al/effects/compressor.cpp | 292 +- libs/openal-soft/al/effects/convolution.cpp | 178 +- libs/openal-soft/al/effects/dedicated.cpp | 92 +- libs/openal-soft/al/effects/distortion.cpp | 636 +- libs/openal-soft/al/effects/echo.cpp | 637 +- libs/openal-soft/al/effects/effects.cpp | 63 - libs/openal-soft/al/effects/effects.h | 103 +- libs/openal-soft/al/effects/equalizer.cpp | 1056 +-- libs/openal-soft/al/effects/fshifter.cpp | 530 +- libs/openal-soft/al/effects/modulator.cpp | 537 +- libs/openal-soft/al/effects/null.cpp | 229 +- libs/openal-soft/al/effects/pshifter.cpp | 385 +- libs/openal-soft/al/effects/reverb.cpp | 3210 ++----- libs/openal-soft/al/effects/vmorpher.cpp | 799 +- libs/openal-soft/al/error.cpp | 113 +- libs/openal-soft/al/error.h | 27 + libs/openal-soft/al/event.cpp | 219 +- libs/openal-soft/al/extension.cpp | 61 +- libs/openal-soft/al/filter.cpp | 955 +- libs/openal-soft/al/filter.h | 76 +- libs/openal-soft/al/listener.cpp | 485 +- libs/openal-soft/al/listener.h | 4 +- libs/openal-soft/al/source.cpp | 7598 +++++++--------- libs/openal-soft/al/source.h | 1509 ++-- libs/openal-soft/al/state.cpp | 1110 +-- libs/openal-soft/alc/alc.cpp | 2802 +++--- libs/openal-soft/alc/alconfig.cpp | 409 +- libs/openal-soft/alc/alconfig.h | 21 +- libs/openal-soft/alc/alu.cpp | 1461 ++-- libs/openal-soft/alc/alu.h | 10 +- libs/openal-soft/alc/backends/alsa.cpp | 325 +- libs/openal-soft/alc/backends/base.cpp | 138 +- libs/openal-soft/alc/backends/base.h | 53 +- libs/openal-soft/alc/backends/coreaudio.cpp | 340 +- libs/openal-soft/alc/backends/coreaudio.h | 2 + libs/openal-soft/alc/backends/dsound.cpp | 190 +- libs/openal-soft/alc/backends/jack.cpp | 195 +- libs/openal-soft/alc/backends/loopback.cpp | 6 +- libs/openal-soft/alc/backends/null.cpp | 34 +- libs/openal-soft/alc/backends/oboe.cpp | 96 +- libs/openal-soft/alc/backends/opensl.cpp | 266 +- libs/openal-soft/alc/backends/oss.cpp | 232 +- libs/openal-soft/alc/backends/pipewire.cpp | 1179 ++- libs/openal-soft/alc/backends/pipewire.h | 2 + libs/openal-soft/alc/backends/portaudio.cpp | 130 +- libs/openal-soft/alc/backends/pulseaudio.cpp | 877 +- libs/openal-soft/alc/backends/pulseaudio.h | 2 + libs/openal-soft/alc/backends/sdl2.cpp | 62 +- libs/openal-soft/alc/backends/sndio.cpp | 247 +- libs/openal-soft/alc/backends/solaris.cpp | 61 +- libs/openal-soft/alc/backends/wasapi.cpp | 2059 +++-- libs/openal-soft/alc/backends/wasapi.h | 2 + libs/openal-soft/alc/backends/wave.cpp | 170 +- libs/openal-soft/alc/backends/winmm.cpp | 93 +- libs/openal-soft/alc/context.cpp | 1434 ++- libs/openal-soft/alc/context.h | 652 +- libs/openal-soft/alc/device.cpp | 20 +- libs/openal-soft/alc/device.h | 99 +- libs/openal-soft/alc/effects/autowah.cpp | 135 +- libs/openal-soft/alc/effects/base.h | 39 +- libs/openal-soft/alc/effects/chorus.cpp | 259 +- libs/openal-soft/alc/effects/compressor.cpp | 85 +- libs/openal-soft/alc/effects/convolution.cpp | 625 +- libs/openal-soft/alc/effects/dedicated.cpp | 106 +- libs/openal-soft/alc/effects/distortion.cpp | 52 +- libs/openal-soft/alc/effects/echo.cpp | 81 +- libs/openal-soft/alc/effects/equalizer.cpp | 98 +- libs/openal-soft/alc/effects/fshifter.cpp | 170 +- libs/openal-soft/alc/effects/modulator.cpp | 202 +- libs/openal-soft/alc/effects/null.cpp | 11 +- libs/openal-soft/alc/effects/pshifter.cpp | 275 +- libs/openal-soft/alc/effects/reverb.cpp | 1674 ++-- libs/openal-soft/alc/effects/vmorpher.cpp | 200 +- libs/openal-soft/alc/events.cpp | 95 + libs/openal-soft/alc/events.h | 50 + libs/openal-soft/alc/export_list.h | 922 ++ libs/openal-soft/alc/inprogext.h | 99 +- libs/openal-soft/alc/panning.cpp | 829 +- libs/openal-soft/alsoftrc.sample | 167 +- libs/openal-soft/appveyor.yml | 6 +- libs/openal-soft/cmake/FindFFmpeg.cmake | 18 +- libs/openal-soft/cmake/FindOpenSL.cmake | 12 +- libs/openal-soft/cmake/FindPulseAudio.cmake | 9 - libs/openal-soft/cmake/FindSDL2.cmake | 191 - libs/openal-soft/cmake/bin2h.script.cmake | 5 +- libs/openal-soft/common/albit.h | 15 + libs/openal-soft/common/albyte.h | 17 - libs/openal-soft/common/alcomplex.cpp | 221 +- libs/openal-soft/common/alcomplex.h | 2 +- libs/openal-soft/common/aldeque.h | 16 - libs/openal-soft/common/alfstream.cpp | 150 - libs/openal-soft/common/alfstream.h | 74 - libs/openal-soft/common/almalloc.cpp | 61 - libs/openal-soft/common/almalloc.h | 303 +- libs/openal-soft/common/alnumbers.h | 26 +- libs/openal-soft/common/alnumeric.h | 178 +- libs/openal-soft/common/aloptional.h | 353 - .../common/{threads.cpp => alsem.cpp} | 75 +- libs/openal-soft/common/alsem.h | 43 + libs/openal-soft/common/alspan.h | 509 +- libs/openal-soft/common/alstring.cpp | 48 +- libs/openal-soft/common/alstring.h | 29 +- libs/openal-soft/common/althrd_setname.cpp | 77 + libs/openal-soft/common/althrd_setname.h | 6 + libs/openal-soft/common/althreads.h | 143 + libs/openal-soft/common/altraits.h | 14 + libs/openal-soft/common/atomic.h | 87 +- libs/openal-soft/common/comptr.h | 91 +- libs/openal-soft/common/dynload.cpp | 4 +- libs/openal-soft/common/flexarray.h | 138 + libs/openal-soft/common/intrusive_ptr.h | 47 +- libs/openal-soft/common/opthelpers.h | 79 +- libs/openal-soft/common/pffft.cpp | 2299 +++++ libs/openal-soft/common/pffft.h | 212 + libs/openal-soft/common/phase_shifter.h | 172 +- .../common/polyphase_resampler.cpp | 142 +- libs/openal-soft/common/polyphase_resampler.h | 12 +- libs/openal-soft/common/ringbuffer.cpp | 216 +- libs/openal-soft/common/ringbuffer.h | 152 +- libs/openal-soft/common/strutils.cpp | 43 +- libs/openal-soft/common/strutils.h | 12 +- libs/openal-soft/common/threads.h | 48 - libs/openal-soft/common/vecmat.h | 48 +- libs/openal-soft/common/vector.h | 3 +- libs/openal-soft/common/win_main_utf8.h | 13 +- libs/openal-soft/config.h.in | 15 +- libs/openal-soft/core/ambdec.cpp | 442 +- libs/openal-soft/core/ambdec.h | 22 +- libs/openal-soft/core/ambidefs.cpp | 314 +- libs/openal-soft/core/ambidefs.h | 302 +- libs/openal-soft/core/async_event.h | 93 +- libs/openal-soft/core/bformatdec.cpp | 189 +- libs/openal-soft/core/bformatdec.h | 44 +- libs/openal-soft/core/bs2b.cpp | 148 +- libs/openal-soft/core/bs2b.h | 85 +- libs/openal-soft/core/bsinc_defs.h | 4 +- libs/openal-soft/core/bsinc_tables.cpp | 236 +- libs/openal-soft/core/bsinc_tables.h | 10 +- libs/openal-soft/core/buffer_storage.cpp | 37 - libs/openal-soft/core/buffer_storage.h | 64 +- libs/openal-soft/core/bufferline.h | 2 +- libs/openal-soft/core/context.cpp | 159 +- libs/openal-soft/core/context.h | 73 +- libs/openal-soft/core/converter.cpp | 349 +- libs/openal-soft/core/converter.h | 32 +- libs/openal-soft/core/cpu_caps.cpp | 16 +- libs/openal-soft/core/cpu_caps.h | 5 +- libs/openal-soft/core/cubic_defs.h | 15 + libs/openal-soft/core/cubic_tables.cpp | 91 + libs/openal-soft/core/cubic_tables.h | 38 + libs/openal-soft/core/dbus_wrap.cpp | 22 +- libs/openal-soft/core/dbus_wrap.h | 4 +- libs/openal-soft/core/devformat.cpp | 4 + libs/openal-soft/core/devformat.h | 22 +- libs/openal-soft/core/device.cpp | 11 +- libs/openal-soft/core/device.h | 219 +- libs/openal-soft/core/effects/base.h | 278 +- libs/openal-soft/core/effectslot.cpp | 13 +- libs/openal-soft/core/effectslot.h | 15 +- libs/openal-soft/core/except.cpp | 11 +- libs/openal-soft/core/except.h | 10 +- libs/openal-soft/core/filters/biquad.cpp | 21 +- libs/openal-soft/core/filters/biquad.h | 11 +- libs/openal-soft/core/filters/nfc.cpp | 78 +- libs/openal-soft/core/filters/nfc.h | 35 +- libs/openal-soft/core/filters/splitter.cpp | 25 +- libs/openal-soft/core/filters/splitter.h | 13 +- libs/openal-soft/core/fmt_traits.cpp | 8 +- libs/openal-soft/core/fmt_traits.h | 64 +- libs/openal-soft/core/fpu_ctrl.cpp | 82 +- libs/openal-soft/core/fpu_ctrl.h | 23 +- libs/openal-soft/core/front_stablizer.h | 18 +- libs/openal-soft/core/helpers.cpp | 556 +- libs/openal-soft/core/helpers.h | 22 +- libs/openal-soft/core/hrtf.cpp | 764 +- libs/openal-soft/core/hrtf.h | 53 +- libs/openal-soft/core/logging.cpp | 172 +- libs/openal-soft/core/logging.h | 32 +- libs/openal-soft/core/mastering.cpp | 239 +- libs/openal-soft/core/mastering.h | 32 +- libs/openal-soft/core/mixer.cpp | 44 +- libs/openal-soft/core/mixer.h | 54 +- libs/openal-soft/core/mixer/defs.h | 46 +- libs/openal-soft/core/mixer/hrtfbase.h | 9 +- libs/openal-soft/core/mixer/mixer_c.cpp | 257 +- libs/openal-soft/core/mixer/mixer_neon.cpp | 423 +- libs/openal-soft/core/mixer/mixer_sse.cpp | 321 +- libs/openal-soft/core/mixer/mixer_sse2.cpp | 160 +- libs/openal-soft/core/mixer/mixer_sse41.cpp | 160 +- libs/openal-soft/core/resampler_limits.h | 4 +- libs/openal-soft/core/rtkit.cpp | 28 +- libs/openal-soft/core/storage_formats.cpp | 85 + libs/openal-soft/core/storage_formats.h | 54 + libs/openal-soft/core/uhjfilter.cpp | 583 +- libs/openal-soft/core/uhjfilter.h | 209 +- libs/openal-soft/core/uiddefs.cpp | 6 +- libs/openal-soft/core/voice.cpp | 1313 ++- libs/openal-soft/core/voice.h | 112 +- libs/openal-soft/core/voice_change.h | 4 - libs/openal-soft/docs/3D7.1.txt | 15 +- libs/openal-soft/docs/ambdec.txt | 11 +- libs/openal-soft/docs/ambisonics.txt | 23 +- libs/openal-soft/docs/env-vars.txt | 15 + libs/openal-soft/docs/hrtf.txt | 4 +- libs/openal-soft/examples/alconvolve.c | 73 +- libs/openal-soft/examples/alffplay.cpp | 583 +- libs/openal-soft/examples/alhrtf.c | 4 +- libs/openal-soft/examples/allatency.c | 4 +- libs/openal-soft/examples/alloopback.c | 9 +- libs/openal-soft/examples/almultireverb.c | 14 +- libs/openal-soft/examples/alplay.c | 187 +- libs/openal-soft/examples/alrecord.c | 2 +- libs/openal-soft/examples/alreverb.c | 6 +- libs/openal-soft/examples/alstream.c | 255 +- libs/openal-soft/examples/alstreamcb.cpp | 246 +- libs/openal-soft/examples/altonegen.c | 77 +- libs/openal-soft/examples/common/alhelpers.c | 35 + libs/openal-soft/examples/common/alhelpers.h | 2 +- libs/openal-soft/hrtf/Default HRTF.mhr | Bin 80353 -> 159841 bytes libs/openal-soft/include/AL/al.h | 508 +- libs/openal-soft/include/AL/alc.h | 149 +- libs/openal-soft/include/AL/alext.h | 649 +- libs/openal-soft/include/AL/efx-presets.h | 2 + libs/openal-soft/include/AL/efx.h | 134 +- libs/openal-soft/libopenal.version | 192 + libs/openal-soft/presets/3D7.1.ambdec | 49 +- libs/openal-soft/presets/hex-quad.ambdec | 53 + libs/openal-soft/presets/presets.txt | 18 +- libs/openal-soft/router/al.cpp | 60 +- libs/openal-soft/router/alc.cpp | 288 +- libs/openal-soft/router/router.cpp | 67 +- libs/openal-soft/router/router.h | 52 +- libs/openal-soft/tests/CMakeLists.txt | 24 + libs/openal-soft/tests/example.t.cpp | 16 + libs/openal-soft/utils/CIAIR.def | 7780 ++++++++--------- .../utils/alsoft-config/CMakeLists.txt | 5 +- .../utils/alsoft-config/mainwindow.cpp | 477 +- .../utils/alsoft-config/mainwindow.h | 34 +- .../utils/alsoft-config/mainwindow.ui | 335 +- libs/openal-soft/utils/makemhr/loaddef.cpp | 702 +- libs/openal-soft/utils/makemhr/loaddef.h | 4 +- libs/openal-soft/utils/makemhr/loadsofa.cpp | 260 +- libs/openal-soft/utils/makemhr/loadsofa.h | 2 +- libs/openal-soft/utils/makemhr/makemhr.cpp | 909 +- libs/openal-soft/utils/makemhr/makemhr.h | 70 +- libs/openal-soft/utils/openal-info.c | 97 +- libs/openal-soft/utils/sofa-info.cpp | 3 +- libs/openal-soft/utils/sofa-support.cpp | 8 +- libs/openal-soft/utils/uhjdecoder.cpp | 96 +- libs/openal-soft/utils/uhjencoder.cpp | 229 +- 294 files changed, 44342 insertions(+), 40077 deletions(-) create mode 100644 libs/openal-soft/.github/workflows/ci.yml create mode 100644 libs/openal-soft/.github/workflows/makemhr.yml create mode 100644 libs/openal-soft/LICENSE-pffft create mode 100644 libs/openal-soft/al/debug.cpp create mode 100644 libs/openal-soft/al/debug.h create mode 100644 libs/openal-soft/al/direct_defs.h rename libs/openal-soft/al/{eax_api.cpp => eax/api.cpp} (67%) rename libs/openal-soft/al/{eax_api.h => eax/api.h} (81%) create mode 100644 libs/openal-soft/al/eax/call.cpp create mode 100644 libs/openal-soft/al/eax/call.h create mode 100644 libs/openal-soft/al/eax/effect.h create mode 100644 libs/openal-soft/al/eax/exception.cpp create mode 100644 libs/openal-soft/al/eax/exception.h rename libs/openal-soft/al/{eax_fx_slot_index.cpp => eax/fx_slot_index.cpp} (95%) rename libs/openal-soft/al/{eax_fx_slot_index.h => eax/fx_slot_index.h} (83%) rename libs/openal-soft/al/{eax_fx_slots.cpp => eax/fx_slots.cpp} (77%) rename libs/openal-soft/al/{eax_fx_slots.h => eax/fx_slots.h} (54%) create mode 100644 libs/openal-soft/al/eax/globals.h create mode 100644 libs/openal-soft/al/eax/utils.cpp rename libs/openal-soft/al/{eax_utils.h => eax/utils.h} (65%) rename libs/openal-soft/al/{eax_x_ram.h => eax/x_ram.h} (63%) delete mode 100644 libs/openal-soft/al/eax_eax_call.cpp delete mode 100644 libs/openal-soft/al/eax_eax_call.h delete mode 100644 libs/openal-soft/al/eax_effect.cpp delete mode 100644 libs/openal-soft/al/eax_effect.h delete mode 100644 libs/openal-soft/al/eax_exception.cpp delete mode 100644 libs/openal-soft/al/eax_exception.h delete mode 100644 libs/openal-soft/al/eax_globals.cpp delete mode 100644 libs/openal-soft/al/eax_globals.h delete mode 100644 libs/openal-soft/al/eax_utils.cpp delete mode 100644 libs/openal-soft/al/eax_x_ram.cpp create mode 100644 libs/openal-soft/al/error.h create mode 100644 libs/openal-soft/alc/events.cpp create mode 100644 libs/openal-soft/alc/events.h create mode 100644 libs/openal-soft/alc/export_list.h delete mode 100644 libs/openal-soft/cmake/FindSDL2.cmake delete mode 100644 libs/openal-soft/common/albyte.h delete mode 100644 libs/openal-soft/common/aldeque.h delete mode 100644 libs/openal-soft/common/alfstream.cpp delete mode 100644 libs/openal-soft/common/alfstream.h delete mode 100644 libs/openal-soft/common/almalloc.cpp delete mode 100644 libs/openal-soft/common/aloptional.h rename libs/openal-soft/common/{threads.cpp => alsem.cpp} (61%) create mode 100644 libs/openal-soft/common/alsem.h create mode 100644 libs/openal-soft/common/althrd_setname.cpp create mode 100644 libs/openal-soft/common/althrd_setname.h create mode 100644 libs/openal-soft/common/althreads.h create mode 100644 libs/openal-soft/common/altraits.h create mode 100644 libs/openal-soft/common/flexarray.h create mode 100644 libs/openal-soft/common/pffft.cpp create mode 100644 libs/openal-soft/common/pffft.h delete mode 100644 libs/openal-soft/common/threads.h create mode 100644 libs/openal-soft/core/cubic_defs.h create mode 100644 libs/openal-soft/core/cubic_tables.cpp create mode 100644 libs/openal-soft/core/cubic_tables.h create mode 100644 libs/openal-soft/core/storage_formats.cpp create mode 100644 libs/openal-soft/core/storage_formats.h create mode 100644 libs/openal-soft/libopenal.version create mode 100644 libs/openal-soft/presets/hex-quad.ambdec create mode 100644 libs/openal-soft/tests/CMakeLists.txt create mode 100644 libs/openal-soft/tests/example.t.cpp diff --git a/libs/openal-soft/.github/workflows/ci.yml b/libs/openal-soft/.github/workflows/ci.yml new file mode 100644 index 00000000..b1c3b413 --- /dev/null +++ b/libs/openal-soft/.github/workflows/ci.yml @@ -0,0 +1,152 @@ +name: CI + +on: [push, pull_request] + +jobs: + build: + name: ${{matrix.config.name}} + runs-on: ${{matrix.config.os}} + strategy: + fail-fast: false + matrix: + config: + - { + name: "Win32-Release", + os: windows-latest, + cmake_opts: "-A Win32 \ + -DALSOFT_TESTS=ON \ + -DALSOFT_BUILD_ROUTER=ON \ + -DALSOFT_REQUIRE_WINMM=ON \ + -DALSOFT_REQUIRE_DSOUND=ON \ + -DALSOFT_REQUIRE_WASAPI=ON", + build_type: "Release" + } + - { + name: "Win32-Debug", + os: windows-latest, + cmake_opts: "-A Win32 \ + -DALSOFT_TESTS=ON \ + -DALSOFT_BUILD_ROUTER=ON \ + -DALSOFT_REQUIRE_WINMM=ON \ + -DALSOFT_REQUIRE_DSOUND=ON \ + -DALSOFT_REQUIRE_WASAPI=ON", + build_type: "Debug" + } + - { + name: "Win64-Release", + os: windows-latest, + cmake_opts: "-A x64 \ + -DALSOFT_TESTS=ON \ + -DALSOFT_BUILD_ROUTER=ON \ + -DALSOFT_REQUIRE_WINMM=ON \ + -DALSOFT_REQUIRE_DSOUND=ON \ + -DALSOFT_REQUIRE_WASAPI=ON", + build_type: "Release" + } + - { + name: "Win64-Debug", + os: windows-latest, + cmake_opts: "-A x64 \ + -DALSOFT_TESTS=ON \ + -DALSOFT_BUILD_ROUTER=ON \ + -DALSOFT_REQUIRE_WINMM=ON \ + -DALSOFT_REQUIRE_DSOUND=ON \ + -DALSOFT_REQUIRE_WASAPI=ON", + build_type: "Debug" + } + - { + name: "Win64-UWP", + os: windows-latest, + cmake_opts: "-A x64 \ + -DALSOFT_TESTS=OFF \ + -DCMAKE_SYSTEM_NAME=WindowsStore \ + \"-DCMAKE_SYSTEM_VERSION=10.0\" \ + -DALSOFT_BUILD_ROUTER=ON \ + -DALSOFT_REQUIRE_WASAPI=ON", + build_type: "Release" + } + - { + name: "macOS-Release", + os: macos-latest, + cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON \ + -DALSOFT_TESTS=ON", + build_type: "Release" + } + - { + name: "iOS-Release", + os: macos-latest, + cmake_opts: "-GXcode \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DALSOFT_REQUIRE_COREAUDIO=ON \ + -DALSOFT_UTILS=OFF \ + -DALSOFT_EXAMPLES=OFF \ + -DALSOFT_TESTS=OFF \ + -DALSOFT_INSTALL=OFF \ + \"-DCMAKE_OSX_ARCHITECTURES=arm64\"", + build_type: "Release" + } + - { + name: "Linux-Release", + os: ubuntu-latest, + cmake_opts: "-DALSOFT_REQUIRE_RTKIT=ON \ + -DALSOFT_REQUIRE_ALSA=ON \ + -DALSOFT_REQUIRE_OSS=ON \ + -DALSOFT_REQUIRE_PORTAUDIO=ON \ + -DALSOFT_REQUIRE_PULSEAUDIO=ON \ + -DALSOFT_REQUIRE_JACK=ON \ + -DALSOFT_REQUIRE_PIPEWIRE=ON \ + -DALSOFT_TESTS=ON", + deps_cmdline: "sudo apt update && sudo apt-get install -qq \ + libpulse-dev \ + portaudio19-dev \ + libasound2-dev \ + libjack-dev \ + libpipewire-0.3-dev \ + qtbase5-dev \ + libdbus-1-dev", + build_type: "Release" + } + + steps: + - uses: actions/checkout@v3 + + - name: Install Dependencies + shell: bash + run: | + if [[ ! -z "${{matrix.config.deps_cmdline}}" ]]; then + eval ${{matrix.config.deps_cmdline}} + fi + + - name: Configure + shell: bash + run: | + cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} ${{matrix.config.cmake_opts}} . + + - name: Build + shell: bash + run: | + cmake --build build --config ${{matrix.config.build_type}} + + - name: Test + shell: bash + run: | + cd build + ctest + + - name: Create Archive + if: ${{ matrix.config.os == 'windows-latest' }} + shell: bash + run: | + cd build + mkdir archive + mkdir archive/router + cp ${{matrix.config.build_type}}/soft_oal.dll archive + cp ${{matrix.config.build_type}}/OpenAL32.dll archive/router + + - name: Upload Archive + # Upload package as an artifact of this workflow. + uses: actions/upload-artifact@v3.1.2 + if: ${{ matrix.config.os == 'windows-latest' }} + with: + name: soft_oal-${{matrix.config.name}} + path: build/archive diff --git a/libs/openal-soft/.github/workflows/makemhr.yml b/libs/openal-soft/.github/workflows/makemhr.yml new file mode 100644 index 00000000..65486150 --- /dev/null +++ b/libs/openal-soft/.github/workflows/makemhr.yml @@ -0,0 +1,76 @@ +name: makemhr + +on: + push: + paths: + - 'utils/makemhr/**' + - '.github/workflows/makemhr.yml' + + workflow_dispatch: + +env: + BUILD_TYPE: Release + +jobs: + Win64: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + + - name: Get current date + run: echo "CurrentDate=$(date +'%Y-%m-%d')" >> $env:GITHUB_ENV + + - name: Get commit hash + run: echo "CommitHash=$(git rev-parse --short=7 HEAD)" >> $env:GITHUB_ENV + + - name: Clone libmysofa + run: git clone --depth 1 --branch v1.3.1 https://github.com/hoene/libmysofa.git libmysofa + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@v1.1.3 + + - name: Restore libmysofa NuGet packages + working-directory: ${{github.workspace}}/libmysofa + run: nuget restore ${{github.workspace}}/libmysofa/windows/libmysofa.sln + + - name: Build libmysofa + working-directory: ${{github.workspace}}/libmysofa + run: msbuild /m /p:Configuration=${{env.BUILD_TYPE}} ${{github.workspace}}/libmysofa/windows/libmysofa.sln + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -D "MYSOFA_LIBRARY=${{github.workspace}}/libmysofa/windows/bin/x64/Release/mysofa.lib" -D "MYSOFA_INCLUDE_DIR=${{github.workspace}}/libmysofa/src/hrtf" -D "ZLIB_LIBRARY=${{github.workspace}}/libmysofa/windows/third-party/zlib-1.2.11/lib/zlib.lib" -D "ZLIB_INCLUDE_DIR=${{github.workspace}}/libmysofa/windows/third-party/zlib-1.2.11/include" + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Make Artifacts folder + run: | + mkdir "Artifacts" + mkdir "Release" + + - name: Collect artifacts + run: | + copy "build/Release/makemhr.exe" "Artifacts/makemhr.exe" + copy "libmysofa/windows/third-party/zlib-1.2.11/bin/zlib.dll" "Artifacts/zlib.dll" + + - name: Upload makemhr artifact + uses: actions/upload-artifact@v3.1.2 + with: + name: makemhr + path: "Artifacts/" + + - name: Compress artifacts + uses: papeloto/action-zip@v1 + with: + files: Artifacts/ + dest: "Release/makemhr.zip" + + - name: GitHub pre-release + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{secrets.GITHUB_TOKEN}}" + automatic_release_tag: "makemhr" + prerelease: true + title: "[${{env.CurrentDate}}] makemhr-${{env.CommitHash}}" + files: "Release/makemhr.zip" diff --git a/libs/openal-soft/.gitignore b/libs/openal-soft/.gitignore index 4a8212b5..688980a5 100644 --- a/libs/openal-soft/.gitignore +++ b/libs/openal-soft/.gitignore @@ -1,6 +1,7 @@ build*/ winbuild win64build +.vs/ ## kdevelop *.kdev4 diff --git a/libs/openal-soft/CMakeLists.txt b/libs/openal-soft/CMakeLists.txt index 8c93b974..b2bc06a0 100644 --- a/libs/openal-soft/CMakeLists.txt +++ b/libs/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,10 +29,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "iOS") FORCE) endif() endif() +elseif(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + set(ALSOFT_UWP TRUE) endif() -project(OpenAL) - if(COMMAND CMAKE_POLICY) cmake_policy(SET CMP0003 NEW) cmake_policy(SET CMP0005 NEW) @@ -44,11 +45,25 @@ if(COMMAND CMAKE_POLICY) if(POLICY CMP0054) cmake_policy(SET CMP0054 NEW) endif(POLICY CMP0054) + if(POLICY CMP0058) + cmake_policy(SET CMP0058 NEW) + endif(POLICY CMP0058) + if(POLICY CMP0063) + cmake_policy(SET CMP0063 NEW) + endif(POLICY CMP0063) if(POLICY CMP0075) cmake_policy(SET CMP0075 NEW) endif(POLICY CMP0075) + if(POLICY CMP0092) + cmake_policy(SET CMP0092 NEW) + endif(POLICY CMP0092) + if(POLICY CMP0117) + cmake_policy(SET CMP0117 NEW) + endif(POLICY CMP0117) endif(COMMAND CMAKE_POLICY) +project(OpenAL) + if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." @@ -60,6 +75,13 @@ if(NOT CMAKE_DEBUG_POSTFIX) FORCE) endif() +set(DEFAULT_TARGET_PROPS + # Require C++17. + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED TRUE + # Prefer C11, but support C99 and earlier when possible. + C_STANDARD 11) + set(CMAKE_MODULE_PATH "${OpenAL_SOURCE_DIR}/cmake") include(CheckFunctionExists) @@ -76,6 +98,7 @@ include(CMakePackageConfigHelpers) include(GNUInstallDirs) find_package(PkgConfig) +find_package(SDL2 QUIET) option(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON) @@ -86,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) @@ -97,6 +121,11 @@ option(ALSOFT_UPDATE_BUILD_VERSION "Update git build version info" ON) option(ALSOFT_EAX "Enable legacy EAX extensions" ${WIN32}) +option(ALSOFT_SEARCH_INSTALL_DATADIR "Search the installation data directory" OFF) +if(ALSOFT_SEARCH_INSTALL_DATADIR) + set(ALSOFT_INSTALL_DATADIR ${CMAKE_INSTALL_FULL_DATADIR}) +endif() + if(DEFINED SHARE_INSTALL_DIR) message(WARNING "SHARE_INSTALL_DIR is deprecated. Use the variables provided by the GNUInstallDirs module instead") set(CMAKE_INSTALL_DATADIR "${SHARE_INSTALL_DIR}") @@ -120,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) @@ -137,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) @@ -150,20 +181,27 @@ if(NOT LIBTYPE) endif() set(LIB_MAJOR_VERSION "1") -set(LIB_MINOR_VERSION "22") -set(LIB_REVISION "0") +set(LIB_MINOR_VERSION "23") +set(LIB_REVISION "1") set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0) set(EXPORT_DECL "") -# Require C++14 -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED TRUE) - -# Prefer C11, but support C99 and C90 too. -set(CMAKE_C_STANDARD 11) +# 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() if(NOT WIN32) # Check if _POSIX_C_SOURCE and _XOPEN_SOURCE needs to be set for POSIX functions @@ -219,14 +257,7 @@ if(MSVC) if(HAVE_PERMISSIVE_SWITCH) set(C_FLAGS ${C_FLAGS} $<$:/permissive->) endif() - set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030) - # Remove /W3, which is added by default, since we set /W4. Some build - # generators with MSVC complain about both /W3 and /W4 being specified. - foreach(flag_var CMAKE_C_FLAGS CMAKE_CXX_FLAGS) - if(${flag_var} MATCHES "/W3") - string(REGEX REPLACE "/W3" "" ${flag_var} "${${flag_var}}") - endif() - endforeach() + set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030 /wd5051) if(NOT DXSDK_DIR) string(REGEX REPLACE "\\\\" "/" DXSDK_DIR "$ENV{DXSDK_DIR}") @@ -254,6 +285,16 @@ else() -Wpedantic $<$:-Wold-style-cast -Wnon-virtual-dtor -Woverloaded-virtual>) + check_cxx_compiler_flag(-Wno-c++20-attribute-extensions HAVE_WNO_CXX20_ATTR_EXT) + if(HAVE_WNO_CXX20_ATTR_EXT) + set(C_FLAGS ${C_FLAGS} $<$:-Wno-c++20-attribute-extensions>) + else() + check_cxx_compiler_flag(-Wno-c++20-extensions HAVE_WNO_CXX20_EXT) + if(HAVE_WNO_CXX20_EXT) + set(C_FLAGS ${C_FLAGS} $<$:-Wno-c++20-extensions>) + endif() + endif() + if(ALSOFT_WERROR) set(C_FLAGS ${C_FLAGS} -Werror) endif() @@ -288,7 +329,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) @@ -296,14 +337,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) @@ -311,45 +352,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) - check_c_compiler_flag(-fvisibility=hidden HAVE_VISIBILITY_HIDDEN_SWITCH) - if(HAVE_VISIBILITY_HIDDEN_SWITCH) - set(C_FLAGS ${C_FLAGS} -fvisibility=hidden) + if(HAVE_GCC_PROTECTED_VISIBILITY OR HAVE_GCC_DEFAULT_VISIBILITY) + set(CMAKE_C_VISIBILITY_PRESET hidden) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) endif() - endif() - set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}") + endif() endif() @@ -380,71 +414,62 @@ set(HAVE_SSE4_1 0) set(HAVE_NEON 0) # Check for SSE support +option(ALSOFT_CPUEXT_SSE "Enable SSE support" ON) option(ALSOFT_REQUIRE_SSE "Require SSE support" OFF) -if(HAVE_XMMINTRIN_H) - option(ALSOFT_CPUEXT_SSE "Enable SSE support" ON) - if(ALSOFT_CPUEXT_SSE) - set(HAVE_SSE 1) - endif() +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) option(ALSOFT_REQUIRE_SSE2 "Require SSE2 support" OFF) -if(HAVE_EMMINTRIN_H) - option(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON) - if(HAVE_SSE AND ALSOFT_CPUEXT_SSE2) - set(HAVE_SSE2 1) - endif() +if(ALSOFT_CPUEXT_SSE2 AND HAVE_SSE AND HAVE_EMMINTRIN_H) + set(HAVE_SSE2 1) endif() if(ALSOFT_REQUIRE_SSE2 AND NOT HAVE_SSE2) message(FATAL_ERROR "Failed to enable required SSE2 CPU extensions") endif() +option(ALSOFT_CPUEXT_SSE3 "Enable SSE3 support" ON) option(ALSOFT_REQUIRE_SSE3 "Require SSE3 support" OFF) -if(HAVE_PMMINTRIN_H) - option(ALSOFT_CPUEXT_SSE3 "Enable SSE3 support" ON) - if(HAVE_SSE2 AND ALSOFT_CPUEXT_SSE3) - set(HAVE_SSE3 1) - endif() +if(ALSOFT_CPUEXT_SSE3 AND HAVE_SSE2 AND HAVE_PMMINTRIN_H) + set(HAVE_SSE3 1) endif() if(ALSOFT_REQUIRE_SSE3 AND NOT HAVE_SSE3) message(FATAL_ERROR "Failed to enable required SSE3 CPU extensions") endif() +option(ALSOFT_CPUEXT_SSE4_1 "Enable SSE4.1 support" ON) option(ALSOFT_REQUIRE_SSE4_1 "Require SSE4.1 support" OFF) -if(HAVE_SMMINTRIN_H) - option(ALSOFT_CPUEXT_SSE4_1 "Enable SSE4.1 support" ON) - if(HAVE_SSE3 AND ALSOFT_CPUEXT_SSE4_1) - set(HAVE_SSE4_1 1) - endif() +if(ALSOFT_CPUEXT_SSE4_1 AND HAVE_SSE3 AND HAVE_SMMINTRIN_H) + set(HAVE_SSE4_1 1) endif() if(ALSOFT_REQUIRE_SSE4_1 AND NOT HAVE_SSE4_1) message(FATAL_ERROR "Failed to enable required SSE4.1 CPU extensions") endif() # Check for ARM Neon support +option(ALSOFT_CPUEXT_NEON "Enable ARM NEON support" ON) option(ALSOFT_REQUIRE_NEON "Require ARM NEON support" OFF) -if(HAVE_ARM_NEON_H) - option(ALSOFT_CPUEXT_NEON "Enable ARM NEON support" ON) - if(ALSOFT_CPUEXT_NEON) - check_c_source_compiles("#include - int main() - { - int32x4_t ret4 = vdupq_n_s32(0); - return vgetq_lane_s32(ret4, 0); - }" HAVE_NEON_INTRINSICS) - if(HAVE_NEON_INTRINSICS) - set(HAVE_NEON 1) - endif() +if(ALSOFT_CPUEXT_NEON AND HAVE_ARM_NEON_H) + check_c_source_compiles("#include + int main() + { + int32x4_t ret4 = vdupq_n_s32(0); + return vgetq_lane_s32(ret4, 0); + }" HAVE_NEON_INTRINSICS) + if(HAVE_NEON_INTRINSICS) + set(HAVE_NEON 1) 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) @@ -468,7 +493,8 @@ if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2) # assumes the stack is suitably aligned. Older Linux code or other # 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(EXPORT_DECL "${EXPORT_DECL}__attribute__((force_align_arg_pointer))") + set(ALSOFT_FORCE_ALIGN "__attribute__((force_align_arg_pointer))") endif() endif() endif() @@ -510,7 +536,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) @@ -539,8 +565,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,26 +606,29 @@ check_symbol_exists(getopt unistd.h HAVE_GETOPT) # router, and certain tools and examples. set(COMMON_OBJS 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 @@ -610,8 +637,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) @@ -638,6 +663,9 @@ set(CORE_OBJS core/converter.h core/cpu_caps.cpp core/cpu_caps.h + core/cubic_defs.h + core/cubic_tables.cpp + core/cubic_tables.h core/devformat.cpp core/devformat.h core/device.cpp @@ -669,6 +697,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 @@ -677,33 +707,45 @@ set(CORE_OBJS core/voice_change.h) set(HAVE_RTKIT 0) -option(ALSOFT_REQUIRE_RTKIT "Require RTKit/D-Bus support" FALSE) -find_package(DBus1 QUIET) -if(DBus1_FOUND) +if(NOT WIN32) option(ALSOFT_RTKIT "Enable RTKit support" ON) + option(ALSOFT_REQUIRE_RTKIT "Require RTKit/D-Bus support" FALSE) if(ALSOFT_RTKIT) - set(HAVE_RTKIT 1) - set(CORE_OBJS ${CORE_OBJS} core/dbus_wrap.cpp core/dbus_wrap.h core/rtkit.cpp core/rtkit.h) - if(WIN32 OR HAVE_DLFCN_H) - set(INC_PATHS ${INC_PATHS} ${DBus1_INCLUDE_DIRS}) - set(CPP_DEFS ${CPP_DEFS} ${DBus1_DEFINITIONS}) - else() - set(EXTRA_LIBS ${EXTRA_LIBS} ${DBus1_LIBRARIES}) + find_package(DBus1 QUIET) + if(NOT DBus1_FOUND AND PkgConfig_FOUND) + pkg_check_modules(DBUS dbus-1) endif() + if(DBus1_FOUND OR DBUS_FOUND) + set(HAVE_RTKIT 1) + set(CORE_OBJS ${CORE_OBJS} core/dbus_wrap.cpp core/dbus_wrap.h + core/rtkit.cpp core/rtkit.h) + if(NOT DBus1_FOUND) + set(INC_PATHS ${INC_PATHS} ${DBUS_INCLUDE_DIRS}) + set(CPP_DEFS ${CPP_DEFS} ${DBUS_CFLAGS_OTHER}) + if(NOT HAVE_DLFCN_H) + set(EXTRA_LIBS ${EXTRA_LIBS} ${DBUS_LINK_LIBRARIES}) + endif() + elseif(HAVE_DLFCN_H) + set(INC_PATHS ${INC_PATHS} ${DBus1_INCLUDE_DIRS}) + set(CPP_DEFS ${CPP_DEFS} ${DBus1_DEFINITIONS}) + else() + set(EXTRA_LIBS ${EXTRA_LIBS} ${DBus1_LIBRARIES}) + endif() + endif() + else() + set(MISSING_VARS "") + if(NOT DBus1_INCLUDE_DIRS) + set(MISSING_VARS "${MISSING_VARS} DBus1_INCLUDE_DIRS") + endif() + if(NOT DBus1_LIBRARIES) + set(MISSING_VARS "${MISSING_VARS} DBus1_LIBRARIES") + endif() + message(STATUS "Could NOT find DBus1 (missing:${MISSING_VARS})") + unset(MISSING_VARS) endif() -else() - set(MISSING_VARS "") - if(NOT DBus1_INCLUDE_DIRS) - set(MISSING_VARS "${MISSING_VARS} DBus1_INCLUDE_DIRS") - endif() - if(NOT DBus1_LIBRARIES) - set(MISSING_VARS "${MISSING_VARS} DBus1_LIBRARIES") - endif() - message(STATUS "Could NOT find DBus1 (missing:${MISSING_VARS})") - unset(MISSING_VARS) endif() if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT) - message(FATAL_ERROR "Failed to enabled required RTKit support") + message(FATAL_ERROR "Failed to enable required RTKit support") endif() # Default mixers, always available @@ -719,6 +761,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 @@ -738,6 +783,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 @@ -775,32 +821,32 @@ 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) -if (ALSOFT_EAX) +if(ALSOFT_EAX) set(OPENAL_OBJS ${OPENAL_OBJS} - al/eax_api.cpp - al/eax_api.h - al/eax_eax_call.cpp - al/eax_eax_call.h - al/eax_effect.cpp - al/eax_effect.h - al/eax_exception.cpp - al/eax_exception.h - al/eax_fx_slot_index.cpp - al/eax_fx_slot_index.h - al/eax_fx_slots.cpp - al/eax_fx_slots.h - al/eax_globals.cpp - al/eax_globals.h - al/eax_utils.cpp - al/eax_utils.h - al/eax_x_ram.cpp - al/eax_x_ram.h -) -endif () + al/eax/api.cpp + al/eax/api.h + al/eax/call.cpp + al/eax/call.h + al/eax/effect.h + al/eax/exception.cpp + al/eax/exception.h + al/eax/fx_slot_index.cpp + al/eax/fx_slot_index.h + al/eax/fx_slots.cpp + al/eax/fx_slots.h + al/eax/globals.h + al/eax/utils.cpp + al/eax/utils.h + al/eax/x_ram.h + ) +endif() # Include SIMD mixers set(CPU_EXTS "Default") @@ -864,137 +910,156 @@ set(ALC_OBJS ${ALC_OBJS} alc/backends/null.h ) -# Check ALSA backend -option(ALSOFT_REQUIRE_ALSA "Require ALSA backend" OFF) -find_package(ALSA) -if(ALSA_FOUND) - option(ALSOFT_BACKEND_ALSA "Enable ALSA backend" ON) - if(ALSOFT_BACKEND_ALSA) - set(HAVE_ALSA 1) - set(BACKENDS "${BACKENDS} ALSA${IS_LINKED},") - set(ALC_OBJS ${ALC_OBJS} alc/backends/alsa.cpp alc/backends/alsa.h) - add_backend_libs(${ALSA_LIBRARIES}) - set(INC_PATHS ${INC_PATHS} ${ALSA_INCLUDE_DIRS}) - endif() -endif() -if(ALSOFT_REQUIRE_ALSA AND NOT HAVE_ALSA) - message(FATAL_ERROR "Failed to enabled required ALSA backend") -endif() - -# Check OSS backend -option(ALSOFT_REQUIRE_OSS "Require OSS backend" OFF) -find_package(OSS) -if(OSS_FOUND) - option(ALSOFT_BACKEND_OSS "Enable OSS backend" ON) - if(ALSOFT_BACKEND_OSS) - set(HAVE_OSS 1) - set(BACKENDS "${BACKENDS} OSS,") - set(ALC_OBJS ${ALC_OBJS} alc/backends/oss.cpp alc/backends/oss.h) - if(OSS_LIBRARIES) - set(EXTRA_LIBS ${OSS_LIBRARIES} ${EXTRA_LIBS}) - endif() - set(INC_PATHS ${INC_PATHS} ${OSS_INCLUDE_DIRS}) - endif() -endif() -if(ALSOFT_REQUIRE_OSS AND NOT HAVE_OSS) - message(FATAL_ERROR "Failed to enabled required OSS backend") -endif() - # Check PipeWire backend +option(ALSOFT_BACKEND_PIPEWIRE "Enable PipeWire backend" ON) option(ALSOFT_REQUIRE_PIPEWIRE "Require PipeWire backend" OFF) -if(PkgConfig_FOUND) - pkg_check_modules(PIPEWIRE libpipewire-0.3) +if(ALSOFT_BACKEND_PIPEWIRE AND PkgConfig_FOUND) + pkg_check_modules(PIPEWIRE libpipewire-0.3>=0.3.23) if(PIPEWIRE_FOUND) - option(ALSOFT_BACKEND_PIPEWIRE "Enable PipeWire backend" ON) - if(ALSOFT_BACKEND_PIPEWIRE) - set(HAVE_PIPEWIRE 1) - set(BACKENDS "${BACKENDS} PipeWire${IS_LINKED},") - set(ALC_OBJS ${ALC_OBJS} alc/backends/pipewire.cpp alc/backends/pipewire.h) - add_backend_libs(${PIPEWIRE_LIBRARIES}) - set(INC_PATHS ${INC_PATHS} ${PIPEWIRE_INCLUDE_DIRS}) - endif() + set(HAVE_PIPEWIRE 1) + set(BACKENDS "${BACKENDS} PipeWire${IS_LINKED},") + set(ALC_OBJS ${ALC_OBJS} alc/backends/pipewire.cpp alc/backends/pipewire.h) + add_backend_libs(${PIPEWIRE_LIBRARIES}) + set(INC_PATHS ${INC_PATHS} ${PIPEWIRE_INCLUDE_DIRS}) endif() endif() 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 Solaris backend -option(ALSOFT_REQUIRE_SOLARIS "Require Solaris backend" OFF) -find_package(AudioIO) -if(AUDIOIO_FOUND) - option(ALSOFT_BACKEND_SOLARIS "Enable Solaris backend" ON) - if(ALSOFT_BACKEND_SOLARIS) - set(HAVE_SOLARIS 1) - set(BACKENDS "${BACKENDS} Solaris,") - set(ALC_OBJS ${ALC_OBJS} alc/backends/solaris.cpp alc/backends/solaris.h) - set(INC_PATHS ${INC_PATHS} ${AUDIOIO_INCLUDE_DIRS}) +# Check PulseAudio backend +option(ALSOFT_BACKEND_PULSEAUDIO "Enable PulseAudio backend" ON) +option(ALSOFT_REQUIRE_PULSEAUDIO "Require PulseAudio backend" OFF) +if(ALSOFT_BACKEND_PULSEAUDIO) + find_package(PulseAudio) + if(PULSEAUDIO_FOUND) + set(HAVE_PULSEAUDIO 1) + set(BACKENDS "${BACKENDS} PulseAudio${IS_LINKED},") + set(ALC_OBJS ${ALC_OBJS} alc/backends/pulseaudio.cpp alc/backends/pulseaudio.h) + add_backend_libs(${PULSEAUDIO_LIBRARY}) + set(INC_PATHS ${INC_PATHS} ${PULSEAUDIO_INCLUDE_DIR}) endif() endif() +if(ALSOFT_REQUIRE_PULSEAUDIO AND NOT HAVE_PULSEAUDIO) + message(FATAL_ERROR "Failed to enable required PulseAudio backend") +endif() + +if(NOT WIN32) + # Check ALSA backend + option(ALSOFT_BACKEND_ALSA "Enable ALSA backend" ON) + option(ALSOFT_REQUIRE_ALSA "Require ALSA backend" OFF) + if(ALSOFT_BACKEND_ALSA) + find_package(ALSA) + if(ALSA_FOUND) + set(HAVE_ALSA 1) + set(BACKENDS "${BACKENDS} ALSA${IS_LINKED},") + set(ALC_OBJS ${ALC_OBJS} alc/backends/alsa.cpp alc/backends/alsa.h) + add_backend_libs(${ALSA_LIBRARIES}) + set(INC_PATHS ${INC_PATHS} ${ALSA_INCLUDE_DIRS}) + endif() + endif() + + # Check OSS backend + option(ALSOFT_BACKEND_OSS "Enable OSS backend" ON) + option(ALSOFT_REQUIRE_OSS "Require OSS backend" OFF) + if(ALSOFT_BACKEND_OSS) + find_package(OSS) + if(OSS_FOUND) + set(HAVE_OSS 1) + set(BACKENDS "${BACKENDS} OSS,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/oss.cpp alc/backends/oss.h) + if(OSS_LIBRARIES) + set(EXTRA_LIBS ${OSS_LIBRARIES} ${EXTRA_LIBS}) + endif() + set(INC_PATHS ${INC_PATHS} ${OSS_INCLUDE_DIRS}) + endif() + endif() + + # Check Solaris backend + option(ALSOFT_BACKEND_SOLARIS "Enable Solaris backend" ON) + option(ALSOFT_REQUIRE_SOLARIS "Require Solaris backend" OFF) + if(ALSOFT_BACKEND_SOLARIS) + find_package(AudioIO) + if(AUDIOIO_FOUND) + set(HAVE_SOLARIS 1) + set(BACKENDS "${BACKENDS} Solaris,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/solaris.cpp alc/backends/solaris.h) + set(INC_PATHS ${INC_PATHS} ${AUDIOIO_INCLUDE_DIRS}) + endif() + endif() + + # Check SndIO backend + option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON) + option(ALSOFT_REQUIRE_SNDIO "Require SndIO backend" OFF) + if(ALSOFT_BACKEND_SNDIO) + find_package(SoundIO) + if(SOUNDIO_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}) + endif() + endif() +endif() +if(ALSOFT_REQUIRE_ALSA AND NOT HAVE_ALSA) + message(FATAL_ERROR "Failed to enable required ALSA backend") +endif() +if(ALSOFT_REQUIRE_OSS AND NOT HAVE_OSS) + 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") -endif() - -# Check SndIO backend -option(ALSOFT_REQUIRE_SNDIO "Require SndIO backend" OFF) -find_package(SoundIO) -if(SOUNDIO_FOUND) - option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON) - if(ALSOFT_BACKEND_SNDIO) - 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}) - endif() + 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 -option(ALSOFT_REQUIRE_WINMM "Require Windows Multimedia backend" OFF) -option(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF) -option(ALSOFT_REQUIRE_WASAPI "Require WASAPI backend" OFF) if(WIN32) - # Check MMSystem backend - option(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON) - if(ALSOFT_BACKEND_WINMM) - set(HAVE_WINMM 1) - set(BACKENDS "${BACKENDS} WinMM,") - set(ALC_OBJS ${ALC_OBJS} alc/backends/winmm.cpp alc/backends/winmm.h) - # 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 (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() - # Check DSound backend - 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) + # Check DSound backend option(ALSOFT_BACKEND_DSOUND "Enable DirectSound backend" ON) + option(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF) if(ALSOFT_BACKEND_DSOUND) - set(HAVE_DSOUND 1) - set(BACKENDS "${BACKENDS} DirectSound,") - set(ALC_OBJS ${ALC_OBJS} alc/backends/dsound.cpp alc/backends/dsound.h) + 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}) + if(NOT HAVE_DSOUND_H) + set(INC_PATHS ${INC_PATHS} ${DSOUND_INCLUDE_DIR}) + endif() endif() endif() endif() # Check for WASAPI backend - check_include_file(mmdeviceapi.h HAVE_MMDEVICEAPI_H) - if(HAVE_MMDEVICEAPI_H) - option(ALSOFT_BACKEND_WASAPI "Enable WASAPI backend" ON) - if(ALSOFT_BACKEND_WASAPI) + option(ALSOFT_BACKEND_WASAPI "Enable WASAPI backend" ON) + option(ALSOFT_REQUIRE_WASAPI "Require WASAPI backend" OFF) + if(ALSOFT_BACKEND_WASAPI) + check_include_file(mmdeviceapi.h HAVE_MMDEVICEAPI_H) + if(HAVE_MMDEVICEAPI_H) set(HAVE_WASAPI 1) set(BACKENDS "${BACKENDS} WASAPI,") set(ALC_OBJS ${ALC_OBJS} alc/backends/wasapi.cpp alc/backends/wasapi.h) @@ -1002,55 +1067,21 @@ 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") -endif() - -# Check PortAudio backend -option(ALSOFT_REQUIRE_PORTAUDIO "Require PortAudio backend" OFF) -find_package(PortAudio) -if(PORTAUDIO_FOUND) - option(ALSOFT_BACKEND_PORTAUDIO "Enable PortAudio backend" ON) - if(ALSOFT_BACKEND_PORTAUDIO) - set(HAVE_PORTAUDIO 1) - set(BACKENDS "${BACKENDS} PortAudio${IS_LINKED},") - set(ALC_OBJS ${ALC_OBJS} alc/backends/portaudio.cpp alc/backends/portaudio.h) - add_backend_libs(${PORTAUDIO_LIBRARIES}) - set(INC_PATHS ${INC_PATHS} ${PORTAUDIO_INCLUDE_DIRS}) - endif() -endif() -if(ALSOFT_REQUIRE_PORTAUDIO AND NOT HAVE_PORTAUDIO) - message(FATAL_ERROR "Failed to enabled required PortAudio backend") -endif() - -# Check PulseAudio backend -option(ALSOFT_REQUIRE_PULSEAUDIO "Require PulseAudio backend" OFF) -find_package(PulseAudio) -if(PULSEAUDIO_FOUND) - option(ALSOFT_BACKEND_PULSEAUDIO "Enable PulseAudio backend" ON) - if(ALSOFT_BACKEND_PULSEAUDIO) - set(HAVE_PULSEAUDIO 1) - set(BACKENDS "${BACKENDS} PulseAudio${IS_LINKED},") - set(ALC_OBJS ${ALC_OBJS} alc/backends/pulseaudio.cpp alc/backends/pulseaudio.h) - add_backend_libs(${PULSEAUDIO_LIBRARIES}) - set(INC_PATHS ${INC_PATHS} ${PULSEAUDIO_INCLUDE_DIRS}) - endif() -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 WASAPI backend") endif() # Check JACK backend +option(ALSOFT_BACKEND_JACK "Enable JACK backend" ON) option(ALSOFT_REQUIRE_JACK "Require JACK backend" OFF) -find_package(JACK) -if(JACK_FOUND) - option(ALSOFT_BACKEND_JACK "Enable JACK backend" ON) - if(ALSOFT_BACKEND_JACK) +if(ALSOFT_BACKEND_JACK) + find_package(JACK) + if(JACK_FOUND) set(HAVE_JACK 1) set(BACKENDS "${BACKENDS} JACK${IS_LINKED},") set(ALC_OBJS ${ALC_OBJS} alc/backends/jack.cpp alc/backends/jack.h) @@ -1059,119 +1090,126 @@ if(JACK_FOUND) 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 +option(ALSOFT_BACKEND_COREAUDIO "Enable CoreAudio backend" ON) option(ALSOFT_REQUIRE_COREAUDIO "Require CoreAudio backend" OFF) -find_library(COREAUDIO_FRAMEWORK NAMES CoreAudio) -find_path(AUDIOUNIT_INCLUDE_DIR NAMES AudioUnit/AudioUnit.h) -if(COREAUDIO_FRAMEWORK AND AUDIOUNIT_INCLUDE_DIR) - option(ALSOFT_BACKEND_COREAUDIO "Enable CoreAudio backend" ON) - if(ALSOFT_BACKEND_COREAUDIO) +if(ALSOFT_BACKEND_COREAUDIO) + find_library(COREAUDIO_FRAMEWORK NAMES CoreAudio) + find_path(AUDIOUNIT_INCLUDE_DIR NAMES AudioUnit/AudioUnit.h) + if(COREAUDIO_FRAMEWORK AND AUDIOUNIT_INCLUDE_DIR) set(HAVE_COREAUDIO 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/coreaudio.cpp alc/backends/coreaudio.h) set(BACKENDS "${BACKENDS} CoreAudio,") - if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + + set(EXTRA_LIBS -Wl,-framework,CoreAudio ${EXTRA_LIBS}) + if(CMAKE_SYSTEM_NAME MATCHES "^(iOS|tvOS)$") find_library(COREFOUNDATION_FRAMEWORK NAMES CoreFoundation) - set(EXTRA_LIBS ${COREAUDIO_FRAMEWORK} ${COREFOUNDATION_FRAMEWORK} ${EXTRA_LIBS}) + if(COREFOUNDATION_FRAMEWORK) + set(EXTRA_LIBS -Wl,-framework,CoreFoundation ${EXTRA_LIBS}) + endif() else() - set(EXTRA_LIBS ${COREAUDIO_FRAMEWORK} /System/Library/Frameworks/AudioUnit.framework - /System/Library/Frameworks/ApplicationServices.framework ${EXTRA_LIBS}) + set(EXTRA_LIBS -Wl,-framework,AudioUnit,-framework,ApplicationServices ${EXTRA_LIBS}) endif() # Some versions of OSX may need the AudioToolbox framework. Add it if # it's found. find_library(AUDIOTOOLBOX_LIBRARY NAMES AudioToolbox) if(AUDIOTOOLBOX_LIBRARY) - set(EXTRA_LIBS ${AUDIOTOOLBOX_LIBRARY} ${EXTRA_LIBS}) + set(EXTRA_LIBS -Wl,-framework,AudioToolbox ${EXTRA_LIBS}) endif() set(INC_PATHS ${INC_PATHS} ${AUDIOUNIT_INCLUDE_DIR}) endif() endif() if(ALSOFT_REQUIRE_COREAUDIO AND NOT HAVE_COREAUDIO) - message(FATAL_ERROR "Failed to enabled required CoreAudio backend") -endif() - -# Check for OpenSL (Android) backend -option(ALSOFT_REQUIRE_OPENSL "Require OpenSL backend" OFF) -find_package(OpenSL) -if(OPENSL_FOUND) - option(ALSOFT_BACKEND_OPENSL "Enable OpenSL backend" ON) - if(ALSOFT_BACKEND_OPENSL) - set(HAVE_OPENSL 1) - set(ALC_OBJS ${ALC_OBJS} alc/backends/opensl.cpp alc/backends/opensl.h) - set(BACKENDS "${BACKENDS} OpenSL,") - set(EXTRA_LIBS "OpenSL::OpenSLES" ${EXTRA_LIBS}) - 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 CoreAudio backend") endif() # Check for Oboe (Android) backend -set(OBOE_TARGET ) -if(ANDROID) - set(OBOE_SOURCE "" CACHE STRING "Source directory for Oboe.") - if(OBOE_SOURCE) - # Force Oboe to build with hidden symbols. Don't want to be exporting - # them from OpenAL. - set(OLD_CXX_FLAGS ${CMAKE_CXX_FLAGS}) - check_cxx_compiler_flag(-fvisibility=hidden HAVE_VISIBILITY_HIDDEN_SWITCH) - if(HAVE_VISIBILITY_HIDDEN_SWITCH) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") - endif() - add_subdirectory(${OBOE_SOURCE} ./oboe) - set(CMAKE_CXX_FLAGS ${OLD_CXX_FLAGS}) - unset(OLD_CXX_FLAGS) - - set(OBOE_TARGET oboe) - else() - find_package(oboe CONFIG) - if(NOT TARGET oboe::oboe) - find_package(Oboe) - endif() - if(TARGET oboe::oboe) - set(OBOE_TARGET "oboe::oboe") +option(ALSOFT_BACKEND_OBOE "Enable Oboe backend" ON) +option(ALSOFT_REQUIRE_OBOE "Require Oboe backend" OFF) +if(ALSOFT_BACKEND_OBOE) + set(OBOE_TARGET ) + if(ANDROID) + set(OBOE_SOURCE "" CACHE STRING "Source directory for Oboe.") + if(OBOE_SOURCE) + add_subdirectory(${OBOE_SOURCE} ./oboe) + set(OBOE_TARGET oboe) + else() + find_package(oboe CONFIG) + if(NOT TARGET oboe::oboe) + find_package(Oboe) + endif() + if(TARGET oboe::oboe) + set(OBOE_TARGET "oboe::oboe") + endif() endif() endif() -endif() -option(ALSOFT_REQUIRE_OBOE "Require Oboe backend" OFF) -if(OBOE_TARGET) - option(ALSOFT_BACKEND_OBOE "Enable Oboe backend" ON) - if(ALSOFT_BACKEND_OBOE) + if(OBOE_TARGET) set(HAVE_OBOE 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/oboe.cpp alc/backends/oboe.h) set(BACKENDS "${BACKENDS} Oboe,") set(EXTRA_LIBS ${OBOE_TARGET} ${EXTRA_LIBS}) - - if(MEGA) - set(ALC_OBJS ${ALC_OBJS} opensl_latency.cpp) - endif() 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 +option(ALSOFT_BACKEND_OPENSL "Enable OpenSL backend" ON) +option(ALSOFT_REQUIRE_OPENSL "Require OpenSL backend" OFF) +if(ALSOFT_BACKEND_OPENSL) + find_package(OpenSL) + if(OPENSL_FOUND) + set(HAVE_OPENSL 1) + set(ALC_OBJS ${ALC_OBJS} alc/backends/opensl.cpp alc/backends/opensl.h) + set(BACKENDS "${BACKENDS} OpenSL,") + set(EXTRA_LIBS ${OPENSL_LIBRARIES} ${EXTRA_LIBS}) + set(INC_PATHS ${INC_PATHS} ${OPENSL_INCLUDE_DIRS}) + endif() +endif() +if(ALSOFT_REQUIRE_OPENSL AND NOT HAVE_OPENSL) + message(FATAL_ERROR "Failed to enable required OpenSL backend") +endif() + +# Check PortAudio backend +option(ALSOFT_BACKEND_PORTAUDIO "Enable PortAudio backend" ON) +option(ALSOFT_REQUIRE_PORTAUDIO "Require PortAudio backend" OFF) +if(ALSOFT_BACKEND_PORTAUDIO) + find_package(PortAudio) + if(PORTAUDIO_FOUND) + set(HAVE_PORTAUDIO 1) + set(BACKENDS "${BACKENDS} PortAudio${IS_LINKED},") + set(ALC_OBJS ${ALC_OBJS} alc/backends/portaudio.cpp alc/backends/portaudio.h) + add_backend_libs(${PORTAUDIO_LIBRARIES}) + set(INC_PATHS ${INC_PATHS} ${PORTAUDIO_INCLUDE_DIRS}) + endif() +endif() +if(ALSOFT_REQUIRE_PORTAUDIO AND NOT HAVE_PORTAUDIO) + message(FATAL_ERROR "Failed to enable required PortAudio backend") endif() # Check for SDL2 backend +# Off by default, since it adds a runtime dependency +option(ALSOFT_BACKEND_SDL2 "Enable SDL2 backend" OFF) option(ALSOFT_REQUIRE_SDL2 "Require SDL2 backend" OFF) -find_package(SDL2) -if(SDL2_FOUND) - # Off by default, since it adds a runtime dependency - option(ALSOFT_BACKEND_SDL2 "Enable SDL2 backend" OFF) - if(ALSOFT_BACKEND_SDL2) +if(ALSOFT_BACKEND_SDL2) + if(SDL2_FOUND) set(HAVE_SDL2 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/sdl2.cpp alc/backends/sdl2.h) set(BACKENDS "${BACKENDS} SDL2,") - set(EXTRA_LIBS ${SDL2_LIBRARY} ${EXTRA_LIBS}) - set(INC_PATHS ${INC_PATHS} ${SDL2_INCLUDE_DIR}) + set(EXTRA_LIBS ${EXTRA_LIBS} SDL2::SDL2) + else() + message(STATUS "Could NOT find 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 @@ -1188,14 +1226,31 @@ set(BACKENDS "${BACKENDS} Null") find_package(Git) if(ALSOFT_UPDATE_BUILD_VERSION AND GIT_FOUND AND EXISTS "${OpenAL_SOURCE_DIR}/.git") + set(GIT_DIR "${OpenAL_SOURCE_DIR}/.git") + + # Check if this is a submodule, if it is then find the .git directory + if(NOT IS_DIRECTORY "${OpenAL_SOURCE_DIR}/.git") + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE ${submodule}) + string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) + endif() + # Get the current working branch and its latest abbreviated commit hash - add_custom_target(build_version - ${CMAKE_COMMAND} -D GIT_EXECUTABLE=${GIT_EXECUTABLE} -D LIB_VERSION=${LIB_VERSION} + add_custom_command(OUTPUT "${OpenAL_BINARY_DIR}/version_witness.txt" + BYPRODUCTS "${OpenAL_BINARY_DIR}/version.h" + COMMAND ${CMAKE_COMMAND} -D GIT_EXECUTABLE=${GIT_EXECUTABLE} -D LIB_VERSION=${LIB_VERSION} -D LIB_VERSION_NUM=${LIB_VERSION_NUM} -D SRC=${OpenAL_SOURCE_DIR}/version.h.in -D DST=${OpenAL_BINARY_DIR}/version.h -P ${OpenAL_SOURCE_DIR}/version.cmake + COMMAND ${CMAKE_COMMAND} -E touch "${OpenAL_BINARY_DIR}/version_witness.txt" WORKING_DIRECTORY "${OpenAL_SOURCE_DIR}" + MAIN_DEPENDENCY "${OpenAL_SOURCE_DIR}/version.h.in" + DEPENDS "${GIT_DIR}/index" "${OpenAL_SOURCE_DIR}/version.cmake" VERBATIM ) + + add_custom_target(build_version DEPENDS "${OpenAL_BINARY_DIR}/version_witness.txt") else() set(GIT_BRANCH "UNKNOWN") set(GIT_COMMIT_HASH "unknown") @@ -1209,11 +1264,11 @@ option(ALSOFT_EMBED_HRTF_DATA "Embed the HRTF data files (increases library foot if(ALSOFT_EMBED_HRTF_DATA) macro(make_hrtf_header FILENAME VARNAME) set(infile "${OpenAL_SOURCE_DIR}/hrtf/${FILENAME}") - set(outfile "${OpenAL_BINARY_DIR}/${VARNAME}.h") + set(outfile "${OpenAL_BINARY_DIR}/${VARNAME}.txt") add_custom_command(OUTPUT "${outfile}" COMMAND ${CMAKE_COMMAND} -D "INPUT_FILE=${infile}" -D "OUTPUT_FILE=${outfile}" - -D "VARIABLE_NAME=${VARNAME}" -P "${CMAKE_MODULE_PATH}/bin2h.script.cmake" + -P "${CMAKE_MODULE_PATH}/bin2h.script.cmake" WORKING_DIRECTORY "${OpenAL_SOURCE_DIR}" DEPENDS "${infile}" "${CMAKE_MODULE_PATH}/bin2h.script.cmake" VERBATIM @@ -1221,7 +1276,7 @@ if(ALSOFT_EMBED_HRTF_DATA) set(ALC_OBJS ${ALC_OBJS} "${outfile}") endmacro() - make_hrtf_header("Default HRTF.mhr" "hrtf_default") + make_hrtf_header("Default HRTF.mhr" "default_hrtf") endif() @@ -1236,7 +1291,6 @@ if(ALSOFT_UTILS) endif() if(ALSOFT_UTILS OR ALSOFT_EXAMPLES) find_package(SndFile) - find_package(SDL2) if(SDL2_FOUND) find_package(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE) endif() @@ -1251,9 +1305,9 @@ endif() # Needed for openal.pc.in set(prefix ${CMAKE_INSTALL_PREFIX}) set(exec_prefix "\${prefix}") -set(libdir "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") -set(bindir "\${exec_prefix}/${CMAKE_INSTALL_BINDIR}") -set(includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") +set(libdir "${CMAKE_INSTALL_FULL_LIBDIR}") +set(bindir "${CMAKE_INSTALL_FULL_BINDIR}") +set(includedir "${CMAKE_INSTALL_FULL_INCLUDEDIR}") set(PACKAGE_VERSION "${LIB_VERSION}") set(PKG_CONFIG_CFLAGS ) set(PKG_CONFIG_PRIVATE_LIBS ) @@ -1261,9 +1315,12 @@ if(LIBTYPE STREQUAL "STATIC") set(PKG_CONFIG_CFLAGS -DAL_LIBTYPE_STATIC) foreach(FLAG ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) # If this is already a linker flag, or is a full path+file, add it - # as-is. Otherwise, it's a name intended to be dressed as -lname. + # as-is. If it's an SDL2 target, add the link flag for it. Otherwise, + # it's a name intended to be dressed as -lname. if(FLAG MATCHES "^-.*" OR EXISTS "${FLAG}") set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} ${FLAG}") + elseif(FLAG MATCHES "^SDL2::SDL2") + set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -lSDL2") else() set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -l${FLAG}") endif() @@ -1280,11 +1337,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 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) @@ -1319,7 +1377,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 $ @@ -1328,8 +1386,8 @@ else() ${OpenAL_SOURCE_DIR}/common ${OpenAL_BINARY_DIR} ) - set_target_properties(OpenAL PROPERTIES PREFIX "") - set_target_properties(OpenAL PROPERTIES OUTPUT_NAME ${LIBNAME}) + set_target_properties(OpenAL PROPERTIES ${DEFAULT_TARGET_PROPS} PREFIX "" + OUTPUT_NAME ${LIBNAME}) if(TARGET build_version) add_dependencies(OpenAL build_version) endif() @@ -1342,19 +1400,46 @@ else() # !important: for osx framework public header works, the headers must be in # the project - set(TARGET_PUBLIC_HEADERS include/AL/al.h include/AL/alc.h include/AL/alext.h include/AL/efx.h) + set(TARGET_PUBLIC_HEADERS include/AL/al.h include/AL/alc.h include/AL/alext.h include/AL/efx.h + include/AL/efx-presets.h) + add_library(${IMPL_TARGET} SHARED ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS} ${RC_CONFIG} ${TARGET_PUBLIC_HEADERS}) 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. + target_link_options(${IMPL_TARGET} PRIVATE + "-Wl,--version-script=${OpenAL_SOURCE_DIR}/libopenal.version") + endif() if(APPLE AND ALSOFT_OSX_FRAMEWORK) # Sets framework name to soft_oal to avoid ambiguity with the system OpenAL.framework set(LIBNAME "soft_oal") if(GIT_FOUND) - EXECUTE_PROCESS(COMMAND ${GIT_EXECUTABLE} rev-list --count head + execute_process(COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD TIMEOUT 5 OUTPUT_VARIABLE BUNDLE_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) @@ -1375,8 +1460,8 @@ else() FRAMEWORK_VERSION C MACOSX_FRAMEWORK_NAME "${IMPL_TARGET}" MACOSX_FRAMEWORK_IDENTIFIER "org.openal-soft.openal" - MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${LIB_VERSION} - MACOSX_FRAMEWORK_BUNDLE_VERSION ${BUNDLE_VERSION} + MACOSX_FRAMEWORK_SHORT_VERSION_STRING "${LIB_VERSION}" + MACOSX_FRAMEWORK_BUNDLE_VERSION "${BUNDLE_VERSION}" XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "" XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO" @@ -1385,8 +1470,6 @@ else() endif() endif() -set(OPENAL_LIB_NAME ${IMPL_TARGET} PARENT_SCOPE) - target_include_directories(${IMPL_TARGET} PUBLIC $ @@ -1401,7 +1484,8 @@ target_include_directories(${IMPL_TARGET} ${OpenAL_SOURCE_DIR}/common ) -set_target_properties(${IMPL_TARGET} PROPERTIES OUTPUT_NAME ${LIBNAME} +set_target_properties(${IMPL_TARGET} PROPERTIES ${DEFAULT_TARGET_PROPS} + OUTPUT_NAME ${LIBNAME} VERSION ${LIB_VERSION} SOVERSION ${LIB_MAJOR_VERSION} ) @@ -1425,11 +1509,15 @@ 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,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 + # Technically OpenAL32.def was created by the build, but cmake + # doesn't recognize it due to -Wl,--output-def,OpenAL32.def being + # manually specified. But declaring the file here allows it to be + # properly cleaned, e.g. during make clean. + BYPRODUCTS OpenAL32.def OpenAL32.lib COMMENT "Stripping ordinals from OpenAL32.def and generating OpenAL32.lib..." VERBATIM ) @@ -1452,7 +1540,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 "") @@ -1527,6 +1618,7 @@ if(ALSOFT_UTILS) target_include_directories(openal-info PRIVATE ${OpenAL_SOURCE_DIR}/common) target_compile_options(openal-info PRIVATE ${C_FLAGS}) target_link_libraries(openal-info PRIVATE ${LINKER_FLAGS} OpenAL ${UNICODE_FLAG}) + set_target_properties(openal-info PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info) endif() @@ -1537,16 +1629,18 @@ 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}) add_executable(uhjencoder utils/uhjencoder.cpp) target_compile_definitions(uhjencoder PRIVATE ${CPP_DEFS}) target_include_directories(uhjencoder PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(uhjencoder PRIVATE ${C_FLAGS}) - target_link_libraries(uhjencoder PUBLIC common + target_link_libraries(uhjencoder PUBLIC alcommon PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) + set_target_properties(uhjencoder PROPERTIES ${DEFAULT_TARGET_PROPS}) endif() if(MYSOFA_FOUND) @@ -1557,7 +1651,8 @@ 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 utils/makemhr/loaddef.cpp @@ -1575,6 +1670,7 @@ if(ALSOFT_UTILS) PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/utils) target_compile_options(makemhr PRIVATE ${C_FLAGS}) target_link_libraries(makemhr PRIVATE ${LINKER_FLAGS} sofa-support ${UNICODE_FLAG}) + set_target_properties(makemhr PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} makemhr) endif() @@ -1585,6 +1681,7 @@ if(ALSOFT_UTILS) target_include_directories(sofa-info PRIVATE ${OpenAL_SOURCE_DIR}/utils) target_compile_options(sofa-info PRIVATE ${C_FLAGS}) target_link_libraries(sofa-info PRIVATE ${LINKER_FLAGS} sofa-support ${UNICODE_FLAG}) + set_target_properties(sofa-info PROPERTIES ${DEFAULT_TARGET_PROPS}) endif() message(STATUS "Building utility programs") @@ -1596,20 +1693,23 @@ 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}) +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) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} altonegen alrecord) @@ -1619,36 +1719,44 @@ 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 - ${UNICODE_FLAG}) + 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 @@ -1660,9 +1768,9 @@ if(ALSOFT_EXAMPLES) if(SDL2_FOUND) add_executable(alloopback examples/alloopback.c) - target_include_directories(alloopback PRIVATE ${SDL2_INCLUDE_DIR}) target_link_libraries(alloopback - PRIVATE ${LINKER_FLAGS} ${SDL2_LIBRARY} 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) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alloopback) @@ -1673,33 +1781,33 @@ if(ALSOFT_EXAMPLES) set(FFVER_OK FALSE) if(FFMPEG_FOUND) set(FFVER_OK TRUE) - if(AVFORMAT_VERSION VERSION_LESS "57.56.101") - message(STATUS "libavformat is too old! (${AVFORMAT_VERSION}, wanted 57.56.101)") + if(AVFORMAT_VERSION VERSION_LESS "59.27.100") + message(STATUS "libavformat is too old! (${AVFORMAT_VERSION}, wanted 59.27.100)") set(FFVER_OK FALSE) endif() - if(AVCODEC_VERSION VERSION_LESS "57.64.101") - message(STATUS "libavcodec is too old! (${AVCODEC_VERSION}, wanted 57.64.101)") + if(AVCODEC_VERSION VERSION_LESS "59.37.100") + message(STATUS "libavcodec is too old! (${AVCODEC_VERSION}, wanted 59.37.100)") set(FFVER_OK FALSE) endif() - if(AVUTIL_VERSION VERSION_LESS "55.34.101") - message(STATUS "libavutil is too old! (${AVUTIL_VERSION}, wanted 55.34.101)") + if(AVUTIL_VERSION VERSION_LESS "57.28.100") + message(STATUS "libavutil is too old! (${AVUTIL_VERSION}, wanted 57.28.100)") set(FFVER_OK FALSE) endif() - if(SWSCALE_VERSION VERSION_LESS "4.2.100") - message(STATUS "libswscale is too old! (${SWSCALE_VERSION}, wanted 4.2.100)") + if(SWSCALE_VERSION VERSION_LESS "6.7.100") + message(STATUS "libswscale is too old! (${SWSCALE_VERSION}, wanted 6.7.100)") set(FFVER_OK FALSE) endif() - if(SWRESAMPLE_VERSION VERSION_LESS "2.3.100") - message(STATUS "libswresample is too old! (${SWRESAMPLE_VERSION}, wanted 2.3.100)") + if(SWRESAMPLE_VERSION VERSION_LESS "4.7.100") + message(STATUS "libswresample is too old! (${SWRESAMPLE_VERSION}, wanted 4.7.100)") set(FFVER_OK FALSE) endif() endif() if(FFVER_OK) add_executable(alffplay examples/alffplay.cpp) - target_include_directories(alffplay - PRIVATE ${SDL2_INCLUDE_DIR} ${FFMPEG_INCLUDE_DIRS}) + target_include_directories(alffplay PRIVATE ${FFMPEG_INCLUDE_DIRS}) target_link_libraries(alffplay - PRIVATE ${LINKER_FLAGS} ${SDL2_LIBRARY} ${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) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alffplay) @@ -1710,6 +1818,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} @@ -1717,7 +1829,3 @@ if(EXTRA_INSTALLS) LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() - -if(MEGA) - install(TARGETS ${IMPL_TARGET} RUNTIME DESTINATION . LIBRARY DESTINATION .) -endif() diff --git a/libs/openal-soft/ChangeLog b/libs/openal-soft/ChangeLog index c26f096d..e4236f85 100644 --- a/libs/openal-soft/ChangeLog +++ b/libs/openal-soft/ChangeLog @@ -1,3 +1,115 @@ +openal-soft-1.23.1: + + Implemented the AL_SOFT_UHJ_ex extension. + + Implemented the AL_SOFT_buffer_length_query extension. + + Implemented the AL_SOFT_source_start_delay extension. + + Implemented the AL_EXT_STATIC_BUFFER extension. + + Fixed compiling with certain older versions of GCC. + + Fixed compiling as a submodule. + + Fixed compiling with newer versions of Oboe. + + Improved EAX effect version switching. + + Improved the quality of the reverb modulator. + + Improved performance of the cubic resampler. + + Added a compatibility option to restore AL_SOFT_buffer_sub_data. The option + disables AL_EXT_SOURCE_RADIUS due to incompatibility. + + Reduced CPU usage when EAX is initialized and FXSlot0 or FXSlot1 are not + used. + + Reduced memory usage for ADPCM buffer formats. They're no longer converted + to 16-bit samples on load. + +openal-soft-1.23.0: + + Fixed CoreAudio capture support. + + Fixed handling per-version EAX properties. + + Fixed interpolating changes to the Super Stereo width source property. + + Fixed detection of the update and buffer size from PipeWire. + + Fixed resuming playback devices with OpenSL. + + Fixed support for certain OpenAL implementations with the router. + + Improved reverb environment transitions. + + Improved performance of convolution reverb. + + Improved quality and performance of the pitch shifter effect slightly. + + Improved sub-sample precision for resampled sources. + + Improved blending spatialized multi-channel sources that use the source + radius property. + + Improved mixing 2D ambisonic sources for higher-order 3D ambisonic mixing. + + Improved quadraphonic and 7.1 surround sound output slightly. + + Added config options for UHJ encoding/decoding quality. Including Super + Stereo processing. + + Added a config option for specifying the speaker distance. + + Added a compatibility config option for specifying the NFC distance + scaling. + + Added a config option for mixing on PipeWire's non-real-time thread. + + Added support for virtual source nodes with PipeWire capture. + + Added the ability for the WASAPI backend to use different playback rates. + + Added support for SOFA files that define per-response delays in makemhr. + + Changed the default fallback playback sample rate to 48khz. This doesn't + affect most backends, which can detect a default rate from the system. + + Changed the default resampler to cubic. + + Changed the default HRTF size from 32 to 64 points. + +openal-soft-1.22.2: + + Fixed PipeWire version check. + + Fixed building with PipeWire versions before 0.3.33. + +openal-soft-1.22.1: + + Fixed CoreAudio capture. + + Fixed air absorption strength. + + Fixed handling 5.1 devices on Windows that use Rear channels instead of + Side channels. + + Fixed some compilation issues on MinGW. + + Fixed ALSA not being used on some systems without PipeWire and PulseAudio. + + Fixed OpenSL capturing noise. + + Fixed Oboe capture failing with some buffer sizes. + + Added checks for the runtime PipeWire version. The same or newer version + than is used for building will be needed at runtime for the backend to + work. + + Separated 3D7.1 into its own speaker configuration. + openal-soft-1.22.0: Implemented the ALC_SOFT_reopen_device extension. This allows for moving diff --git a/libs/openal-soft/LICENSE-pffft b/libs/openal-soft/LICENSE-pffft new file mode 100644 index 00000000..5f1a8493 --- /dev/null +++ b/libs/openal-soft/LICENSE-pffft @@ -0,0 +1,38 @@ +A modified PFFFT is included, with the following license. + +Copyright (c) 2023 Christopher Robinson + +Copyright (c) 2013 Julien Pommier ( 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/libs/openal-soft/OpenALConfig.cmake.in b/libs/openal-soft/OpenALConfig.cmake.in index 128c1a4e..9704d3c4 100644 --- a/libs/openal-soft/OpenALConfig.cmake.in +++ b/libs/openal-soft/OpenALConfig.cmake.in @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.1...3.18) include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake") diff --git a/libs/openal-soft/README.md b/libs/openal-soft/README.md index 0c9d3027..dac53e71 100644 --- a/libs/openal-soft/README.md +++ b/libs/openal-soft/README.md @@ -1,12 +1,12 @@ -OpenAL soft +OpenAL Soft =========== -`master` branch CI status : [![Build Status](https://travis-ci.org/kcat/openal-soft.svg?branch=master)](https://travis-ci.org/kcat/openal-soft) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true)](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true) +`master` branch CI status : [![GitHub Actions Status](https://github.com/kcat/openal-soft/actions/workflows/ci.yml/badge.svg)](https://github.com/kcat/openal-soft/actions) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true)](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true) OpenAL Soft is an LGPL-licensed, cross-platform, software implementation of the OpenAL 3D audio API. It's forked from the open-sourced Windows version available originally from openal.org's SVN repository (now defunct). OpenAL provides capabilities for playing audio in a virtual 3D environment. Distance attenuation, doppler shift, and directional sound emitters are among the features handled by the API. More advanced effects, including air absorption, occlusion, and environmental reverb, are available through the EFX extension. It also facilitates streaming audio, multi-channel buffers, and audio capture. -More information is available on the [official website](http://openal-soft.org/) +More information is available on the [official website](http://openal-soft.org/). Source Install ------------- @@ -17,20 +17,37 @@ directory, and run: cmake .. ``` -Assuming configuration went well, you can then build it, typically using GNU -Make (KDevelop, MSVC, and others are possible depending on your system setup -and CMake configuration). +Alternatively, you can use any available CMake front-end, like cmake-gui, +ccmake, or your preferred IDE's CMake project parser. + +Assuming configuration went well, you can then build it. The command +`cmake --build .` will instruct CMake to build the project with the toolchain +chosen during configuration (often GNU Make or NMake, although others are +possible). Please Note: Double check that the appropriate backends were detected. Often, complaints of no sound, crashing, and missing devices can be solved by making sure the correct backends are being used. CMake's output will identify which backends were enabled. -For most systems, you will likely want to make sure ALSA, OSS, and PulseAudio -were detected (if your target system uses them). For Windows, make sure -DirectSound was detected. +For most systems, you will likely want to make sure PipeWire, PulseAudio, and +ALSA were detected (if your target system uses them). For Windows, make sure +WASAPI was detected. +Building openal-soft - Using vcpkg +---------------------------------- + +You can download and install openal-soft using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install openal-soft + +The openal-soft port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + Utilities --------- The source package comes with an informational utility, openal-info, and is @@ -47,6 +64,33 @@ as application-agnostic behavior of the library. See alsoftrc.sample for available settings. +Language Bindings +----------------- + +As a C API, OpenAL Soft can be used directly by any language that can use +functions with C linkage. For languages that can't directly use C-style +headers, bindings may be developed to allow code written in that language to +call into the library. Some bindings for some languages are listed here. + +C# Bindings: +* [OpenTK](https://opentk.net/) includes low-level C# bindings for the OpenAL +API, including some extensions. It also includes utility libraries for math and +linear algebra, which can be useful for 3D calculations. + +Java Bindings: +* [JOAL](https://jogamp.org/joal/www/), part of the JogAmp project, includes +Java bindings for the OpenAL API, usable with OpenAL Soft. It also includes a +higher level Sound3D Toolkit API and utility functions to make easier use of +OpenAL features and capabilities. + +Python Bindings: +* [PyOpenAL](https://pypi.org/project/PyOpenAL/). Also includes methods to play +wave files and, with PyOgg, also Vorbis, Opus, and FLAC. + +Other bindings for these and other languages also exist. This list will grow as +more bindings are found. + + Acknowledgements ---------------- diff --git a/libs/openal-soft/al/auxeffectslot.cpp b/libs/openal-soft/al/auxeffectslot.cpp index c33fb149..53cbdf7e 100644 --- a/libs/openal-soft/al/auxeffectslot.cpp +++ b/libs/openal-soft/al/auxeffectslot.cpp @@ -24,116 +24,120 @@ #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_exception.h" -#include "eax_utils.h" -#endif // 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::EAXReverb: return ReverbStateFactory_getFactory(); + case EffectSlotType::Reverb: return StdReverbStateFactory_getFactory(); + case EffectSlotType::Autowah: return AutowahStateFactory_getFactory(); + case EffectSlotType::Chorus: return ChorusStateFactory_getFactory(); + case EffectSlotType::Compressor: return CompressorStateFactory_getFactory(); + case EffectSlotType::Distortion: return DistortionStateFactory_getFactory(); + case EffectSlotType::Echo: return EchoStateFactory_getFactory(); + case EffectSlotType::Equalizer: return EqualizerStateFactory_getFactory(); + case EffectSlotType::Flanger: return FlangerStateFactory_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(); + case EffectSlotType::DedicatedDialog: return DedicatedDialogStateFactory_getFactory(); + case EffectSlotType::DedicatedLFE: return DedicatedLfeStateFactory_getFactory(); + case EffectSlotType::Convolution: return ConvolutionStateFactory_getFactory(); + } + return nullptr; } -inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept +inline auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= context->mEffectSlotList.size()) + if(lidx >= context->mEffectSlotList.size()) UNLIKELY return nullptr; EffectSlotSubList &sublist{context->mEffectSlotList[lidx]}; - if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + 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}; - if UNLIKELY(lidx >= device->EffectList.size()) + if(lidx >= device->EffectList.size()) UNLIKELY return nullptr; EffectSubList &sublist = device->EffectList[lidx]; - if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Effects + slidx; + return al::to_address(sublist.Effects->begin() + slidx); } -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}; - if UNLIKELY(lidx >= device->BufferList.size()) + if(lidx >= device->BufferList.size()) UNLIKELY return nullptr; BufferSubList &sublist = device->BufferList[lidx]; - if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Buffers + slidx; -} - - -inline auto GetEffectBuffer(ALbuffer *buffer) noexcept -> EffectState::Buffer -{ - if(!buffer) return EffectState::Buffer{}; - return EffectState::Buffer{buffer, buffer->mData}; + return al::to_address(sublist.Buffers->begin() + slidx); } @@ -146,9 +150,9 @@ void AddActiveEffectSlots(const al::span auxslots, ALCcontext *co /* Insert the new effect slots into the head of the array, followed by the * existing ones. */ - EffectSlotArray *newarray = EffectSlot::CreatePtrArray(newcount); + auto newarray = EffectSlot::CreatePtrArray(newcount); auto slotiter = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(), - [](ALeffectslot *auxslot) noexcept { return &auxslot->mSlot; }); + std::mem_fn(&ALeffectslot::mSlot)); std::copy(curarray->begin(), curarray->end(), slotiter); /* Remove any duplicates (first instance of each will be kept). */ @@ -164,21 +168,19 @@ void AddActiveEffectSlots(const al::span auxslots, ALCcontext *co /* Reallocate newarray if the new size ended up smaller from duplicate * removal. */ - if UNLIKELY(newcount < newarray->size()) + if(newcount < newarray->size()) UNLIKELY { - curarray = newarray; + auto oldarray = std::move(newarray); newarray = EffectSlot::CreatePtrArray(newcount); - std::copy_n(curarray->begin(), newcount, newarray->begin()); - delete curarray; - curarray = nullptr; + std::copy_n(oldarray->begin(), newcount, newarray->begin()); } std::uninitialized_fill_n(newarray->end(), newcount, nullptr); - curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel); - context->mDevice->waitForMix(); + auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray), + std::memory_order_acq_rel); + std::ignore = context->mDevice->waitForMix(); - al::destroy_n(curarray->end(), curarray->size()); - delete curarray; + std::destroy_n(oldarray->end(), oldarray->size()); } void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext *context) @@ -189,39 +191,36 @@ 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()); /* Remove elements from newarray that match any ID in slotids. */ for(const ALeffectslot *auxslot : auxslots) { auto slot_match = [auxslot](EffectSlot *slot) noexcept -> bool - { return (slot == &auxslot->mSlot); }; + { return (slot == auxslot->mSlot); }; new_end = std::remove_if(newarray->begin(), new_end, slot_match); } /* Reallocate with the new size. */ auto newsize = static_cast(std::distance(newarray->begin(), new_end)); - if LIKELY(newsize != newarray->size()) + if(newsize != newarray->size()) LIKELY { - curarray = newarray; + auto oldarray = std::move(newarray); newarray = EffectSlot::CreatePtrArray(newsize); - std::copy_n(curarray->begin(), newsize, newarray->begin()); - - delete curarray; - curarray = nullptr; + std::copy_n(oldarray->begin(), newsize, newarray->begin()); } std::uninitialized_fill_n(newarray->end(), newsize, nullptr); - curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel); - context->mDevice->waitForMix(); + auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray), + std::memory_order_acq_rel); + std::ignore = context->mDevice->waitForMix(); - al::destroy_n(curarray->end(), curarray->size()); - delete curarray; + std::destroy_n(curarray->end(), curarray->size()); } -EffectSlotType EffectSlotTypeFromEnum(ALenum type) +constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType { switch(type) { @@ -241,38 +240,35 @@ EffectSlotType EffectSlotTypeFromEnum(ALenum type) 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_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)); })}; while(needed > count) { - if UNLIKELY(context->mEffectSlotList.size() >= 1<<25) + 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 UNLIKELY(!sublist->EffectSlots) - { - 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) { @@ -283,10 +279,11 @@ ALeffectslot *AllocEffectSlot(ALCcontext *context) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALeffectslot *slot{al::construct_at(sublist->EffectSlots + slidx)}; - aluInitEffectPanning(&slot->mSlot, context); + ALeffectslot *slot{al::construct_at(al::to_address(sublist->EffectSlots->begin() + slidx), + context)}; + aluInitEffectPanning(slot->mSlot, context); - /* Add 1 to avoid source ID 0. */ + /* Add 1 to avoid ID 0. */ slot->id = ((lidx<<6) | slidx) + 1; context->mNumEffectSlots += 1; @@ -297,11 +294,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--; @@ -321,145 +320,129 @@ 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 UNLIKELY(!context) return; +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; - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Generating %d effect slots", n); - if UNLIKELY(n <= 0) return; - - std::unique_lock slotlock{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())}; - if(!slot) return; - 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())}; - if(!slot) - { - slotlock.unlock(); - alDeleteAuxiliaryEffectSlots(static_cast(ids.size()), ids.data()); - return; - } - 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 +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} -AL_API void AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; +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 + throw al::context_error{AL_INVALID_VALUE, "Deleting %d effect slots", n}; + if(n <= 0) UNLIKELY return; - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Deleting %d effect slots", n); - if UNLIKELY(n <= 0) return; - - std::lock_guard _{context->mEffectSlotLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; if(n == 1) { - ALeffectslot *slot{LookupEffectSlot(context.get(), effectslots[0])}; - if UNLIKELY(!slot) - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslots[0]); - return; - } - if UNLIKELY(ReadRef(slot->ref) != 0) - { - 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 UNLIKELY(!slot) - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslots[i]); - return; - } - if UNLIKELY(ReadRef(slot->ref) != 0) - { - 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 LIKELY(context) - { - 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 slotid) noexcept { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) UNLIKELY return; - std::lock_guard _{context->mEffectSlotLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)}; - if UNLIKELY(!slot) + if(!slot) UNLIKELY { context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid); return; @@ -473,52 +456,53 @@ START_API_FUNC 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 +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) noexcept { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) UNLIKELY return; - if UNLIKELY(n < 0) + if(n < 0) UNLIKELY context->setError(AL_INVALID_VALUE, "Playing %d effect slots", n); - if UNLIKELY(n <= 0) return; + 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 UNLIKELY(!slot) + auto ids = al::span{slotids, static_cast(n)}; + auto slots = std::vector(ids.size()); + std::lock_guard slotlock{context->mEffectSlotLock}; + try { + auto lookupslot = [&context](const ALuint id) -> ALeffectslot* { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotids[i]); - return; - } + ALeffectslot *slot{LookupEffectSlot(context.get(), id)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id}; - if(slot->mState != SlotState::Playing) - { - slot->mPropsDirty = false; - slot->updateProps(context.get()); - } - slots[i] = slot; - }; + if(slot->mState != SlotState::Playing) + { + slot->mPropsDirty = false; + slot->updateProps(context.get()); + } + return slot; + }; + std::transform(ids.cbegin(), ids.cend(), slots.begin(), lookupslot); + } + catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); + return; + } 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 +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) noexcept { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) UNLIKELY return; - std::lock_guard _{context->mEffectSlotLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)}; - if UNLIKELY(!slot) + if(!slot) UNLIKELY { context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid); return; @@ -527,101 +511,99 @@ START_API_FUNC 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 +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) noexcept { ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + if(!context) UNLIKELY return; - if UNLIKELY(n < 0) + if(n < 0) UNLIKELY context->setError(AL_INVALID_VALUE, "Stopping %d effect slots", n); - if UNLIKELY(n <= 0) return; + 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 UNLIKELY(!slot) + auto ids = al::span{slotids, static_cast(n)}; + auto slots = std::vector(ids.size()); + std::lock_guard slotlock{context->mEffectSlotLock}; + try { + auto lookupslot = [&context](const ALuint id) -> ALeffectslot* { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotids[i]); - return; - } - - slots[i] = slot; - }; + if(ALeffectslot *slot{LookupEffectSlot(context.get(), id)}) + return slot; + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id}; + }; + std::transform(ids.cbegin(), ids.cend(), slots.begin(), lookupslot); + } + catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); + return; + } 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 UNLIKELY(!context) return; +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}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if UNLIKELY(!slot) - SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) UNLIKELY + 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) - SETERR_RETURN(context, 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 UNLIKELY(err != AL_NO_ERROR) - { - context->setError(err, "Effect initialization failed"); - return; - } - if UNLIKELY(slot->mState == SlotState::Initial) + 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)) - SETERR_RETURN(context, AL_INVALID_VALUE,, - "Effect slot auxiliary send auto out of range"); - if UNLIKELY(slot->AuxSendAuto == !!value) - 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) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect slot target ID"); - if UNLIKELY(slot->Target == target) + throw al::context_error{AL_INVALID_VALUE, "Invalid effect slot target ID"}; + if(slot->Target == target) UNLIKELY return; if(target) { @@ -629,9 +611,9 @@ START_API_FUNC while(checker && checker != slot) checker = checker->Target; if(checker) - SETERR_RETURN(context, 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}) @@ -642,39 +624,40 @@ 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) - SETERR_RETURN(context, AL_INVALID_OPERATION,, - "Setting buffer on playing effect slot %u", slot->id); + throw al::context_error{AL_INVALID_OPERATION, + "Setting buffer on playing effect slot %u", slot->id}; if(ALbuffer *buffer{slot->Buffer}) { - if UNLIKELY(buffer->id == static_cast(value)) + if(buffer->id == static_cast(value)) UNLIKELY return; } - else if UNLIKELY(value == 0) + else if(value == 0) UNLIKELY return; { - std::lock_guard ___{device->BufferLock}; + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard bufferlock{device->BufferLock}; ALbuffer *buffer{}; if(value) { buffer = LookupBuffer(device, static_cast(value)); - if(!buffer) SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid buffer ID"); + if(!buffer) + throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value}; if(buffer->mCallback) - SETERR_RETURN(context, 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); } @@ -685,24 +668,25 @@ START_API_FUNC FPUCtl mixer_mode{}; auto *state = slot->Effect.State.get(); - state->deviceUpdate(device, GetEffectBuffer(buffer)); + state->deviceUpdate(device, buffer); } - break; + UpdateProps(slot, context); + return; case AL_EFFECTSLOT_STATE_SOFT: - SETERR_RETURN(context, AL_INVALID_OPERATION,, "AL_EFFECTSLOT_STATE_SOFT is read-only"); - - default: - SETERR_RETURN(context, 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: @@ -710,129 +694,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 UNLIKELY(!context) return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if UNLIKELY(!slot) - SETERR_RETURN(context, 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: - SETERR_RETURN(context, 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 UNLIKELY(!context) 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 UNLIKELY(!slot) - SETERR_RETURN(context, 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)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Effect slot gain out of range"); - if UNLIKELY(slot->Gain == value) - return; - slot->Gain = value; - break; - - default: - SETERR_RETURN(context, 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 UNLIKELY(!context) return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if UNLIKELY(!slot) - SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot); - - switch(param) - { - default: - SETERR_RETURN(context, AL_INVALID_ENUM,, - "Invalid effect slot float-vector property 0x%04x", param); - } + 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 UNLIKELY(!context) return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if UNLIKELY(!slot) - SETERR_RETURN(context, 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: @@ -840,86 +825,85 @@ 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 UNLIKELY(!context) return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if UNLIKELY(!slot) - SETERR_RETURN(context, 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 UNLIKELY(!context) return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if UNLIKELY(!slot) - SETERR_RETURN(context, 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 UNLIKELY(!context) 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 UNLIKELY(!slot) - SETERR_RETURN(context, 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() +ALeffectslot::ALeffectslot(ALCcontext *context) { EffectStateFactory *factory{getFactoryByType(EffectSlotType::None)}; if(!factory) throw std::runtime_error{"Failed to get null effect factory"}; al::intrusive_ptr state{factory->create()}; Effect.State = state; - mSlot.mEffectState = state.release(); + + mSlot = context->getEffectSlot(); + mSlot->InUse = true; + mSlot->mEffectState = std::move(state); } ALeffectslot::~ALeffectslot() @@ -931,19 +915,14 @@ ALeffectslot::~ALeffectslot() DecrementRef(Buffer->ref); Buffer = nullptr; - EffectSlotProps *props{mSlot.Update.exchange(nullptr)}; - if(props) - { - 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; - if(mSlot.mEffectState) - mSlot.mEffectState->release(); + 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)}; @@ -962,7 +941,7 @@ ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProp state->mOutTarget = device->Dry.Buffer; { FPUCtl mixer_mode{}; - state->deviceUpdate(device, GetEffectBuffer(Buffer)); + state->deviceUpdate(device, Buffer); } Effect.Type = newtype; @@ -972,9 +951,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; @@ -984,766 +964,552 @@ 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; props->AuxSendAuto = AuxSendAuto; - props->Target = Target ? &Target->mSlot : nullptr; + props->Target = Target ? Target->mSlot : nullptr; props->Type = Effect.Type; props->Props = Effect.Props; props->State = Effect.State; /* Set the new container for updating internal parameters. */ - props = mSlot.Update.exchange(props, std::memory_order_acq_rel); + props = mSlot->Update.exchange(props, std::memory_order_acq_rel); if(props) { /* If there was an unused update container, put it back in the * 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}; -#ifdef ALSOFT_EAX - if(context->has_eax()) - context->eax_commit_fx_slots(); -#endif + 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(slot.mState != SlotState::Stopped && 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; } #ifdef ALSOFT_EAX -namespace { - -class EaxFxSlotException : - public EaxException +void ALeffectslot::eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index) { -public: - explicit EaxFxSlotException( - const char* message) - : - EaxException{"EAX_FX_SLOT", message} - { - } -}; // EaxFxSlotException - - -} // namespace - - -void ALeffectslot::eax_initialize( - ALCcontext& al_context, - EaxFxSlotIndexValue index) -{ - eax_al_context_ = &al_context; - - if (index >= EAX_MAX_FXSLOTS) - { + if(index >= EAX_MAX_FXSLOTS) eax_fail("Index out of range."); - } + eax_al_context_ = &al_context; eax_fx_slot_index_ = index; + eax_fx_slot_set_defaults(); - eax_initialize_eax(); - eax_initialize_lock(); - eax_initialize_effects(); + eax_effect_ = std::make_unique(); + if(index == 0) eax_effect_->init(); + else if(index == 1) eax_effect_->init(); + else eax_effect_->init(); } -const EAX50FXSLOTPROPERTIES& ALeffectslot::eax_get_eax_fx_slot() const noexcept +void ALeffectslot::eax_commit() { - return eax_eax_fx_slot_; -} - -void ALeffectslot::eax_ensure_is_unlocked() const -{ - if (eax_is_locked_) - eax_fail("Locked."); -} - -void ALeffectslot::eax_validate_fx_slot_effect( - const GUID& eax_effect_id) -{ - eax_ensure_is_unlocked(); - - if (eax_effect_id != EAX_NULL_GUID && - eax_effect_id != EAX_REVERB_EFFECT && - eax_effect_id != EAX_AGCCOMPRESSOR_EFFECT && - eax_effect_id != EAX_AUTOWAH_EFFECT && - eax_effect_id != EAX_CHORUS_EFFECT && - eax_effect_id != EAX_DISTORTION_EFFECT && - eax_effect_id != EAX_ECHO_EFFECT && - eax_effect_id != EAX_EQUALIZER_EFFECT && - eax_effect_id != EAX_FLANGER_EFFECT && - eax_effect_id != EAX_FREQUENCYSHIFTER_EFFECT && - eax_effect_id != EAX_VOCALMORPHER_EFFECT && - eax_effect_id != EAX_PITCHSHIFTER_EFFECT && - eax_effect_id != EAX_RINGMODULATOR_EFFECT) + if(eax_df_ != EaxDirtyFlags{}) { - eax_fail("Unsupported EAX effect GUID."); - } -} + auto df = EaxDirtyFlags{}; + switch(eax_version_) + { + case 1: + case 2: + case 3: + eax5_fx_slot_commit(eax123_, df); + break; + case 4: + eax4_fx_slot_commit(df); + break; + case 5: + eax5_fx_slot_commit(eax5_, df); + break; + } + eax_df_ = EaxDirtyFlags{}; -void ALeffectslot::eax_validate_fx_slot_volume( - long eax_volume) -{ - eax_validate_range( - "Volume", - eax_volume, - EAXFXSLOT_MINVOLUME, - EAXFXSLOT_MAXVOLUME); -} - -void ALeffectslot::eax_validate_fx_slot_lock( - long eax_lock) -{ - eax_ensure_is_unlocked(); - - eax_validate_range( - "Lock", - eax_lock, - EAXFXSLOT_MINLOCK, - EAXFXSLOT_MAXLOCK); -} - -void ALeffectslot::eax_validate_fx_slot_flags( - unsigned long eax_flags, - int eax_version) -{ - eax_validate_range( - "Flags", - eax_flags, - 0UL, - ~(eax_version == 4 ? EAX40FXSLOTFLAGS_RESERVED : EAX50FXSLOTFLAGS_RESERVED)); -} - -void ALeffectslot::eax_validate_fx_slot_occlusion( - long eax_occlusion) -{ - eax_validate_range( - "Occlusion", - eax_occlusion, - EAXFXSLOT_MINOCCLUSION, - EAXFXSLOT_MAXOCCLUSION); -} - -void ALeffectslot::eax_validate_fx_slot_occlusion_lf_ratio( - float eax_occlusion_lf_ratio) -{ - eax_validate_range( - "Occlusion LF Ratio", - eax_occlusion_lf_ratio, - EAXFXSLOT_MINOCCLUSIONLFRATIO, - EAXFXSLOT_MAXOCCLUSIONLFRATIO); -} - -void ALeffectslot::eax_validate_fx_slot_all( - const EAX40FXSLOTPROPERTIES& fx_slot, - int eax_version) -{ - eax_validate_fx_slot_effect(fx_slot.guidLoadEffect); - eax_validate_fx_slot_volume(fx_slot.lVolume); - eax_validate_fx_slot_lock(fx_slot.lLock); - eax_validate_fx_slot_flags(fx_slot.ulFlags, eax_version); -} - -void ALeffectslot::eax_validate_fx_slot_all( - const EAX50FXSLOTPROPERTIES& fx_slot, - int eax_version) -{ - eax_validate_fx_slot_all(static_cast(fx_slot), eax_version); - - eax_validate_fx_slot_occlusion(fx_slot.lOcclusion); - eax_validate_fx_slot_occlusion_lf_ratio(fx_slot.flOcclusionLFRatio); -} - -void ALeffectslot::eax_set_fx_slot_effect( - const GUID& eax_effect_id) -{ - if (eax_eax_fx_slot_.guidLoadEffect == eax_effect_id) - { - return; + if((df & eax_volume_dirty_bit) != EaxDirtyFlags{}) + eax_fx_slot_set_volume(); + if((df & eax_flags_dirty_bit) != EaxDirtyFlags{}) + eax_fx_slot_set_flags(); } - eax_eax_fx_slot_.guidLoadEffect = eax_effect_id; - - eax_set_fx_slot_effect(); + if(eax_effect_->commit(eax_version_)) + eax_set_efx_slot_effect(*eax_effect_); } -void ALeffectslot::eax_set_fx_slot_volume( - long eax_volume) +[[noreturn]] void ALeffectslot::eax_fail(const char* message) { - if (eax_eax_fx_slot_.lVolume == eax_volume) + throw Exception{message}; +} + +[[noreturn]] void ALeffectslot::eax_fail_unknown_effect_id() +{ + eax_fail("Unknown effect ID."); +} + +[[noreturn]] void ALeffectslot::eax_fail_unknown_property_id() +{ + eax_fail("Unknown property ID."); +} + +[[noreturn]] void ALeffectslot::eax_fail_unknown_version() +{ + eax_fail("Unknown version."); +} + +void ALeffectslot::eax4_fx_slot_ensure_unlocked() const +{ + if(eax4_fx_slot_is_legacy()) + eax_fail("Locked legacy slot."); +} + +ALenum ALeffectslot::eax_get_efx_effect_type(const GUID& guid) +{ + if(guid == EAX_NULL_GUID) + return AL_EFFECT_NULL; + if(guid == EAX_AUTOWAH_EFFECT) + return AL_EFFECT_AUTOWAH; + if(guid == EAX_CHORUS_EFFECT) + return AL_EFFECT_CHORUS; + if(guid == EAX_AGCCOMPRESSOR_EFFECT) + return AL_EFFECT_COMPRESSOR; + if(guid == EAX_DISTORTION_EFFECT) + return AL_EFFECT_DISTORTION; + if(guid == EAX_REVERB_EFFECT) + return AL_EFFECT_EAXREVERB; + if(guid == EAX_ECHO_EFFECT) + return AL_EFFECT_ECHO; + if(guid == EAX_EQUALIZER_EFFECT) + return AL_EFFECT_EQUALIZER; + if(guid == EAX_FLANGER_EFFECT) + return AL_EFFECT_FLANGER; + if(guid == EAX_FREQUENCYSHIFTER_EFFECT) + return AL_EFFECT_FREQUENCY_SHIFTER; + if(guid == EAX_PITCHSHIFTER_EFFECT) + return AL_EFFECT_PITCH_SHIFTER; + if(guid == EAX_RINGMODULATOR_EFFECT) + return AL_EFFECT_RING_MODULATOR; + if(guid == EAX_VOCALMORPHER_EFFECT) + return AL_EFFECT_VOCAL_MORPHER; + + eax_fail_unknown_effect_id(); +} + +const GUID& ALeffectslot::eax_get_eax_default_effect_guid() const noexcept +{ + switch(eax_fx_slot_index_) { - return; - } - - eax_eax_fx_slot_.lVolume = eax_volume; - - eax_set_fx_slot_volume(); -} - -void ALeffectslot::eax_set_fx_slot_lock( - long eax_lock) -{ - if (eax_eax_fx_slot_.lLock == eax_lock) - { - return; - } - - eax_eax_fx_slot_.lLock = eax_lock; -} - -void ALeffectslot::eax_set_fx_slot_flags( - unsigned long eax_flags) -{ - if (eax_eax_fx_slot_.ulFlags == eax_flags) - { - return; - } - - eax_eax_fx_slot_.ulFlags = eax_flags; - - eax_set_fx_slot_flags(); -} - -// [[nodiscard]] -bool ALeffectslot::eax_set_fx_slot_occlusion( - long eax_occlusion) -{ - if (eax_eax_fx_slot_.lOcclusion == eax_occlusion) - { - return false; - } - - eax_eax_fx_slot_.lOcclusion = eax_occlusion; - - return true; -} - -// [[nodiscard]] -bool ALeffectslot::eax_set_fx_slot_occlusion_lf_ratio( - float eax_occlusion_lf_ratio) -{ - if (eax_eax_fx_slot_.flOcclusionLFRatio == eax_occlusion_lf_ratio) - { - return false; - } - - eax_eax_fx_slot_.flOcclusionLFRatio = eax_occlusion_lf_ratio; - - return true; -} - -void ALeffectslot::eax_set_fx_slot_all( - const EAX40FXSLOTPROPERTIES& eax_fx_slot) -{ - eax_set_fx_slot_effect(eax_fx_slot.guidLoadEffect); - eax_set_fx_slot_volume(eax_fx_slot.lVolume); - eax_set_fx_slot_lock(eax_fx_slot.lLock); - eax_set_fx_slot_flags(eax_fx_slot.ulFlags); -} - -// [[nodiscard]] -bool ALeffectslot::eax_set_fx_slot_all( - const EAX50FXSLOTPROPERTIES& eax_fx_slot) -{ - eax_set_fx_slot_all(static_cast(eax_fx_slot)); - - const auto is_occlusion_modified = eax_set_fx_slot_occlusion(eax_fx_slot.lOcclusion); - const auto is_occlusion_lf_ratio_modified = eax_set_fx_slot_occlusion_lf_ratio(eax_fx_slot.flOcclusionLFRatio); - - return is_occlusion_modified || is_occlusion_lf_ratio_modified; -} - -void ALeffectslot::eax_unlock_legacy() noexcept -{ - assert(eax_fx_slot_index_ < 2); - eax_is_locked_ = false; - eax_eax_fx_slot_.lLock = EAXFXSLOT_UNLOCKED; -} - -[[noreturn]] -void ALeffectslot::eax_fail( - const char* message) -{ - throw EaxFxSlotException{message}; -} - -GUID ALeffectslot::eax_get_eax_default_effect_guid() const noexcept -{ - switch (eax_fx_slot_index_) - { - case 0: return EAX_REVERB_EFFECT; - case 1: return EAX_CHORUS_EFFECT; - default: return EAX_NULL_GUID; + case 0: return EAX_REVERB_EFFECT; + case 1: return EAX_CHORUS_EFFECT; + default: return EAX_NULL_GUID; } } long ALeffectslot::eax_get_eax_default_lock() const noexcept { - return eax_fx_slot_index_ < 2 ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED; + return eax4_fx_slot_is_legacy() ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED; } -void ALeffectslot::eax_set_eax_fx_slot_defaults() +void ALeffectslot::eax4_fx_slot_set_defaults(Eax4Props& props) noexcept { - eax_eax_fx_slot_.guidLoadEffect = eax_get_eax_default_effect_guid(); - eax_eax_fx_slot_.lVolume = EAXFXSLOT_DEFAULTVOLUME; - eax_eax_fx_slot_.lLock = eax_get_eax_default_lock(); - eax_eax_fx_slot_.ulFlags = EAX40FXSLOT_DEFAULTFLAGS; - eax_eax_fx_slot_.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; - eax_eax_fx_slot_.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; + props.guidLoadEffect = eax_get_eax_default_effect_guid(); + props.lVolume = EAXFXSLOT_DEFAULTVOLUME; + props.lLock = eax_get_eax_default_lock(); + props.ulFlags = EAX40FXSLOT_DEFAULTFLAGS; } -void ALeffectslot::eax_initialize_eax() +void ALeffectslot::eax5_fx_slot_set_defaults(Eax5Props& props) noexcept { - eax_set_eax_fx_slot_defaults(); + props.guidLoadEffect = eax_get_eax_default_effect_guid(); + props.lVolume = EAXFXSLOT_DEFAULTVOLUME; + props.lLock = EAXFXSLOT_UNLOCKED; + props.ulFlags = EAX50FXSLOT_DEFAULTFLAGS; + props.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; + props.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; } -void ALeffectslot::eax_initialize_lock() +void ALeffectslot::eax_fx_slot_set_defaults() { - eax_is_locked_ = (eax_fx_slot_index_ < 2); + eax5_fx_slot_set_defaults(eax123_.i); + eax4_fx_slot_set_defaults(eax4_.i); + eax5_fx_slot_set_defaults(eax5_.i); + eax_ = eax5_.i; + eax_df_ = EaxDirtyFlags{}; } -void ALeffectslot::eax_initialize_effects() +void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) { - eax_set_fx_slot_effect(); -} - -void ALeffectslot::eax_get_fx_slot_all( - const EaxEaxCall& eax_call) const -{ - switch (eax_call.get_version()) + switch(call.get_property_id()) { - case 4: - eax_call.set_value(eax_eax_fx_slot_); - break; - - case 5: - eax_call.set_value(eax_eax_fx_slot_); - break; - - default: - eax_fail("Unsupported EAX version."); + case EAXFXSLOT_ALLPARAMETERS: + call.set_value(props); + break; + case EAXFXSLOT_LOADEFFECT: + call.set_value(props.guidLoadEffect); + break; + case EAXFXSLOT_VOLUME: + call.set_value(props.lVolume); + break; + case EAXFXSLOT_LOCK: + call.set_value(props.lLock); + break; + case EAXFXSLOT_FLAGS: + call.set_value(props.ulFlags); + break; + default: + eax_fail_unknown_property_id(); } } -void ALeffectslot::eax_get_fx_slot( - const EaxEaxCall& eax_call) const +void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) { - switch (eax_call.get_property_id()) + switch(call.get_property_id()) { - case EAXFXSLOT_ALLPARAMETERS: - eax_get_fx_slot_all(eax_call); - break; - - case EAXFXSLOT_LOADEFFECT: - eax_call.set_value(eax_eax_fx_slot_.guidLoadEffect); - break; - - case EAXFXSLOT_VOLUME: - eax_call.set_value(eax_eax_fx_slot_.lVolume); - break; - - case EAXFXSLOT_LOCK: - eax_call.set_value(eax_eax_fx_slot_.lLock); - break; - - case EAXFXSLOT_FLAGS: - eax_call.set_value(eax_eax_fx_slot_.ulFlags); - break; - - case EAXFXSLOT_OCCLUSION: - eax_call.set_value(eax_eax_fx_slot_.lOcclusion); - break; - - case EAXFXSLOT_OCCLUSIONLFRATIO: - eax_call.set_value(eax_eax_fx_slot_.flOcclusionLFRatio); - break; - - default: - eax_fail("Unsupported FX slot property id."); + case EAXFXSLOT_ALLPARAMETERS: + call.set_value(props); + break; + case EAXFXSLOT_LOADEFFECT: + call.set_value(props.guidLoadEffect); + break; + case EAXFXSLOT_VOLUME: + call.set_value(props.lVolume); + break; + case EAXFXSLOT_LOCK: + call.set_value(props.lLock); + break; + case EAXFXSLOT_FLAGS: + call.set_value(props.ulFlags); + break; + case EAXFXSLOT_OCCLUSION: + call.set_value(props.lOcclusion); + break; + case EAXFXSLOT_OCCLUSIONLFRATIO: + call.set_value(props.flOcclusionLFRatio); + break; + default: + eax_fail_unknown_property_id(); } } -// [[nodiscard]] -bool ALeffectslot::eax_get( - const EaxEaxCall& eax_call) +void ALeffectslot::eax_fx_slot_get(const EaxCall& call) const { - switch (eax_call.get_property_set_id()) + switch(call.get_version()) { - case EaxEaxCallPropertySetId::fx_slot: - eax_get_fx_slot(eax_call); - break; + case 4: eax4_fx_slot_get(call, eax4_.i); break; + case 5: eax5_fx_slot_get(call, eax5_.i); break; + default: eax_fail_unknown_version(); + } +} - case EaxEaxCallPropertySetId::fx_slot_effect: - eax_dispatch_effect(eax_call); - break; - - default: - eax_fail("Unsupported property id."); +bool ALeffectslot::eax_get(const EaxCall& call) +{ + switch(call.get_property_set_id()) + { + case EaxCallPropertySetId::fx_slot: + eax_fx_slot_get(call); + break; + case EaxCallPropertySetId::fx_slot_effect: + eax_effect_->get(call); + break; + default: + eax_fail_unknown_property_id(); } return false; } -void ALeffectslot::eax_set_fx_slot_effect( - ALenum al_effect_type) +void ALeffectslot::eax_fx_slot_load_effect(int version, ALenum altype) { - if(!IsValidEffectType(al_effect_type)) - eax_fail("Unsupported effect."); - - eax_effect_ = nullptr; - eax_effect_ = eax_create_eax_effect(al_effect_type); - - eax_set_effect_slot_effect(*eax_effect_); + if(!IsValidEffectType(altype)) + altype = AL_EFFECT_NULL; + eax_effect_->set_defaults(version, altype); } -void ALeffectslot::eax_set_fx_slot_effect() +void ALeffectslot::eax_fx_slot_set_volume() { - auto al_effect_type = ALenum{}; - - if (false) - { - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_NULL_GUID) - { - al_effect_type = AL_EFFECT_NULL; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_AUTOWAH_EFFECT) - { - al_effect_type = AL_EFFECT_AUTOWAH; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_CHORUS_EFFECT) - { - al_effect_type = AL_EFFECT_CHORUS; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_AGCCOMPRESSOR_EFFECT) - { - al_effect_type = AL_EFFECT_COMPRESSOR; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_DISTORTION_EFFECT) - { - al_effect_type = AL_EFFECT_DISTORTION; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_REVERB_EFFECT) - { - al_effect_type = AL_EFFECT_EAXREVERB; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_ECHO_EFFECT) - { - al_effect_type = AL_EFFECT_ECHO; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_EQUALIZER_EFFECT) - { - al_effect_type = AL_EFFECT_EQUALIZER; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_FLANGER_EFFECT) - { - al_effect_type = AL_EFFECT_FLANGER; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_FREQUENCYSHIFTER_EFFECT) - { - al_effect_type = AL_EFFECT_FREQUENCY_SHIFTER; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_PITCHSHIFTER_EFFECT) - { - al_effect_type = AL_EFFECT_PITCH_SHIFTER; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_RINGMODULATOR_EFFECT) - { - al_effect_type = AL_EFFECT_RING_MODULATOR; - } - else if (eax_eax_fx_slot_.guidLoadEffect == EAX_VOCALMORPHER_EFFECT) - { - al_effect_type = AL_EFFECT_VOCAL_MORPHER; - } - else - { - eax_fail("Unsupported effect."); - } - - eax_set_fx_slot_effect(al_effect_type); + 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); } -void ALeffectslot::eax_set_efx_effect_slot_gain() +void ALeffectslot::eax_fx_slot_set_environment_flag() { - const auto gain = level_mb_to_gain( - static_cast(clamp( - eax_eax_fx_slot_.lVolume, - EAXFXSLOT_MINVOLUME, - EAXFXSLOT_MAXVOLUME))); - - eax_set_effect_slot_gain(gain); + eax_set_efx_slot_send_auto((eax_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u); } -void ALeffectslot::eax_set_fx_slot_volume() +void ALeffectslot::eax_fx_slot_set_flags() { - eax_set_efx_effect_slot_gain(); + eax_fx_slot_set_environment_flag(); } -void ALeffectslot::eax_set_effect_slot_send_auto() +void ALeffectslot::eax4_fx_slot_set_all(const EaxCall& call) { - eax_set_effect_slot_send_auto((eax_eax_fx_slot_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0); + eax4_fx_slot_ensure_unlocked(); + const auto& src = call.get_value(); + Eax4AllValidator{}(src); + auto& dst = eax4_.i; + eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect. + eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{}); + eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{}); + eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{}); + dst = src; } -void ALeffectslot::eax_set_fx_slot_flags() +void ALeffectslot::eax5_fx_slot_set_all(const EaxCall& call) { - eax_set_effect_slot_send_auto(); + const auto& src = call.get_value(); + Eax5AllValidator{}(src); + auto& dst = eax5_.i; + eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect. + eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{}); + eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{}); + eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{}); + eax_df_ |= (dst.lOcclusion != src.lOcclusion ? eax_flags_dirty_bit : EaxDirtyFlags{}); + eax_df_ |= (dst.flOcclusionLFRatio != src.flOcclusionLFRatio ? eax_flags_dirty_bit : EaxDirtyFlags{}); + dst = src; } -void ALeffectslot::eax_set_fx_slot_effect( - const EaxEaxCall& eax_call) +bool ALeffectslot::eax_fx_slot_should_update_sources() const noexcept { - const auto& eax_effect_id = - eax_call.get_value(); + static constexpr auto dirty_bits = + eax_occlusion_dirty_bit | + eax_occlusion_lf_ratio_dirty_bit | + eax_flags_dirty_bit; - eax_validate_fx_slot_effect(eax_effect_id); - eax_set_fx_slot_effect(eax_effect_id); + return (eax_df_ & dirty_bits) != EaxDirtyFlags{}; } -void ALeffectslot::eax_set_fx_slot_volume( - const EaxEaxCall& eax_call) +// Returns `true` if all sources should be updated, or `false` otherwise. +bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call) { - const auto& eax_volume = - eax_call.get_value(); + auto& dst = eax4_.i; - eax_validate_fx_slot_volume(eax_volume); - eax_set_fx_slot_volume(eax_volume); -} - -void ALeffectslot::eax_set_fx_slot_lock( - const EaxEaxCall& eax_call) -{ - const auto& eax_lock = - eax_call.get_value(); - - eax_validate_fx_slot_lock(eax_lock); - eax_set_fx_slot_lock(eax_lock); -} - -void ALeffectslot::eax_set_fx_slot_flags( - const EaxEaxCall& eax_call) -{ - const auto& eax_flags = - eax_call.get_value(); - - eax_validate_fx_slot_flags(eax_flags, eax_call.get_version()); - eax_set_fx_slot_flags(eax_flags); -} - -// [[nodiscard]] -bool ALeffectslot::eax_set_fx_slot_occlusion( - const EaxEaxCall& eax_call) -{ - const auto& eax_occlusion = - eax_call.get_value(); - - eax_validate_fx_slot_occlusion(eax_occlusion); - - return eax_set_fx_slot_occlusion(eax_occlusion); -} - -// [[nodiscard]] -bool ALeffectslot::eax_set_fx_slot_occlusion_lf_ratio( - const EaxEaxCall& eax_call) -{ - const auto& eax_occlusion_lf_ratio = - eax_call.get_value(); - - eax_validate_fx_slot_occlusion_lf_ratio(eax_occlusion_lf_ratio); - - return eax_set_fx_slot_occlusion_lf_ratio(eax_occlusion_lf_ratio); -} - -// [[nodiscard]] -bool ALeffectslot::eax_set_fx_slot_all( - const EaxEaxCall& eax_call) -{ - switch (eax_call.get_version()) + switch(call.get_property_id()) { - case 4: - { - const auto& eax_all = - eax_call.get_value(); + case EAXFXSLOT_NONE: + break; + case EAXFXSLOT_ALLPARAMETERS: + eax4_fx_slot_set_all(call); + if((eax_df_ & eax_load_effect_dirty_bit)) + eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect)); + break; + case EAXFXSLOT_LOADEFFECT: + eax4_fx_slot_ensure_unlocked(); + eax_fx_slot_set_dirty(call, dst.guidLoadEffect, eax_df_); + if((eax_df_ & eax_load_effect_dirty_bit)) + eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect)); + break; + case EAXFXSLOT_VOLUME: + eax_fx_slot_set(call, dst.lVolume, eax_df_); + break; + case EAXFXSLOT_LOCK: + eax4_fx_slot_ensure_unlocked(); + eax_fx_slot_set(call, dst.lLock, eax_df_); + break; + case EAXFXSLOT_FLAGS: + eax_fx_slot_set(call, dst.ulFlags, eax_df_); + break; + default: + eax_fail_unknown_property_id(); + } - eax_validate_fx_slot_all(eax_all, eax_call.get_version()); - eax_set_fx_slot_all(eax_all); + return eax_fx_slot_should_update_sources(); +} - return false; - } +// Returns `true` if all sources should be updated, or `false` otherwise. +bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call) +{ + auto& dst = eax5_.i; - case 5: - { - const auto& eax_all = - eax_call.get_value(); + switch(call.get_property_id()) + { + case EAXFXSLOT_NONE: + break; + case EAXFXSLOT_ALLPARAMETERS: + eax5_fx_slot_set_all(call); + if((eax_df_ & eax_load_effect_dirty_bit)) + eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect)); + break; + case EAXFXSLOT_LOADEFFECT: + eax_fx_slot_set_dirty(call, dst.guidLoadEffect, eax_df_); + if((eax_df_ & eax_load_effect_dirty_bit)) + eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect)); + break; + case EAXFXSLOT_VOLUME: + eax_fx_slot_set(call, dst.lVolume, eax_df_); + break; + case EAXFXSLOT_LOCK: + eax_fx_slot_set(call, dst.lLock, eax_df_); + break; + case EAXFXSLOT_FLAGS: + eax_fx_slot_set(call, dst.ulFlags, eax_df_); + break; + case EAXFXSLOT_OCCLUSION: + eax_fx_slot_set(call, dst.lOcclusion, eax_df_); + break; + case EAXFXSLOT_OCCLUSIONLFRATIO: + eax_fx_slot_set(call, dst.flOcclusionLFRatio, eax_df_); + break; + default: + eax_fail_unknown_property_id(); + } - eax_validate_fx_slot_all(eax_all, eax_call.get_version()); - return eax_set_fx_slot_all(eax_all); - } + return eax_fx_slot_should_update_sources(); +} - default: - eax_fail("Unsupported EAX version."); +// Returns `true` if all sources should be updated, or `false` otherwise. +bool ALeffectslot::eax_fx_slot_set(const EaxCall& call) +{ + switch (call.get_version()) + { + case 4: return eax4_fx_slot_set(call); + case 5: return eax5_fx_slot_set(call); + default: eax_fail_unknown_version(); } } -bool ALeffectslot::eax_set_fx_slot( - const EaxEaxCall& eax_call) +// Returns `true` if all sources should be updated, or `false` otherwise. +bool ALeffectslot::eax_set(const EaxCall& call) { - switch (eax_call.get_property_id()) + bool ret{false}; + + switch(call.get_property_set_id()) { - case EAXFXSLOT_NONE: - return false; + case EaxCallPropertySetId::fx_slot: ret = eax_fx_slot_set(call); break; + case EaxCallPropertySetId::fx_slot_effect: eax_effect_->set(call); break; + default: eax_fail_unknown_property_id(); + } - case EAXFXSLOT_ALLPARAMETERS: - return eax_set_fx_slot_all(eax_call); + const auto version = call.get_version(); + if(eax_version_ != version) + eax_df_ = ~EaxDirtyFlags{}; + eax_version_ = version; - case EAXFXSLOT_LOADEFFECT: - eax_set_fx_slot_effect(eax_call); - return false; + return ret; +} - case EAXFXSLOT_VOLUME: - eax_set_fx_slot_volume(eax_call); - return false; +void ALeffectslot::eax4_fx_slot_commit(EaxDirtyFlags& dst_df) +{ + eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::guidLoadEffect); + eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lVolume); + eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lLock); + eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::ulFlags); - case EAXFXSLOT_LOCK: - eax_set_fx_slot_lock(eax_call); - return false; + auto& dst_i = eax_; - case EAXFXSLOT_FLAGS: - eax_set_fx_slot_flags(eax_call); - return false; + if(dst_i.lOcclusion != EAXFXSLOT_DEFAULTOCCLUSION) { + dst_df |= eax_occlusion_dirty_bit; + dst_i.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; + } - case EAXFXSLOT_OCCLUSION: - return eax_set_fx_slot_occlusion(eax_call); - - case EAXFXSLOT_OCCLUSIONLFRATIO: - return eax_set_fx_slot_occlusion_lf_ratio(eax_call); - - - default: - eax_fail("Unsupported FX slot property id."); + if(dst_i.flOcclusionLFRatio != EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO) { + dst_df |= eax_occlusion_lf_ratio_dirty_bit; + dst_i.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; } } -// [[nodiscard]] -bool ALeffectslot::eax_set(const EaxEaxCall& eax_call) +void ALeffectslot::eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df) { - switch(eax_call.get_property_set_id()) - { - case EaxEaxCallPropertySetId::fx_slot: - return eax_set_fx_slot(eax_call); - - case EaxEaxCallPropertySetId::fx_slot_effect: - eax_dispatch_effect(eax_call); - break; - - default: - eax_fail("Unsupported property id."); - } - - return false; + eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::guidLoadEffect); + eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::lVolume); + eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::lLock); + eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::ulFlags); + eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::lOcclusion); + eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::flOcclusionLFRatio); } -void ALeffectslot::eax_dispatch_effect(const EaxEaxCall& eax_call) -{ if(eax_effect_) eax_effect_->dispatch(eax_call); } - -void ALeffectslot::eax_apply_deferred() -{ - /* The other FXSlot properties (volume, effect, etc) aren't deferred? */ - - auto is_changed = false; - if(eax_effect_) - is_changed = eax_effect_->apply_deferred(); - if(is_changed) - eax_set_effect_slot_effect(*eax_effect_); -} - - -void ALeffectslot::eax_set_effect_slot_effect(EaxEffect &effect) +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_); - if (error != AL_NO_ERROR) - { + 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."); return; } - if (mState == SlotState::Initial) - { + if(mState == SlotState::Initial) { mPropsDirty = false; updateProps(eax_al_context_); - auto effect_slot_ptr = this; - AddActiveEffectSlots({&effect_slot_ptr, 1}, eax_al_context_); mState = SlotState::Playing; - return; } - UpdateProps(this, eax_al_context_); + mPropsDirty = true; #undef EAX_PREFIX } -void ALeffectslot::eax_set_effect_slot_send_auto( - bool is_send_auto) +void ALeffectslot::eax_set_efx_slot_send_auto(bool is_send_auto) { if(AuxSendAuto == is_send_auto) return; AuxSendAuto = is_send_auto; - UpdateProps(this, eax_al_context_); + mPropsDirty = true; } -void ALeffectslot::eax_set_effect_slot_gain( - ALfloat gain) +void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain) { #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_GAIN] " @@ -1752,69 +1518,53 @@ void ALeffectslot::eax_set_effect_slot_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); - UpdateProps(this, eax_al_context_); + Gain = std::clamp(gain, 0.0f, 1.0f); + mPropsDirty = true; #undef EAX_PREFIX } - void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot) { assert(effect_slot); eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot); } - -EaxAlEffectSlotUPtr eax_create_al_effect_slot( - ALCcontext& context) +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) - { + if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) { ERR(EAX_PREFIX "%s\n", "Out of memory."); return nullptr; } - if (!EnsureEffectSlots(&context, 1)) - { + if(!EnsureEffectSlots(&context, 1)) { ERR(EAX_PREFIX "%s\n", "Failed to ensure."); return nullptr; } - auto effect_slot = EaxAlEffectSlotUPtr{AllocEffectSlot(&context)}; - if (!effect_slot) - { - ERR(EAX_PREFIX "%s\n", "Failed to allocate."); - return nullptr; - } - - return effect_slot; + return EaxAlEffectSlotUPtr{AllocEffectSlot(&context)}; #undef EAX_PREFIX } -void eax_delete_al_effect_slot( - ALCcontext& context, - ALeffectslot& effect_slot) +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; } auto effect_slot_ptr = &effect_slot; - RemoveActiveEffectSlots({&effect_slot_ptr, 1}, &context); FreeEffectSlot(&context, &effect_slot); diff --git a/libs/openal-soft/al/auxeffectslot.h b/libs/openal-soft/al/auxeffectslot.h index ca0dcd31..4c7d5733 100644 --- a/libs/openal-soft/al/auxeffectslot.h +++ b/libs/openal-soft/al/auxeffectslot.h @@ -1,33 +1,41 @@ #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_eax_call.h" -#include "eax_effect.h" -#include "eax_fx_slot_index.h" +#include "eax/api.h" +#include "eax/call.h" +#include "eax/effect.h" +#include "eax/exception.h" +#include "eax/fx_slot_index.h" +#include "eax/utils.h" #endif // ALSOFT_EAX struct ALbuffer; -struct ALeffect; -struct WetBuffer; +#ifdef ALSOFT_EAX +class EaxFxSlotException : public EaxException { +public: + explicit EaxFxSlotException(const char* message) + : EaxException{"EAX_FX_SLOT", message} + {} +}; +#endif // ALSOFT_EAX enum class SlotState : ALenum { Initial = AL_INITIAL, @@ -36,242 +44,342 @@ enum class SlotState : ALenum { }; struct ALeffectslot { + ALuint EffectId{}; float Gain{1.0f}; bool AuxSendAuto{true}; ALeffectslot *Target{nullptr}; ALbuffer *Buffer{nullptr}; - struct { + struct EffectData { EffectSlotType Type{EffectSlotType::None}; EffectProps Props{}; al::intrusive_ptr State; - } Effect; + }; + EffectData Effect; bool mPropsDirty{true}; SlotState mState{SlotState::Initial}; - RefCount ref{0u}; + std::atomic ref{0u}; - EffectSlot mSlot; + EffectSlot *mSlot{nullptr}; /* Self ID */ ALuint id{}; - ALeffectslot(); + ALeffectslot(ALCcontext *context); ALeffectslot(const ALeffectslot&) = delete; ALeffectslot& operator=(const ALeffectslot&) = delete; ~ALeffectslot(); - ALenum initEffect(ALenum effectType, const EffectProps &effectProps, ALCcontext *context); - void updateProps(ALCcontext *context); + ALenum initEffect(ALuint effectId, ALenum effectType, const EffectProps &effectProps, + ALCcontext *context); + void updateProps(ALCcontext *context) const; - /* This can be new'd for the context's default effect slot. */ - DEF_NEWDEL(ALeffectslot) + static void SetName(ALCcontext *context, ALuint id, std::string_view name); #ifdef ALSOFT_EAX public: - void eax_initialize( - ALCcontext& al_context, - EaxFxSlotIndexValue index); + void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index); - const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept; + [[nodiscard]] auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return eax_fx_slot_index_; } + [[nodiscard]] auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES& + { return eax_; } + // Returns `true` if all sources should be updated, or `false` otherwise. + [[nodiscard]] auto eax_dispatch(const EaxCall& call) -> bool + { return call.is_get() ? eax_get(call) : eax_set(call); } - // [[nodiscard]] - bool eax_dispatch(const EaxEaxCall& eax_call) - { return eax_call.is_get() ? eax_get(eax_call) : eax_set(eax_call); } - - - void eax_unlock_legacy() noexcept; - - void eax_commit() { eax_apply_deferred(); } + void eax_commit(); private: + static constexpr auto eax_load_effect_dirty_bit = EaxDirtyFlags{1} << 0; + static constexpr auto eax_volume_dirty_bit = EaxDirtyFlags{1} << 1; + static constexpr auto eax_lock_dirty_bit = EaxDirtyFlags{1} << 2; + static constexpr auto eax_flags_dirty_bit = EaxDirtyFlags{1} << 3; + static constexpr auto eax_occlusion_dirty_bit = EaxDirtyFlags{1} << 4; + static constexpr auto eax_occlusion_lf_ratio_dirty_bit = EaxDirtyFlags{1} << 5; + + using Exception = EaxFxSlotException; + + using Eax4Props = EAX40FXSLOTPROPERTIES; + + struct Eax4State { + Eax4Props i; // Immediate. + }; + + using Eax5Props = EAX50FXSLOTPROPERTIES; + + struct Eax5State { + Eax5Props i; // Immediate. + }; + + struct EaxRangeValidator { + template + void operator()( + const char* name, + const TValue& value, + const TValue& min_value, + const TValue& max_value) const + { + eax_validate_range(name, value, min_value, max_value); + } + }; + + struct Eax4GuidLoadEffectValidator { + void operator()(const GUID& guidLoadEffect) const + { + if (guidLoadEffect != EAX_NULL_GUID && + guidLoadEffect != EAX_REVERB_EFFECT && + guidLoadEffect != EAX_AGCCOMPRESSOR_EFFECT && + guidLoadEffect != EAX_AUTOWAH_EFFECT && + guidLoadEffect != EAX_CHORUS_EFFECT && + guidLoadEffect != EAX_DISTORTION_EFFECT && + guidLoadEffect != EAX_ECHO_EFFECT && + guidLoadEffect != EAX_EQUALIZER_EFFECT && + guidLoadEffect != EAX_FLANGER_EFFECT && + guidLoadEffect != EAX_FREQUENCYSHIFTER_EFFECT && + guidLoadEffect != EAX_VOCALMORPHER_EFFECT && + guidLoadEffect != EAX_PITCHSHIFTER_EFFECT && + guidLoadEffect != EAX_RINGMODULATOR_EFFECT) + { + eax_fail_unknown_effect_id(); + } + } + }; + + struct Eax4VolumeValidator { + void operator()(long lVolume) const + { + EaxRangeValidator{}( + "Volume", + lVolume, + EAXFXSLOT_MINVOLUME, + EAXFXSLOT_MAXVOLUME); + } + }; + + struct Eax4LockValidator { + void operator()(long lLock) const + { + EaxRangeValidator{}( + "Lock", + lLock, + EAXFXSLOT_MINLOCK, + EAXFXSLOT_MAXLOCK); + } + }; + + struct Eax4FlagsValidator { + void operator()(unsigned long ulFlags) const + { + EaxRangeValidator{}( + "Flags", + ulFlags, + 0UL, + ~EAX40FXSLOTFLAGS_RESERVED); + } + }; + + struct Eax4AllValidator { + void operator()(const EAX40FXSLOTPROPERTIES& all) const + { + Eax4GuidLoadEffectValidator{}(all.guidLoadEffect); + Eax4VolumeValidator{}(all.lVolume); + Eax4LockValidator{}(all.lLock); + Eax4FlagsValidator{}(all.ulFlags); + } + }; + + struct Eax5OcclusionValidator { + void operator()(long lOcclusion) const + { + EaxRangeValidator{}( + "Occlusion", + lOcclusion, + EAXFXSLOT_MINOCCLUSION, + EAXFXSLOT_MAXOCCLUSION); + } + }; + + struct Eax5OcclusionLfRatioValidator { + void operator()(float flOcclusionLFRatio) const + { + EaxRangeValidator{}( + "Occlusion LF Ratio", + flOcclusionLFRatio, + EAXFXSLOT_MINOCCLUSIONLFRATIO, + EAXFXSLOT_MAXOCCLUSIONLFRATIO); + } + }; + + struct Eax5FlagsValidator { + void operator()(unsigned long ulFlags) const + { + EaxRangeValidator{}( + "Flags", + ulFlags, + 0UL, + ~EAX50FXSLOTFLAGS_RESERVED); + } + }; + + struct Eax5AllValidator { + void operator()(const EAX50FXSLOTPROPERTIES& all) const + { + Eax4AllValidator{}(static_cast(all)); + Eax5OcclusionValidator{}(all.lOcclusion); + Eax5OcclusionLfRatioValidator{}(all.flOcclusionLFRatio); + } + }; + ALCcontext* eax_al_context_{}; - EaxFxSlotIndexValue eax_fx_slot_index_{}; - - EAX50FXSLOTPROPERTIES eax_eax_fx_slot_{}; - + int eax_version_{}; // Current EAX version. + EaxDirtyFlags eax_df_{}; // Dirty flags for the current EAX version. EaxEffectUPtr eax_effect_{}; - bool eax_is_locked_{}; + Eax5State eax123_{}; // EAX1/EAX2/EAX3 state. + Eax4State eax4_{}; // EAX4 state. + Eax5State eax5_{}; // EAX5 state. + Eax5Props eax_{}; // Current EAX state. + [[noreturn]] static void eax_fail(const char* message); + [[noreturn]] static void eax_fail_unknown_effect_id(); + [[noreturn]] static void eax_fail_unknown_property_id(); + [[noreturn]] static void eax_fail_unknown_version(); - [[noreturn]] - static void eax_fail( - const char* message); + // Gets a new value from EAX call, + // validates it, + // sets a dirty flag only if the new value differs form the old one, + // and assigns the new value. + template + static void eax_fx_slot_set(const EaxCall& call, TProperties& dst, EaxDirtyFlags& dirty_flags) + { + const auto& src = call.get_value(); + TValidator{}(src); + dirty_flags |= (dst != src ? TDirtyBit : EaxDirtyFlags{}); + dst = src; + } + // Gets a new value from EAX call, + // validates it, + // sets a dirty flag without comparing the values, + // and assigns the new value. + template + static void eax_fx_slot_set_dirty(const EaxCall& call, TProperties& dst, + EaxDirtyFlags& dirty_flags) + { + const auto& src = call.get_value(); + TValidator{}(src); + dirty_flags |= TDirtyBit; + dst = src; + } - GUID eax_get_eax_default_effect_guid() const noexcept; - long eax_get_eax_default_lock() const noexcept; + [[nodiscard]] constexpr auto eax4_fx_slot_is_legacy() const noexcept -> bool + { return eax_fx_slot_index_ < 2; } - void eax_set_eax_fx_slot_defaults(); + void eax4_fx_slot_ensure_unlocked() const; - void eax_initialize_eax(); + [[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 eax_initialize_lock(); + void eax4_fx_slot_set_defaults(Eax4Props& props) noexcept; + void eax5_fx_slot_set_defaults(Eax5Props& props) noexcept; + void eax4_fx_slot_set_current_defaults(const Eax4Props& props) noexcept; + void eax5_fx_slot_set_current_defaults(const Eax5Props& props) noexcept; + void eax_fx_slot_set_current_defaults(); + void eax_fx_slot_set_defaults(); + static void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props); + static void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props); + void eax_fx_slot_get(const EaxCall& call) const; + // Returns `true` if all sources should be updated, or `false` otherwise. + bool eax_get(const EaxCall& call); - void eax_initialize_effects(); + void eax_fx_slot_load_effect(int version, ALenum altype); + void eax_fx_slot_set_volume(); + void eax_fx_slot_set_environment_flag(); + void eax_fx_slot_set_flags(); + void eax4_fx_slot_set_all(const EaxCall& call); + void eax5_fx_slot_set_all(const EaxCall& call); - void eax_get_fx_slot_all( - const EaxEaxCall& eax_call) const; + [[nodiscard]] auto eax_fx_slot_should_update_sources() const noexcept -> bool; - void eax_get_fx_slot( - const EaxEaxCall& eax_call) const; + // Returns `true` if all sources should be updated, or `false` otherwise. + bool eax4_fx_slot_set(const EaxCall& call); + // Returns `true` if all sources should be updated, or `false` otherwise. + bool eax5_fx_slot_set(const EaxCall& call); + // Returns `true` if all sources should be updated, or `false` otherwise. + bool eax_fx_slot_set(const EaxCall& call); + // Returns `true` if all sources should be updated, or `false` otherwise. + bool eax_set(const EaxCall& call); - // [[nodiscard]] - bool eax_get( - const EaxEaxCall& eax_call); + template< + EaxDirtyFlags TDirtyBit, + typename TMemberResult, + typename TProps, + typename TState> + void eax_fx_slot_commit_property(TState& state, EaxDirtyFlags& dst_df, + TMemberResult TProps::*member) noexcept + { + auto& src_i = state.i; + auto& dst_i = eax_; + if((eax_df_ & TDirtyBit) != EaxDirtyFlags{}) + { + dst_df |= TDirtyBit; + dst_i.*member = src_i.*member; + } + } - void eax_set_fx_slot_effect( - ALenum effect_type); - - void eax_set_fx_slot_effect(); - - - void eax_set_efx_effect_slot_gain(); - - void eax_set_fx_slot_volume(); - - - void eax_set_effect_slot_send_auto(); - - void eax_set_fx_slot_flags(); - - - void eax_ensure_is_unlocked() const; - - - void eax_validate_fx_slot_effect( - const GUID& eax_effect_id); - - void eax_validate_fx_slot_volume( - long eax_volume); - - void eax_validate_fx_slot_lock( - long eax_lock); - - void eax_validate_fx_slot_flags( - unsigned long eax_flags, - int eax_version); - - void eax_validate_fx_slot_occlusion( - long eax_occlusion); - - void eax_validate_fx_slot_occlusion_lf_ratio( - float eax_occlusion_lf_ratio); - - void eax_validate_fx_slot_all( - const EAX40FXSLOTPROPERTIES& fx_slot, - int eax_version); - - void eax_validate_fx_slot_all( - const EAX50FXSLOTPROPERTIES& fx_slot, - int eax_version); - - - void eax_set_fx_slot_effect( - const GUID& eax_effect_id); - - void eax_set_fx_slot_volume( - long eax_volume); - - void eax_set_fx_slot_lock( - long eax_lock); - - void eax_set_fx_slot_flags( - unsigned long eax_flags); - - // [[nodiscard]] - bool eax_set_fx_slot_occlusion( - long eax_occlusion); - - // [[nodiscard]] - bool eax_set_fx_slot_occlusion_lf_ratio( - float eax_occlusion_lf_ratio); - - void eax_set_fx_slot_all( - const EAX40FXSLOTPROPERTIES& eax_fx_slot); - - // [[nodiscard]] - bool eax_set_fx_slot_all( - const EAX50FXSLOTPROPERTIES& eax_fx_slot); - - - void eax_set_fx_slot_effect( - const EaxEaxCall& eax_call); - - void eax_set_fx_slot_volume( - const EaxEaxCall& eax_call); - - void eax_set_fx_slot_lock( - const EaxEaxCall& eax_call); - - void eax_set_fx_slot_flags( - const EaxEaxCall& eax_call); - - // [[nodiscard]] - bool eax_set_fx_slot_occlusion( - const EaxEaxCall& eax_call); - - // [[nodiscard]] - bool eax_set_fx_slot_occlusion_lf_ratio( - const EaxEaxCall& eax_call); - - // [[nodiscard]] - bool eax_set_fx_slot_all( - const EaxEaxCall& eax_call); - - bool eax_set_fx_slot( - const EaxEaxCall& eax_call); - - void eax_apply_deferred(); - - // [[nodiscard]] - bool eax_set( - const EaxEaxCall& eax_call); - - - void eax_dispatch_effect( - const EaxEaxCall& eax_call); - + void eax4_fx_slot_commit(EaxDirtyFlags& dst_df); + void eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df); // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)` - void eax_set_effect_slot_effect(EaxEffect &effect); + void eax_set_efx_slot_effect(EaxEffect &effect); // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_AUXILIARY_SEND_AUTO, value)` - void eax_set_effect_slot_send_auto(bool is_send_auto); + void eax_set_efx_slot_send_auto(bool is_send_auto); // `alAuxiliaryEffectSlotf(effect_slot, AL_EFFECTSLOT_GAIN, gain)` - void eax_set_effect_slot_gain(ALfloat gain); + void eax_set_efx_slot_gain(ALfloat gain); public: class EaxDeleter { public: void operator()(ALeffectslot *effect_slot); - }; // EaxAlEffectSlotDeleter + }; #endif // ALSOFT_EAX }; void UpdateAllEffectSlotProps(ALCcontext *context); #ifdef ALSOFT_EAX - using EaxAlEffectSlotUPtr = std::unique_ptr; - -EaxAlEffectSlotUPtr eax_create_al_effect_slot( - ALCcontext& context); - -void eax_delete_al_effect_slot( - ALCcontext& context, - ALeffectslot& effect_slot); +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/libs/openal-soft/al/buffer.cpp b/libs/openal-soft/al/buffer.cpp index 13407103..883878b2 100644 --- a/libs/openal-soft/al/buffer.cpp +++ b/libs/openal-soft/al/buffer.cpp @@ -26,274 +26,63 @@ #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 "alspan.h" +#include "core/device.h" #include "core/logging.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 "eax_globals.h" -#include "eax_x_ram.h" +#include + +#include "eax/globals.h" +#include "eax/x_ram.h" #endif // ALSOFT_EAX namespace { -constexpr int MaxAdpcmChannels{2}; +using SubListAllocator = al::allocator>; -/* IMA ADPCM Stepsize table */ -constexpr int IMAStep_size[89] = { - 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, - 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, - 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, - 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, - 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, - 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, - 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442, - 11487,12635,13899,15289,16818,18500,20350,22358,24633,27086,29794, - 32767 -}; - -/* IMA4 ADPCM Codeword decode table */ -constexpr int IMA4Codeword[16] = { - 1, 3, 5, 7, 9, 11, 13, 15, - -1,-3,-5,-7,-9,-11,-13,-15, -}; - -/* IMA4 ADPCM Step index adjust decode table */ -constexpr int IMA4Index_adjust[16] = { - -1,-1,-1,-1, 2, 4, 6, 8, - -1,-1,-1,-1, 2, 4, 6, 8 -}; - - -/* MSADPCM Adaption table */ -constexpr int MSADPCMAdaption[16] = { - 230, 230, 230, 230, 307, 409, 512, 614, - 768, 614, 512, 409, 307, 230, 230, 230 -}; - -/* MSADPCM Adaption Coefficient tables */ -constexpr int MSADPCMAdaptionCoeff[7][2] = { - { 256, 0 }, - { 512, -256 }, - { 0, 0 }, - { 192, 64 }, - { 240, 0 }, - { 460, -208 }, - { 392, -232 } -}; - - -void DecodeIMA4Block(int16_t *dst, const al::byte *src, size_t numchans, size_t align) -{ - int sample[MaxAdpcmChannels]{}; - int index[MaxAdpcmChannels]{}; - ALuint code[MaxAdpcmChannels]{}; - - for(size_t c{0};c < numchans;c++) - { - sample[c] = src[0] | (src[1]<<8); - sample[c] = (sample[c]^0x8000) - 32768; - src += 2; - index[c] = src[0] | (src[1]<<8); - index[c] = clampi((index[c]^0x8000) - 32768, 0, 88); - src += 2; - - *(dst++) = static_cast(sample[c]); - } - - for(size_t i{1};i < align;i++) - { - if((i&7) == 1) - { - for(size_t c{0};c < numchans;c++) - { - code[c] = ALuint{src[0]} | (ALuint{src[1]}<< 8) | (ALuint{src[2]}<<16) - | (ALuint{src[3]}<<24); - src += 4; - } - } - - for(size_t c{0};c < numchans;c++) - { - const ALuint nibble{code[c]&0xf}; - code[c] >>= 4; - - sample[c] += IMA4Codeword[nibble] * IMAStep_size[index[c]] / 8; - sample[c] = clampi(sample[c], -32768, 32767); - - index[c] += IMA4Index_adjust[nibble]; - index[c] = clampi(index[c], 0, 88); - - *(dst++) = static_cast(sample[c]); - } - } -} - -void DecodeMSADPCMBlock(int16_t *dst, const al::byte *src, size_t numchans, size_t align) -{ - uint8_t blockpred[MaxAdpcmChannels]{}; - int delta[MaxAdpcmChannels]{}; - int16_t samples[MaxAdpcmChannels][2]{}; - - for(size_t c{0};c < numchans;c++) - { - blockpred[c] = std::min(src[0], 6); - ++src; - } - for(size_t c{0};c < numchans;c++) - { - delta[c] = src[0] | (src[1]<<8); - delta[c] = (delta[c]^0x8000) - 32768; - src += 2; - } - for(size_t c{0};c < numchans;c++) - { - samples[c][0] = static_cast(src[0] | (src[1]<<8)); - src += 2; - } - for(size_t c{0};c < numchans;c++) - { - samples[c][1] = static_cast(src[0] | (src[1]<<8)); - src += 2; - } - - /* Second sample is written first. */ - for(size_t c{0};c < numchans;c++) - *(dst++) = samples[c][1]; - for(size_t c{0};c < numchans;c++) - *(dst++) = samples[c][0]; - - int num{0}; - for(size_t i{2};i < align;i++) - { - for(size_t c{0};c < numchans;c++) - { - /* Read the nibble (first is in the upper bits). */ - al::byte nibble; - if(!(num++ & 1)) - nibble = *src >> 4; - else - nibble = *(src++) & 0x0f; - - int pred{(samples[c][0]*MSADPCMAdaptionCoeff[blockpred[c]][0] + - samples[c][1]*MSADPCMAdaptionCoeff[blockpred[c]][1]) / 256}; - pred += ((nibble^0x08) - 0x08) * delta[c]; - pred = clampi(pred, -32768, 32767); - - samples[c][1] = samples[c][0]; - samples[c][0] = static_cast(pred); - - delta[c] = (MSADPCMAdaption[nibble] * delta[c]) / 256; - delta[c] = maxi(16, delta[c]); - - *(dst++) = static_cast(pred); - } - } -} - -void Convert_int16_ima4(int16_t *dst, const al::byte *src, size_t numchans, size_t len, - size_t align) -{ - assert(numchans <= MaxAdpcmChannels); - const size_t byte_align{((align-1)/2 + 4) * numchans}; - - len /= align; - while(len--) - { - DecodeIMA4Block(dst, src, numchans, align); - src += byte_align; - dst += align*numchans; - } -} - -void Convert_int16_msadpcm(int16_t *dst, const al::byte *src, size_t numchans, size_t len, - size_t align) -{ - assert(numchans <= MaxAdpcmChannels); - const size_t byte_align{((align-2)/2 + 7) * numchans}; - - len /= align; - while(len--) - { - DecodeMSADPCMBlock(dst, src, numchans, align); - src += byte_align; - dst += align*numchans; - } -} - - -ALuint BytesFromUserFmt(UserFmtType type) noexcept -{ - switch(type) - { - case UserFmtUByte: return sizeof(uint8_t); - case UserFmtShort: return sizeof(int16_t); - case UserFmtFloat: return sizeof(float); - case UserFmtDouble: return sizeof(double); - case UserFmtMulaw: return sizeof(uint8_t); - case UserFmtAlaw: return sizeof(uint8_t); - case UserFmtIMA4: break; /* not handled here */ - case UserFmtMSADPCM: break; /* not handled here */ - } - return 0; -} -ALuint ChannelsFromUserFmt(UserFmtChannels chans, ALuint ambiorder) noexcept -{ - switch(chans) - { - case UserFmtMono: return 1; - case UserFmtStereo: return 2; - case UserFmtRear: return 2; - case UserFmtQuad: return 4; - case UserFmtX51: return 6; - case UserFmtX61: return 7; - case UserFmtX71: return 8; - case UserFmtBFormat2D: return (ambiorder*2) + 1; - case UserFmtBFormat3D: return (ambiorder+1) * (ambiorder+1); - case UserFmtUHJ2: return 2; - case UserFmtUHJ3: return 3; - case UserFmtUHJ4: return 4; - } - return 0; -} - -al::optional AmbiLayoutFromEnum(ALenum layout) +constexpr auto AmbiLayoutFromEnum(ALenum layout) noexcept -> std::optional { switch(layout) { - case AL_FUMA_SOFT: return al::make_optional(AmbiLayout::FuMa); - case AL_ACN_SOFT: return al::make_optional(AmbiLayout::ACN); + 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) { @@ -303,17 +92,17 @@ 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) { - case AL_FUMA_SOFT: return al::make_optional(AmbiScaling::FuMa); - case AL_SN3D_SOFT: return al::make_optional(AmbiScaling::SN3D); - case AL_N3D_SOFT: return al::make_optional(AmbiScaling::N3D); + case AL_FUMA_SOFT: return AmbiScaling::FuMa; + 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) { @@ -325,44 +114,29 @@ ALenum EnumFromAmbiScaling(AmbiScaling scale) throw std::runtime_error{"Invalid AmbiScaling: "+std::to_string(int(scale))}; } -al::optional FmtFromUserFmt(UserFmtChannels chans) -{ - switch(chans) - { - case UserFmtMono: return al::make_optional(FmtMono); - case UserFmtStereo: return al::make_optional(FmtStereo); - case UserFmtRear: return al::make_optional(FmtRear); - case UserFmtQuad: return al::make_optional(FmtQuad); - case UserFmtX51: return al::make_optional(FmtX51); - case UserFmtX61: return al::make_optional(FmtX61); - case UserFmtX71: return al::make_optional(FmtX71); - case UserFmtBFormat2D: return al::make_optional(FmtBFormat2D); - case UserFmtBFormat3D: return al::make_optional(FmtBFormat3D); - case UserFmtUHJ2: return al::make_optional(FmtUHJ2); - case UserFmtUHJ3: return al::make_optional(FmtUHJ3); - case UserFmtUHJ4: return al::make_optional(FmtUHJ4); - } - return al::nullopt; -} -al::optional FmtFromUserFmt(UserFmtType type) -{ - switch(type) - { - case UserFmtUByte: return al::make_optional(FmtUByte); - case UserFmtShort: return al::make_optional(FmtShort); - case UserFmtFloat: return al::make_optional(FmtFloat); - case UserFmtDouble: return al::make_optional(FmtDouble); - case UserFmtMulaw: return al::make_optional(FmtMulaw); - case UserFmtAlaw: return al::make_optional(FmtAlaw); - /* ADPCM not handled here. */ - case UserFmtIMA4: break; - case UserFmtMSADPCM: break; - } - return al::nullopt; -} - - #ifdef ALSOFT_EAX +constexpr auto EaxStorageFromEnum(ALenum scale) noexcept -> std::optional +{ + switch(scale) + { + case AL_STORAGE_AUTOMATIC: return EaxStorage::Automatic; + case AL_STORAGE_ACCESSIBLE: return EaxStorage::Accessible; + case AL_STORAGE_HARDWARE: return EaxStorage::Hardware; + } + return std::nullopt; +} +constexpr auto EnumFromEaxStorage(EaxStorage storage) -> ALenum +{ + switch(storage) + { + case EaxStorage::Automatic: return AL_STORAGE_AUTOMATIC; + case EaxStorage::Accessible: return AL_STORAGE_ACCESSIBLE; + case EaxStorage::Hardware: return AL_STORAGE_HARDWARE; + } + throw std::runtime_error{"Invalid EaxStorage: "+std::to_string(int(storage))}; +} + + bool eax_x_ram_check_availability(const ALCdevice &device, const ALbuffer &buffer, const ALuint newsize) noexcept { @@ -387,7 +161,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; @@ -403,32 +177,30 @@ 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)); })}; while(needed > count) { - if UNLIKELY(device->BufferList.size() >= 1<<25) + 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 UNLIKELY(!sublist->Buffers) - { - 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 @@ -437,7 +209,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; @@ -453,34 +225,36 @@ 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) +inline auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= device->BufferList.size()) + if(lidx >= device->BufferList.size()) UNLIKELY return nullptr; BufferSubList &sublist = device->BufferList[lidx]; - if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Buffers + slidx; + return al::to_address(sublist.Buffers->begin() + slidx); } -ALuint SanitizeAlignment(UserFmtType type, ALuint align) +constexpr auto SanitizeAlignment(FmtType type, ALuint align) noexcept -> ALuint { if(align == 0) { - if(type == UserFmtIMA4) + if(type == FmtIMA4) { /* Here is where things vary: * nVidia and Apple use 64+1 sample frames per block -> block_size=36 bytes per channel @@ -488,18 +262,18 @@ ALuint SanitizeAlignment(UserFmtType type, ALuint align) */ return 65; } - if(type == UserFmtMSADPCM) + if(type == FmtMSADPCM) return 64; return 1; } - if(type == UserFmtIMA4) + if(type == FmtIMA4) { /* IMA4 block alignment must be a multiple of 8, plus 1. */ if((align&7) == 1) return static_cast(align); return 0; } - if(type == UserFmtMSADPCM) + if(type == FmtMSADPCM) { /* MSADPCM block alignment must be a multiple of 2. */ if((align&1) == 0) return static_cast(align); @@ -510,199 +284,148 @@ ALuint SanitizeAlignment(UserFmtType type, ALuint align) } -const ALchar *NameFromUserFmtType(UserFmtType type) -{ - switch(type) - { - case UserFmtUByte: return "UInt8"; - case UserFmtShort: return "Int16"; - case UserFmtFloat: return "Float32"; - case UserFmtDouble: return "Float64"; - case UserFmtMulaw: return "muLaw"; - case UserFmtAlaw: return "aLaw"; - case UserFmtIMA4: return "IMA4 ADPCM"; - case UserFmtMSADPCM: return "MSADPCM"; - } - return ""; -} - /** Loads the specified data into the buffer, using the specified format. */ -void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, - UserFmtChannels SrcChannels, UserFmtType SrcType, const al::byte *SrcData, +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 UNLIKELY(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) - SETERR_RETURN(context, AL_INVALID_OPERATION,, "Modifying storage for in-use buffer %u", - ALBuf->id); - - /* Currently no channel configurations need to be converted. */ - auto DstChannels = FmtFromUserFmt(SrcChannels); - if UNLIKELY(!DstChannels) - SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format"); - - /* IMA4 and MSADPCM convert to 16-bit short. - * - * TODO: Currently we can only map samples when they're not converted. To - * allow it would need some kind of double-buffering to hold onto a copy of - * the original data. - */ - if((access&MAP_READ_WRITE_FLAGS)) - { - if UNLIKELY(SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM) - SETERR_RETURN(context, AL_INVALID_VALUE,, "%s samples cannot be mapped", - NameFromUserFmtType(SrcType)); - } - auto DstType = (SrcType == UserFmtIMA4 || SrcType == UserFmtMSADPCM) - ? al::make_optional(FmtShort) : FmtFromUserFmt(SrcType); - if UNLIKELY(!DstType) - SETERR_RETURN(context, AL_INVALID_ENUM, , "Invalid format"); + 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(SrcType, unpackalign)}; - if UNLIKELY(align < 1) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid unpack alignment %u for %s samples", - unpackalign, NameFromUserFmtType(SrcType)); + 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 ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder : - (IsUHJ(*DstChannels) ? 1 : 0)}; + const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : + (IsUHJ(DstChannels) ? 1 : 0)}; if((access&AL_PRESERVE_DATA_BIT_SOFT)) { /* Can only preserve data with the same format and alignment. */ - if UNLIKELY(ALBuf->mChannels != *DstChannels || ALBuf->OriginalType != SrcType) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched format"); - if UNLIKELY(ALBuf->OriginalAlign != align) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched alignment"); + 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) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Preserving data of mismatched order"); + throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched order"}; } - /* Convert the input/source size in bytes to sample frames using the unpack - * block alignment. - */ - const ALuint SrcByteAlign{ChannelsFromUserFmt(SrcChannels, ambiorder) * - ((SrcType == UserFmtIMA4) ? (align-1)/2 + 4 : - (SrcType == UserFmtMSADPCM) ? (align-2)/2 + 7 : - (align * BytesFromUserFmt(SrcType)))}; - if UNLIKELY((size%SrcByteAlign) != 0) - SETERR_RETURN(context, AL_INVALID_VALUE,, + /* Convert the size in bytes to blocks using the unpack block alignment. */ + const ALuint NumChannels{ChannelsFromFmt(DstChannels, ambiorder)}; + const ALuint BlockSize{NumChannels * + ((DstType == FmtIMA4) ? (align-1)/2 + 4 : + (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : + (align * BytesFromFmt(DstType)))}; + 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, SrcByteAlign, align); + size, BlockSize, align}; + const ALuint blocks{size / BlockSize}; - if UNLIKELY(size/SrcByteAlign > std::numeric_limits::max()/align) - SETERR_RETURN(context, AL_OUT_OF_MEMORY,, - "Buffer size overflow, %d blocks x %d samples per block", size/SrcByteAlign, align); - const ALuint frames{size / SrcByteAlign * align}; + 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}; - /* Convert the sample frames to the number of bytes needed for internal - * storage. - */ - ALuint NumChannels{ChannelsFromFmt(*DstChannels, ambiorder)}; - ALuint FrameSize{NumChannels * BytesFromFmt(*DstType)}; - if UNLIKELY(frames > std::numeric_limits::max()/FrameSize) - SETERR_RETURN(context, AL_OUT_OF_MEMORY,, - "Buffer size overflow, %d frames x %d bytes per frame", frames, FrameSize); - size_t newsize{static_cast(frames) * FrameSize}; + const size_t newsize{static_cast(blocks) * BlockSize}; #ifdef ALSOFT_EAX - if(ALBuf->eax_x_ram_mode == AL_STORAGE_HARDWARE) + if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) { ALCdevice &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, size)) - SETERR_RETURN(context, AL_OUT_OF_MEMORY,, - "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size); + 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 - /* Round up to the next 16-byte multiple. This could reallocate only when - * increasing or the new size is less than half the current, but then the - * buffer's AL_SIZE would not be very reliable for accounting buffer memory - * usage, and reporting the real size could cause problems for apps that - * use AL_SIZE to try to get the buffer's play length. + /* This could reallocate only when increasing the size or the new size is + * less than half the current, but then the buffer's AL_SIZE would not be + * very reliable for accounting buffer memory usage, and reporting the real + * size could cause problems for apps that use AL_SIZE to try to get the + * buffer's play length. */ - newsize = RoundUp(newsize, 16); - if(newsize != ALBuf->mData.size()) + 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->mData.size())}; - std::copy_n(ALBuf->mData.begin(), tocopy, newdata.begin()); + const size_t tocopy{std::min(newdata.size(), ALBuf->mDataStorage.size())}; + std::copy_n(ALBuf->mDataStorage.begin(), tocopy, newdata.begin()); } - newdata.swap(ALBuf->mData); + newdata.swap(ALBuf->mDataStorage); } + ALBuf->mData = ALBuf->mDataStorage; +#ifdef ALSOFT_EAX + eax_x_ram_clear(*context->mALDevice, *ALBuf); +#endif + + if(SrcData != nullptr && !ALBuf->mData.empty()) + std::copy_n(SrcData, blocks*BlockSize, ALBuf->mData.begin()); + ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? align : 1; - if(SrcType == UserFmtIMA4) - { - assert(*DstType == FmtShort); - if(SrcData != nullptr && !ALBuf->mData.empty()) - Convert_int16_ima4(reinterpret_cast(ALBuf->mData.data()), SrcData, - NumChannels, frames, align); - ALBuf->OriginalAlign = align; - } - else if(SrcType == UserFmtMSADPCM) - { - assert(*DstType == FmtShort); - if(SrcData != nullptr && !ALBuf->mData.empty()) - Convert_int16_msadpcm(reinterpret_cast(ALBuf->mData.data()), SrcData, - NumChannels, frames, align); - ALBuf->OriginalAlign = align; - } - else - { - assert(DstType.has_value()); - if(SrcData != nullptr && !ALBuf->mData.empty()) - std::copy_n(SrcData, frames*FrameSize, ALBuf->mData.begin()); - ALBuf->OriginalAlign = 1; - } ALBuf->OriginalSize = size; - ALBuf->OriginalType = SrcType; ALBuf->Access = access; ALBuf->mSampleRate = static_cast(freq); - ALBuf->mChannels = *DstChannels; - ALBuf->mType = *DstType; + ALBuf->mChannels = DstChannels; + ALBuf->mType = DstType; ALBuf->mAmbiOrder = ambiorder; ALBuf->mCallback = nullptr; ALBuf->mUserData = nullptr; - ALBuf->mSampleLen = frames; + ALBuf->mSampleLen = blocks * align; ALBuf->mLoopStart = 0; ALBuf->mLoopEnd = ALBuf->mSampleLen; #ifdef ALSOFT_EAX - if(eax_g_is_enabled && ALBuf->eax_x_ram_mode != AL_STORAGE_ACCESSIBLE) + if(eax_g_is_enabled && ALBuf->eax_x_ram_mode == EaxStorage::Hardware) eax_x_ram_apply(*context->mALDevice, *ALBuf); #endif } /** Prepares the buffer to use the specified callback, using the specified format. */ -void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, - UserFmtChannels SrcChannels, UserFmtType SrcType, ALBUFFERCALLBACKTYPESOFT callback, +void PrepareCallback(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, + const FmtChannels DstChannels, const FmtType DstType, ALBUFFERCALLBACKTYPESOFT callback, void *userptr) { - if UNLIKELY(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) - SETERR_RETURN(context, 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}; - /* Currently no channel configurations need to be converted. */ - auto DstChannels = FmtFromUserFmt(SrcChannels); - if UNLIKELY(!DstChannels) - SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid format"); + const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : + (IsUHJ(DstChannels) ? 1 : 0)}; - /* IMA4 and MSADPCM convert to 16-bit short. Not supported with callbacks. */ - auto DstType = FmtFromUserFmt(SrcType); - if UNLIKELY(!DstType) - SETERR_RETURN(context, AL_INVALID_ENUM,, "Unsupported callback format"); + 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 ambiorder{IsBFormat(*DstChannels) ? ALBuf->UnpackAmbiOrder : - (IsUHJ(*DstChannels) ? 1 : 0)}; + const ALuint BlockSize{ChannelsFromFmt(DstChannels, ambiorder) * + ((DstType == FmtIMA4) ? (align-1)/2 + 4 : + (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : + (align * BytesFromFmt(DstType)))}; - static constexpr uint line_size{BufferLineSize + MaxPostVoiceLoad}; - al::vector(FrameSizeFromFmt(*DstChannels, *DstType, ambiorder) * - size_t{line_size}).swap(ALBuf->mData); + /* The maximum number of samples a callback buffer may need to store is a + * full mixing line * max pitch * channel count, since it may need to hold + * a full line's worth of sample frames before downsampling. An additional + * MaxResamplerEdge is needed for "future" samples during resampling (the + * voice will hold a history for the past samples). + */ + static constexpr size_t line_size{DeviceBase::MixerLineSize*MaxPitch + MaxResamplerEdge}; + const size_t line_blocks{(line_size + align-1) / align}; + + using BufferVectorType = decltype(ALBuf->mDataStorage); + BufferVectorType(line_blocks*BlockSize).swap(ALBuf->mDataStorage); + ALBuf->mData = ALBuf->mDataStorage; #ifdef ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); @@ -711,14 +434,13 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALBuf->mCallback = callback; ALBuf->mUserData = userptr; - ALBuf->OriginalType = SrcType; ALBuf->OriginalSize = 0; - ALBuf->OriginalAlign = 1; ALBuf->Access = 0; + ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? align : 1; ALBuf->mSampleRate = static_cast(freq); - ALBuf->mChannels = *DstChannels; - ALBuf->mType = *DstType; + ALBuf->mChannels = DstChannels; + ALBuf->mType = DstType; ALBuf->mAmbiOrder = ambiorder; ALBuf->mSampleLen = 0; @@ -726,821 +448,869 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALBuf->mLoopEnd = ALBuf->mSampleLen; } +/** Prepares the buffer to use caller-specified storage. */ +void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, + const FmtChannels DstChannels, const FmtType DstType, std::byte *sdata, const ALuint sdatalen) +{ + 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}; -struct DecompResult { UserFmtChannels channels; UserFmtType type; }; -al::optional DecomposeUserFormat(ALenum format) + 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)}; + + auto get_type_alignment = [](const FmtType type) noexcept -> ALuint + { + /* NOTE: This only needs to be the required alignment for the CPU to + * read/write the given sample type in the mixer. + */ + switch(type) + { + 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); + case FmtAlaw: return alignof(ALubyte); + case FmtIMA4: break; + case FmtMSADPCM: break; + } + return 1; + }; + const auto typealign = get_type_alignment(DstType); + if((reinterpret_cast(sdata) & (typealign-1)) != 0) + 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)}; + + /* Convert the size in bytes to blocks using the unpack block alignment. */ + const ALuint NumChannels{ChannelsFromFmt(DstChannels, ambiorder)}; + const ALuint BlockSize{NumChannels * + ((DstType == FmtIMA4) ? (align-1)/2 + 4 : + (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : + (align * BytesFromFmt(DstType)))}; + 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}; + const ALuint blocks{sdatalen / 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)) + throw al::context_error{AL_OUT_OF_MEMORY, + "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, + sdatalen}; + } +#endif + + decltype(ALBuf->mDataStorage){}.swap(ALBuf->mDataStorage); + ALBuf->mData = {static_cast(sdata), sdatalen}; + +#ifdef ALSOFT_EAX + eax_x_ram_clear(*context->mALDevice, *ALBuf); +#endif + + ALBuf->mCallback = nullptr; + ALBuf->mUserData = nullptr; + + ALBuf->OriginalSize = sdatalen; + ALBuf->Access = 0; + + ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? align : 1; + ALBuf->mSampleRate = static_cast(freq); + ALBuf->mChannels = DstChannels; + ALBuf->mType = DstType; + ALBuf->mAmbiOrder = ambiorder; + + ALBuf->mSampleLen = blocks * align; + ALBuf->mLoopStart = 0; + ALBuf->mLoopEnd = ALBuf->mSampleLen; + +#ifdef ALSOFT_EAX + if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) + eax_x_ram_apply(*context->mALDevice, *ALBuf); +#endif +} + + +struct DecompResult { FmtChannels channels; FmtType type; }; +auto DecomposeUserFormat(ALenum format) noexcept -> std::optional { struct FormatMap { ALenum format; - UserFmtChannels channels; - UserFmtType type; + DecompResult result; }; - static const std::array UserFmtList{{ - { AL_FORMAT_MONO8, UserFmtMono, UserFmtUByte }, - { AL_FORMAT_MONO16, UserFmtMono, UserFmtShort }, - { AL_FORMAT_MONO_FLOAT32, UserFmtMono, UserFmtFloat }, - { AL_FORMAT_MONO_DOUBLE_EXT, UserFmtMono, UserFmtDouble }, - { AL_FORMAT_MONO_IMA4, UserFmtMono, UserFmtIMA4 }, - { AL_FORMAT_MONO_MSADPCM_SOFT, UserFmtMono, UserFmtMSADPCM }, - { AL_FORMAT_MONO_MULAW, UserFmtMono, UserFmtMulaw }, - { AL_FORMAT_MONO_ALAW_EXT, UserFmtMono, UserFmtAlaw }, + 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, UserFmtStereo, UserFmtUByte }, - { AL_FORMAT_STEREO16, UserFmtStereo, UserFmtShort }, - { AL_FORMAT_STEREO_FLOAT32, UserFmtStereo, UserFmtFloat }, - { AL_FORMAT_STEREO_DOUBLE_EXT, UserFmtStereo, UserFmtDouble }, - { AL_FORMAT_STEREO_IMA4, UserFmtStereo, UserFmtIMA4 }, - { AL_FORMAT_STEREO_MSADPCM_SOFT, UserFmtStereo, UserFmtMSADPCM }, - { AL_FORMAT_STEREO_MULAW, UserFmtStereo, UserFmtMulaw }, - { AL_FORMAT_STEREO_ALAW_EXT, UserFmtStereo, UserFmtAlaw }, + 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, UserFmtRear, UserFmtUByte }, - { AL_FORMAT_REAR16, UserFmtRear, UserFmtShort }, - { AL_FORMAT_REAR32, UserFmtRear, UserFmtFloat }, - { AL_FORMAT_REAR_MULAW, UserFmtRear, UserFmtMulaw }, + 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, UserFmtQuad, UserFmtUByte }, - { AL_FORMAT_QUAD16_LOKI, UserFmtQuad, UserFmtShort }, + FormatMap{AL_FORMAT_QUAD8_LOKI, {FmtQuad, FmtUByte}}, + FormatMap{AL_FORMAT_QUAD16_LOKI, {FmtQuad, FmtShort}}, - { AL_FORMAT_QUAD8, UserFmtQuad, UserFmtUByte }, - { AL_FORMAT_QUAD16, UserFmtQuad, UserFmtShort }, - { AL_FORMAT_QUAD32, UserFmtQuad, UserFmtFloat }, - { AL_FORMAT_QUAD_MULAW, UserFmtQuad, UserFmtMulaw }, + 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, UserFmtX51, UserFmtUByte }, - { AL_FORMAT_51CHN16, UserFmtX51, UserFmtShort }, - { AL_FORMAT_51CHN32, UserFmtX51, UserFmtFloat }, - { AL_FORMAT_51CHN_MULAW, UserFmtX51, UserFmtMulaw }, + 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, UserFmtX61, UserFmtUByte }, - { AL_FORMAT_61CHN16, UserFmtX61, UserFmtShort }, - { AL_FORMAT_61CHN32, UserFmtX61, UserFmtFloat }, - { AL_FORMAT_61CHN_MULAW, UserFmtX61, UserFmtMulaw }, + 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, UserFmtX71, UserFmtUByte }, - { AL_FORMAT_71CHN16, UserFmtX71, UserFmtShort }, - { AL_FORMAT_71CHN32, UserFmtX71, UserFmtFloat }, - { AL_FORMAT_71CHN_MULAW, UserFmtX71, UserFmtMulaw }, + 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, UserFmtBFormat2D, UserFmtUByte }, - { AL_FORMAT_BFORMAT2D_16, UserFmtBFormat2D, UserFmtShort }, - { AL_FORMAT_BFORMAT2D_FLOAT32, UserFmtBFormat2D, UserFmtFloat }, - { AL_FORMAT_BFORMAT2D_MULAW, UserFmtBFormat2D, UserFmtMulaw }, + 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, UserFmtBFormat3D, UserFmtUByte }, - { AL_FORMAT_BFORMAT3D_16, UserFmtBFormat3D, UserFmtShort }, - { AL_FORMAT_BFORMAT3D_FLOAT32, UserFmtBFormat3D, UserFmtFloat }, - { AL_FORMAT_BFORMAT3D_MULAW, UserFmtBFormat3D, UserFmtMulaw }, + 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, UserFmtUHJ2, UserFmtUByte }, - { AL_FORMAT_UHJ2CHN16_SOFT, UserFmtUHJ2, UserFmtShort }, - { AL_FORMAT_UHJ2CHN_FLOAT32_SOFT, UserFmtUHJ2, UserFmtFloat }, + 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, UserFmtUHJ3, UserFmtUByte }, - { AL_FORMAT_UHJ3CHN16_SOFT, UserFmtUHJ3, UserFmtShort }, - { AL_FORMAT_UHJ3CHN_FLOAT32_SOFT, UserFmtUHJ3, UserFmtFloat }, + 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, UserFmtUHJ4, UserFmtUByte }, - { AL_FORMAT_UHJ4CHN16_SOFT, UserFmtUHJ4, UserFmtShort }, - { AL_FORMAT_UHJ4CHN_FLOAT32_SOFT, UserFmtUHJ4, UserFmtFloat }, - }}; + 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 UNLIKELY(!context) return; - - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Generating %d buffers", n); - if UNLIKELY(n <= 0) return; +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 LIKELY(n == 1) - { - /* Special handling for the easy and normal case. */ - ALbuffer *buffer{AllocBuffer(device)}; - buffers[0] = buffer->id; - } - else - { - /* Store the allocated buffer IDs in a separate local list, to avoid - * modifying the user storage in case of failure. - */ - al::vector 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 UNLIKELY(!context) return; - - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n); - if UNLIKELY(n <= 0) return; +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 UNLIKELY(!ALBuf) - { - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", bid); - return false; - } - if UNLIKELY(ReadRef(ALBuf->ref) != 0) - { - context->setError(AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid); - return false; - } - return true; + 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 UNLIKELY(invbuf != buffers_end) 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 LIKELY(context) - { - 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()}; - if UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(size < 0) - context->setError(AL_INVALID_VALUE, "Negative storage size %d", size); - else if UNLIKELY(freq < 1) - context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); - else if UNLIKELY((flags&INVALID_STORAGE_MASK) != 0) - context->setError(AL_INVALID_VALUE, "Invalid storage flags 0x%x", - flags&INVALID_STORAGE_MASK); - else if UNLIKELY((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS)) - context->setError(AL_INVALID_VALUE, - "Declaring persistently mapped storage without read or write access"); - else - { - auto usrfmt = DecomposeUserFormat(format); - if UNLIKELY(!usrfmt) - context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); - else - { - LoadData(context.get(), albuf, freq, static_cast(size), usrfmt->channels, - usrfmt->type, static_cast(data), flags); - } - } + auto context = GetContextRef(); + if(!context) UNLIKELY return; + alBufferStorageDirectSOFT(context.get(), buffer, format, data, size, freq, 0); } -END_API_FUNC -AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return nullptr; +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 UNLIKELY(!albuf) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY((access&INVALID_MAP_FLAGS) != 0) - context->setError(AL_INVALID_VALUE, "Invalid map flags 0x%x", access&INVALID_MAP_FLAGS); - else if UNLIKELY(!(access&MAP_READ_WRITE_FLAGS)) - context->setError(AL_INVALID_VALUE, "Mapping buffer %u without read or write access", - buffer); - else - { - ALbitfieldSOFT unavailable = (albuf->Access^access) & access; - if UNLIKELY(ReadRef(albuf->ref) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) - context->setError(AL_INVALID_OPERATION, - "Mapping in-use buffer %u without persistent mapping", buffer); - else if UNLIKELY(albuf->MappedAccess != 0) - context->setError(AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer); - else if UNLIKELY((unavailable&AL_MAP_READ_BIT_SOFT)) - context->setError(AL_INVALID_VALUE, - "Mapping buffer %u for reading without read access", buffer); - else if UNLIKELY((unavailable&AL_MAP_WRITE_BIT_SOFT)) - context->setError(AL_INVALID_VALUE, - "Mapping buffer %u for writing without write access", buffer); - else if UNLIKELY((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) - context->setError(AL_INVALID_VALUE, - "Mapping buffer %u persistently without persistent access", buffer); - else if UNLIKELY(offset < 0 || length <= 0 - || static_cast(offset) >= albuf->OriginalSize - || static_cast(length) > albuf->OriginalSize - static_cast(offset)) - context->setError(AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u", - offset, length, buffer); - else - { - void *retval{albuf->mData.data() + offset}; - albuf->MappedAccess = access; - albuf->MappedOffset = offset; - albuf->MappedSize = length; - return retval; - } - } - - return nullptr; -} -END_API_FUNC - -AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(albuf->MappedAccess == 0) - context->setError(AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer); - else - { - albuf->MappedAccess = 0; - albuf->MappedOffset = 0; - albuf->MappedSize = 0; - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) - context->setError(AL_INVALID_OPERATION, "Flushing buffer %u while not mapped for writing", - buffer); - else if UNLIKELY(offset < albuf->MappedOffset || length <= 0 - || offset >= albuf->MappedOffset+albuf->MappedSize - || length > albuf->MappedOffset+albuf->MappedSize-offset) - context->setError(AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u", offset, - length, buffer); - else - { - /* FIXME: Need to use some method of double-buffering for the mixer and - * app to hold separate memory, which can be safely transfered - * asynchronously. Currently we just say the app shouldn't write where - * OpenAL's reading, and hope for the best... - */ - std::atomic_thread_fence(std::memory_order_seq_cst); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) - { - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - return; - } + 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 UNLIKELY(!usrfmt) + if(!usrfmt) + throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + + 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()); +} + +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 buflock{device->BufferLock}; + + 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; +} + +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 buflock{device->BufferLock}; + + 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()); +} + +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 buflock{device->BufferLock}; + + 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) + 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()); +} + +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 buflock{device->BufferLock}; + + 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) + 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) + 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) + 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{ + (albuf->mType == FmtIMA4) ? ((align-1)/2 + 4) * num_chans : + (albuf->mType == FmtMSADPCM) ? ((align-2)/2 + 7) * num_chans : + (align * albuf->bytesFromFmt() * num_chans)}; + + 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, "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) + 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}; + + std::memcpy(albuf->mData.data()+offset, data, static_cast(length)); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +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 buflock{device->BufferLock}; + + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + + switch(param) { - context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); + } + 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()); +} + +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 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-float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +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 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, 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 buflock{device->BufferLock}; + + 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) + 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) + 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(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)) + { + 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; } - ALuint unpack_align{albuf->UnpackAlign}; - ALuint align{SanitizeAlignment(usrfmt->type, unpack_align)}; - if UNLIKELY(align < 1) - context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u", unpack_align); - else if UNLIKELY(long{usrfmt->channels} != long{albuf->mChannels} - || usrfmt->type != albuf->OriginalType) - context->setError(AL_INVALID_ENUM, "Unpacking data with mismatched format"); - else if UNLIKELY(align != albuf->OriginalAlign) - context->setError(AL_INVALID_VALUE, - "Unpacking data with alignment %u does not match original alignment %u", align, - albuf->OriginalAlign); - else if UNLIKELY(albuf->isBFormat() && albuf->UnpackAmbiOrder != albuf->mAmbiOrder) - context->setError(AL_INVALID_VALUE, "Unpacking data with mismatched ambisonic order"); - else if UNLIKELY(albuf->MappedAccess != 0) - context->setError(AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u", buffer); - else - { - ALuint num_chans{albuf->channelsFromFmt()}; - ALuint frame_size{num_chans * albuf->bytesFromFmt()}; - ALuint byte_align{ - (albuf->OriginalType == UserFmtIMA4) ? ((align-1)/2 + 4) * num_chans : - (albuf->OriginalType == UserFmtMSADPCM) ? ((align-2)/2 + 7) * num_chans : - (align * frame_size) - }; - - if UNLIKELY(offset < 0 || length < 0 || static_cast(offset) > albuf->OriginalSize - || static_cast(length) > albuf->OriginalSize-static_cast(offset)) - context->setError(AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u", - offset, length, buffer); - else if UNLIKELY((static_cast(offset)%byte_align) != 0) - context->setError(AL_INVALID_VALUE, - "Sub-range offset %d is not a multiple of frame size %d (%d unpack alignment)", - offset, byte_align, align); - else if UNLIKELY((static_cast(length)%byte_align) != 0) - context->setError(AL_INVALID_VALUE, - "Sub-range length %d is not a multiple of frame size %d (%d unpack alignment)", - length, byte_align, align); - else - { - /* offset -> byte offset, length -> sample count */ - size_t byteoff{static_cast(offset)/byte_align * align * frame_size}; - size_t samplen{static_cast(length)/byte_align * align}; - - void *dst = albuf->mData.data() + byteoff; - if(usrfmt->type == UserFmtIMA4 && albuf->mType == FmtShort) - Convert_int16_ima4(static_cast(dst), static_cast(data), - num_chans, samplen, align); - else if(usrfmt->type == UserFmtMSADPCM && albuf->mType == FmtShort) - Convert_int16_msadpcm(static_cast(dst), - static_cast(data), num_chans, samplen, align); - else - { - assert(long{usrfmt->type} == long{albuf->mType}); - memcpy(dst, data, size_t{samplen} * frame_size); - } - } - } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param}; } -END_API_FUNC - - -AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint /*buffer*/, ALuint /*samplerate*/, - ALenum /*internalformat*/, ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, - const ALvoid* /*data*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - context->setError(AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported"); +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 UNLIKELY(!context) return; - - context->setError(AL_INVALID_OPERATION, "alBufferSubSamplesSOFT not supported"); -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/, - ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, ALvoid* /*data*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - context->setError(AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported"); -} -END_API_FUNC - -AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return AL_FALSE; - - context->setError(AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported"); - return AL_FALSE; -} -END_API_FUNC - - -AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat /*value*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; +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 _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) - 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 3-integer property 0x%04x", param}; } -END_API_FUNC - -AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, - ALfloat /*value1*/, ALfloat /*value2*/, ALfloat /*value3*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param); - } +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 UNLIKELY(!context) return; +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"}; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param); - } -} -END_API_FUNC - - -AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) + switch(param) { case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: - if UNLIKELY(value < 0) - context->setError(AL_INVALID_VALUE, "Invalid unpack block alignment %d", value); - else - albuf->UnpackAlign = static_cast(value); - break; - case AL_PACK_BLOCK_ALIGNMENT_SOFT: - if UNLIKELY(value < 0) - context->setError(AL_INVALID_VALUE, "Invalid pack block alignment %d", value); - else - albuf->PackAlign = static_cast(value); - break; - case AL_AMBISONIC_LAYOUT_SOFT: - if UNLIKELY(ReadRef(albuf->ref) != 0) - context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's ambisonic layout", - buffer); - else if UNLIKELY(value != AL_FUMA_SOFT && value != AL_ACN_SOFT) - context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic layout %04x", value); - else - albuf->mAmbiLayout = AmbiLayoutFromEnum(value).value(); - break; - case AL_AMBISONIC_SCALING_SOFT: - if UNLIKELY(ReadRef(albuf->ref) != 0) - context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's ambisonic scaling", - buffer); - else if UNLIKELY(value != AL_FUMA_SOFT && value != AL_SN3D_SOFT && value != AL_N3D_SOFT) - context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic scaling %04x", value); - else - albuf->mAmbiScaling = AmbiScalingFromEnum(value).value(); - break; - case AL_UNPACK_AMBISONIC_ORDER_SOFT: - if UNLIKELY(value < 1 || value > 14) - 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); + alBufferiDirect(context, buffer, param, *values); + return; } -} -END_API_FUNC - -AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, - ALint /*value1*/, ALint /*value2*/, ALint /*value3*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param); - } -} -END_API_FUNC + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; -AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values) -START_API_FUNC -{ - if(values) - { - switch(param) - { - case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: - case AL_PACK_BLOCK_ALIGNMENT_SOFT: - case AL_AMBISONIC_LAYOUT_SOFT: - case AL_AMBISONIC_SCALING_SOFT: - case AL_UNPACK_AMBISONIC_ORDER_SOFT: - alBufferi(buffer, param, values[0]); - return; - } - } - - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + switch(param) { case AL_LOOP_POINTS_SOFT: - if UNLIKELY(ReadRef(albuf->ref) != 0) - context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's loop points", - buffer); - else if UNLIKELY(values[0] < 0 || values[0] >= values[1] - || static_cast(values[1]) > albuf->mSampleLen) - 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 UNLIKELY(!context) 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 UNLIKELY(!albuf) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param); - } -} -END_API_FUNC + 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"}; -AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value1 || !value2 || !value3) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values) -START_API_FUNC -{ switch(param) { case AL_SEC_LENGTH_SOFT: - alGetBufferf(buffer, param, values); + *value = (albuf->mSampleRate < 1) ? 0.0f : + (static_cast(albuf->mSampleLen) / static_cast(albuf->mSampleRate)); return; } - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param); - } + 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 UNLIKELY(!context) 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 UNLIKELY(!albuf) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + 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 = static_cast(albuf->bytesFromFmt() * 8); - break; + *value = (albuf->mType == FmtIMA4 || albuf->mType == FmtMSADPCM) ? 4 + : static_cast(albuf->bytesFromFmt() * 8); + return; case AL_CHANNELS: *value = static_cast(albuf->channelsFromFmt()); - break; + return; case AL_SIZE: - *value = static_cast(albuf->mSampleLen * albuf->frameSizeFromFmt()); - break; + *value = albuf->mCallback ? 0 : static_cast(albuf->mData.size()); + return; + + case AL_BYTE_LENGTH_SOFT: + *value = static_cast(albuf->mSampleLen / albuf->mBlockAlign + * albuf->blockSizeFromFmt()); + return; + + case AL_SAMPLE_LENGTH_SOFT: + *value = static_cast(albuf->mSampleLen); + 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 UNLIKELY(!context) 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 UNLIKELY(LookupBuffer(device, buffer) == nullptr) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value1 || !value2 || !value3) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param); - } -} -END_API_FUNC + 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: @@ -1555,308 +1325,342 @@ 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 UNLIKELY(!context) return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + 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 UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if UNLIKELY(!albuf) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(freq < 1) - context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); - else if UNLIKELY(callback == nullptr) - context->setError(AL_INVALID_VALUE, "NULL callback"); - else - { - auto usrfmt = DecomposeUserFormat(format); - if UNLIKELY(!usrfmt) - 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 UNLIKELY(!context) 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 UNLIKELY(!albuf) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + 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 UNLIKELY(!context) 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 UNLIKELY(LookupBuffer(device, buffer) == nullptr) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!value1 || !value2 || !value3) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-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; } - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - if UNLIKELY(LookupBuffer(device, buffer) == nullptr) - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + 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) { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer pointer-vector property 0x%04x", 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; + + 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"}; - switch(value) - { - case AL_STORAGE_AUTOMATIC: - case AL_STORAGE_HARDWARE: - case AL_STORAGE_ACCESSIBLE: - break; - - default: - context->setError(AL_INVALID_ENUM, EAX_PREFIX "Unsupported X-RAM mode 0x%x", value); - return ALC_FALSE; - } + const auto storage = EaxStorageFromEnum(value); + if(!storage) + 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 buffer = buffers[i]; - if(buffer == AL_NONE) - continue; + const auto bufid = *buffers; + if(bufid == AL_NONE) + return AL_TRUE; - const auto al_buffer = LookupBuffer(device, buffer); - if (!al_buffer) - { - ERR(EAX_PREFIX "Invalid buffer ID %u.\n", buffer); - return ALC_FALSE; - } + const auto buffer = LookupBuffer(device, bufid); + 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(value == AL_STORAGE_HARDWARE && !al_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(unlikely(std::numeric_limits::max()-al_buffer->OriginalSize < total_needed)) - { - context->setError(AL_OUT_OF_MEMORY, EAX_PREFIX "Buffer size overflow (%u + %zu)\n", - al_buffer->OriginalSize, total_needed); - return ALC_FALSE; - } - total_needed += al_buffer->OriginalSize; + if(!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_INVALID_ENUM, 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 buffer = buffers[i]; - if(buffer == AL_NONE) + if(bufid == AL_NONE) continue; - const auto al_buffer = LookupBuffer(device, buffer); - assert(al_buffer); + const auto buffer = LookupBuffer(device, bufid); + if(!buffer) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bufid}; - if(value != AL_STORAGE_ACCESSIBLE) - eax_x_ram_apply(*device, *al_buffer); + /* 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 - eax_x_ram_clear(*device, *al_buffer); - al_buffer->eax_x_ram_mode = value; + eax_x_ram_clear(*device, *buffer); + buffer->eax_x_ram_mode = *storage; } 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 al_buffer->eax_x_ram_mode; - -#undef EAX_PREFIX + return EnumFromEaxStorage(al_buffer->eax_x_ram_mode); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "[EAXGetBufferMode] %s", e.what()); + return AL_NONE; } -END_API_FUNC #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/buffer.h b/libs/openal-soft/al/buffer.h index b3a0f0d8..f7661ec8 100644 --- a/libs/openal-soft/al/buffer.h +++ b/libs/openal-soft/al/buffer.h @@ -1,57 +1,37 @@ #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, + Hardware +}; #endif // ALSOFT_EAX -/* User formats */ -enum UserFmtType : unsigned char { - UserFmtUByte = FmtUByte, - UserFmtShort = FmtShort, - UserFmtFloat = FmtFloat, - UserFmtMulaw = FmtMulaw, - UserFmtAlaw = FmtAlaw, - UserFmtDouble = FmtDouble, - - UserFmtIMA4 = 128, - UserFmtMSADPCM, -}; -enum UserFmtChannels : unsigned char { - UserFmtMono = FmtMono, - UserFmtStereo = FmtStereo, - UserFmtRear = FmtRear, - UserFmtQuad = FmtQuad, - UserFmtX51 = FmtX51, - UserFmtX61 = FmtX61, - UserFmtX71 = FmtX71, - UserFmtBFormat2D = FmtBFormat2D, - UserFmtBFormat3D = FmtBFormat3D, - UserFmtUHJ2 = FmtUHJ2, - UserFmtUHJ3 = FmtUHJ3, - UserFmtUHJ4 = FmtUHJ4, -}; - struct ALbuffer : public BufferStorage { ALbitfieldSOFT Access{0u}; - al::vector mData; + al::vector mDataStorage; - UserFmtType OriginalType{UserFmtShort}; ALuint OriginalSize{0}; - ALuint OriginalAlign{0}; ALuint UnpackAlign{0}; ALuint PackAlign{0}; @@ -65,17 +45,34 @@ struct ALbuffer : public BufferStorage { ALuint mLoopEnd{0u}; /* Number of times buffer was attached to a source (deletion can only occur when 0) */ - RefCount ref{0u}; + std::atomic ref{0u}; /* Self ID */ ALuint id{0}; - DISABLE_ALLOC() + static void SetName(ALCcontext *context, ALuint id, std::string_view name); + + DISABLE_ALLOC #ifdef ALSOFT_EAX - ALenum eax_x_ram_mode{AL_STORAGE_AUTOMATIC}; + EaxStorage eax_x_ram_mode{EaxStorage::Automatic}; bool eax_x_ram_is_hardware{}; #endif // ALSOFT_EAX }; +struct BufferSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> 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/libs/openal-soft/al/debug.cpp b/libs/openal-soft/al/debug.cpp new file mode 100644 index 00000000..39318144 --- /dev/null +++ b/libs/openal-soft/al/debug.cpp @@ -0,0 +1,620 @@ +#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}; + + if(identifier == AL_SOURCE_EXT) + return ALsource::SetName(context, name, objname); + if(identifier == AL_BUFFER) + return ALbuffer::SetName(context, name, objname); + if(identifier == AL_FILTER_EXT) + return ALfilter::SetName(context, name, objname); + if(identifier == AL_EFFECT_EXT) + return ALeffect::SetName(context, name, objname); + if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT) + return ALeffectslot::SetName(context, name, objname); + + throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label) +FORCE_ALIGN void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, + ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) noexcept +try { + if(bufSize < 0) + throw al::context_error{AL_INVALID_VALUE, "Negative label bufSize"}; + + if(!label && !length) + throw al::context_error{AL_INVALID_VALUE, "Null length and label"}; + if(label && bufSize == 0) + throw al::context_error{AL_INVALID_VALUE, "Zero label bufSize"}; + + const auto labelOut = al::span{label, label ? static_cast(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/libs/openal-soft/al/debug.h b/libs/openal-soft/al/debug.h new file mode 100644 index 00000000..d1792adb --- /dev/null +++ b/libs/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/libs/openal-soft/al/direct_defs.h b/libs/openal-soft/al/direct_defs.h new file mode 100644 index 00000000..4119182f --- /dev/null +++ b/libs/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/libs/openal-soft/al/eax_api.cpp b/libs/openal-soft/al/eax/api.cpp similarity index 67% rename from libs/openal-soft/al/eax_api.cpp rename to libs/openal-soft/al/eax/api.cpp index 6b1f7fcf..f0809df1 100644 --- a/libs/openal-soft/al/eax_api.cpp +++ b/libs/openal-soft/al/eax/api.cpp @@ -9,7 +9,7 @@ #include -#include "al/eax_api.h" +#include "api.h" const GUID DSPROPSETID_EAX_ReverbProperties = @@ -269,74 +269,15 @@ const GUID EAX_RINGMODULATOR_EFFECT = }; -bool operator==( - const EAX40CONTEXTPROPERTIES& lhs, - const EAX40CONTEXTPROPERTIES& rhs) noexcept -{ - return - lhs.guidPrimaryFXSlotID == rhs.guidPrimaryFXSlotID && - lhs.flDistanceFactor == rhs.flDistanceFactor && - lhs.flAirAbsorptionHF == rhs.flAirAbsorptionHF && - lhs.flHFReference == rhs.flHFReference; -} +const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX40_FXSlot0; +const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX50_FXSlot0; -bool operator==( - const EAX50CONTEXTPROPERTIES& lhs, - const EAX50CONTEXTPROPERTIES& rhs) noexcept -{ - return - static_cast(lhs) == static_cast(rhs) && - lhs.flMacroFXFactor == rhs.flMacroFXFactor; -} - - -const GUID EAXCONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX40_FXSlot0; - -bool operator==( - const EAX40FXSLOTPROPERTIES& lhs, - const EAX40FXSLOTPROPERTIES& rhs) noexcept -{ - return - lhs.guidLoadEffect == rhs.guidLoadEffect && - lhs.lVolume == rhs.lVolume && - lhs.lLock == rhs.lLock && - lhs.ulFlags == rhs.ulFlags; -} - -bool operator==( - const EAX50FXSLOTPROPERTIES& lhs, - const EAX50FXSLOTPROPERTIES& rhs) noexcept -{ - return - static_cast(lhs) == static_cast(rhs) && - lhs.lOcclusion == rhs.lOcclusion && - lhs.flOcclusionLFRatio == rhs.flOcclusionLFRatio; -} - -const EAX50ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS +const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID = EAX40ACTIVEFXSLOTS {{ EAX_NULL_GUID, EAXPROPERTYID_EAX40_FXSlot0, }}; -bool operator==( - const EAX50ACTIVEFXSLOTS& lhs, - const EAX50ACTIVEFXSLOTS& rhs) noexcept -{ - return std::equal( - std::cbegin(lhs.guidActiveFXSlots), - std::cend(lhs.guidActiveFXSlots), - std::begin(rhs.guidActiveFXSlots)); -} - -bool operator!=( - const EAX50ACTIVEFXSLOTS& lhs, - const EAX50ACTIVEFXSLOTS& rhs) noexcept -{ - return !(lhs == rhs); -} - - const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS {{ EAX_NULL_GUID, @@ -354,44 +295,569 @@ const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOT EAX_NULL_GUID, }}; -bool operator==( - const EAXREVERBPROPERTIES& lhs, - const EAXREVERBPROPERTIES& rhs) noexcept -{ - return - lhs.ulEnvironment == rhs.ulEnvironment && - lhs.flEnvironmentSize == rhs.flEnvironmentSize && - lhs.flEnvironmentDiffusion == rhs.flEnvironmentDiffusion && - lhs.lRoom == rhs.lRoom && - lhs.lRoomHF == rhs.lRoomHF && - lhs.lRoomLF == rhs.lRoomLF && - lhs.flDecayTime == rhs.flDecayTime && - lhs.flDecayHFRatio == rhs.flDecayHFRatio && - lhs.flDecayLFRatio == rhs.flDecayLFRatio && - lhs.lReflections == rhs.lReflections && - lhs.flReflectionsDelay == rhs.flReflectionsDelay && - lhs.vReflectionsPan == rhs.vReflectionsPan && - lhs.lReverb == rhs.lReverb && - lhs.flReverbDelay == rhs.flReverbDelay && - lhs.vReverbPan == rhs.vReverbPan && - lhs.flEchoTime == rhs.flEchoTime && - lhs.flEchoDepth == rhs.flEchoDepth && - lhs.flModulationTime == rhs.flModulationTime && - lhs.flModulationDepth == rhs.flModulationDepth && - lhs.flAirAbsorptionHF == rhs.flAirAbsorptionHF && - lhs.flHFReference == rhs.flHFReference && - lhs.flLFReference == rhs.flLFReference && - lhs.flRoomRolloffFactor == rhs.flRoomRolloffFactor && - lhs.ulFlags == rhs.ulFlags; -} -bool operator!=( - const EAXREVERBPROPERTIES& lhs, - const EAXREVERBPROPERTIES& rhs) noexcept -{ - return !(lhs == rhs); -} +// EAX1 ===================================================================== +namespace { +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_GENERIC = {EAX_ENVIRONMENT_GENERIC, 0.5F, 1.493F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PADDEDCELL = {EAX_ENVIRONMENT_PADDEDCELL, 0.25F, 0.1F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ROOM = {EAX_ENVIRONMENT_ROOM, 0.417F, 0.4F, 0.666F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_BATHROOM = {EAX_ENVIRONMENT_BATHROOM, 0.653F, 1.499F, 0.166F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_LIVINGROOM = {EAX_ENVIRONMENT_LIVINGROOM, 0.208F, 0.478F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONEROOM = {EAX_ENVIRONMENT_STONEROOM, 0.5F, 2.309F, 0.888F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_AUDITORIUM = {EAX_ENVIRONMENT_AUDITORIUM, 0.403F, 4.279F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CONCERTHALL = {EAX_ENVIRONMENT_CONCERTHALL, 0.5F, 3.961F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CAVE = {EAX_ENVIRONMENT_CAVE, 0.5F, 2.886F, 1.304F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ARENA = {EAX_ENVIRONMENT_ARENA, 0.361F, 7.284F, 0.332F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HANGAR = {EAX_ENVIRONMENT_HANGAR, 0.5F, 10.0F, 0.3F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CARPETTEDHALLWAY = {EAX_ENVIRONMENT_CARPETEDHALLWAY, 0.153F, 0.259F, 2.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HALLWAY = {EAX_ENVIRONMENT_HALLWAY, 0.361F, 1.493F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONECORRIDOR = {EAX_ENVIRONMENT_STONECORRIDOR, 0.444F, 2.697F, 0.638F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ALLEY = {EAX_ENVIRONMENT_ALLEY, 0.25F, 1.752F, 0.776F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_FOREST = {EAX_ENVIRONMENT_FOREST, 0.111F, 3.145F, 0.472F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CITY = {EAX_ENVIRONMENT_CITY, 0.111F, 2.767F, 0.224F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_MOUNTAINS = {EAX_ENVIRONMENT_MOUNTAINS, 0.194F, 7.841F, 0.472F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_QUARRY = {EAX_ENVIRONMENT_QUARRY, 1.0F, 1.499F, 0.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PLAIN = {EAX_ENVIRONMENT_PLAIN, 0.097F, 2.767F, 0.224F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PARKINGLOT = {EAX_ENVIRONMENT_PARKINGLOT, 0.208F, 1.652F, 1.5F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_SEWERPIPE = {EAX_ENVIRONMENT_SEWERPIPE, 0.652F, 2.886F, 0.25F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_UNDERWATER = {EAX_ENVIRONMENT_UNDERWATER, 1.0F, 1.499F, 0.0F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DRUGGED = {EAX_ENVIRONMENT_DRUGGED, 0.875F, 8.392F, 1.388F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DIZZY = {EAX_ENVIRONMENT_DIZZY, 0.139F, 17.234F, 0.666F}; +constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PSYCHOTIC = {EAX_ENVIRONMENT_PSYCHOTIC, 0.486F, 7.563F, 0.806F}; +} // namespace + +const Eax1ReverbPresets EAX1REVERB_PRESETS{{ + EAX1REVERB_PRESET_GENERIC, + EAX1REVERB_PRESET_PADDEDCELL, + EAX1REVERB_PRESET_ROOM, + EAX1REVERB_PRESET_BATHROOM, + EAX1REVERB_PRESET_LIVINGROOM, + EAX1REVERB_PRESET_STONEROOM, + EAX1REVERB_PRESET_AUDITORIUM, + EAX1REVERB_PRESET_CONCERTHALL, + EAX1REVERB_PRESET_CAVE, + EAX1REVERB_PRESET_ARENA, + EAX1REVERB_PRESET_HANGAR, + EAX1REVERB_PRESET_CARPETTEDHALLWAY, + EAX1REVERB_PRESET_HALLWAY, + EAX1REVERB_PRESET_STONECORRIDOR, + EAX1REVERB_PRESET_ALLEY, + EAX1REVERB_PRESET_FOREST, + EAX1REVERB_PRESET_CITY, + EAX1REVERB_PRESET_MOUNTAINS, + EAX1REVERB_PRESET_QUARRY, + EAX1REVERB_PRESET_PLAIN, + EAX1REVERB_PRESET_PARKINGLOT, + EAX1REVERB_PRESET_SEWERPIPE, + EAX1REVERB_PRESET_UNDERWATER, + EAX1REVERB_PRESET_DRUGGED, + EAX1REVERB_PRESET_DIZZY, + EAX1REVERB_PRESET_PSYCHOTIC, +}}; + +// EAX2 ===================================================================== + +namespace { + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_GENERIC{ + EAX2LISTENER_DEFAULTROOM, + EAX2LISTENER_DEFAULTROOMHF, + EAX2LISTENER_DEFAULTROOMROLLOFFFACTOR, + EAX2LISTENER_DEFAULTDECAYTIME, + EAX2LISTENER_DEFAULTDECAYHFRATIO, + EAX2LISTENER_DEFAULTREFLECTIONS, + EAX2LISTENER_DEFAULTREFLECTIONSDELAY, + EAX2LISTENER_DEFAULTREVERB, + EAX2LISTENER_DEFAULTREVERBDELAY, + EAX2LISTENER_DEFAULTENVIRONMENT, + EAX2LISTENER_DEFAULTENVIRONMENTSIZE, + EAX2LISTENER_DEFAULTENVIRONMENTDIFFUSION, + EAX2LISTENER_DEFAULTAIRABSORPTIONHF, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PADDEDCELL{ + -1'000L, + -6'000L, + 0.0F, + 0.17F, + 0.1F, + -1'204L, + 0.001F, + 207L, + 0.002F, + EAX2_ENVIRONMENT_PADDEDCELL, + 1.4F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_ROOM{ + -1'000L, + -454L, + 0.0F, + 0.4F, + 0.83F, + -1'646L, + 0.002F, + 53L, + 0.003F, + EAX2_ENVIRONMENT_ROOM, + 1.9F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_BATHROOM{ + -1'000L, + -1'200L, + 0.0F, + 1.49F, + 0.54F, + -370L, + 0.007F, + 1'030L, + 0.011F, + EAX2_ENVIRONMENT_BATHROOM, + 1.4F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_LIVINGROOM{ + -1'000L, + -6'000L, + 0.0F, + 0.5F, + 0.1F, + -1'376L, + 0.003F, + -1'104L, + 0.004F, + EAX2_ENVIRONMENT_LIVINGROOM, + 2.5F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_STONEROOM{ + -1'000L, + -300L, + 0.0F, + 2.31F, + 0.64F, + -711L, + 0.012F, + 83L, + 0.017F, + EAX2_ENVIRONMENT_STONEROOM, + 11.6F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_AUDITORIUM{ + -1'000L, + -476L, + 0.0F, + 4.32F, + 0.59F, + -789L, + 0.02F, + -289L, + 0.03F, + EAX2_ENVIRONMENT_AUDITORIUM, + 21.6F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CONCERTHALL{ + -1'000L, + -500L, + 0.0F, + 3.92F, + 0.7F, + -1'230L, + 0.02F, + -2L, + 0.029F, + EAX2_ENVIRONMENT_CONCERTHALL, + 19.6F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CAVE{ + -1'000L, + 0L, + 0.0F, + 2.91F, + 1.3F, + -602L, + 0.015F, + -302L, + 0.022F, + EAX2_ENVIRONMENT_CAVE, + 14.6F, + 1.0F, + -5.0F, + EAX2LISTENERFLAGS_DECAYTIMESCALE | + EAX2LISTENERFLAGS_REFLECTIONSSCALE | + EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | + EAX2LISTENERFLAGS_REVERBSCALE | + EAX2LISTENERFLAGS_REVERBDELAYSCALE, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_ARENA{ + -1'000L, + -698L, + 0.0F, + 7.24F, + 0.33F, + -1'166L, + 0.02F, + 16L, + 0.03F, + EAX2_ENVIRONMENT_ARENA, + 36.2F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_HANGAR{ + -1'000L, + -1'000L, + 0.0F, + 10.05F, + 0.23F, + -602L, + 0.02F, + 198L, + 0.03F, + EAX2_ENVIRONMENT_HANGAR, + 50.3F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CARPETTEDHALLWAY{ + -1'000L, + -4'000L, + 0.0F, + 0.3F, + 0.1F, + -1'831L, + 0.002F, + -1'630L, + 0.03F, + EAX2_ENVIRONMENT_CARPETEDHALLWAY, + 1.9F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_HALLWAY{ + -1'000L, + -300L, + 0.0F, + 1.49F, + 0.59F, + -1'219L, + 0.007F, + 441L, + 0.011F, + EAX2_ENVIRONMENT_HALLWAY, + 1.8F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_STONECORRIDOR{ + -1'000L, + -237L, + 0.0F, + 2.7F, + 0.79F, + -1'214L, + 0.013F, + 395L, + 0.02F, + EAX2_ENVIRONMENT_STONECORRIDOR, + 13.5F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_ALLEY{ + -1'000L, + -270L, + 0.0F, + 1.49F, + 0.86F, + -1'204L, + 0.007F, + -4L, + 0.011F, + EAX2_ENVIRONMENT_ALLEY, + 7.5F, + 0.3F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_FOREST{ + -1'000L, + -3'300L, + 0.0F, + 1.49F, + 0.54F, + -2'560L, + 0.162F, + -229L, + 0.088F, + EAX2_ENVIRONMENT_FOREST, + 38.0F, + 0.3F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CITY{ + -1'000L, + -800L, + 0.0F, + 1.49F, + 0.67F, + -2'273L, + 0.007F, + -1'691L, + 0.011F, + EAX2_ENVIRONMENT_CITY, + 7.5F, + 0.5F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_MOUNTAINS{ + -1'000L, + -2'500L, + 0.0F, + 1.49F, + 0.21F, + -2'780L, + 0.3F, + -1'434L, + 0.1F, + EAX2_ENVIRONMENT_MOUNTAINS, + 100.0F, + 0.27F, + -5.0F, + EAX2LISTENERFLAGS_DECAYTIMESCALE | + EAX2LISTENERFLAGS_REFLECTIONSSCALE | + EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | + EAX2LISTENERFLAGS_REVERBSCALE | + EAX2LISTENERFLAGS_REVERBDELAYSCALE, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_QUARRY{ + -1'000L, + -1'000L, + 0.0F, + 1.49F, + 0.83F, + -10'000L, + 0.061F, + 500L, + 0.025F, + EAX2_ENVIRONMENT_QUARRY, + 17.5F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PLAIN{ + -1'000L, + -2'000L, + 0.0F, + 1.49F, + 0.5F, + -2'466L, + 0.179F, + -1'926L, + 0.1F, + EAX2_ENVIRONMENT_PLAIN, + 42.5F, + 0.21F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PARKINGLOT{ + -1'000L, + 0L, + 0.0F, + 1.65F, + 1.5F, + -1'363L, + 0.008F, + -1'153L, + 0.012F, + EAX2_ENVIRONMENT_PARKINGLOT, + 8.3F, + 1.0F, + -5.0F, + EAX2LISTENERFLAGS_DECAYTIMESCALE | + EAX2LISTENERFLAGS_REFLECTIONSSCALE | + EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | + EAX2LISTENERFLAGS_REVERBSCALE | + EAX2LISTENERFLAGS_REVERBDELAYSCALE, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_SEWERPIPE{ + -1'000L, + -1'000L, + 0.0F, + 2.81F, + 0.14F, + 429L, + 0.014F, + 1'023L, + 0.021F, + EAX2_ENVIRONMENT_SEWERPIPE, + 1.7F, + 0.8F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_UNDERWATER{ + -1'000L, + -4'000L, + 0.0F, + 1.49F, + 0.1F, + -449L, + 0.007F, + 1'700L, + 0.011F, + EAX2_ENVIRONMENT_UNDERWATER, + 1.8F, + 1.0F, + -5.0F, + EAX2LISTENER_DEFAULTFLAGS, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_DRUGGED{ + -1'000L, + 0L, + 0.0F, + 8.39F, + 1.39F, + -115L, + 0.002F, + 985L, + 0.03F, + EAX2_ENVIRONMENT_DRUGGED, + 1.9F, + 0.5F, + -5.0F, + EAX2LISTENERFLAGS_DECAYTIMESCALE | + EAX2LISTENERFLAGS_REFLECTIONSSCALE | + EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | + EAX2LISTENERFLAGS_REVERBSCALE | + EAX2LISTENERFLAGS_REVERBDELAYSCALE, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_DIZZY{ + -1'000L, + -400L, + 0.0F, + 17.23F, + 0.56F, + -1'713L, + 0.02F, + -613L, + 0.03F, + EAX2_ENVIRONMENT_DIZZY, + 1.8F, + 0.6F, + -5.0F, + EAX2LISTENERFLAGS_DECAYTIMESCALE | + EAX2LISTENERFLAGS_REFLECTIONSSCALE | + EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | + EAX2LISTENERFLAGS_REVERBSCALE | + EAX2LISTENERFLAGS_REVERBDELAYSCALE, +}; + +constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PSYCHOTIC{ + -1'000L, + -151L, + 0.0F, + 7.56F, + 0.91F, + -626L, + 0.02F, + 774L, + 0.03F, + EAX2_ENVIRONMENT_PSYCHOTIC, + 1.0F, + 0.5F, + -5.0F, + EAX2LISTENERFLAGS_DECAYTIMESCALE | + EAX2LISTENERFLAGS_REFLECTIONSSCALE | + EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | + EAX2LISTENERFLAGS_REVERBSCALE | + EAX2LISTENERFLAGS_REVERBDELAYSCALE, +}; + +} // namespace + +const Eax2ReverbPresets EAX2REVERB_PRESETS{ + EAX2REVERB_PRESET_GENERIC, + EAX2REVERB_PRESET_PADDEDCELL, + EAX2REVERB_PRESET_ROOM, + EAX2REVERB_PRESET_BATHROOM, + EAX2REVERB_PRESET_LIVINGROOM, + EAX2REVERB_PRESET_STONEROOM, + EAX2REVERB_PRESET_AUDITORIUM, + EAX2REVERB_PRESET_CONCERTHALL, + EAX2REVERB_PRESET_CAVE, + EAX2REVERB_PRESET_ARENA, + EAX2REVERB_PRESET_HANGAR, + EAX2REVERB_PRESET_CARPETTEDHALLWAY, + EAX2REVERB_PRESET_HALLWAY, + EAX2REVERB_PRESET_STONECORRIDOR, + EAX2REVERB_PRESET_ALLEY, + EAX2REVERB_PRESET_FOREST, + EAX2REVERB_PRESET_CITY, + EAX2REVERB_PRESET_MOUNTAINS, + EAX2REVERB_PRESET_QUARRY, + EAX2REVERB_PRESET_PLAIN, + EAX2REVERB_PRESET_PARKINGLOT, + EAX2REVERB_PRESET_SEWERPIPE, + EAX2REVERB_PRESET_UNDERWATER, + EAX2REVERB_PRESET_DRUGGED, + EAX2REVERB_PRESET_DIZZY, + EAX2REVERB_PRESET_PSYCHOTIC, +}; + +// EAX3+ ==================================================================== namespace { @@ -1153,61 +1619,3 @@ const EaxReverbPresets EAXREVERB_PRESETS{{ EAXREVERB_PRESET_DIZZY, EAXREVERB_PRESET_PSYCHOTIC, }}; - -namespace { -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_GENERIC = {EAX_ENVIRONMENT_GENERIC, 0.5F, 1.493F, 0.5F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PADDEDCELL = {EAX_ENVIRONMENT_PADDEDCELL, 0.25F, 0.1F, 0.0F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ROOM = {EAX_ENVIRONMENT_ROOM, 0.417F, 0.4F, 0.666F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_BATHROOM = {EAX_ENVIRONMENT_BATHROOM, 0.653F, 1.499F, 0.166F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_LIVINGROOM = {EAX_ENVIRONMENT_LIVINGROOM, 0.208F, 0.478F, 0.0F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONEROOM = {EAX_ENVIRONMENT_STONEROOM, 0.5F, 2.309F, 0.888F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_AUDITORIUM = {EAX_ENVIRONMENT_AUDITORIUM, 0.403F, 4.279F, 0.5F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CONCERTHALL = {EAX_ENVIRONMENT_CONCERTHALL, 0.5F, 3.961F, 0.5F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CAVE = {EAX_ENVIRONMENT_CAVE, 0.5F, 2.886F, 1.304F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ARENA = {EAX_ENVIRONMENT_ARENA, 0.361F, 7.284F, 0.332F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HANGAR = {EAX_ENVIRONMENT_HANGAR, 0.5F, 10.0F, 0.3F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CARPETTEDHALLWAY = {EAX_ENVIRONMENT_CARPETEDHALLWAY, 0.153F, 0.259F, 2.0F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HALLWAY = {EAX_ENVIRONMENT_HALLWAY, 0.361F, 1.493F, 0.0F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONECORRIDOR = {EAX_ENVIRONMENT_STONECORRIDOR, 0.444F, 2.697F, 0.638F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ALLEY = {EAX_ENVIRONMENT_ALLEY, 0.25F, 1.752F, 0.776F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_FOREST = {EAX_ENVIRONMENT_FOREST, 0.111F, 3.145F, 0.472F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CITY = {EAX_ENVIRONMENT_CITY, 0.111F, 2.767F, 0.224F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_MOUNTAINS = {EAX_ENVIRONMENT_MOUNTAINS, 0.194F, 7.841F, 0.472F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_QUARRY = {EAX_ENVIRONMENT_QUARRY, 1.0F, 1.499F, 0.5F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PLAIN = {EAX_ENVIRONMENT_PLAIN, 0.097F, 2.767F, 0.224F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PARKINGLOT = {EAX_ENVIRONMENT_PARKINGLOT, 0.208F, 1.652F, 1.5F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_SEWERPIPE = {EAX_ENVIRONMENT_SEWERPIPE, 0.652F, 2.886F, 0.25F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_UNDERWATER = {EAX_ENVIRONMENT_UNDERWATER, 1.0F, 1.499F, 0.0F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DRUGGED = {EAX_ENVIRONMENT_DRUGGED, 0.875F, 8.392F, 1.388F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DIZZY = {EAX_ENVIRONMENT_DIZZY, 0.139F, 17.234F, 0.666F}; -constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PSYCHOTIC = {EAX_ENVIRONMENT_PSYCHOTIC, 0.486F, 7.563F, 0.806F}; -} // namespace - -const Eax1ReverbPresets EAX1REVERB_PRESETS{{ - EAX1REVERB_PRESET_GENERIC, - EAX1REVERB_PRESET_PADDEDCELL, - EAX1REVERB_PRESET_ROOM, - EAX1REVERB_PRESET_BATHROOM, - EAX1REVERB_PRESET_LIVINGROOM, - EAX1REVERB_PRESET_STONEROOM, - EAX1REVERB_PRESET_AUDITORIUM, - EAX1REVERB_PRESET_CONCERTHALL, - EAX1REVERB_PRESET_CAVE, - EAX1REVERB_PRESET_ARENA, - EAX1REVERB_PRESET_HANGAR, - EAX1REVERB_PRESET_CARPETTEDHALLWAY, - EAX1REVERB_PRESET_HALLWAY, - EAX1REVERB_PRESET_STONECORRIDOR, - EAX1REVERB_PRESET_ALLEY, - EAX1REVERB_PRESET_FOREST, - EAX1REVERB_PRESET_CITY, - EAX1REVERB_PRESET_MOUNTAINS, - EAX1REVERB_PRESET_QUARRY, - EAX1REVERB_PRESET_PLAIN, - EAX1REVERB_PRESET_PARKINGLOT, - EAX1REVERB_PRESET_SEWERPIPE, - EAX1REVERB_PRESET_UNDERWATER, - EAX1REVERB_PRESET_DRUGGED, - EAX1REVERB_PRESET_DIZZY, - EAX1REVERB_PRESET_PSYCHOTIC, -}}; diff --git a/libs/openal-soft/al/eax_api.h b/libs/openal-soft/al/eax/api.h similarity index 81% rename from libs/openal-soft/al/eax_api.h rename to libs/openal-soft/al/eax/api.h index d0737d1d..3e621d30 100644 --- a/libs/openal-soft/al/eax_api.h +++ b/libs/openal-soft/al/eax/api.h @@ -10,41 +10,43 @@ // +#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; +}; inline bool operator==(const GUID& lhs, const GUID& rhs) noexcept -{ - return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0; -} +{ return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0; } inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept -{ - return !(lhs == rhs); -} -#endif // GUID_DEFINED +{ return !(lhs == rhs); } +#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; -enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned int -{ +enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned int { DSPROPERTY_EAX_ALL, DSPROPERTY_EAX_ENVIRONMENT, DSPROPERTY_EAX_VOLUME, @@ -52,37 +54,25 @@ enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned int DSPROPERTY_EAX_DAMPING, }; // DSPROPERTY_EAX_REVERBPROPERTY -struct EAX_REVERBPROPERTIES -{ +struct EAX_REVERBPROPERTIES { unsigned long environment; float fVolume; float fDecayTime_sec; float fDamping; }; // EAX_REVERBPROPERTIES -inline bool operator==(const EAX_REVERBPROPERTIES& lhs, const EAX_REVERBPROPERTIES& rhs) noexcept -{ - return std::memcmp(&lhs, &rhs, sizeof(EAX_REVERBPROPERTIES)) == 0; -} extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties; -enum DSPROPERTY_EAXBUFFER_REVERBPROPERTY : unsigned int -{ +enum DSPROPERTY_EAXBUFFER_REVERBPROPERTY : unsigned int { DSPROPERTY_EAXBUFFER_ALL, DSPROPERTY_EAXBUFFER_REVERBMIX, }; // DSPROPERTY_EAXBUFFER_REVERBPROPERTY -struct EAXBUFFER_REVERBPROPERTIES -{ +struct EAXBUFFER_REVERBPROPERTIES { float fMix; }; -inline bool operator==(const EAXBUFFER_REVERBPROPERTIES& lhs, const EAXBUFFER_REVERBPROPERTIES& rhs) noexcept -{ - return lhs.fMix == rhs.fMix; -} - constexpr auto EAX_BUFFER_MINREVERBMIX = 0.0F; constexpr auto EAX_BUFFER_MAXREVERBMIX = 1.0F; constexpr auto EAX_REVERBMIX_USEDISTANCE = -1.0F; @@ -90,9 +80,7 @@ constexpr auto EAX_REVERBMIX_USEDISTANCE = -1.0F; extern const GUID DSPROPSETID_EAX20_ListenerProperties; -enum DSPROPERTY_EAX20_LISTENERPROPERTY : - unsigned int -{ +enum DSPROPERTY_EAX20_LISTENERPROPERTY : unsigned int { DSPROPERTY_EAX20LISTENER_NONE, DSPROPERTY_EAX20LISTENER_ALLPARAMETERS, DSPROPERTY_EAX20LISTENER_ROOM, @@ -111,8 +99,7 @@ enum DSPROPERTY_EAX20_LISTENERPROPERTY : DSPROPERTY_EAX20LISTENER_FLAGS }; // DSPROPERTY_EAX20_LISTENERPROPERTY -struct EAX20LISTENERPROPERTIES -{ +struct EAX20LISTENERPROPERTIES { long lRoom; // room effect level at low frequencies long lRoomHF; // room effect high-frequency level re. low frequency level float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect @@ -129,13 +116,109 @@ struct EAX20LISTENERPROPERTIES unsigned long dwFlags; // modifies the behavior of properties }; // EAX20LISTENERPROPERTIES +enum : unsigned long { + EAX2_ENVIRONMENT_GENERIC, + EAX2_ENVIRONMENT_PADDEDCELL, + EAX2_ENVIRONMENT_ROOM, + EAX2_ENVIRONMENT_BATHROOM, + EAX2_ENVIRONMENT_LIVINGROOM, + EAX2_ENVIRONMENT_STONEROOM, + EAX2_ENVIRONMENT_AUDITORIUM, + EAX2_ENVIRONMENT_CONCERTHALL, + EAX2_ENVIRONMENT_CAVE, + EAX2_ENVIRONMENT_ARENA, + EAX2_ENVIRONMENT_HANGAR, + EAX2_ENVIRONMENT_CARPETEDHALLWAY, + EAX2_ENVIRONMENT_HALLWAY, + EAX2_ENVIRONMENT_STONECORRIDOR, + EAX2_ENVIRONMENT_ALLEY, + EAX2_ENVIRONMENT_FOREST, + EAX2_ENVIRONMENT_CITY, + EAX2_ENVIRONMENT_MOUNTAINS, + EAX2_ENVIRONMENT_QUARRY, + EAX2_ENVIRONMENT_PLAIN, + EAX2_ENVIRONMENT_PARKINGLOT, + EAX2_ENVIRONMENT_SEWERPIPE, + EAX2_ENVIRONMENT_UNDERWATER, + EAX2_ENVIRONMENT_DRUGGED, + EAX2_ENVIRONMENT_DIZZY, + EAX2_ENVIRONMENT_PSYCHOTIC, + + EAX2_ENVIRONMENT_COUNT, +}; + +constexpr auto EAX2LISTENERFLAGS_DECAYTIMESCALE = 0x00000001UL; +constexpr auto EAX2LISTENERFLAGS_REFLECTIONSSCALE = 0x00000002UL; +constexpr auto EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE = 0x00000004UL; +constexpr auto EAX2LISTENERFLAGS_REVERBSCALE = 0x00000008UL; +constexpr auto EAX2LISTENERFLAGS_REVERBDELAYSCALE = 0x00000010UL; +constexpr auto EAX2LISTENERFLAGS_DECAYHFLIMIT = 0x00000020UL; +constexpr auto EAX2LISTENERFLAGS_RESERVED = 0xFFFFFFC0UL; + +constexpr auto EAX2LISTENER_MINROOM = -10'000L; +constexpr auto EAX2LISTENER_MAXROOM = 0L; +constexpr auto EAX2LISTENER_DEFAULTROOM = -1'000L; + +constexpr auto EAX2LISTENER_MINROOMHF = -10'000L; +constexpr auto EAX2LISTENER_MAXROOMHF = 0L; +constexpr auto EAX2LISTENER_DEFAULTROOMHF = -100L; + +constexpr auto EAX2LISTENER_MINROOMROLLOFFFACTOR = 0.0F; +constexpr auto EAX2LISTENER_MAXROOMROLLOFFFACTOR = 10.0F; +constexpr auto EAX2LISTENER_DEFAULTROOMROLLOFFFACTOR = 0.0F; + +constexpr auto EAX2LISTENER_MINDECAYTIME = 0.1F; +constexpr auto EAX2LISTENER_MAXDECAYTIME = 20.0F; +constexpr auto EAX2LISTENER_DEFAULTDECAYTIME = 1.49F; + +constexpr auto EAX2LISTENER_MINDECAYHFRATIO = 0.1F; +constexpr auto EAX2LISTENER_MAXDECAYHFRATIO = 2.0F; +constexpr auto EAX2LISTENER_DEFAULTDECAYHFRATIO = 0.83F; + +constexpr auto EAX2LISTENER_MINREFLECTIONS = -10'000L; +constexpr auto EAX2LISTENER_MAXREFLECTIONS = 1'000L; +constexpr auto EAX2LISTENER_DEFAULTREFLECTIONS = -2'602L; + +constexpr auto EAX2LISTENER_MINREFLECTIONSDELAY = 0.0F; +constexpr auto EAX2LISTENER_MAXREFLECTIONSDELAY = 0.3F; +constexpr auto EAX2LISTENER_DEFAULTREFLECTIONSDELAY = 0.007F; + +constexpr auto EAX2LISTENER_MINREVERB = -10'000L; +constexpr auto EAX2LISTENER_MAXREVERB = 2'000L; +constexpr auto EAX2LISTENER_DEFAULTREVERB = 200L; + +constexpr auto EAX2LISTENER_MINREVERBDELAY = 0.0F; +constexpr auto EAX2LISTENER_MAXREVERBDELAY = 0.1F; +constexpr auto EAX2LISTENER_DEFAULTREVERBDELAY = 0.011F; + +constexpr auto EAX2LISTENER_MINENVIRONMENT = 0UL; +constexpr auto EAX2LISTENER_MAXENVIRONMENT = EAX2_ENVIRONMENT_COUNT - 1; +constexpr auto EAX2LISTENER_DEFAULTENVIRONMENT = EAX2_ENVIRONMENT_GENERIC; + +constexpr auto EAX2LISTENER_MINENVIRONMENTSIZE = 1.0F; +constexpr auto EAX2LISTENER_MAXENVIRONMENTSIZE = 100.0F; +constexpr auto EAX2LISTENER_DEFAULTENVIRONMENTSIZE = 7.5F; + +constexpr auto EAX2LISTENER_MINENVIRONMENTDIFFUSION = 0.0F; +constexpr auto EAX2LISTENER_MAXENVIRONMENTDIFFUSION = 1.0F; +constexpr auto EAX2LISTENER_DEFAULTENVIRONMENTDIFFUSION = 1.0F; + +constexpr auto EAX2LISTENER_MINAIRABSORPTIONHF = -100.0F; +constexpr auto EAX2LISTENER_MAXAIRABSORPTIONHF = 0.0F; +constexpr auto EAX2LISTENER_DEFAULTAIRABSORPTIONHF = -5.0F; + +constexpr auto EAX2LISTENER_DEFAULTFLAGS = + EAX2LISTENERFLAGS_DECAYTIMESCALE | + EAX2LISTENERFLAGS_REFLECTIONSSCALE | + EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | + EAX2LISTENERFLAGS_REVERBSCALE | + EAX2LISTENERFLAGS_REVERBDELAYSCALE | + EAX2LISTENERFLAGS_DECAYHFLIMIT; + extern const GUID DSPROPSETID_EAX20_BufferProperties; - -enum DSPROPERTY_EAX20_BUFFERPROPERTY : - unsigned int -{ +enum DSPROPERTY_EAX20_BUFFERPROPERTY : unsigned int { DSPROPERTY_EAX20BUFFER_NONE, DSPROPERTY_EAX20BUFFER_ALLPARAMETERS, DSPROPERTY_EAX20BUFFER_DIRECT, @@ -153,9 +236,7 @@ enum DSPROPERTY_EAX20_BUFFERPROPERTY : DSPROPERTY_EAX20BUFFER_FLAGS }; // DSPROPERTY_EAX20_BUFFERPROPERTY - -struct EAX20BUFFERPROPERTIES -{ +struct EAX20BUFFERPROPERTIES { long lDirect; // direct path level long lDirectHF; // direct path level at high frequencies long lRoom; // room effect level @@ -171,7 +252,6 @@ struct EAX20BUFFERPROPERTIES unsigned long dwFlags; // modifies the behavior of properties }; // EAX20BUFFERPROPERTIES - extern const GUID DSPROPSETID_EAX30_ListenerProperties; extern const GUID DSPROPSETID_EAX30_BufferProperties; @@ -197,16 +277,19 @@ extern const GUID EAX_NULL_GUID; extern const GUID EAX_PrimaryFXSlotID; -struct EAXVECTOR -{ +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); } @@ -216,24 +299,19 @@ extern const GUID EAXPROPERTYID_EAX40_Context; extern const GUID EAXPROPERTYID_EAX50_Context; // EAX50 -enum : - unsigned long -{ - HEADPHONES = 0, - SPEAKERS_2, - SPEAKERS_4, - SPEAKERS_5, // 5.1 speakers - SPEAKERS_6, // 6.1 speakers - SPEAKERS_7, // 7.1 speakers -}; +constexpr auto HEADPHONES = 0UL; +constexpr auto SPEAKERS_2 = 1UL; +constexpr auto SPEAKERS_4 = 2UL; +constexpr auto SPEAKERS_5 = 3UL; // 5.1 speakers +constexpr auto SPEAKERS_6 = 4UL; // 6.1 speakers +constexpr auto SPEAKERS_7 = 5UL; // 7.1 speakers + +constexpr auto EAXCONTEXT_MINSPEAKERCONFIG = HEADPHONES; +constexpr auto EAXCONTEXT_MAXSPEAKERCONFIG = SPEAKERS_7; // EAX50 -enum : - unsigned long -{ - EAX_40 = 5, // EAX 4.0 - EAX_50 = 6, // EAX 5.0 -}; +constexpr auto EAX_40 = 5UL; // EAX 4.0 +constexpr auto EAX_50 = 6UL; // EAX 5.0 constexpr auto EAXCONTEXT_MINEAXSESSION = EAX_40; constexpr auto EAXCONTEXT_MAXEAXSESSION = EAX_50; @@ -244,15 +322,12 @@ constexpr auto EAXCONTEXT_MAXMAXACTIVESENDS = 4UL; constexpr auto EAXCONTEXT_DEFAULTMAXACTIVESENDS = 2UL; // EAX50 -struct EAXSESSIONPROPERTIES -{ +struct EAXSESSIONPROPERTIES { unsigned long ulEAXVersion; unsigned long ulMaxActiveSends; }; // EAXSESSIONPROPERTIES -enum EAXCONTEXT_PROPERTY : - unsigned int -{ +enum EAXCONTEXT_PROPERTY : unsigned int { EAXCONTEXT_NONE = 0, EAXCONTEXT_ALLPARAMETERS, EAXCONTEXT_PRIMARYFXSLOTID, @@ -267,28 +342,16 @@ enum EAXCONTEXT_PROPERTY : EAXCONTEXT_MACROFXFACTOR, }; // EAXCONTEXT_PROPERTY -struct EAX40CONTEXTPROPERTIES -{ +struct EAX40CONTEXTPROPERTIES { GUID guidPrimaryFXSlotID; float flDistanceFactor; float flAirAbsorptionHF; float flHFReference; }; // EAX40CONTEXTPROPERTIES -struct EAX50CONTEXTPROPERTIES : - public EAX40CONTEXTPROPERTIES -{ +struct EAX50CONTEXTPROPERTIES : public EAX40CONTEXTPROPERTIES { float flMacroFXFactor; -}; // EAX40CONTEXTPROPERTIES - - -bool operator==( - const EAX40CONTEXTPROPERTIES& lhs, - const EAX40CONTEXTPROPERTIES& rhs) noexcept; - -bool operator==( - const EAX50CONTEXTPROPERTIES& lhs, - const EAX50CONTEXTPROPERTIES& rhs) noexcept; +}; // EAX50CONTEXTPROPERTIES constexpr auto EAXCONTEXT_MINDISTANCEFACTOR = FLT_MIN; @@ -307,28 +370,21 @@ 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; - extern const GUID EAXPROPERTYID_EAX40_FXSlot1; - extern const GUID EAXPROPERTYID_EAX50_FXSlot1; - extern const GUID EAXPROPERTYID_EAX40_FXSlot2; - extern const GUID EAXPROPERTYID_EAX50_FXSlot2; - extern const GUID EAXPROPERTYID_EAX40_FXSlot3; - extern const GUID EAXPROPERTYID_EAX50_FXSlot3; -extern const GUID EAXCONTEXT_DEFAULTPRIMARYFXSLOTID; +extern const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID; +extern const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID; -enum EAXFXSLOT_PROPERTY : - unsigned int -{ +enum EAXFXSLOT_PROPERTY : unsigned int { EAXFXSLOT_PARAMETER = 0, EAXFXSLOT_NONE = 0x10000, @@ -358,9 +414,7 @@ constexpr auto EAXFXSLOT_DEFAULTVOLUME = 0L; constexpr auto EAXFXSLOT_MINLOCK = 0L; constexpr auto EAXFXSLOT_MAXLOCK = 1L; -enum : - long -{ +enum : long { EAXFXSLOT_UNLOCKED = 0, EAXFXSLOT_LOCKED = 1 }; @@ -379,39 +433,24 @@ constexpr auto EAX50FXSLOT_DEFAULTFLAGS = EAXFXSLOTFLAGS_ENVIRONMENT | EAXFXSLOTFLAGS_UPMIX; // ignored for reverb; -struct EAX40FXSLOTPROPERTIES -{ +struct EAX40FXSLOTPROPERTIES { GUID guidLoadEffect; long lVolume; long lLock; unsigned long ulFlags; }; // EAX40FXSLOTPROPERTIES -struct EAX50FXSLOTPROPERTIES : - public EAX40FXSLOTPROPERTIES -{ +struct EAX50FXSLOTPROPERTIES : public EAX40FXSLOTPROPERTIES { long lOcclusion; float flOcclusionLFRatio; }; // EAX50FXSLOTPROPERTIES -bool operator==( - const EAX40FXSLOTPROPERTIES& lhs, - const EAX40FXSLOTPROPERTIES& rhs) noexcept; - -bool operator==( - const EAX50FXSLOTPROPERTIES& lhs, - const EAX50FXSLOTPROPERTIES& rhs) noexcept; - extern const GUID EAXPROPERTYID_EAX40_Source; - extern const GUID EAXPROPERTYID_EAX50_Source; // Source object properties -enum EAXSOURCE_PROPERTY : - unsigned int -{ +enum EAXSOURCE_PROPERTY : unsigned int { // EAX30 - EAXSOURCE_NONE, EAXSOURCE_ALLPARAMETERS, EAXSOURCE_OBSTRUCTIONPARAMETERS, @@ -437,7 +476,6 @@ enum EAXSOURCE_PROPERTY : EAXSOURCE_FLAGS, // EAX40 - EAXSOURCE_SENDPARAMETERS, EAXSOURCE_ALLSENDPARAMETERS, EAXSOURCE_OCCLUSIONSENDPARAMETERS, @@ -445,7 +483,6 @@ enum EAXSOURCE_PROPERTY : EAXSOURCE_ACTIVEFXSLOTID, // EAX50 - EAXSOURCE_MACROFXFACTOR, EAXSOURCE_SPEAKERLEVELS, EAXSOURCE_ALL2DPARAMETERS, @@ -457,9 +494,7 @@ constexpr auto EAXSOURCEFLAGS_ROOMAUTO = 0x00000002UL; // relates to EAXSOURCE_R constexpr auto EAXSOURCEFLAGS_ROOMHFAUTO = 0x00000004UL; // relates to EAXSOURCE_ROOMHF // EAX50 constexpr auto EAXSOURCEFLAGS_3DELEVATIONFILTER = 0x00000008UL; -// EAX50 constexpr auto EAXSOURCEFLAGS_UPMIX = 0x00000010UL; -// EAX50 constexpr auto EAXSOURCEFLAGS_APPLYSPEAKERLEVELS = 0x00000020UL; constexpr auto EAX20SOURCEFLAGS_RESERVED = 0xFFFFFFF8UL; // reserved future use @@ -548,8 +583,6 @@ constexpr auto EAXSOURCE_MINMACROFXFACTOR = 0.0F; constexpr auto EAXSOURCE_MAXMACROFXFACTOR = 1.0F; constexpr auto EAXSOURCE_DEFAULTMACROFXFACTOR = 1.0F; -// EAX50 - constexpr auto EAXSOURCE_MINSPEAKERLEVEL = -10'000L; constexpr auto EAXSOURCE_MAXSPEAKERLEVEL = 0L; constexpr auto EAXSOURCE_DEFAULTSPEAKERLEVEL = -10'000L; @@ -559,9 +592,7 @@ constexpr auto EAXSOURCE_DEFAULTFLAGS = EAXSOURCEFLAGS_ROOMAUTO | EAXSOURCEFLAGS_ROOMHFAUTO; -enum : - long -{ +enum : long { EAXSPEAKER_FRONT_LEFT = 1, EAXSPEAKER_FRONT_CENTER = 2, EAXSPEAKER_FRONT_RIGHT = 3, @@ -581,8 +612,7 @@ constexpr auto EAX50SOURCE_DEFAULTFLAGS = EAXSOURCEFLAGS_ROOMHFAUTO | EAXSOURCEFLAGS_UPMIX; -struct EAX30SOURCEPROPERTIES -{ +struct EAX30SOURCEPROPERTIES { long lDirect; // direct path level (at low and mid frequencies) long lDirectHF; // relative direct path level at high frequencies long lRoom; // room effect level (at low and mid frequencies) @@ -593,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 @@ -603,14 +633,11 @@ struct EAX30SOURCEPROPERTIES unsigned long ulFlags; // modifies the behavior of properties }; // EAX30SOURCEPROPERTIES -struct EAX50SOURCEPROPERTIES : - public EAX30SOURCEPROPERTIES -{ +struct EAX50SOURCEPROPERTIES : public EAX30SOURCEPROPERTIES { float flMacroFXFactor; }; // EAX50SOURCEPROPERTIES -struct EAXSOURCEALLSENDPROPERTIES -{ +struct EAXSOURCEALLSENDPROPERTIES { GUID guidReceivingFXSlotID; long lSend; // send level (at low and mid frequencies) long lSendHF; // relative send level at high frequencies @@ -622,8 +649,7 @@ struct EAXSOURCEALLSENDPROPERTIES float flExclusionLFRatio; }; // EAXSOURCEALLSENDPROPERTIES -struct EAXSOURCE2DPROPERTIES -{ +struct EAXSOURCE2DPROPERTIES { long lDirect; // direct path level (at low and mid frequencies) long lDirectHF; // relative direct path level at high frequencies long lRoom; // room effect level (at low and mid frequencies) @@ -631,40 +657,27 @@ struct EAXSOURCE2DPROPERTIES unsigned long ulFlags; // modifies the behavior of properties }; // EAXSOURCE2DPROPERTIES -struct EAXSPEAKERLEVELPROPERTIES -{ +struct EAXSPEAKERLEVELPROPERTIES { long lSpeakerID; long lLevel; }; // EAXSPEAKERLEVELPROPERTIES -struct EAX40ACTIVEFXSLOTS -{ - GUID guidActiveFXSlots[EAX40_MAX_ACTIVE_FXSLOTS]; +struct EAX40ACTIVEFXSLOTS { + std::array guidActiveFXSlots; }; // EAX40ACTIVEFXSLOTS -struct EAX50ACTIVEFXSLOTS -{ - GUID guidActiveFXSlots[EAX50_MAX_ACTIVE_FXSLOTS]; +struct EAX50ACTIVEFXSLOTS { + std::array guidActiveFXSlots; }; // EAX50ACTIVEFXSLOTS -bool operator==( - const EAX50ACTIVEFXSLOTS& lhs, - const EAX50ACTIVEFXSLOTS& rhs) noexcept; - -bool operator!=( - const EAX50ACTIVEFXSLOTS& lhs, - const EAX50ACTIVEFXSLOTS& rhs) noexcept; - // Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property. -struct EAXOBSTRUCTIONPROPERTIES -{ +struct EAXOBSTRUCTIONPROPERTIES { long lObstruction; float flObstructionLFRatio; }; // EAXOBSTRUCTIONPROPERTIES // Use this structure for EAXSOURCE_OCCLUSIONPARAMETERS property. -struct EAXOCCLUSIONPROPERTIES -{ +struct EAXOCCLUSIONPROPERTIES { long lOcclusion; float flOcclusionLFRatio; float flOcclusionRoomRatio; @@ -672,23 +685,20 @@ struct EAXOCCLUSIONPROPERTIES }; // EAXOCCLUSIONPROPERTIES // Use this structure for EAXSOURCE_EXCLUSIONPARAMETERS property. -struct EAXEXCLUSIONPROPERTIES -{ +struct EAXEXCLUSIONPROPERTIES { long lExclusion; float flExclusionLFRatio; }; // EAXEXCLUSIONPROPERTIES // Use this structure for EAXSOURCE_SENDPARAMETERS properties. -struct EAXSOURCESENDPROPERTIES -{ +struct EAXSOURCESENDPROPERTIES { GUID guidReceivingFXSlotID; long lSend; long lSendHF; }; // EAXSOURCESENDPROPERTIES // Use this structure for EAXSOURCE_OCCLUSIONSENDPARAMETERS -struct EAXSOURCEOCCLUSIONSENDPROPERTIES -{ +struct EAXSOURCEOCCLUSIONSENDPROPERTIES { GUID guidReceivingFXSlotID; long lOcclusion; float flOcclusionLFRatio; @@ -697,14 +707,13 @@ struct EAXSOURCEOCCLUSIONSENDPROPERTIES }; // EAXSOURCEOCCLUSIONSENDPROPERTIES // Use this structure for EAXSOURCE_EXCLUSIONSENDPARAMETERS -struct EAXSOURCEEXCLUSIONSENDPROPERTIES -{ +struct EAXSOURCEEXCLUSIONSENDPROPERTIES { GUID guidReceivingFXSlotID; long lExclusion; float flExclusionLFRatio; }; // EAXSOURCEEXCLUSIONSENDPROPERTIES -extern const EAX50ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID; +extern const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID; extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; @@ -716,9 +725,7 @@ extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID; extern const GUID EAX_REVERB_EFFECT; // Reverb effect properties -enum EAXREVERB_PROPERTY : - unsigned int -{ +enum EAXREVERB_PROPERTY : unsigned int { EAXREVERB_NONE, EAXREVERB_ALLPARAMETERS, EAXREVERB_ENVIRONMENT, @@ -748,9 +755,7 @@ enum EAXREVERB_PROPERTY : }; // EAXREVERB_PROPERTY // used by EAXREVERB_ENVIRONMENT -enum : - unsigned long -{ +enum : unsigned long { EAX_ENVIRONMENT_GENERIC, EAX_ENVIRONMENT_PADDEDCELL, EAX_ENVIRONMENT_ROOM, @@ -816,8 +821,7 @@ constexpr auto EAXREVERBFLAGS_DECAYHFLIMIT = 0x00000020UL; constexpr auto EAXREVERBFLAGS_RESERVED = 0xFFFFFF00UL; // reserved future use -struct EAXREVERBPROPERTIES -{ +struct EAXREVERBPROPERTIES { unsigned long ulEnvironment; // sets all reverb properties float flEnvironmentSize; // environment size in meters float flEnvironmentDiffusion; // environment diffusion @@ -842,16 +846,13 @@ 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 -bool operator==( - const EAXREVERBPROPERTIES& lhs, - const EAXREVERBPROPERTIES& rhs) noexcept; - -bool operator!=( - const EAXREVERBPROPERTIES& lhs, - const EAXREVERBPROPERTIES& rhs) noexcept; - constexpr auto EAXREVERB_MINENVIRONMENT = static_cast(EAX_ENVIRONMENT_GENERIC); constexpr auto EAX1REVERB_MAXENVIRONMENT = static_cast(EAX_ENVIRONMENT_PSYCHOTIC); @@ -957,29 +958,29 @@ constexpr auto EAXREVERB_DEFAULTFLAGS = EAXREVERBFLAGS_DECAYHFLIMIT; -using EaxReverbPresets = std::array; -extern const EaxReverbPresets EAXREVERB_PRESETS; - - using Eax1ReverbPresets = std::array; extern const Eax1ReverbPresets EAX1REVERB_PRESETS; +using Eax2ReverbPresets = std::array; +extern const Eax2ReverbPresets EAX2REVERB_PRESETS; + +using EaxReverbPresets = std::array; +extern const EaxReverbPresets EAXREVERB_PRESETS; + // AGC Compressor Effect extern const GUID EAX_AGCCOMPRESSOR_EFFECT; -enum EAXAGCCOMPRESSOR_PROPERTY : - unsigned int -{ +enum EAXAGCCOMPRESSOR_PROPERTY : unsigned int { EAXAGCCOMPRESSOR_NONE, EAXAGCCOMPRESSOR_ALLPARAMETERS, EAXAGCCOMPRESSOR_ONOFF, }; // EAXAGCCOMPRESSOR_PROPERTY -struct EAXAGCCOMPRESSORPROPERTIES -{ +struct EAXAGCCOMPRESSORPROPERTIES { unsigned long ulOnOff; // Switch Compressor on or off + DECL_EQOP(EAXAGCCOMPRESSORPROPERTIES, ulOnOff) }; // EAXAGCCOMPRESSORPROPERTIES @@ -992,9 +993,7 @@ constexpr auto EAXAGCCOMPRESSOR_DEFAULTONOFF = EAXAGCCOMPRESSOR_MAXONOFF; extern const GUID EAX_AUTOWAH_EFFECT; -enum EAXAUTOWAH_PROPERTY : - unsigned int -{ +enum EAXAUTOWAH_PROPERTY : unsigned int { EAXAUTOWAH_NONE, EAXAUTOWAH_ALLPARAMETERS, EAXAUTOWAH_ATTACKTIME, @@ -1003,12 +1002,12 @@ enum EAXAUTOWAH_PROPERTY : EAXAUTOWAH_PEAKLEVEL, }; // EAXAUTOWAH_PROPERTY -struct EAXAUTOWAHPROPERTIES -{ +struct EAXAUTOWAHPROPERTIES { float flAttackTime; // Attack time (seconds) float flReleaseTime; // Release time (seconds) long lResonance; // Resonance (mB) long lPeakLevel; // Peak level (mB) + DECL_EQOP(EAXAUTOWAHPROPERTIES, flAttackTime, flReleaseTime, lResonance, lPeakLevel) }; // EAXAUTOWAHPROPERTIES @@ -1033,10 +1032,7 @@ constexpr auto EAXAUTOWAH_DEFAULTPEAKLEVEL = 2100L; extern const GUID EAX_CHORUS_EFFECT; - -enum EAXCHORUS_PROPERTY : - unsigned int -{ +enum EAXCHORUS_PROPERTY : unsigned int { EAXCHORUS_NONE, EAXCHORUS_ALLPARAMETERS, EAXCHORUS_WAVEFORM, @@ -1047,21 +1043,19 @@ enum EAXCHORUS_PROPERTY : EAXCHORUS_DELAY, }; // EAXCHORUS_PROPERTY -enum : - unsigned long -{ +enum : unsigned long { EAX_CHORUS_SINUSOID, EAX_CHORUS_TRIANGLE, }; -struct EAXCHORUSPROPERTIES -{ +struct EAXCHORUSPROPERTIES { unsigned long ulWaveform; // Waveform selector - see enum above long lPhase; // Phase (Degrees) float flRate; // Rate (Hz) float flDepth; // Depth (0 to 1) float flFeedback; // Feedback (-1 to 1) float flDelay; // Delay (seconds) + DECL_EQOP(EAXCHORUSPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay) }; // EAXCHORUSPROPERTIES @@ -1094,9 +1088,7 @@ constexpr auto EAXCHORUS_DEFAULTDELAY = 0.016F; extern const GUID EAX_DISTORTION_EFFECT; -enum EAXDISTORTION_PROPERTY : - unsigned int -{ +enum EAXDISTORTION_PROPERTY : unsigned int { EAXDISTORTION_NONE, EAXDISTORTION_ALLPARAMETERS, EAXDISTORTION_EDGE, @@ -1106,14 +1098,13 @@ enum EAXDISTORTION_PROPERTY : EAXDISTORTION_EQBANDWIDTH, }; // EAXDISTORTION_PROPERTY - -struct EAXDISTORTIONPROPERTIES -{ +struct EAXDISTORTIONPROPERTIES { float flEdge; // Controls the shape of the distortion (0 to 1) long lGain; // Controls the post distortion gain (mB) float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz) float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz) float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz) + DECL_EQOP(EAXDISTORTIONPROPERTIES, flEdge, lGain, flLowPassCutOff, flEQCenter, flEQBandwidth) }; // EAXDISTORTIONPROPERTIES @@ -1142,10 +1133,7 @@ constexpr auto EAXDISTORTION_DEFAULTEQBANDWIDTH = 3600.0F; extern const GUID EAX_ECHO_EFFECT; - -enum EAXECHO_PROPERTY : - unsigned int -{ +enum EAXECHO_PROPERTY : unsigned int { EAXECHO_NONE, EAXECHO_ALLPARAMETERS, EAXECHO_DELAY, @@ -1155,14 +1143,13 @@ enum EAXECHO_PROPERTY : EAXECHO_SPREAD, }; // EAXECHO_PROPERTY - -struct EAXECHOPROPERTIES -{ +struct EAXECHOPROPERTIES { float flDelay; // Controls the initial delay time (seconds) float flLRDelay; // Controls the delay time between the first and second taps (seconds) float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1) float flFeedback; // Controls the duration of echo repetition (0 to 1) float flSpread; // Controls the left-right spread of the echoes + DECL_EQOP(EAXECHOPROPERTIES, flDelay, flLRDelay, flDamping, flFeedback, flSpread) }; // EAXECHOPROPERTIES @@ -1191,10 +1178,7 @@ constexpr auto EAXECHO_DEFAULTSPREAD = -1.0F; extern const GUID EAX_EQUALIZER_EFFECT; - -enum EAXEQUALIZER_PROPERTY : - unsigned int -{ +enum EAXEQUALIZER_PROPERTY : unsigned int { EAXEQUALIZER_NONE, EAXEQUALIZER_ALLPARAMETERS, EAXEQUALIZER_LOWGAIN, @@ -1209,9 +1193,7 @@ enum EAXEQUALIZER_PROPERTY : EAXEQUALIZER_HIGHCUTOFF, }; // EAXEQUALIZER_PROPERTY - -struct EAXEQUALIZERPROPERTIES -{ +struct EAXEQUALIZERPROPERTIES { long lLowGain; // (mB) float flLowCutOff; // (Hz) long lMid1Gain; // (mB) @@ -1222,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 @@ -1270,9 +1254,7 @@ constexpr auto EAXEQUALIZER_DEFAULTHIGHCUTOFF = 6000.0F; extern const GUID EAX_FLANGER_EFFECT; -enum EAXFLANGER_PROPERTY : - unsigned int -{ +enum EAXFLANGER_PROPERTY : unsigned int { EAXFLANGER_NONE, EAXFLANGER_ALLPARAMETERS, EAXFLANGER_WAVEFORM, @@ -1283,21 +1265,19 @@ enum EAXFLANGER_PROPERTY : EAXFLANGER_DELAY, }; // EAXFLANGER_PROPERTY -enum : - unsigned long -{ +enum : unsigned long { EAX_FLANGER_SINUSOID, EAX_FLANGER_TRIANGLE, }; -struct EAXFLANGERPROPERTIES -{ +struct EAXFLANGERPROPERTIES { unsigned long ulWaveform; // Waveform selector - see enum above long lPhase; // Phase (Degrees) float flRate; // Rate (Hz) float flDepth; // Depth (0 to 1) float flFeedback; // Feedback (0 to 1) float flDelay; // Delay (seconds) + DECL_EQOP(EAXFLANGERPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay) }; // EAXFLANGERPROPERTIES @@ -1330,9 +1310,7 @@ constexpr auto EAXFLANGER_DEFAULTDELAY = 0.002F; extern const GUID EAX_FREQUENCYSHIFTER_EFFECT; -enum EAXFREQUENCYSHIFTER_PROPERTY : - unsigned int -{ +enum EAXFREQUENCYSHIFTER_PROPERTY : unsigned int { EAXFREQUENCYSHIFTER_NONE, EAXFREQUENCYSHIFTER_ALLPARAMETERS, EAXFREQUENCYSHIFTER_FREQUENCY, @@ -1340,19 +1318,17 @@ enum EAXFREQUENCYSHIFTER_PROPERTY : EAXFREQUENCYSHIFTER_RIGHTDIRECTION, }; // EAXFREQUENCYSHIFTER_PROPERTY -enum : - unsigned long -{ +enum : unsigned long { EAX_FREQUENCYSHIFTER_DOWN, EAX_FREQUENCYSHIFTER_UP, EAX_FREQUENCYSHIFTER_OFF }; -struct EAXFREQUENCYSHIFTERPROPERTIES -{ +struct EAXFREQUENCYSHIFTERPROPERTIES { float flFrequency; // (Hz) unsigned long ulLeftDirection; // see enum above unsigned long ulRightDirection; // see enum above + DECL_EQOP(EAXFREQUENCYSHIFTERPROPERTIES, flFrequency, ulLeftDirection, ulRightDirection) }; // EAXFREQUENCYSHIFTERPROPERTIES @@ -1373,9 +1349,7 @@ constexpr auto EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION = EAXFREQUENCYSHIFTER_M extern const GUID EAX_VOCALMORPHER_EFFECT; -enum EAXVOCALMORPHER_PROPERTY : - unsigned int -{ +enum EAXVOCALMORPHER_PROPERTY : unsigned int { EAXVOCALMORPHER_NONE, EAXVOCALMORPHER_ALLPARAMETERS, EAXVOCALMORPHER_PHONEMEA, @@ -1386,9 +1360,7 @@ enum EAXVOCALMORPHER_PROPERTY : EAXVOCALMORPHER_RATE, }; // EAXVOCALMORPHER_PROPERTY -enum : - unsigned long -{ +enum : unsigned long { A, E, I, @@ -1421,23 +1393,22 @@ enum : Z, }; -enum : - unsigned long -{ +enum : unsigned long { EAX_VOCALMORPHER_SINUSOID, EAX_VOCALMORPHER_TRIANGLE, EAX_VOCALMORPHER_SAWTOOTH }; // Use this structure for EAXVOCALMORPHER_ALLPARAMETERS -struct EAXVOCALMORPHERPROPERTIES -{ +struct EAXVOCALMORPHERPROPERTIES { unsigned long ulPhonemeA; // see enum above long lPhonemeACoarseTuning; // (semitones) unsigned long ulPhonemeB; // see enum above long lPhonemeBCoarseTuning; // (semitones) unsigned long ulWaveform; // Waveform selector - see enum above float flRate; // (Hz) + DECL_EQOP(EAXVOCALMORPHERPROPERTIES, ulPhonemeA, lPhonemeACoarseTuning, ulPhonemeB, + lPhonemeBCoarseTuning, ulWaveform, flRate) }; // EAXVOCALMORPHERPROPERTIES @@ -1470,19 +1441,17 @@ constexpr auto EAXVOCALMORPHER_DEFAULTRATE = 1.41F; extern const GUID EAX_PITCHSHIFTER_EFFECT; -enum EAXPITCHSHIFTER_PROPERTY : - unsigned int -{ +enum EAXPITCHSHIFTER_PROPERTY : unsigned int { EAXPITCHSHIFTER_NONE, EAXPITCHSHIFTER_ALLPARAMETERS, EAXPITCHSHIFTER_COARSETUNE, EAXPITCHSHIFTER_FINETUNE, }; // EAXPITCHSHIFTER_PROPERTY -struct EAXPITCHSHIFTERPROPERTIES -{ +struct EAXPITCHSHIFTERPROPERTIES { long lCoarseTune; // Amount of pitch shift (semitones) long lFineTune; // Amount of pitch shift (cents) + DECL_EQOP(EAXPITCHSHIFTERPROPERTIES, lCoarseTune, lFineTune) }; // EAXPITCHSHIFTERPROPERTIES @@ -1499,9 +1468,7 @@ constexpr auto EAXPITCHSHIFTER_DEFAULTFINETUNE = 0L; extern const GUID EAX_RINGMODULATOR_EFFECT; -enum EAXRINGMODULATOR_PROPERTY : - unsigned int -{ +enum EAXRINGMODULATOR_PROPERTY : unsigned int { EAXRINGMODULATOR_NONE, EAXRINGMODULATOR_ALLPARAMETERS, EAXRINGMODULATOR_FREQUENCY, @@ -1509,20 +1476,18 @@ enum EAXRINGMODULATOR_PROPERTY : EAXRINGMODULATOR_WAVEFORM, }; // EAXRINGMODULATOR_PROPERTY -enum : - unsigned long -{ +enum : unsigned long { EAX_RINGMODULATOR_SINUSOID, EAX_RINGMODULATOR_SAWTOOTH, EAX_RINGMODULATOR_SQUARE, }; // Use this structure for EAXRINGMODULATOR_ALLPARAMETERS -struct EAXRINGMODULATORPROPERTIES -{ +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 @@ -1553,5 +1518,5 @@ using LPEAXGET = ALenum(AL_APIENTRY*)( ALvoid* property_buffer, ALuint property_size); - +#undef DECL_EQOP #endif // !EAX_API_INCLUDED diff --git a/libs/openal-soft/al/eax/call.cpp b/libs/openal-soft/al/eax/call.cpp new file mode 100644 index 00000000..013a3992 --- /dev/null +++ b/libs/openal-soft/al/eax/call.cpp @@ -0,0 +1,218 @@ +#include "config.h" +#include "call.h" +#include "exception.h" + +namespace { + +constexpr auto deferred_flag = 0x80000000U; + +class EaxCallException : public EaxException { +public: + explicit EaxCallException(const char* message) + : EaxException{"EAX_CALL", message} + {} +}; // EaxCallException + +} // namespace + +EaxCall::EaxCall( + EaxCallType type, + const GUID& property_set_guid, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size) + : mCallType{type}, mIsDeferred{(property_id & deferred_flag) != 0} + , mPropertyId{property_id & ~deferred_flag}, mPropertySourceId{property_source_id} + , mPropertyBuffer{property_buffer}, mPropertyBufferSize{property_size} +{ + switch(mCallType) + { + case EaxCallType::get: + case EaxCallType::set: + break; + + default: + fail("Invalid type."); + } + + if (false) + { + } + else if (property_set_guid == EAXPROPERTYID_EAX40_Context) + { + mVersion = 4; + mPropertySetId = EaxCallPropertySetId::context; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_Context) + { + mVersion = 5; + mPropertySetId = EaxCallPropertySetId::context; + } + else if (property_set_guid == DSPROPSETID_EAX20_ListenerProperties) + { + mVersion = 2; + mFxSlotIndex = 0u; + mPropertySetId = EaxCallPropertySetId::fx_slot_effect; + } + else if (property_set_guid == DSPROPSETID_EAX30_ListenerProperties) + { + mVersion = 3; + mFxSlotIndex = 0u; + mPropertySetId = EaxCallPropertySetId::fx_slot_effect; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot0) + { + mVersion = 4; + mFxSlotIndex = 0u; + mPropertySetId = EaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot0) + { + mVersion = 5; + mFxSlotIndex = 0u; + mPropertySetId = EaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot1) + { + mVersion = 4; + mFxSlotIndex = 1u; + mPropertySetId = EaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot1) + { + mVersion = 5; + mFxSlotIndex = 1u; + mPropertySetId = EaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot2) + { + mVersion = 4; + mFxSlotIndex = 2u; + mPropertySetId = EaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot2) + { + mVersion = 5; + mFxSlotIndex = 2u; + mPropertySetId = EaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot3) + { + mVersion = 4; + mFxSlotIndex = 3u; + mPropertySetId = EaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot3) + { + mVersion = 5; + mFxSlotIndex = 3u; + mPropertySetId = EaxCallPropertySetId::fx_slot; + } + else if (property_set_guid == DSPROPSETID_EAX20_BufferProperties) + { + mVersion = 2; + mPropertySetId = EaxCallPropertySetId::source; + } + else if (property_set_guid == DSPROPSETID_EAX30_BufferProperties) + { + mVersion = 3; + mPropertySetId = EaxCallPropertySetId::source; + } + else if (property_set_guid == EAXPROPERTYID_EAX40_Source) + { + mVersion = 4; + mPropertySetId = EaxCallPropertySetId::source; + } + else if (property_set_guid == EAXPROPERTYID_EAX50_Source) + { + mVersion = 5; + mPropertySetId = EaxCallPropertySetId::source; + } + else if (property_set_guid == DSPROPSETID_EAX_ReverbProperties) + { + mVersion = 1; + mFxSlotIndex = 0u; + mPropertySetId = EaxCallPropertySetId::fx_slot_effect; + } + else if (property_set_guid == DSPROPSETID_EAXBUFFER_ReverbProperties) + { + mVersion = 1; + mPropertySetId = EaxCallPropertySetId::source; + } + else + { + fail("Unsupported property set id."); + } + + switch(mPropertyId) + { + case EAXCONTEXT_LASTERROR: + case EAXCONTEXT_SPEAKERCONFIG: + case EAXCONTEXT_EAXSESSION: + case EAXFXSLOT_NONE: + case EAXFXSLOT_ALLPARAMETERS: + case EAXFXSLOT_LOADEFFECT: + case EAXFXSLOT_VOLUME: + case EAXFXSLOT_LOCK: + case EAXFXSLOT_FLAGS: + case EAXFXSLOT_OCCLUSION: + case EAXFXSLOT_OCCLUSIONLFRATIO: + // EAX allow to set "defer" flag on immediate-only properties. + // If we don't clear our flag then "applyAllUpdates" in EAX context won't be called. + mIsDeferred = false; + break; + } + + if(!mIsDeferred) + { + if(mPropertySetId != EaxCallPropertySetId::fx_slot && mPropertyId != 0) + { + if(mPropertyBuffer == nullptr) + fail("Null property buffer."); + + if(mPropertyBufferSize == 0) + fail("Empty property."); + } + } + + if(mPropertySetId == EaxCallPropertySetId::source && mPropertySourceId == 0) + fail("Null AL source id."); + + if(mPropertySetId == EaxCallPropertySetId::fx_slot) + { + if(mPropertyId < EAXFXSLOT_NONE) + mPropertySetId = EaxCallPropertySetId::fx_slot_effect; + } +} + +[[noreturn]] void EaxCall::fail(const char* message) +{ + throw EaxCallException{message}; +} + +[[noreturn]] void EaxCall::fail_too_small() +{ + fail("Property buffer too small."); +} + +EaxCall create_eax_call( + EaxCallType type, + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size) +{ + if(!property_set_id) + throw EaxCallException{"Null property set ID."}; + + return EaxCall{ + type, + *property_set_id, + property_id, + property_source_id, + property_buffer, + property_size + }; +} diff --git a/libs/openal-soft/al/eax/call.h b/libs/openal-soft/al/eax/call.h new file mode 100644 index 00000000..72f96bbe --- /dev/null +++ b/libs/openal-soft/al/eax/call.h @@ -0,0 +1,97 @@ +#ifndef EAX_EAX_CALL_INCLUDED +#define EAX_EAX_CALL_INCLUDED + +#include "AL/al.h" +#include "alnumeric.h" +#include "alspan.h" +#include "api.h" +#include "fx_slot_index.h" + +enum class EaxCallType { + none, + get, + set, +}; // EaxCallType + +enum class EaxCallPropertySetId { + none, + context, + fx_slot, + source, + fx_slot_effect, +}; // EaxCallPropertySetId + +class EaxCall { +public: + EaxCall( + EaxCallType type, + const GUID& property_set_guid, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size); + + [[nodiscard]] auto is_get() const noexcept -> bool { return mCallType == EaxCallType::get; } + [[nodiscard]] auto is_deferred() const noexcept -> bool { return mIsDeferred; } + [[nodiscard]] auto get_version() const noexcept -> int { return mVersion; } + [[nodiscard]] auto get_property_set_id() const noexcept -> EaxCallPropertySetId { return mPropertySetId; } + [[nodiscard]] auto get_property_id() const noexcept -> ALuint { return mPropertyId; } + [[nodiscard]] auto get_property_al_name() const noexcept -> ALuint { return mPropertySourceId; } + [[nodiscard]] auto get_fx_slot_index() const noexcept -> EaxFxSlotIndex { return mFxSlotIndex; } + + template + [[nodiscard]] auto get_value() const -> TValue& + { + if(mPropertyBufferSize < sizeof(TValue)) + fail_too_small(); + + return *static_cast(mPropertyBuffer); + } + + template + [[nodiscard]] auto get_values(size_t max_count) const -> al::span + { + if(max_count == 0 || mPropertyBufferSize < sizeof(TValue)) + fail_too_small(); + + const auto count = std::min(mPropertyBufferSize/sizeof(TValue), max_count); + return {static_cast(mPropertyBuffer), count}; + } + + template + [[nodiscard]] auto get_values() const -> al::span + { + return get_values(~0_uz); + } + + template + auto set_value(const TValue& value) const -> void + { + get_value() = value; + } + +private: + const EaxCallType mCallType; + int mVersion{}; + EaxFxSlotIndex mFxSlotIndex{}; + EaxCallPropertySetId mPropertySetId{EaxCallPropertySetId::none}; + bool mIsDeferred; + + const ALuint mPropertyId; + const ALuint mPropertySourceId; + ALvoid*const mPropertyBuffer; + const ALuint mPropertyBufferSize; + + [[noreturn]] static void fail(const char* message); + [[noreturn]] static void fail_too_small(); +}; // EaxCall + +EaxCall create_eax_call( + EaxCallType type, + const GUID* property_set_id, + ALuint property_id, + ALuint property_source_id, + ALvoid* property_buffer, + ALuint property_size); + +#endif // !EAX_EAX_CALL_INCLUDED diff --git a/libs/openal-soft/al/eax/effect.h b/libs/openal-soft/al/eax/effect.h new file mode 100644 index 00000000..9b1f8bd3 --- /dev/null +++ b/libs/openal-soft/al/eax/effect.h @@ -0,0 +1,457 @@ +#ifndef EAX_EFFECT_INCLUDED +#define EAX_EFFECT_INCLUDED + + +#include +#include +#include + +#include "alnumeric.h" +#include "AL/al.h" +#include "AL/alext.h" +#include "core/effects/base.h" +#include "call.h" + +struct EaxEffectErrorMessages { + static constexpr auto unknown_property_id() noexcept { return "Unknown property id."; } + static constexpr auto unknown_version() noexcept { return "Unknown version."; } +}; // EaxEffectErrorMessages + +using EaxEffectProps = std::variant; + +template +struct overloaded : Ts... { using Ts::operator()...; }; + +template +overloaded(Ts...) -> overloaded; + +constexpr ALenum EnumFromEaxEffectType(const EaxEffectProps &props) +{ + return std::visit(overloaded{ + [](const std::monostate&) noexcept { return AL_EFFECT_NULL; }, + [](const EAXREVERBPROPERTIES&) noexcept { return AL_EFFECT_EAXREVERB; }, + [](const EAXCHORUSPROPERTIES&) noexcept { return AL_EFFECT_CHORUS; }, + [](const EAXAUTOWAHPROPERTIES&) noexcept { return AL_EFFECT_AUTOWAH; }, + [](const EAXAGCCOMPRESSORPROPERTIES&) noexcept { return AL_EFFECT_COMPRESSOR; }, + [](const EAXDISTORTIONPROPERTIES&) noexcept { return AL_EFFECT_DISTORTION; }, + [](const EAXECHOPROPERTIES&) noexcept { return AL_EFFECT_ECHO; }, + [](const EAXEQUALIZERPROPERTIES&) noexcept { return AL_EFFECT_EQUALIZER; }, + [](const EAXFLANGERPROPERTIES&) noexcept { return AL_EFFECT_FLANGER; }, + [](const EAXFREQUENCYSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_FREQUENCY_SHIFTER; }, + [](const EAXRINGMODULATORPROPERTIES&) noexcept { return AL_EFFECT_RING_MODULATOR; }, + [](const EAXPITCHSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_PITCH_SHIFTER; }, + [](const EAXVOCALMORPHERPROPERTIES&) noexcept { return AL_EFFECT_VOCAL_MORPHER; } + }, props); +} + +struct EaxReverbCommitter { + struct Exception; + + EaxReverbCommitter(EaxEffectProps &eaxprops, EffectProps &alprops) + : mEaxProps{eaxprops}, mAlProps{alprops} + { } + + EaxEffectProps &mEaxProps; + EffectProps &mAlProps; + + [[noreturn]] static void fail(const char* message); + [[noreturn]] static void fail_unknown_property_id() + { fail(EaxEffectErrorMessages::unknown_property_id()); } + + template + static void defer(const EaxCall& call, TProperty& property) + { + const auto& value = call.get_value(); + TValidator{}(value); + property = value; + } + + template + static void defer(const EaxCall& call, TProperties& properties, TProperty&) + { + const auto& value = call.get_value(); + TValidator{}(value); + TDeferrer{}(properties, value); + } + + template + static void defer3(const EaxCall& call, EAXREVERBPROPERTIES& properties, TProperty& property) + { + const auto& value = call.get_value(); + TValidator{}(value); + if (value == property) + return; + property = value; + properties.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED; + } + + + bool commit(const EAX_REVERBPROPERTIES &props); + bool commit(const EAX20LISTENERPROPERTIES &props); + bool commit(const EAXREVERBPROPERTIES &props); + + static void SetDefaults(EAX_REVERBPROPERTIES &props); + static void SetDefaults(EAX20LISTENERPROPERTIES &props); + static void SetDefaults(EAXREVERBPROPERTIES &props); + static void SetDefaults(EaxEffectProps &props); + + static void Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props); + static void Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props); + static void Get(const EaxCall &call, const EAXREVERBPROPERTIES &props); + + static void Set(const EaxCall &call, EAX_REVERBPROPERTIES &props); + static void Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props); + static void Set(const EaxCall &call, EAXREVERBPROPERTIES &props); + + static void translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept; + static void translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept; +}; + +template +struct EaxCommitter { + struct Exception; + + EaxCommitter(EaxEffectProps &eaxprops, EffectProps &alprops) + : mEaxProps{eaxprops}, mAlProps{alprops} + { } + + EaxEffectProps &mEaxProps; + EffectProps &mAlProps; + + template + static void defer(const EaxCall& call, TProperty& property) + { + const auto& value = call.get_value(); + TValidator{}(value); + property = value; + } + + [[noreturn]] static void fail(const char *message); + [[noreturn]] static void fail_unknown_property_id() + { fail(EaxEffectErrorMessages::unknown_property_id()); } +}; + +struct EaxAutowahCommitter : public EaxCommitter { + 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: + EaxEffect() noexcept = default; + ~EaxEffect() = default; + + ALenum al_effect_type_{AL_EFFECT_NULL}; + EffectProps al_effect_props_{}; + + using Props1 = EAX_REVERBPROPERTIES; + using Props2 = EAX20LISTENERPROPERTIES; + using Props3 = EAXREVERBPROPERTIES; + using Props4 = EaxEffectProps; + + struct State1 { + Props1 i; // Immediate. + Props1 d; // Deferred. + }; + + struct State2 { + Props2 i; // Immediate. + Props2 d; // Deferred. + }; + + struct State3 { + Props3 i; // Immediate. + Props3 d; // Deferred. + }; + + struct State4 { + Props4 i; // Immediate. + Props4 d; // Deferred. + }; + + int version_{}; + bool changed_{}; + Props4 props_{}; + State1 state1_{}; + State2 state2_{}; + State3 state3_{}; + State4 state4_{}; + State4 state5_{}; + + + static void call_set_defaults(const ALenum altype, EaxEffectProps &props) + { + switch(altype) + { + case AL_EFFECT_EAXREVERB: return EaxReverbCommitter::SetDefaults(props); + case AL_EFFECT_CHORUS: return EaxChorusCommitter::SetDefaults(props); + case AL_EFFECT_AUTOWAH: return EaxAutowahCommitter::SetDefaults(props); + case AL_EFFECT_COMPRESSOR: return EaxCompressorCommitter::SetDefaults(props); + case AL_EFFECT_DISTORTION: return EaxDistortionCommitter::SetDefaults(props); + case AL_EFFECT_ECHO: return EaxEchoCommitter::SetDefaults(props); + case AL_EFFECT_EQUALIZER: return EaxEqualizerCommitter::SetDefaults(props); + case AL_EFFECT_FLANGER: return EaxFlangerCommitter::SetDefaults(props); + case AL_EFFECT_FREQUENCY_SHIFTER: return EaxFrequencyShifterCommitter::SetDefaults(props); + case AL_EFFECT_RING_MODULATOR: return EaxModulatorCommitter::SetDefaults(props); + case AL_EFFECT_PITCH_SHIFTER: return EaxPitchShifterCommitter::SetDefaults(props); + case AL_EFFECT_VOCAL_MORPHER: return EaxVocalMorpherCommitter::SetDefaults(props); + case AL_EFFECT_NULL: break; + } + return EaxNullCommitter::SetDefaults(props); + } + + template + void init() + { + EaxReverbCommitter::SetDefaults(state1_.d); + state1_.i = state1_.d; + EaxReverbCommitter::SetDefaults(state2_.d); + state2_.i = state2_.d; + EaxReverbCommitter::SetDefaults(state3_.d); + state3_.i = state3_.d; + T::SetDefaults(state4_.d); + state4_.i = state4_.d; + T::SetDefaults(state5_.d); + state5_.i = state5_.d; + } + + void set_defaults(int eax_version, ALenum altype) + { + switch(eax_version) + { + case 1: EaxReverbCommitter::SetDefaults(state1_.d); break; + case 2: EaxReverbCommitter::SetDefaults(state2_.d); break; + case 3: EaxReverbCommitter::SetDefaults(state3_.d); break; + case 4: call_set_defaults(altype, state4_.d); break; + case 5: call_set_defaults(altype, state5_.d); break; + } + changed_ = true; + } + + + static void call_set(const EaxCall &call, EaxEffectProps &props) + { + return std::visit([&](auto &arg) + { return CommitterFor::Set(call, arg); }, + props); + } + + void set(const EaxCall &call) + { + switch(call.get_version()) + { + case 1: EaxReverbCommitter::Set(call, state1_.d); break; + case 2: EaxReverbCommitter::Set(call, state2_.d); break; + case 3: EaxReverbCommitter::Set(call, state3_.d); break; + case 4: call_set(call, state4_.d); break; + case 5: call_set(call, state5_.d); break; + } + changed_ = true; + } + + + static void call_get(const EaxCall &call, const EaxEffectProps &props) + { + return std::visit([&](auto &arg) + { return CommitterFor::Get(call, arg); }, + props); + } + + void get(const EaxCall &call) const + { + switch(call.get_version()) + { + case 1: EaxReverbCommitter::Get(call, state1_.d); break; + case 2: EaxReverbCommitter::Get(call, state2_.d); break; + case 3: EaxReverbCommitter::Get(call, state3_.d); break; + case 4: call_get(call, state4_.d); break; + case 5: call_get(call, state5_.d); break; + } + } + + + bool call_commit(const EaxEffectProps &props) + { + return std::visit([&](auto &arg) + { return CommitterFor{props_, al_effect_props_}.commit(arg); }, + props); + } + + bool commit(int eax_version) + { + changed_ |= version_ != eax_version; + if(!changed_) return false; + + bool ret{version_ != eax_version}; + version_ = eax_version; + changed_ = false; + + switch(eax_version) + { + case 1: + state1_.i = state1_.d; + ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state1_.d); + break; + case 2: + state2_.i = state2_.d; + ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state2_.d); + break; + case 3: + state3_.i = state3_.d; + ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state3_.d); + break; + case 4: + state4_.i = state4_.d; + ret |= call_commit(state4_.d); + break; + case 5: + state5_.i = state5_.d; + ret |= call_commit(state5_.d); + break; + } + al_effect_type_ = EnumFromEaxEffectType(props_); + return ret; + } +#undef EAXCALL +}; // EaxEffect + +using EaxEffectUPtr = std::unique_ptr; + +#endif // !EAX_EFFECT_INCLUDED diff --git a/libs/openal-soft/al/eax/exception.cpp b/libs/openal-soft/al/eax/exception.cpp new file mode 100644 index 00000000..e4945d88 --- /dev/null +++ b/libs/openal-soft/al/eax/exception.cpp @@ -0,0 +1,32 @@ +#include "config.h" + +#include "exception.h" + +#include +#include + + +EaxException::EaxException(std::string_view context, std::string_view message) + : std::runtime_error{make_message(context, message)} +{ +} +EaxException::~EaxException() = default; + + +std::string EaxException::make_message(std::string_view context, std::string_view message) +{ + auto what = std::string{}; + if(context.empty() && message.empty()) + return what; + + what.reserve((!context.empty() ? context.size() + 3 : 0) + message.length() + 1); + if(!context.empty()) + { + what += "["; + what += context; + what += "] "; + } + what += message; + + return what; +} diff --git a/libs/openal-soft/al/eax/exception.h b/libs/openal-soft/al/eax/exception.h new file mode 100644 index 00000000..336654f0 --- /dev/null +++ b/libs/openal-soft/al/eax/exception.h @@ -0,0 +1,18 @@ +#ifndef EAX_EXCEPTION_INCLUDED +#define EAX_EXCEPTION_INCLUDED + +#include +#include +#include + + +class EaxException : public std::runtime_error { + static std::string make_message(std::string_view context, std::string_view message); + +public: + EaxException(std::string_view context, std::string_view message); + ~EaxException() override; +}; // EaxException + + +#endif // !EAX_EXCEPTION_INCLUDED diff --git a/libs/openal-soft/al/eax_fx_slot_index.cpp b/libs/openal-soft/al/eax/fx_slot_index.cpp similarity index 95% rename from libs/openal-soft/al/eax_fx_slot_index.cpp rename to libs/openal-soft/al/eax/fx_slot_index.cpp index 9aa695ad..28b11882 100644 --- a/libs/openal-soft/al/eax_fx_slot_index.cpp +++ b/libs/openal-soft/al/eax/fx_slot_index.cpp @@ -1,8 +1,8 @@ #include "config.h" -#include "eax_fx_slot_index.h" +#include "fx_slot_index.h" -#include "eax_exception.h" +#include "exception.h" namespace diff --git a/libs/openal-soft/al/eax_fx_slot_index.h b/libs/openal-soft/al/eax/fx_slot_index.h similarity index 83% rename from libs/openal-soft/al/eax_fx_slot_index.h rename to libs/openal-soft/al/eax/fx_slot_index.h index e1e5475a..9f350d9b 100644 --- a/libs/openal-soft/al/eax_fx_slot_index.h +++ b/libs/openal-soft/al/eax/fx_slot_index.h @@ -3,17 +3,16 @@ #include +#include -#include "aloptional.h" -#include "eax_api.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/libs/openal-soft/al/eax_fx_slots.cpp b/libs/openal-soft/al/eax/fx_slots.cpp similarity index 77% rename from libs/openal-soft/al/eax_fx_slots.cpp rename to libs/openal-soft/al/eax/fx_slots.cpp index 3a27dabd..d04b70df 100644 --- a/libs/openal-soft/al/eax_fx_slots.cpp +++ b/libs/openal-soft/al/eax/fx_slots.cpp @@ -1,12 +1,11 @@ #include "config.h" -#include "eax_fx_slots.h" +#include "fx_slots.h" #include -#include "eax_exception.h" - -#include "eax_api.h" +#include "api.h" +#include "exception.h" namespace @@ -29,8 +28,7 @@ public: } // namespace -void EaxFxSlots::initialize( - ALCcontext& al_context) +void EaxFxSlots::initialize(ALCcontext& al_context) { initialize_fx_slots(al_context); } @@ -57,12 +55,6 @@ ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index) return *fx_slots_[index.value()]; } -void EaxFxSlots::unlock_legacy() noexcept -{ - fx_slots_[0]->eax_unlock_legacy(); - fx_slots_[1]->eax_unlock_legacy(); -} - [[noreturn]] void EaxFxSlots::fail( const char* message) @@ -70,8 +62,7 @@ void EaxFxSlots::fail( throw EaxFxSlotsException{message}; } -void EaxFxSlots::initialize_fx_slots( - ALCcontext& al_context) +void EaxFxSlots::initialize_fx_slots(ALCcontext& al_context) { auto fx_slot_index = EaxFxSlotIndexValue{}; diff --git a/libs/openal-soft/al/eax_fx_slots.h b/libs/openal-soft/al/eax/fx_slots.h similarity index 54% rename from libs/openal-soft/al/eax_fx_slots.h rename to libs/openal-soft/al/eax/fx_slots.h index a104c6ab..b7ed1031 100644 --- a/libs/openal-soft/al/eax_fx_slots.h +++ b/libs/openal-soft/al/eax/fx_slots.h @@ -6,16 +6,15 @@ #include "al/auxeffectslot.h" -#include "eax_api.h" - -#include "eax_fx_slot_index.h" +#include "api.h" +#include "call.h" +#include "fx_slot_index.h" class EaxFxSlots { public: - void initialize( - ALCcontext& al_context); + void initialize(ALCcontext& al_context); void uninitialize() noexcept; @@ -26,14 +25,9 @@ public: } - const ALeffectslot& get( - EaxFxSlotIndex index) const; - - ALeffectslot& get( - EaxFxSlotIndex index); - - void unlock_legacy() noexcept; + [[nodiscard]] auto get(EaxFxSlotIndex index) const -> const ALeffectslot&; + [[nodiscard]] auto get(EaxFxSlotIndex index) -> ALeffectslot&; private: using Items = std::array; @@ -43,11 +37,9 @@ private: [[noreturn]] - static void fail( - const char* message); + static void fail(const char* message); - void initialize_fx_slots( - ALCcontext& al_context); + void initialize_fx_slots(ALCcontext& al_context); }; // EaxFxSlots diff --git a/libs/openal-soft/al/eax/globals.h b/libs/openal-soft/al/eax/globals.h new file mode 100644 index 00000000..6f3e9078 --- /dev/null +++ b/libs/openal-soft/al/eax/globals.h @@ -0,0 +1,6 @@ +#ifndef EAX_GLOBALS_INCLUDED +#define EAX_GLOBALS_INCLUDED + +inline bool eax_g_is_enabled{true}; + +#endif /* EAX_GLOBALS_INCLUDED */ diff --git a/libs/openal-soft/al/eax/utils.cpp b/libs/openal-soft/al/eax/utils.cpp new file mode 100644 index 00000000..871ab764 --- /dev/null +++ b/libs/openal-soft/al/eax/utils.cpp @@ -0,0 +1,26 @@ +#include "config.h" + +#include "utils.h" + +#include +#include + +#include "alstring.h" +#include "core/logging.h" + + +void eax_log_exception(std::string_view message) noexcept +{ + const auto exception_ptr = std::current_exception(); + assert(exception_ptr); + + try { + std::rethrow_exception(exception_ptr); + } + catch(const std::exception& ex) { + ERR("%.*s %s\n", al::sizei(message), message.data(), ex.what()); + } + catch(...) { + ERR("%.*s %s\n", al::sizei(message), message.data(), "Generic exception."); + } +} diff --git a/libs/openal-soft/al/eax_utils.h b/libs/openal-soft/al/eax/utils.h similarity index 65% rename from libs/openal-soft/al/eax_utils.h rename to libs/openal-soft/al/eax/utils.h index d3d4a196..8e0f975f 100644 --- a/libs/openal-soft/al/eax_utils.h +++ b/libs/openal-soft/al/eax/utils.h @@ -4,34 +4,26 @@ #include #include #include +#include #include +#include "opthelpers.h" -struct EaxAlLowPassParam -{ +using EaxDirtyFlags = unsigned int; + +struct EaxAlLowPassParam { float gain; float gain_hf; -}; // EaxAlLowPassParam +}; +void eax_log_exception(std::string_view message) noexcept; -void eax_log_exception( - const char* message = nullptr) noexcept; - - -template< - typename TException, - typename TValue -> -void eax_validate_range( - const char* value_name, - const TValue& value, - const TValue& min_value, +template +void eax_validate_range(std::string_view value_name, const TValue& value, const TValue& min_value, const TValue& max_value) { - if (value >= min_value && value <= max_value) - { + if(value >= min_value && value <= max_value) LIKELY return; - } const auto message = std::string{value_name} + @@ -43,60 +35,38 @@ void eax_validate_range( throw TException{message.c_str()}; } +namespace detail { -namespace detail -{ - - -template< - typename T -> -struct EaxIsBitFieldStruct -{ +template +struct EaxIsBitFieldStruct { private: using yes = std::true_type; using no = std::false_type; - template< - typename U - > + template static auto test(int) -> decltype(std::declval(), yes{}); - template< - typename - > + template static no test(...); - public: static constexpr auto value = std::is_same(0)), yes>::value; -}; // EaxIsBitFieldStruct +}; - -template< - typename T, - typename TValue -> -inline bool eax_bit_fields_are_equal( - const T& lhs, - const T& rhs) noexcept +template +inline bool eax_bit_fields_are_equal(const T& lhs, const T& rhs) noexcept { static_assert(sizeof(T) == sizeof(TValue), "Invalid type size."); - return reinterpret_cast(lhs) == reinterpret_cast(rhs); } - } // namespace detail - template< typename T, std::enable_if_t::value, int> = 0 > -inline bool operator==( - const T& lhs, - const T& rhs) noexcept +inline bool operator==(const T& lhs, const T& rhs) noexcept { using Value = std::conditional_t< sizeof(T) == 1, @@ -107,13 +77,9 @@ inline bool operator==( std::conditional_t< sizeof(T) == 4, std::uint32_t, - void - > - > - >; + void>>>; static_assert(!std::is_same::value, "Unsupported type."); - return detail::eax_bit_fields_are_equal(lhs, rhs); } @@ -121,12 +87,9 @@ template< typename T, std::enable_if_t::value, int> = 0 > -inline bool operator!=( - const T& lhs, - const T& rhs) noexcept +inline bool operator!=(const T& lhs, const T& rhs) noexcept { return !(lhs == rhs); } - #endif // !EAX_UTILS_INCLUDED diff --git a/libs/openal-soft/al/eax_x_ram.h b/libs/openal-soft/al/eax/x_ram.h similarity index 63% rename from libs/openal-soft/al/eax_x_ram.h rename to libs/openal-soft/al/eax/x_ram.h index 438b9916..39bb778b 100644 --- a/libs/openal-soft/al/eax_x_ram.h +++ b/libs/openal-soft/al/eax/x_ram.h @@ -24,15 +24,12 @@ constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC"; constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE"; constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE"; - -ALboolean AL_APIENTRY EAXSetBufferMode( - ALsizei n, - const ALuint* buffers, - ALint value); - -ALenum AL_APIENTRY EAXGetBufferMode( - ALuint buffer, - ALint* pReserved); - +/* NOLINTBEGIN(readability-inconsistent-declaration-parameter-name) + * These functions are defined using macros to forward them in a generic way to + * implementation functions, which gives the parameters generic names. + */ +ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint *buffers, ALint value) noexcept; +ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint *pReserved) noexcept; +/* NOLINTEND(readability-inconsistent-declaration-parameter-name) */ #endif // !EAX_X_RAM_INCLUDED diff --git a/libs/openal-soft/al/eax_eax_call.cpp b/libs/openal-soft/al/eax_eax_call.cpp deleted file mode 100644 index 914d2fbf..00000000 --- a/libs/openal-soft/al/eax_eax_call.cpp +++ /dev/null @@ -1,324 +0,0 @@ -#include "config.h" - -#include "al/eax_eax_call.h" - -#include "al/eax_exception.h" - - -namespace { - -constexpr auto deferred_flag = 0x80000000U; - -class EaxEaxCallException : - public EaxException -{ -public: - explicit EaxEaxCallException( - const char* message) - : - EaxException{"EAX_EAX_CALL", message} - { - } -}; // EaxEaxCallException - -} // namespace - - -EaxEaxCall::EaxEaxCall( - bool is_get, - const GUID& property_set_guid, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_buffer, - ALuint property_size) - : is_get_{is_get}, version_{0}, property_set_id_{EaxEaxCallPropertySetId::none} - , property_id_{property_id & ~deferred_flag}, property_source_id_{property_source_id} - , property_buffer_{property_buffer}, property_size_{property_size} -{ - if (false) - { - } - else if (property_set_guid == EAXPROPERTYID_EAX40_Context) - { - version_ = 4; - property_set_id_ = EaxEaxCallPropertySetId::context; - } - else if (property_set_guid == EAXPROPERTYID_EAX50_Context) - { - version_ = 5; - property_set_id_ = EaxEaxCallPropertySetId::context; - } - else if (property_set_guid == DSPROPSETID_EAX20_ListenerProperties) - { - version_ = 2; - fx_slot_index_ = 0u; - property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; - property_id_ = convert_eax_v2_0_listener_property_id(property_id_); - } - else if (property_set_guid == DSPROPSETID_EAX30_ListenerProperties) - { - version_ = 3; - fx_slot_index_ = 0u; - property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; - } - else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot0) - { - version_ = 4; - fx_slot_index_ = 0u; - property_set_id_ = EaxEaxCallPropertySetId::fx_slot; - } - else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot0) - { - version_ = 5; - fx_slot_index_ = 0u; - property_set_id_ = EaxEaxCallPropertySetId::fx_slot; - } - else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot1) - { - version_ = 4; - fx_slot_index_ = 1u; - property_set_id_ = EaxEaxCallPropertySetId::fx_slot; - } - else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot1) - { - version_ = 5; - fx_slot_index_ = 1u; - property_set_id_ = EaxEaxCallPropertySetId::fx_slot; - } - else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot2) - { - version_ = 4; - fx_slot_index_ = 2u; - property_set_id_ = EaxEaxCallPropertySetId::fx_slot; - } - else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot2) - { - version_ = 5; - fx_slot_index_ = 2u; - property_set_id_ = EaxEaxCallPropertySetId::fx_slot; - } - else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot3) - { - version_ = 4; - fx_slot_index_ = 3u; - property_set_id_ = EaxEaxCallPropertySetId::fx_slot; - } - else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot3) - { - version_ = 5; - fx_slot_index_ = 3u; - property_set_id_ = EaxEaxCallPropertySetId::fx_slot; - } - else if (property_set_guid == DSPROPSETID_EAX20_BufferProperties) - { - version_ = 2; - property_set_id_ = EaxEaxCallPropertySetId::source; - property_id_ = convert_eax_v2_0_buffer_property_id(property_id_); - } - else if (property_set_guid == DSPROPSETID_EAX30_BufferProperties) - { - version_ = 3; - property_set_id_ = EaxEaxCallPropertySetId::source; - } - else if (property_set_guid == EAXPROPERTYID_EAX40_Source) - { - version_ = 4; - property_set_id_ = EaxEaxCallPropertySetId::source; - } - else if (property_set_guid == EAXPROPERTYID_EAX50_Source) - { - version_ = 5; - property_set_id_ = EaxEaxCallPropertySetId::source; - } - else if (property_set_guid == DSPROPSETID_EAX_ReverbProperties) - { - version_ = 1; - fx_slot_index_ = 0u; - property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; - } - else if (property_set_guid == DSPROPSETID_EAXBUFFER_ReverbProperties) - { - version_ = 1; - property_set_id_ = EaxEaxCallPropertySetId::source; - } - else - { - fail("Unsupported property set id."); - } - - if (version_ < 1 || version_ > 5) - { - fail("EAX version out of range."); - } - - if(!(property_id&deferred_flag)) - { - if(property_set_id_ != EaxEaxCallPropertySetId::fx_slot && property_id_ != 0) - { - if (!property_buffer) - { - fail("Null property buffer."); - } - - if (property_size == 0) - { - fail("Empty property."); - } - } - } - - if(property_set_id_ == EaxEaxCallPropertySetId::source && property_source_id_ == 0) - { - fail("Null AL source id."); - } - - if (property_set_id_ == EaxEaxCallPropertySetId::fx_slot) - { - if (property_id_ < EAXFXSLOT_NONE) - { - property_set_id_ = EaxEaxCallPropertySetId::fx_slot_effect; - } - } -} - -[[noreturn]] -void EaxEaxCall::fail( - const char* message) -{ - throw EaxEaxCallException{message}; -} - -ALuint EaxEaxCall::convert_eax_v2_0_listener_property_id( - ALuint property_id) -{ - switch (property_id) - { - case DSPROPERTY_EAX20LISTENER_NONE: - return EAXREVERB_NONE; - - case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: - return EAXREVERB_ALLPARAMETERS; - - case DSPROPERTY_EAX20LISTENER_ROOM: - return EAXREVERB_ROOM; - - case DSPROPERTY_EAX20LISTENER_ROOMHF: - return EAXREVERB_ROOMHF; - - case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: - return EAXREVERB_ROOMROLLOFFFACTOR; - - case DSPROPERTY_EAX20LISTENER_DECAYTIME: - return EAXREVERB_DECAYTIME; - - case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: - return EAXREVERB_DECAYHFRATIO; - - case DSPROPERTY_EAX20LISTENER_REFLECTIONS: - return EAXREVERB_REFLECTIONS; - - case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: - return EAXREVERB_REFLECTIONSDELAY; - - case DSPROPERTY_EAX20LISTENER_REVERB: - return EAXREVERB_REVERB; - - case DSPROPERTY_EAX20LISTENER_REVERBDELAY: - return EAXREVERB_REVERBDELAY; - - case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: - return EAXREVERB_ENVIRONMENT; - - case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: - return EAXREVERB_ENVIRONMENTSIZE; - - case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: - return EAXREVERB_ENVIRONMENTDIFFUSION; - - case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: - return EAXREVERB_AIRABSORPTIONHF; - - case DSPROPERTY_EAX20LISTENER_FLAGS: - return EAXREVERB_FLAGS; - - default: - fail("Unsupported EAX 2.0 listener property id."); - } -} - -ALuint EaxEaxCall::convert_eax_v2_0_buffer_property_id( - ALuint property_id) -{ - switch (property_id) - { - case DSPROPERTY_EAX20BUFFER_NONE: - return EAXSOURCE_NONE; - - case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS: - return EAXSOURCE_ALLPARAMETERS; - - case DSPROPERTY_EAX20BUFFER_DIRECT: - return EAXSOURCE_DIRECT; - - case DSPROPERTY_EAX20BUFFER_DIRECTHF: - return EAXSOURCE_DIRECTHF; - - case DSPROPERTY_EAX20BUFFER_ROOM: - return EAXSOURCE_ROOM; - - case DSPROPERTY_EAX20BUFFER_ROOMHF: - return EAXSOURCE_ROOMHF; - - case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR: - return EAXSOURCE_ROOMROLLOFFFACTOR; - - case DSPROPERTY_EAX20BUFFER_OBSTRUCTION: - return EAXSOURCE_OBSTRUCTION; - - case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO: - return EAXSOURCE_OBSTRUCTIONLFRATIO; - - case DSPROPERTY_EAX20BUFFER_OCCLUSION: - return EAXSOURCE_OCCLUSION; - - case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO: - return EAXSOURCE_OCCLUSIONLFRATIO; - - case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO: - return EAXSOURCE_OCCLUSIONROOMRATIO; - - case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF: - return EAXSOURCE_OUTSIDEVOLUMEHF; - - case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR: - return EAXSOURCE_AIRABSORPTIONFACTOR; - - case DSPROPERTY_EAX20BUFFER_FLAGS: - return EAXSOURCE_FLAGS; - - default: - fail("Unsupported EAX 2.0 buffer property id."); - } -} - - -EaxEaxCall create_eax_call( - bool is_get, - const GUID* property_set_id, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_buffer, - ALuint property_size) -{ - if(!property_set_id) - throw EaxEaxCallException{"Null property set ID."}; - - return EaxEaxCall{ - is_get, - *property_set_id, - property_id, - property_source_id, - property_buffer, - property_size - }; -} diff --git a/libs/openal-soft/al/eax_eax_call.h b/libs/openal-soft/al/eax_eax_call.h deleted file mode 100644 index 7b990d87..00000000 --- a/libs/openal-soft/al/eax_eax_call.h +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef EAX_EAX_CALL_INCLUDED -#define EAX_EAX_CALL_INCLUDED - - -#include "AL/al.h" - -#include "alspan.h" - -#include "eax_api.h" -#include "eax_fx_slot_index.h" - - -enum class EaxEaxCallPropertySetId -{ - none, - - context, - fx_slot, - source, - fx_slot_effect, -}; // EaxEaxCallPropertySetId - - -class EaxEaxCall -{ -public: - EaxEaxCall( - bool is_get, - const GUID& property_set_guid, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_buffer, - ALuint property_size); - - bool is_get() const noexcept { return is_get_; } - int get_version() const noexcept { return version_; } - EaxEaxCallPropertySetId get_property_set_id() const noexcept { return property_set_id_; } - ALuint get_property_id() const noexcept { return property_id_; } - ALuint get_property_al_name() const noexcept { return property_source_id_; } - EaxFxSlotIndex get_fx_slot_index() const noexcept { return fx_slot_index_; } - - template< - typename TException, - typename TValue - > - TValue& get_value() const - { - if (property_size_ < static_cast(sizeof(TValue))) - { - throw TException{"Property buffer too small."}; - } - - return *static_cast(property_buffer_); - } - - template< - typename TException, - typename TValue - > - al::span get_values() const - { - if (property_size_ < static_cast(sizeof(TValue))) - { - throw TException{"Property buffer too small."}; - } - - const auto count = property_size_ / sizeof(TValue); - - return al::span{static_cast(property_buffer_), count}; - } - - template< - typename TException, - typename TValue - > - void set_value( - const TValue& value) const - { - get_value() = value; - } - - -private: - const bool is_get_; - int version_; - EaxFxSlotIndex fx_slot_index_; - EaxEaxCallPropertySetId property_set_id_; - - ALuint property_id_; - const ALuint property_source_id_; - ALvoid*const property_buffer_; - const ALuint property_size_; - - - [[noreturn]] - static void fail( - const char* message); - - - static ALuint convert_eax_v2_0_listener_property_id( - ALuint property_id); - - static ALuint convert_eax_v2_0_buffer_property_id( - ALuint property_id); -}; // EaxEaxCall - - -EaxEaxCall create_eax_call( - bool is_get, - const GUID* property_set_id, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_buffer, - ALuint property_size); - - -#endif // !EAX_EAX_CALL_INCLUDED diff --git a/libs/openal-soft/al/eax_effect.cpp b/libs/openal-soft/al/eax_effect.cpp deleted file mode 100644 index 9cbf4c13..00000000 --- a/libs/openal-soft/al/eax_effect.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "config.h" - -#include "eax_effect.h" diff --git a/libs/openal-soft/al/eax_effect.h b/libs/openal-soft/al/eax_effect.h deleted file mode 100644 index 45315ca6..00000000 --- a/libs/openal-soft/al/eax_effect.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef EAX_EFFECT_INCLUDED -#define EAX_EFFECT_INCLUDED - - -#include - -#include "AL/al.h" -#include "core/effects/base.h" -#include "eax_eax_call.h" - -class EaxEffect -{ -public: - EaxEffect(ALenum type) : al_effect_type_{type} { } - virtual ~EaxEffect() = default; - - const ALenum al_effect_type_; - EffectProps al_effect_props_{}; - - virtual void dispatch(const EaxEaxCall& eax_call) = 0; - - // Returns "true" if any immediated property was changed. - // [[nodiscard]] - virtual bool apply_deferred() = 0; -}; // EaxEffect - - -using EaxEffectUPtr = std::unique_ptr; - -EaxEffectUPtr eax_create_eax_null_effect(); -EaxEffectUPtr eax_create_eax_chorus_effect(); -EaxEffectUPtr eax_create_eax_distortion_effect(); -EaxEffectUPtr eax_create_eax_echo_effect(); -EaxEffectUPtr eax_create_eax_flanger_effect(); -EaxEffectUPtr eax_create_eax_frequency_shifter_effect(); -EaxEffectUPtr eax_create_eax_vocal_morpher_effect(); -EaxEffectUPtr eax_create_eax_pitch_shifter_effect(); -EaxEffectUPtr eax_create_eax_ring_modulator_effect(); -EaxEffectUPtr eax_create_eax_auto_wah_effect(); -EaxEffectUPtr eax_create_eax_compressor_effect(); -EaxEffectUPtr eax_create_eax_equalizer_effect(); -EaxEffectUPtr eax_create_eax_reverb_effect(); - -#endif // !EAX_EFFECT_INCLUDED diff --git a/libs/openal-soft/al/eax_exception.cpp b/libs/openal-soft/al/eax_exception.cpp deleted file mode 100644 index fdeecaaa..00000000 --- a/libs/openal-soft/al/eax_exception.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "config.h" - -#include "eax_exception.h" - -#include - -#include - - -EaxException::EaxException( - const char* context, - const char* message) - : - std::runtime_error{make_message(context, message)} -{ -} - -std::string EaxException::make_message( - const char* context, - const char* message) -{ - const auto context_size = (context ? std::string::traits_type::length(context) : 0); - const auto has_contex = (context_size > 0); - - const auto message_size = (message ? std::string::traits_type::length(message) : 0); - const auto has_message = (message_size > 0); - - if (!has_contex && !has_message) - { - return std::string{}; - } - - static constexpr char left_prefix[] = "["; - const auto left_prefix_size = std::string::traits_type::length(left_prefix); - - static constexpr char right_prefix[] = "] "; - const auto right_prefix_size = std::string::traits_type::length(right_prefix); - - const auto what_size = - ( - has_contex ? - left_prefix_size + context_size + right_prefix_size : - 0) + - message_size + - 1; - - auto what = std::string{}; - what.reserve(what_size); - - if (has_contex) - { - what.append(left_prefix, left_prefix_size); - what.append(context, context_size); - what.append(right_prefix, right_prefix_size); - } - - if (has_message) - { - what.append(message, message_size); - } - - return what; -} diff --git a/libs/openal-soft/al/eax_exception.h b/libs/openal-soft/al/eax_exception.h deleted file mode 100644 index 9a7acf71..00000000 --- a/libs/openal-soft/al/eax_exception.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef EAX_EXCEPTION_INCLUDED -#define EAX_EXCEPTION_INCLUDED - - -#include -#include - - -class EaxException : - public std::runtime_error -{ -public: - EaxException( - const char* context, - const char* message); - - -private: - static std::string make_message( - const char* context, - const char* message); -}; // EaxException - - -#endif // !EAX_EXCEPTION_INCLUDED diff --git a/libs/openal-soft/al/eax_globals.cpp b/libs/openal-soft/al/eax_globals.cpp deleted file mode 100644 index 07909459..00000000 --- a/libs/openal-soft/al/eax_globals.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "config.h" - -#include "eax_globals.h" - - -bool eax_g_is_enabled = true; - - -const char eax1_ext_name[] = "EAX"; -const char eax2_ext_name[] = "EAX2.0"; -const char eax3_ext_name[] = "EAX3.0"; -const char eax4_ext_name[] = "EAX4.0"; -const char eax5_ext_name[] = "EAX5.0"; - -const char eax_x_ram_ext_name[] = "EAX-RAM"; - -const char eax_eax_set_func_name[] = "EAXSet"; -const char eax_eax_get_func_name[] = "EAXGet"; - -const char eax_eax_set_buffer_mode_func_name[] = "EAXSetBufferMode"; -const char eax_eax_get_buffer_mode_func_name[] = "EAXGetBufferMode"; diff --git a/libs/openal-soft/al/eax_globals.h b/libs/openal-soft/al/eax_globals.h deleted file mode 100644 index 1b4d63b8..00000000 --- a/libs/openal-soft/al/eax_globals.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef EAX_GLOBALS_INCLUDED -#define EAX_GLOBALS_INCLUDED - - -extern bool eax_g_is_enabled; - - -extern const char eax1_ext_name[]; -extern const char eax2_ext_name[]; -extern const char eax3_ext_name[]; -extern const char eax4_ext_name[]; -extern const char eax5_ext_name[]; - -extern const char eax_x_ram_ext_name[]; - -extern const char eax_eax_set_func_name[]; -extern const char eax_eax_get_func_name[]; - -extern const char eax_eax_set_buffer_mode_func_name[]; -extern const char eax_eax_get_buffer_mode_func_name[]; - -#endif // !EAX_GLOBALS_INCLUDED diff --git a/libs/openal-soft/al/eax_utils.cpp b/libs/openal-soft/al/eax_utils.cpp deleted file mode 100644 index 67389de4..00000000 --- a/libs/openal-soft/al/eax_utils.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "config.h" - -#include "eax_utils.h" - -#include -#include - -#include "core/logging.h" - - -void eax_log_exception( - const char* message) noexcept -{ - const auto exception_ptr = std::current_exception(); - - assert(exception_ptr); - - if (message) - { - ERR("%s\n", message); - } - - try - { - std::rethrow_exception(exception_ptr); - } - catch (const std::exception& ex) - { - const auto ex_message = ex.what(); - ERR("%s\n", ex_message); - } - catch (...) - { - ERR("%s\n", "Generic exception."); - } -} diff --git a/libs/openal-soft/al/eax_x_ram.cpp b/libs/openal-soft/al/eax_x_ram.cpp deleted file mode 100644 index ac3e7ebb..00000000 --- a/libs/openal-soft/al/eax_x_ram.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "config.h" - -#include "eax_x_ram.h" diff --git a/libs/openal-soft/al/effect.cpp b/libs/openal-soft/al/effect.cpp index 5a74ca53..54c8e0c3 100644 --- a/libs/openal-soft/al/effect.cpp +++ b/libs/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,117 +74,69 @@ const EffectList gEffectList[16]{ { "vmorpher", VMORPHER_EFFECT, AL_EFFECT_VOCAL_MORPHER }, { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT }, { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE }, - { "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_REVERB_SOFT }, -}; + { "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); -} 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)) ? std::addressof(*iter) : nullptr; + switch(type) + { + case AL_EFFECT_NULL: return NullEffectProps; + case AL_EFFECT_EAXREVERB: return ReverbEffectProps; + case AL_EFFECT_REVERB: return StdReverbEffectProps; + case AL_EFFECT_AUTOWAH: return AutowahEffectProps; + case AL_EFFECT_CHORUS: return ChorusEffectProps; + case AL_EFFECT_COMPRESSOR: return CompressorEffectProps; + case AL_EFFECT_DISTORTION: return DistortionEffectProps; + case AL_EFFECT_ECHO: return EchoEffectProps; + case AL_EFFECT_EQUALIZER: return EqualizerEffectProps; + case AL_EFFECT_FLANGER: return FlangerEffectProps; + case AL_EFFECT_FREQUENCY_SHIFTER: return FshifterEffectProps; + case AL_EFFECT_RING_MODULATOR: return ModulatorEffectProps; + case AL_EFFECT_PITCH_SHIFTER: return PshifterEffectProps; + case AL_EFFECT_VOCAL_MORPHER: return VmorpherEffectProps; + case AL_EFFECT_DEDICATED_DIALOGUE: return DedicatedDialogEffectProps; + case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return DedicatedLfeEffectProps; + case AL_EFFECT_CONVOLUTION_SOFT: return ConvolutionEffectProps; + } + return NullEffectProps; } -void InitEffectParams(ALeffect *effect, ALenum type) +void InitEffectParams(ALeffect *effect, ALenum type) noexcept { - const EffectPropsItem *item{getEffectPropsItemByType(type)}; - if(item) - { - effect->Props = item->DefaultProps; - effect->vtab = &item->Vtable; - } - else - { - effect->Props = EffectProps{}; - effect->vtab = &NullEffectVtable; - } + effect->Props = GetDefaultProps(type); effect->type = type; } -bool EnsureEffects(ALCdevice *device, size_t needed) -{ - size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), size_t{0}, +auto EnsureEffects(ALCdevice *device, size_t needed) noexcept -> bool +try { + size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz, [](size_t cur, const EffectSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; while(needed > count) { - if UNLIKELY(device->EffectList.size() >= 1<<25) + if(device->EffectList.size() >= 1<<25) UNLIKELY return false; - device->EffectList.emplace_back(); - auto sublist = device->EffectList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Effects = static_cast(al_calloc(alignof(ALeffect), sizeof(ALeffect)*64)); - if UNLIKELY(!sublist->Effects) - { - device->EffectList.pop_back(); - return false; - } - count += 64; + EffectSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.Effects = SubListAllocator{}.allocate(1); + device->EffectList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } return true; } +catch(...) { + return false; +} -ALeffect *AllocEffect(ALCdevice *device) +ALeffect *AllocEffect(ALCdevice *device) noexcept { auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(), [](const EffectSubList &entry) noexcept -> bool @@ -192,7 +145,7 @@ ALeffect *AllocEffect(ALCdevice *device) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALeffect *effect{al::construct_at(sublist->Effects + slidx)}; + ALeffect *effect{al::construct_at(al::to_address(sublist->Effects->begin() + slidx))}; InitEffectParams(effect, AL_EFFECT_NULL); /* Add 1 to avoid effect ID 0. */ @@ -205,339 +158,347 @@ ALeffect *AllocEffect(ALCdevice *device) void FreeEffect(ALCdevice *device, ALeffect *effect) { + device->mEffectNames.erase(effect->id); + const ALuint id{effect->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; - al::destroy_at(effect); + std::destroy_at(effect); device->EffectList[lidx].FreeMask |= 1_u64 << slidx; } -inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) +inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= device->EffectList.size()) + if(lidx >= device->EffectList.size()) UNLIKELY return nullptr; EffectSubList &sublist = device->EffectList[lidx]; - if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Effects + slidx; + return al::to_address(sublist.Effects->begin() + slidx); } } // namespace -AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Generating %d effects", n); - if UNLIKELY(n <= 0) return; +AL_API DECL_FUNC2(void, alGenEffects, ALsizei,n, ALuint*,effects) +FORCE_ALIGN void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Generating %d effects", n}; + if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{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 LIKELY(n == 1) - { - /* Special handling for the easy and normal case. */ - ALeffect *effect{AllocEffect(device)}; - effects[0] = effect->id; - } - else - { - /* Store the allocated buffer IDs in a separate local list, to avoid - * modifying the user storage in case of failure. - */ - al::vector 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 UNLIKELY(!context) return; - - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Deleting %d effects", n); - if UNLIKELY(n <= 0) return; +AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects) +FORCE_ALIGN void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei n, + const ALuint *effects) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Deleting %d effects", n}; + if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{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 UNLIKELY(inveffect != effects_end) - { - 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 LIKELY(context) - { - 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 UNLIKELY(!context) return; +AL_API DECL_FUNC3(void, alEffecti, ALuint,effect, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, + ALint value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else if(param == AL_EFFECT_TYPE) + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + switch(param) { - bool isOk{value == AL_EFFECT_NULL}; - if(!isOk) + case AL_EFFECT_TYPE: + if(value != AL_EFFECT_NULL) { - for(const EffectList &effectitem : gEffectList) - { - if(value == effectitem.val && !DisabledEffects[effectitem.type]) - { - isOk = true; - break; - } - } + auto check_effect = [value](const EffectList &item) -> bool + { return value == item.val && !DisabledEffects.test(item.type); }; + if(!std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect)) + throw al::context_error{AL_INVALID_VALUE, "Effect type 0x%04x not supported", + value}; } - if(isOk) - InitEffectParams(aleffect, value); - else - context->setError(AL_INVALID_VALUE, "Effect type 0x%04x not supported", value); - } - 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 UNLIKELY(!context) return; + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) + { + using Type = std::remove_cv_t>; + if constexpr(std::is_same_v) + { + if(aleffect->type == AL_EFFECT_REVERB) + return EffectHandler::StdReverbSetParami(arg, param, value); + } + return EffectHandler::SetParami(arg, param, value); + }, aleffect->Props); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +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 UNLIKELY(!aleffect) - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) { - /* Call the appropriate handler */ - ALeffect_setParamiv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + if constexpr(std::is_same_v) + { + if(aleffect->type == AL_EFFECT_REVERB) + return EffectHandler::StdReverbSetParamiv(arg, param, values); + } + return EffectHandler::SetParamiv(arg, param, values); + }, aleffect->Props); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; +AL_API DECL_FUNC3(void, alEffectf, ALuint,effect, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, + ALfloat value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) UNLIKELY + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) { - /* Call the appropriate handler */ - ALeffect_setParamf(aleffect, param, value); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + if constexpr(std::is_same_v) + { + if(aleffect->type == AL_EFFECT_REVERB) + return EffectHandler::StdReverbSetParamf(arg, param, value); + } + return EffectHandler::SetParamf(arg, param, value); + }, aleffect->Props); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; +AL_API DECL_FUNC3(void, alEffectfv, ALuint,effect, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, + const ALfloat *values) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) { - /* Call the appropriate handler */ - ALeffect_setParamfv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + if constexpr(std::is_same_v) + { + if(aleffect->type == AL_EFFECT_REVERB) + return EffectHandler::StdReverbSetParamfv(arg, param, values); + } + return EffectHandler::SetParamfv(arg, param, values); + }, aleffect->Props); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; +AL_API DECL_FUNC3(void, alGetEffecti, ALuint,effect, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, + ALint *value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else if(param == AL_EFFECT_TYPE) + 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 UNLIKELY(!context) return; + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) + { + using Type = std::remove_cv_t>; + if constexpr(std::is_same_v) + { + if(aleffect->type == AL_EFFECT_REVERB) + return EffectHandler::StdReverbGetParami(arg, param, value); + } + return EffectHandler::GetParami(arg, param, value); + }, aleffect->Props); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +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 UNLIKELY(!aleffect) - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) { - /* Call the appropriate handler */ - ALeffect_getParamiv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + if constexpr(std::is_same_v) + { + if(aleffect->type == AL_EFFECT_REVERB) + return EffectHandler::StdReverbGetParamiv(arg, param, values); + } + return EffectHandler::GetParamiv(arg, param, values); + }, aleffect->Props); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; +AL_API DECL_FUNC3(void, alGetEffectf, ALuint,effect, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, + ALfloat *value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) { - /* Call the appropriate handler */ - ALeffect_getParamf(aleffect, param, value); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + if constexpr(std::is_same_v) + { + if(aleffect->type == AL_EFFECT_REVERB) + return EffectHandler::StdReverbGetParamf(arg, param, value); + } + return EffectHandler::GetParamf(arg, param, value); + }, aleffect->Props); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; +AL_API DECL_FUNC3(void, alGetEffectfv, ALuint,effect, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, + ALfloat *values) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if UNLIKELY(!aleffect) - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) { - /* Call the appropriate handler */ - ALeffect_getParamfv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + if constexpr(std::is_same_v) + { + if(aleffect->type == AL_EFFECT_REVERB) + return EffectHandler::StdReverbGetParamfv(arg, param, values); + } + return EffectHandler::GetParamfv(arg, param, values); + }, aleffect->Props); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC void InitEffect(ALeffect *effect) @@ -545,26 +506,43 @@ void InitEffect(ALeffect *effect) InitEffectParams(effect, AL_EFFECT_NULL); } +void ALeffect::SetName(ALCcontext* context, ALuint id, std::string_view name) +{ + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard effectlock{device->EffectLock}; + + auto effect = LookupEffect(device, id); + if(!effect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", id}; + + device->mEffectNames.insert_or_assign(id, name); +} + + EffectSubList::~EffectSubList() { + if(!Effects) + return; + uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; - al::destroy_at(Effects+idx); + std::destroy_at(al::to_address(Effects->begin()+idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; - al_free(Effects); + SubListAllocator{}.deallocate(Effects, 1); Effects = nullptr; } -#define DECL(x) { #x, EFX_REVERB_PRESET_##x } -static const struct { - const char name[32]; +struct EffectPreset { + const char name[32]; /* NOLINT(*-avoid-c-arrays) */ EFXEAXREVERBPROPERTIES props; -} reverblist[] = { +}; +#define DECL(x) EffectPreset{#x, EFX_REVERB_PRESET_##x} +static constexpr std::array reverblist{ DECL(GENERIC), DECL(PADDEDCELL), DECL(ROOM), @@ -694,61 +672,62 @@ static const struct { }; #undef DECL -void LoadReverbPreset(const char *name, ALeffect *effect) +void LoadReverbPreset(const std::string_view name, ALeffect *effect) { - if(al::strcasecmp(name, "NONE") == 0) + using namespace std::string_view_literals; + + if(al::case_compare(name, "NONE"sv) == 0) { InitEffectParams(effect, AL_EFFECT_NULL); TRACE("Loading reverb '%s'\n", "NONE"); return; } - if(!DisabledEffects[EAXREVERB_EFFECT]) + if(!DisabledEffects.test(EAXREVERB_EFFECT)) InitEffectParams(effect, AL_EFFECT_EAXREVERB); - else if(!DisabledEffects[REVERB_EFFECT]) + else if(!DisabledEffects.test(REVERB_EFFECT)) InitEffectParams(effect, AL_EFFECT_REVERB); else InitEffectParams(effect, AL_EFFECT_NULL); for(const auto &reverbitem : reverblist) { - const EFXEAXREVERBPROPERTIES *props; - - if(al::strcasecmp(name, reverbitem.name) != 0) + if(al::case_compare(name, std::data(reverbitem.name)) != 0) continue; - TRACE("Loading reverb '%s'\n", reverbitem.name); - props = &reverbitem.props; - effect->Props.Reverb.Density = props->flDensity; - effect->Props.Reverb.Diffusion = props->flDiffusion; - effect->Props.Reverb.Gain = props->flGain; - effect->Props.Reverb.GainHF = props->flGainHF; - effect->Props.Reverb.GainLF = props->flGainLF; - effect->Props.Reverb.DecayTime = props->flDecayTime; - effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio; - effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio; - effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain; - effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay; - effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0]; - effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1]; - effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2]; - effect->Props.Reverb.LateReverbGain = props->flLateReverbGain; - effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay; - effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0]; - effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1]; - effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2]; - effect->Props.Reverb.EchoTime = props->flEchoTime; - effect->Props.Reverb.EchoDepth = props->flEchoDepth; - effect->Props.Reverb.ModulationTime = props->flModulationTime; - effect->Props.Reverb.ModulationDepth = props->flModulationDepth; - effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF; - effect->Props.Reverb.HFReference = props->flHFReference; - effect->Props.Reverb.LFReference = props->flLFReference; - effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor; - effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit ? AL_TRUE : AL_FALSE; + TRACE("Loading reverb '%s'\n", std::data(reverbitem.name)); + const auto &props = reverbitem.props; + auto &dst = std::get(effect->Props); + dst.Density = props.flDensity; + dst.Diffusion = props.flDiffusion; + dst.Gain = props.flGain; + dst.GainHF = props.flGainHF; + dst.GainLF = props.flGainLF; + dst.DecayTime = props.flDecayTime; + dst.DecayHFRatio = props.flDecayHFRatio; + dst.DecayLFRatio = props.flDecayLFRatio; + dst.ReflectionsGain = props.flReflectionsGain; + dst.ReflectionsDelay = props.flReflectionsDelay; + dst.ReflectionsPan[0] = props.flReflectionsPan[0]; + dst.ReflectionsPan[1] = props.flReflectionsPan[1]; + dst.ReflectionsPan[2] = props.flReflectionsPan[2]; + dst.LateReverbGain = props.flLateReverbGain; + dst.LateReverbDelay = props.flLateReverbDelay; + dst.LateReverbPan[0] = props.flLateReverbPan[0]; + dst.LateReverbPan[1] = props.flLateReverbPan[1]; + dst.LateReverbPan[2] = props.flLateReverbPan[2]; + dst.EchoTime = props.flEchoTime; + dst.EchoDepth = props.flEchoDepth; + dst.ModulationTime = props.flModulationTime; + dst.ModulationDepth = props.flModulationDepth; + dst.AirAbsorptionGainHF = props.flAirAbsorptionGainHF; + dst.HFReference = props.flHFReference; + dst.LFReference = props.flLFReference; + dst.RoomRolloffFactor = props.flRoomRolloffFactor; + dst.DecayHFLimit = props.iDecayHFLimit ? AL_TRUE : AL_FALSE; return; } - WARN("Reverb preset '%s' not found\n", name); + WARN("Reverb preset '%.*s' not found\n", al::sizei(name), name.data()); } bool IsValidEffectType(ALenum type) noexcept @@ -756,10 +735,7 @@ bool IsValidEffectType(ALenum type) noexcept if(type == AL_EFFECT_NULL) return true; - for(const auto &effect_item : gEffectList) - { - if(type == effect_item.val && !DisabledEffects[effect_item.type]) - return true; - } - return false; + auto check_effect = [type](const EffectList &item) noexcept -> bool + { return type == item.val && !DisabledEffects.test(item.type); }; + return std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect); } diff --git a/libs/openal-soft/al/effect.h b/libs/openal-soft/al/effect.h index a1d43313..6e396721 100644 --- a/libs/openal-soft/al/effect.h +++ b/libs/openal-soft/al/effect.h @@ -1,11 +1,19 @@ #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" enum { @@ -27,16 +35,14 @@ 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; struct ALeffect { @@ -45,18 +51,33 @@ struct ALeffect { EffectProps Props{}; - const EffectVtable *vtab{nullptr}; - /* Self ID */ ALuint id{0u}; - DISABLE_ALLOC() + static void SetName(ALCcontext *context, ALuint id, std::string_view name); + + DISABLE_ALLOC }; void InitEffect(ALeffect *effect); -void LoadReverbPreset(const char *name, ALeffect *effect); +void LoadReverbPreset(const std::string_view name, ALeffect *effect); bool IsValidEffectType(ALenum type) noexcept; +struct EffectSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> 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/libs/openal-soft/al/effects/autowah.cpp b/libs/openal-soft/al/effects/autowah.cpp index 273ec7ae..c8b92450 100644 --- a/libs/openal-soft/al/effects/autowah.cpp +++ b/libs/openal-soft/al/effects/autowah.cpp @@ -13,536 +13,228 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" - -#include "al/eax_exception.h" -#include "al/eax_utils.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { -void Autowah_setParamf(EffectProps *props, ALenum param, float val) +constexpr EffectProps genDefaultProps() noexcept +{ + AutowahProps props{}; + props.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME; + props.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME; + props.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE; + props.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN; + return props; +} + +} // namespace + +const EffectProps AutowahEffectProps{genDefaultProps()}; + +void EffectHandler::SetParami(AutowahProps&, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } +void EffectHandler::SetParamiv(AutowahProps&, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", + param}; +} + +void EffectHandler::SetParamf(AutowahProps &props, ALenum param, float val) { switch(param) { case AL_AUTOWAH_ATTACK_TIME: if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) throw effect_exception{AL_INVALID_VALUE, "Autowah attack time out of range"}; - props->Autowah.AttackTime = val; + props.AttackTime = val; break; case AL_AUTOWAH_RELEASE_TIME: if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) throw effect_exception{AL_INVALID_VALUE, "Autowah release time out of range"}; - props->Autowah.ReleaseTime = val; + props.ReleaseTime = val; break; case AL_AUTOWAH_RESONANCE: if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) throw effect_exception{AL_INVALID_VALUE, "Autowah resonance out of range"}; - props->Autowah.Resonance = val; + props.Resonance = val; break; case AL_AUTOWAH_PEAK_GAIN: if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Autowah peak gain out of range"}; - props->Autowah.PeakGain = val; + props.PeakGain = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; } } -void Autowah_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Autowah_setParamf(props, param, vals[0]); } +void EffectHandler::SetParamfv(AutowahProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Autowah_setParami(EffectProps*, ALenum param, int) +void EffectHandler::GetParami(const AutowahProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } -void Autowah_setParamiv(EffectProps*, ALenum param, const int*) +void EffectHandler::GetParamiv(const AutowahProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param}; } -void Autowah_getParamf(const EffectProps *props, ALenum param, float *val) +void EffectHandler::GetParamf(const AutowahProps &props, ALenum param, float *val) { switch(param) { - case AL_AUTOWAH_ATTACK_TIME: - *val = props->Autowah.AttackTime; - break; - - case AL_AUTOWAH_RELEASE_TIME: - *val = props->Autowah.ReleaseTime; - break; - - case AL_AUTOWAH_RESONANCE: - *val = props->Autowah.Resonance; - break; - - case AL_AUTOWAH_PEAK_GAIN: - *val = props->Autowah.PeakGain; - break; + case AL_AUTOWAH_ATTACK_TIME: *val = props.AttackTime; break; + case AL_AUTOWAH_RELEASE_TIME: *val = props.ReleaseTime; break; + case AL_AUTOWAH_RESONANCE: *val = props.Resonance; break; + case AL_AUTOWAH_PEAK_GAIN: *val = props.PeakGain; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; } } -void Autowah_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Autowah_getParamf(props, param, vals); } - -void Autowah_getParami(const EffectProps*, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } -void Autowah_getParamiv(const EffectProps*, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", - param}; -} - -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME; - props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME; - props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE; - props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Autowah); - -const EffectProps AutowahEffectProps{genDefaultProps()}; +void EffectHandler::GetParamfv(const AutowahProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } #ifdef ALSOFT_EAX namespace { -using EaxAutoWahEffectDirtyFlagsValue = std::uint_least8_t; +using AutowahCommitter = EaxCommitter; -struct EaxAutoWahEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxAutoWahEffectDirtyFlagsValue flAttackTime : 1; - EaxAutoWahEffectDirtyFlagsValue flReleaseTime : 1; - EaxAutoWahEffectDirtyFlagsValue lResonance : 1; - EaxAutoWahEffectDirtyFlagsValue lPeakLevel : 1; -}; // EaxAutoWahEffectDirtyFlags - - -class EaxAutoWahEffect final : - public EaxEffect -{ -public: - EaxAutoWahEffect(); - - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAXAUTOWAHPROPERTIES eax_{}; - EAXAUTOWAHPROPERTIES eax_d_{}; - EaxAutoWahEffectDirtyFlags eax_dirty_flags_{}; - - - void set_eax_defaults(); - - - void set_efx_attack_time(); - - void set_efx_release_time(); - - void set_efx_resonance(); - - void set_efx_peak_gain(); - - void set_efx_defaults(); - - - void get(const EaxEaxCall& eax_call); - - - void validate_attack_time( - float flAttackTime); - - void validate_release_time( - float flReleaseTime); - - void validate_resonance( - long lResonance); - - void validate_peak_level( - long lPeakLevel); - - void validate_all( - const EAXAUTOWAHPROPERTIES& eax_all); - - - void defer_attack_time( - float flAttackTime); - - void defer_release_time( - float flReleaseTime); - - void defer_resonance( - long lResonance); - - void defer_peak_level( - long lPeakLevel); - - void defer_all( - const EAXAUTOWAHPROPERTIES& eax_all); - - - void defer_attack_time( - const EaxEaxCall& eax_call); - - void defer_release_time( - const EaxEaxCall& eax_call); - - void defer_resonance( - const EaxEaxCall& eax_call); - - void defer_peak_level( - const EaxEaxCall& eax_call); - - void defer_all( - const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxAutoWahEffect - - -class EaxAutoWahEffectException : - public EaxException -{ -public: - explicit EaxAutoWahEffectException( - const char* message) - : - EaxException{"EAX_AUTO_WAH_EFFECT", message} +struct AttackTimeValidator { + void operator()(float flAttackTime) const { + eax_validate_range( + "Attack Time", + flAttackTime, + EAXAUTOWAH_MINATTACKTIME, + EAXAUTOWAH_MAXATTACKTIME); } -}; // EaxAutoWahEffectException +}; // AttackTimeValidator - -EaxAutoWahEffect::EaxAutoWahEffect() - : EaxEffect{AL_EFFECT_AUTOWAH} -{ - set_eax_defaults(); - set_efx_defaults(); -} - -void EaxAutoWahEffect::dispatch(const EaxEaxCall& eax_call) -{ - eax_call.is_get() ? get(eax_call) : set(eax_call); -} - -void EaxAutoWahEffect::set_eax_defaults() -{ - eax_.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME; - eax_.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME; - eax_.lResonance = EAXAUTOWAH_DEFAULTRESONANCE; - eax_.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL; - - eax_d_ = eax_; -} - -void EaxAutoWahEffect::set_efx_attack_time() -{ - const auto attack_time = clamp( - eax_.flAttackTime, - AL_AUTOWAH_MIN_ATTACK_TIME, - AL_AUTOWAH_MAX_ATTACK_TIME); - - al_effect_props_.Autowah.AttackTime = attack_time; -} - -void EaxAutoWahEffect::set_efx_release_time() -{ - const auto release_time = clamp( - eax_.flReleaseTime, - AL_AUTOWAH_MIN_RELEASE_TIME, - AL_AUTOWAH_MAX_RELEASE_TIME); - - al_effect_props_.Autowah.ReleaseTime = release_time; -} - -void EaxAutoWahEffect::set_efx_resonance() -{ - const auto resonance = clamp( - level_mb_to_gain(static_cast(eax_.lResonance)), - AL_AUTOWAH_MIN_RESONANCE, - AL_AUTOWAH_MAX_RESONANCE); - - al_effect_props_.Autowah.Resonance = resonance; -} - -void EaxAutoWahEffect::set_efx_peak_gain() -{ - const auto peak_gain = clamp( - level_mb_to_gain(static_cast(eax_.lPeakLevel)), - AL_AUTOWAH_MIN_PEAK_GAIN, - AL_AUTOWAH_MAX_PEAK_GAIN); - - al_effect_props_.Autowah.PeakGain = peak_gain; -} - -void EaxAutoWahEffect::set_efx_defaults() -{ - set_efx_attack_time(); - set_efx_release_time(); - set_efx_resonance(); - set_efx_peak_gain(); -} - -void EaxAutoWahEffect::get(const EaxEaxCall& eax_call) -{ - switch (eax_call.get_property_id()) +struct ReleaseTimeValidator { + void operator()(float flReleaseTime) const { - case EAXAUTOWAH_NONE: - break; - - case EAXAUTOWAH_ALLPARAMETERS: - eax_call.set_value(eax_); - break; - - case EAXAUTOWAH_ATTACKTIME: - eax_call.set_value(eax_.flAttackTime); - break; - - case EAXAUTOWAH_RELEASETIME: - eax_call.set_value(eax_.flReleaseTime); - break; - - case EAXAUTOWAH_RESONANCE: - eax_call.set_value(eax_.lResonance); - break; - - case EAXAUTOWAH_PEAKLEVEL: - eax_call.set_value(eax_.lPeakLevel); - break; - - default: - throw EaxAutoWahEffectException{"Unsupported property id."}; + eax_validate_range( + "Release Time", + flReleaseTime, + EAXAUTOWAH_MINRELEASETIME, + EAXAUTOWAH_MAXRELEASETIME); } -} +}; // ReleaseTimeValidator -void EaxAutoWahEffect::validate_attack_time( - float flAttackTime) -{ - eax_validate_range( - "Attack Time", - flAttackTime, - EAXAUTOWAH_MINATTACKTIME, - EAXAUTOWAH_MAXATTACKTIME); -} - -void EaxAutoWahEffect::validate_release_time( - float flReleaseTime) -{ - eax_validate_range( - "Release Time", - flReleaseTime, - EAXAUTOWAH_MINRELEASETIME, - EAXAUTOWAH_MAXRELEASETIME); -} - -void EaxAutoWahEffect::validate_resonance( - long lResonance) -{ - eax_validate_range( - "Resonance", - lResonance, - EAXAUTOWAH_MINRESONANCE, - EAXAUTOWAH_MAXRESONANCE); -} - -void EaxAutoWahEffect::validate_peak_level( - long lPeakLevel) -{ - eax_validate_range( - "Peak Level", - lPeakLevel, - EAXAUTOWAH_MINPEAKLEVEL, - EAXAUTOWAH_MAXPEAKLEVEL); -} - -void EaxAutoWahEffect::validate_all( - const EAXAUTOWAHPROPERTIES& eax_all) -{ - validate_attack_time(eax_all.flAttackTime); - validate_release_time(eax_all.flReleaseTime); - validate_resonance(eax_all.lResonance); - validate_peak_level(eax_all.lPeakLevel); -} - -void EaxAutoWahEffect::defer_attack_time( - float flAttackTime) -{ - eax_d_.flAttackTime = flAttackTime; - eax_dirty_flags_.flAttackTime = (eax_.flAttackTime != eax_d_.flAttackTime); -} - -void EaxAutoWahEffect::defer_release_time( - float flReleaseTime) -{ - eax_d_.flReleaseTime = flReleaseTime; - eax_dirty_flags_.flReleaseTime = (eax_.flReleaseTime != eax_d_.flReleaseTime); -} - -void EaxAutoWahEffect::defer_resonance( - long lResonance) -{ - eax_d_.lResonance = lResonance; - eax_dirty_flags_.lResonance = (eax_.lResonance != eax_d_.lResonance); -} - -void EaxAutoWahEffect::defer_peak_level( - long lPeakLevel) -{ - eax_d_.lPeakLevel = lPeakLevel; - eax_dirty_flags_.lPeakLevel = (eax_.lPeakLevel != eax_d_.lPeakLevel); -} - -void EaxAutoWahEffect::defer_all( - const EAXAUTOWAHPROPERTIES& eax_all) -{ - validate_all(eax_all); - - defer_attack_time(eax_all.flAttackTime); - defer_release_time(eax_all.flReleaseTime); - defer_resonance(eax_all.lResonance); - defer_peak_level(eax_all.lPeakLevel); -} - -void EaxAutoWahEffect::defer_attack_time( - const EaxEaxCall& eax_call) -{ - const auto& attack_time = - eax_call.get_value(); - - validate_attack_time(attack_time); - defer_attack_time(attack_time); -} - -void EaxAutoWahEffect::defer_release_time( - const EaxEaxCall& eax_call) -{ - const auto& release_time = - eax_call.get_value(); - - validate_release_time(release_time); - defer_release_time(release_time); -} - -void EaxAutoWahEffect::defer_resonance( - const EaxEaxCall& eax_call) -{ - const auto& resonance = - eax_call.get_value(); - - validate_resonance(resonance); - defer_resonance(resonance); -} - -void EaxAutoWahEffect::defer_peak_level( - const EaxEaxCall& eax_call) -{ - const auto& peak_level = - eax_call.get_value(); - - validate_peak_level(peak_level); - defer_peak_level(peak_level); -} - -void EaxAutoWahEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto& all = - eax_call.get_value(); - - validate_all(all); - defer_all(all); -} - -// [[nodiscard]] -bool EaxAutoWahEffect::apply_deferred() -{ - if (eax_dirty_flags_ == EaxAutoWahEffectDirtyFlags{}) +struct ResonanceValidator { + void operator()(long lResonance) const { + eax_validate_range( + "Resonance", + lResonance, + EAXAUTOWAH_MINRESONANCE, + EAXAUTOWAH_MAXRESONANCE); + } +}; // ResonanceValidator + +struct PeakLevelValidator { + void operator()(long lPeakLevel) const + { + eax_validate_range( + "Peak Level", + lPeakLevel, + EAXAUTOWAH_MINPEAKLEVEL, + EAXAUTOWAH_MAXPEAKLEVEL); + } +}; // PeakLevelValidator + +struct AllValidator { + void operator()(const EAXAUTOWAHPROPERTIES& all) const + { + AttackTimeValidator{}(all.flAttackTime); + ReleaseTimeValidator{}(all.flReleaseTime); + ResonanceValidator{}(all.lResonance); + PeakLevelValidator{}(all.lPeakLevel); + } +}; // AllValidator + +} // namespace + +template<> +struct AutowahCommitter::Exception : public EaxException +{ + explicit Exception(const char *message) : EaxException{"EAX_AUTOWAH_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void AutowahCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +bool EaxAutowahCommitter::commit(const EAXAUTOWAHPROPERTIES &props) +{ + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; - } - eax_ = eax_d_; - - if (eax_dirty_flags_.flAttackTime) - { - set_efx_attack_time(); - } - - if (eax_dirty_flags_.flReleaseTime) - { - set_efx_release_time(); - } - - if (eax_dirty_flags_.lResonance) - { - set_efx_resonance(); - } - - if (eax_dirty_flags_.lPeakLevel) - { - set_efx_peak_gain(); - } - - eax_dirty_flags_ = EaxAutoWahEffectDirtyFlags{}; + mEaxProps = props; + 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; } -void EaxAutoWahEffect::set(const EaxEaxCall& eax_call) +void EaxAutowahCommitter::SetDefaults(EaxEffectProps &props) { - switch (eax_call.get_property_id()) + static constexpr EAXAUTOWAHPROPERTIES defprops{[] { - case EAXAUTOWAH_NONE: - break; + EAXAUTOWAHPROPERTIES ret{}; + ret.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME; + ret.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME; + ret.lResonance = EAXAUTOWAH_DEFAULTRESONANCE; + ret.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL; + return ret; + }()}; + props = defprops; +} - case EAXAUTOWAH_ALLPARAMETERS: - defer_all(eax_call); - break; - - case EAXAUTOWAH_ATTACKTIME: - defer_attack_time(eax_call); - break; - - case EAXAUTOWAH_RELEASETIME: - defer_release_time(eax_call); - break; - - case EAXAUTOWAH_RESONANCE: - defer_resonance(eax_call); - break; - - case EAXAUTOWAH_PEAKLEVEL: - defer_peak_level(eax_call); - break; - - default: - throw EaxAutoWahEffectException{"Unsupported property id."}; +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); 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(); } } -} // namespace - -EaxEffectUPtr eax_create_eax_auto_wah_effect() +void EaxAutowahCommitter::Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props) { - return std::make_unique<::EaxAutoWahEffect>(); + switch(call.get_property_id()) + { + case EAXAUTOWAH_NONE: 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(); + } } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/chorus.cpp b/libs/openal-soft/al/effects/chorus.cpp index 56318095..93771a1f 100644 --- a/libs/openal-soft/al/effects/chorus.cpp +++ b/libs/openal-soft/al/effects/chorus.cpp @@ -1,23 +1,22 @@ #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_exception.h" -#include "al/eax_utils.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -29,16 +28,16 @@ static_assert(FlangerMaxDelay >= AL_FLANGER_MAX_DELAY, "Flanger max delay too sm static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch"); static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch"); -inline al::optional WaveformFromEnum(ALenum type) +constexpr std::optional WaveformFromEnum(ALenum type) noexcept { switch(type) { - case AL_CHORUS_WAVEFORM_SINUSOID: return al::make_optional(ChorusWaveform::Sinusoid); - case AL_CHORUS_WAVEFORM_TRIANGLE: return al::make_optional(ChorusWaveform::Triangle); + case AL_CHORUS_WAVEFORM_SINUSOID: return ChorusWaveform::Sinusoid; + case AL_CHORUS_WAVEFORM_TRIANGLE: return ChorusWaveform::Triangle; } - return al::nullopt; + return std::nullopt; } -inline ALenum EnumFromWaveform(ChorusWaveform type) +constexpr ALenum EnumFromWaveform(ChorusWaveform type) { switch(type) { @@ -48,13 +47,41 @@ inline ALenum EnumFromWaveform(ChorusWaveform type) throw std::runtime_error{"Invalid chorus waveform: "+std::to_string(static_cast(type))}; } -void Chorus_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultChorusProps() noexcept +{ + ChorusProps props{}; + props.Waveform = WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM).value(); + props.Phase = AL_CHORUS_DEFAULT_PHASE; + props.Rate = AL_CHORUS_DEFAULT_RATE; + props.Depth = AL_CHORUS_DEFAULT_DEPTH; + props.Feedback = AL_CHORUS_DEFAULT_FEEDBACK; + props.Delay = AL_CHORUS_DEFAULT_DELAY; + return props; +} + +constexpr EffectProps genDefaultFlangerProps() noexcept +{ + FlangerProps props{}; + props.Waveform = WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM).value(); + props.Phase = AL_FLANGER_DEFAULT_PHASE; + props.Rate = AL_FLANGER_DEFAULT_RATE; + props.Depth = AL_FLANGER_DEFAULT_DEPTH; + props.Feedback = AL_FLANGER_DEFAULT_FEEDBACK; + props.Delay = AL_FLANGER_DEFAULT_DELAY; + return props; +} + +} // namespace + +const EffectProps ChorusEffectProps{genDefaultChorusProps()}; + +void EffectHandler::SetParami(ChorusProps &props, ALenum param, int val) { switch(param) { case AL_CHORUS_WAVEFORM: if(auto formopt = WaveformFromEnum(val)) - props->Chorus.Waveform = *formopt; + props.Waveform = *formopt; else throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val}; break; @@ -62,115 +89,89 @@ void Chorus_setParami(EffectProps *props, ALenum param, int val) case AL_CHORUS_PHASE: if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE)) throw effect_exception{AL_INVALID_VALUE, "Chorus phase out of range: %d", val}; - props->Chorus.Phase = val; + props.Phase = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; } } -void Chorus_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Chorus_setParami(props, param, vals[0]); } -void Chorus_setParamf(EffectProps *props, ALenum param, float val) +void EffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void EffectHandler::SetParamf(ChorusProps &props, ALenum param, float val) { switch(param) { case AL_CHORUS_RATE: if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) throw effect_exception{AL_INVALID_VALUE, "Chorus rate out of range: %f", val}; - props->Chorus.Rate = val; + props.Rate = val; break; case AL_CHORUS_DEPTH: if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "Chorus depth out of range: %f", val}; - props->Chorus.Depth = val; + props.Depth = val; break; case AL_CHORUS_FEEDBACK: if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) throw effect_exception{AL_INVALID_VALUE, "Chorus feedback out of range: %f", val}; - props->Chorus.Feedback = val; + props.Feedback = val; break; case AL_CHORUS_DELAY: if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) throw effect_exception{AL_INVALID_VALUE, "Chorus delay out of range: %f", val}; - props->Chorus.Delay = val; + props.Delay = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; } } -void Chorus_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Chorus_setParamf(props, param, vals[0]); } +void EffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Chorus_getParami(const EffectProps *props, ALenum param, int *val) +void EffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val) { switch(param) { - case AL_CHORUS_WAVEFORM: - *val = EnumFromWaveform(props->Chorus.Waveform); - break; - - case AL_CHORUS_PHASE: - *val = props->Chorus.Phase; - break; + case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; + case AL_CHORUS_PHASE: *val = props.Phase; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; } } -void Chorus_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Chorus_getParami(props, param, vals); } -void Chorus_getParamf(const EffectProps *props, ALenum param, float *val) +void EffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void EffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val) { switch(param) { - case AL_CHORUS_RATE: - *val = props->Chorus.Rate; - break; - - case AL_CHORUS_DEPTH: - *val = props->Chorus.Depth; - break; - - case AL_CHORUS_FEEDBACK: - *val = props->Chorus.Feedback; - break; - - case AL_CHORUS_DELAY: - *val = props->Chorus.Delay; - break; + case AL_CHORUS_RATE: *val = props.Rate; break; + case AL_CHORUS_DEPTH: *val = props.Depth; break; + case AL_CHORUS_FEEDBACK: *val = props.Feedback; break; + case AL_CHORUS_DELAY: *val = props.Delay; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; } } -void Chorus_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Chorus_getParamf(props, param, vals); } - -const EffectProps genDefaultChorusProps() noexcept -{ - EffectProps props{}; - props.Chorus.Waveform = *WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM); - props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE; - props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE; - props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH; - props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK; - props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY; - return props; -} +void EffectHandler::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 EffectHandler::SetParami(FlangerProps &props, ALenum param, int val) { switch(param) { case AL_FLANGER_WAVEFORM: if(auto formopt = WaveformFromEnum(val)) - props->Chorus.Waveform = *formopt; + props.Waveform = *formopt; else throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val}; break; @@ -178,1180 +179,474 @@ void Flanger_setParami(EffectProps *props, ALenum param, int val) case AL_FLANGER_PHASE: if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE)) throw effect_exception{AL_INVALID_VALUE, "Flanger phase out of range: %d", val}; - props->Chorus.Phase = val; + props.Phase = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; } } -void Flanger_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Flanger_setParami(props, param, vals[0]); } -void Flanger_setParamf(EffectProps *props, ALenum param, float val) +void EffectHandler::SetParamiv(FlangerProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void EffectHandler::SetParamf(FlangerProps &props, ALenum param, float val) { switch(param) { case AL_FLANGER_RATE: if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) throw effect_exception{AL_INVALID_VALUE, "Flanger rate out of range: %f", val}; - props->Chorus.Rate = val; + props.Rate = val; break; case AL_FLANGER_DEPTH: if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "Flanger depth out of range: %f", val}; - props->Chorus.Depth = val; + props.Depth = val; break; case AL_FLANGER_FEEDBACK: if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) throw effect_exception{AL_INVALID_VALUE, "Flanger feedback out of range: %f", val}; - props->Chorus.Feedback = val; + props.Feedback = val; break; case AL_FLANGER_DELAY: if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) throw effect_exception{AL_INVALID_VALUE, "Flanger delay out of range: %f", val}; - props->Chorus.Delay = val; + props.Delay = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; } } -void Flanger_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Flanger_setParamf(props, param, vals[0]); } +void EffectHandler::SetParamfv(FlangerProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Flanger_getParami(const EffectProps *props, ALenum param, int *val) +void EffectHandler::GetParami(const FlangerProps &props, ALenum param, int *val) { switch(param) { - case AL_FLANGER_WAVEFORM: - *val = EnumFromWaveform(props->Chorus.Waveform); - break; - - case AL_FLANGER_PHASE: - *val = props->Chorus.Phase; - break; + case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; + case AL_FLANGER_PHASE: *val = props.Phase; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; } } -void Flanger_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Flanger_getParami(props, param, vals); } -void Flanger_getParamf(const EffectProps *props, ALenum param, float *val) +void EffectHandler::GetParamiv(const FlangerProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void EffectHandler::GetParamf(const FlangerProps &props, ALenum param, float *val) { switch(param) { - case AL_FLANGER_RATE: - *val = props->Chorus.Rate; - break; - - case AL_FLANGER_DEPTH: - *val = props->Chorus.Depth; - break; - - case AL_FLANGER_FEEDBACK: - *val = props->Chorus.Feedback; - break; - - case AL_FLANGER_DELAY: - *val = props->Chorus.Delay; - break; + case AL_FLANGER_RATE: *val = props.Rate; break; + case AL_FLANGER_DEPTH: *val = props.Depth; break; + case AL_FLANGER_FEEDBACK: *val = props.Feedback; break; + case AL_FLANGER_DELAY: *val = props.Delay; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; } } -void Flanger_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Flanger_getParamf(props, param, vals); } - -EffectProps genDefaultFlangerProps() noexcept -{ - EffectProps props{}; - props.Chorus.Waveform = *WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM); - props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE; - props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE; - props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH; - props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK; - props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Chorus); - -const EffectProps ChorusEffectProps{genDefaultChorusProps()}; - -DEFINE_ALEFFECT_VTABLE(Flanger); - -const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; +void EffectHandler::GetParamfv(const FlangerProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } #ifdef ALSOFT_EAX namespace { -void eax_set_efx_waveform( - ALenum waveform, - EffectProps& al_effect_props) -{ - const auto efx_waveform = WaveformFromEnum(waveform); - assert(efx_waveform.has_value()); - al_effect_props.Chorus.Waveform = *efx_waveform; -} +struct EaxChorusTraits { + using EaxProps = EAXCHORUSPROPERTIES; + using Committer = EaxChorusCommitter; + using AlProps = ChorusProps; -void eax_set_efx_phase( - ALint phase, - EffectProps& al_effect_props) -{ - al_effect_props.Chorus.Phase = phase; -} + static constexpr auto efx_effect() { return AL_EFFECT_CHORUS; } -void eax_set_efx_rate( - ALfloat rate, - EffectProps& al_effect_props) -{ - al_effect_props.Chorus.Rate = rate; -} + static constexpr auto eax_none_param_id() { return EAXCHORUS_NONE; } + static constexpr auto eax_allparameters_param_id() { return EAXCHORUS_ALLPARAMETERS; } + static constexpr auto eax_waveform_param_id() { return EAXCHORUS_WAVEFORM; } + static constexpr auto eax_phase_param_id() { return EAXCHORUS_PHASE; } + static constexpr auto eax_rate_param_id() { return EAXCHORUS_RATE; } + static constexpr auto eax_depth_param_id() { return EAXCHORUS_DEPTH; } + static constexpr auto eax_feedback_param_id() { return EAXCHORUS_FEEDBACK; } + static constexpr auto eax_delay_param_id() { return EAXCHORUS_DELAY; } -void eax_set_efx_depth( - ALfloat depth, - EffectProps& al_effect_props) -{ - al_effect_props.Chorus.Depth = depth; -} + static constexpr auto eax_min_waveform() { return EAXCHORUS_MINWAVEFORM; } + static constexpr auto eax_min_phase() { return EAXCHORUS_MINPHASE; } + static constexpr auto eax_min_rate() { return EAXCHORUS_MINRATE; } + static constexpr auto eax_min_depth() { return EAXCHORUS_MINDEPTH; } + static constexpr auto eax_min_feedback() { return EAXCHORUS_MINFEEDBACK; } + static constexpr auto eax_min_delay() { return EAXCHORUS_MINDELAY; } -void eax_set_efx_feedback( - ALfloat feedback, - EffectProps& al_effect_props) -{ - al_effect_props.Chorus.Feedback = feedback; -} + static constexpr auto eax_max_waveform() { return EAXCHORUS_MAXWAVEFORM; } + static constexpr auto eax_max_phase() { return EAXCHORUS_MAXPHASE; } + static constexpr auto eax_max_rate() { return EAXCHORUS_MAXRATE; } + static constexpr auto eax_max_depth() { return EAXCHORUS_MAXDEPTH; } + static constexpr auto eax_max_feedback() { return EAXCHORUS_MAXFEEDBACK; } + static constexpr auto eax_max_delay() { return EAXCHORUS_MAXDELAY; } -void eax_set_efx_delay( - ALfloat delay, - EffectProps& al_effect_props) -{ - al_effect_props.Chorus.Delay = delay; -} + static constexpr auto eax_default_waveform() { return EAXCHORUS_DEFAULTWAVEFORM; } + static constexpr auto eax_default_phase() { return EAXCHORUS_DEFAULTPHASE; } + static constexpr auto eax_default_rate() { return EAXCHORUS_DEFAULTRATE; } + static constexpr auto eax_default_depth() { return EAXCHORUS_DEFAULTDEPTH; } + static constexpr auto eax_default_feedback() { return EAXCHORUS_DEFAULTFEEDBACK; } + static constexpr auto eax_default_delay() { return EAXCHORUS_DEFAULTDELAY; } + static constexpr auto efx_min_waveform() { return AL_CHORUS_MIN_WAVEFORM; } + static constexpr auto efx_min_phase() { return AL_CHORUS_MIN_PHASE; } + static constexpr auto efx_min_rate() { return AL_CHORUS_MIN_RATE; } + static constexpr auto efx_min_depth() { return AL_CHORUS_MIN_DEPTH; } + static constexpr auto efx_min_feedback() { return AL_CHORUS_MIN_FEEDBACK; } + static constexpr auto efx_min_delay() { return AL_CHORUS_MIN_DELAY; } -using EaxChorusEffectDirtyFlagsValue = std::uint_least8_t; + static constexpr auto efx_max_waveform() { return AL_CHORUS_MAX_WAVEFORM; } + static constexpr auto efx_max_phase() { return AL_CHORUS_MAX_PHASE; } + static constexpr auto efx_max_rate() { return AL_CHORUS_MAX_RATE; } + static constexpr auto efx_max_depth() { return AL_CHORUS_MAX_DEPTH; } + static constexpr auto efx_max_feedback() { return AL_CHORUS_MAX_FEEDBACK; } + static constexpr auto efx_max_delay() { return AL_CHORUS_MAX_DELAY; } -struct EaxChorusEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; + static constexpr auto efx_default_waveform() { return AL_CHORUS_DEFAULT_WAVEFORM; } + static constexpr auto efx_default_phase() { return AL_CHORUS_DEFAULT_PHASE; } + static constexpr auto efx_default_rate() { return AL_CHORUS_DEFAULT_RATE; } + static constexpr auto efx_default_depth() { return AL_CHORUS_DEFAULT_DEPTH; } + static constexpr auto efx_default_feedback() { return AL_CHORUS_DEFAULT_FEEDBACK; } + static constexpr auto efx_default_delay() { return AL_CHORUS_DEFAULT_DELAY; } - EaxChorusEffectDirtyFlagsValue ulWaveform : 1; - EaxChorusEffectDirtyFlagsValue lPhase : 1; - EaxChorusEffectDirtyFlagsValue flRate : 1; - EaxChorusEffectDirtyFlagsValue flDepth : 1; - EaxChorusEffectDirtyFlagsValue flFeedback : 1; - EaxChorusEffectDirtyFlagsValue flDelay : 1; -}; // EaxChorusEffectDirtyFlags + static ChorusWaveform eax_waveform(unsigned long type) + { + if(type == EAX_CHORUS_SINUSOID) return ChorusWaveform::Sinusoid; + if(type == EAX_CHORUS_TRIANGLE) return ChorusWaveform::Triangle; + return ChorusWaveform::Sinusoid; + } +}; // EaxChorusTraits +struct EaxFlangerTraits { + using EaxProps = EAXFLANGERPROPERTIES; + using Committer = EaxFlangerCommitter; + using AlProps = FlangerProps; + + static constexpr auto efx_effect() { return AL_EFFECT_FLANGER; } + + static constexpr auto eax_none_param_id() { return EAXFLANGER_NONE; } + static constexpr auto eax_allparameters_param_id() { return EAXFLANGER_ALLPARAMETERS; } + static constexpr auto eax_waveform_param_id() { return EAXFLANGER_WAVEFORM; } + static constexpr auto eax_phase_param_id() { return EAXFLANGER_PHASE; } + static constexpr auto eax_rate_param_id() { return EAXFLANGER_RATE; } + static constexpr auto eax_depth_param_id() { return EAXFLANGER_DEPTH; } + static constexpr auto eax_feedback_param_id() { return EAXFLANGER_FEEDBACK; } + static constexpr auto eax_delay_param_id() { return EAXFLANGER_DELAY; } + + static constexpr auto eax_min_waveform() { return EAXFLANGER_MINWAVEFORM; } + static constexpr auto eax_min_phase() { return EAXFLANGER_MINPHASE; } + static constexpr auto eax_min_rate() { return EAXFLANGER_MINRATE; } + static constexpr auto eax_min_depth() { return EAXFLANGER_MINDEPTH; } + static constexpr auto eax_min_feedback() { return EAXFLANGER_MINFEEDBACK; } + static constexpr auto eax_min_delay() { return EAXFLANGER_MINDELAY; } + + static constexpr auto eax_max_waveform() { return EAXFLANGER_MAXWAVEFORM; } + static constexpr auto eax_max_phase() { return EAXFLANGER_MAXPHASE; } + static constexpr auto eax_max_rate() { return EAXFLANGER_MAXRATE; } + static constexpr auto eax_max_depth() { return EAXFLANGER_MAXDEPTH; } + static constexpr auto eax_max_feedback() { return EAXFLANGER_MAXFEEDBACK; } + static constexpr auto eax_max_delay() { return EAXFLANGER_MAXDELAY; } + + static constexpr auto eax_default_waveform() { return EAXFLANGER_DEFAULTWAVEFORM; } + static constexpr auto eax_default_phase() { return EAXFLANGER_DEFAULTPHASE; } + static constexpr auto eax_default_rate() { return EAXFLANGER_DEFAULTRATE; } + static constexpr auto eax_default_depth() { return EAXFLANGER_DEFAULTDEPTH; } + static constexpr auto eax_default_feedback() { return EAXFLANGER_DEFAULTFEEDBACK; } + static constexpr auto eax_default_delay() { return EAXFLANGER_DEFAULTDELAY; } + + static constexpr auto efx_min_waveform() { return AL_FLANGER_MIN_WAVEFORM; } + static constexpr auto efx_min_phase() { return AL_FLANGER_MIN_PHASE; } + static constexpr auto efx_min_rate() { return AL_FLANGER_MIN_RATE; } + static constexpr auto efx_min_depth() { return AL_FLANGER_MIN_DEPTH; } + static constexpr auto efx_min_feedback() { return AL_FLANGER_MIN_FEEDBACK; } + static constexpr auto efx_min_delay() { return AL_FLANGER_MIN_DELAY; } + + static constexpr auto efx_max_waveform() { return AL_FLANGER_MAX_WAVEFORM; } + static constexpr auto efx_max_phase() { return AL_FLANGER_MAX_PHASE; } + static constexpr auto efx_max_rate() { return AL_FLANGER_MAX_RATE; } + static constexpr auto efx_max_depth() { return AL_FLANGER_MAX_DEPTH; } + static constexpr auto efx_max_feedback() { return AL_FLANGER_MAX_FEEDBACK; } + static constexpr auto efx_max_delay() { return AL_FLANGER_MAX_DELAY; } + + static constexpr auto efx_default_waveform() { return AL_FLANGER_DEFAULT_WAVEFORM; } + static constexpr auto efx_default_phase() { return AL_FLANGER_DEFAULT_PHASE; } + static constexpr auto efx_default_rate() { return AL_FLANGER_DEFAULT_RATE; } + static constexpr auto efx_default_depth() { return AL_FLANGER_DEFAULT_DEPTH; } + static constexpr auto efx_default_feedback() { return AL_FLANGER_DEFAULT_FEEDBACK; } + static constexpr auto efx_default_delay() { return AL_FLANGER_DEFAULT_DELAY; } + + static ChorusWaveform eax_waveform(unsigned long type) + { + if(type == EAX_FLANGER_SINUSOID) return ChorusWaveform::Sinusoid; + if(type == EAX_FLANGER_TRIANGLE) return ChorusWaveform::Triangle; + return ChorusWaveform::Sinusoid; + } +}; // EaxFlangerTraits + +template +struct ChorusFlangerEffect { + using Traits = TTraits; + using EaxProps = typename Traits::EaxProps; + using Committer = typename Traits::Committer; + using AlProps = typename Traits::AlProps; + using Exception = typename Committer::Exception; + + struct WaveformValidator { + void operator()(unsigned long ulWaveform) const + { + eax_validate_range( + "Waveform", + ulWaveform, + Traits::eax_min_waveform(), + Traits::eax_max_waveform()); + } + }; // WaveformValidator + + struct PhaseValidator { + void operator()(long lPhase) const + { + eax_validate_range( + "Phase", + lPhase, + Traits::eax_min_phase(), + Traits::eax_max_phase()); + } + }; // PhaseValidator + + struct RateValidator { + void operator()(float flRate) const + { + eax_validate_range( + "Rate", + flRate, + Traits::eax_min_rate(), + Traits::eax_max_rate()); + } + }; // RateValidator + + struct DepthValidator { + void operator()(float flDepth) const + { + eax_validate_range( + "Depth", + flDepth, + Traits::eax_min_depth(), + Traits::eax_max_depth()); + } + }; // DepthValidator + + struct FeedbackValidator { + void operator()(float flFeedback) const + { + eax_validate_range( + "Feedback", + flFeedback, + Traits::eax_min_feedback(), + Traits::eax_max_feedback()); + } + }; // FeedbackValidator + + struct DelayValidator { + void operator()(float flDelay) const + { + eax_validate_range( + "Delay", + flDelay, + Traits::eax_min_delay(), + Traits::eax_max_delay()); + } + }; // DelayValidator + + struct AllValidator { + void operator()(const EaxProps& all) const + { + WaveformValidator{}(all.ulWaveform); + PhaseValidator{}(all.lPhase); + RateValidator{}(all.flRate); + DepthValidator{}(all.flDepth); + FeedbackValidator{}(all.flFeedback); + DelayValidator{}(all.flDelay); + } + }; // AllValidator -class EaxChorusEffect final : - public EaxEffect -{ public: - EaxChorusEffect(); - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAXCHORUSPROPERTIES eax_{}; - EAXCHORUSPROPERTIES eax_d_{}; - EaxChorusEffectDirtyFlags eax_dirty_flags_{}; - - void set_eax_defaults() noexcept; - - void set_efx_waveform(); - void set_efx_phase(); - void set_efx_rate(); - void set_efx_depth(); - void set_efx_feedback(); - void set_efx_delay(); - void set_efx_defaults(); - - void get(const EaxEaxCall& eax_call); - - void validate_waveform(unsigned long ulWaveform); - void validate_phase(long lPhase); - void validate_rate(float flRate); - void validate_depth(float flDepth); - void validate_feedback(float flFeedback); - void validate_delay(float flDelay); - void validate_all(const EAXCHORUSPROPERTIES& eax_all); - - void defer_waveform(unsigned long ulWaveform); - void defer_phase(long lPhase); - void defer_rate(float flRate); - void defer_depth(float flDepth); - void defer_feedback(float flFeedback); - void defer_delay(float flDelay); - void defer_all(const EAXCHORUSPROPERTIES& eax_all); - - void defer_waveform(const EaxEaxCall& eax_call); - void defer_phase(const EaxEaxCall& eax_call); - void defer_rate(const EaxEaxCall& eax_call); - void defer_depth(const EaxEaxCall& eax_call); - void defer_feedback(const EaxEaxCall& eax_call); - void defer_delay(const EaxEaxCall& eax_call); - void defer_all(const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxChorusEffect - - -class EaxChorusEffectException : - public EaxException -{ -public: - explicit EaxChorusEffectException( - const char* message) - : - EaxException{"EAX_CHORUS_EFFECT", message} + static void SetDefaults(EaxEffectProps &props) { + auto&& all = props.emplace(); + all.ulWaveform = Traits::eax_default_waveform(); + all.lPhase = Traits::eax_default_phase(); + all.flRate = Traits::eax_default_rate(); + all.flDepth = Traits::eax_default_depth(); + all.flFeedback = Traits::eax_default_feedback(); + all.flDelay = Traits::eax_default_delay(); } -}; // EaxChorusEffectException -EaxChorusEffect::EaxChorusEffect() - : EaxEffect{AL_EFFECT_CHORUS} -{ - set_eax_defaults(); - set_efx_defaults(); -} - -void EaxChorusEffect::dispatch(const EaxEaxCall& eax_call) -{ - eax_call.is_get() ? get(eax_call) : set(eax_call); -} - -void EaxChorusEffect::set_eax_defaults() noexcept -{ - eax_.ulWaveform = EAXCHORUS_DEFAULTWAVEFORM; - eax_.lPhase = EAXCHORUS_DEFAULTPHASE; - eax_.flRate = EAXCHORUS_DEFAULTRATE; - eax_.flDepth = EAXCHORUS_DEFAULTDEPTH; - eax_.flFeedback = EAXCHORUS_DEFAULTFEEDBACK; - eax_.flDelay = EAXCHORUS_DEFAULTDELAY; - - eax_d_ = eax_; -} - -void EaxChorusEffect::set_efx_waveform() -{ - const auto waveform = clamp( - static_cast(eax_.ulWaveform), - AL_CHORUS_MIN_WAVEFORM, - AL_CHORUS_MAX_WAVEFORM); - - eax_set_efx_waveform(waveform, al_effect_props_); -} - -void EaxChorusEffect::set_efx_phase() -{ - const auto phase = clamp( - static_cast(eax_.lPhase), - AL_CHORUS_MIN_PHASE, - AL_CHORUS_MAX_PHASE); - - eax_set_efx_phase(phase, al_effect_props_); -} - -void EaxChorusEffect::set_efx_rate() -{ - const auto rate = clamp( - eax_.flRate, - AL_CHORUS_MIN_RATE, - AL_CHORUS_MAX_RATE); - - eax_set_efx_rate(rate, al_effect_props_); -} - -void EaxChorusEffect::set_efx_depth() -{ - const auto depth = clamp( - eax_.flDepth, - AL_CHORUS_MIN_DEPTH, - AL_CHORUS_MAX_DEPTH); - - eax_set_efx_depth(depth, al_effect_props_); -} - -void EaxChorusEffect::set_efx_feedback() -{ - const auto feedback = clamp( - eax_.flFeedback, - AL_CHORUS_MIN_FEEDBACK, - AL_CHORUS_MAX_FEEDBACK); - - eax_set_efx_feedback(feedback, al_effect_props_); -} - -void EaxChorusEffect::set_efx_delay() -{ - const auto delay = clamp( - eax_.flDelay, - AL_CHORUS_MIN_DELAY, - AL_CHORUS_MAX_DELAY); - - eax_set_efx_delay(delay, al_effect_props_); -} - -void EaxChorusEffect::set_efx_defaults() -{ - set_efx_waveform(); - set_efx_phase(); - set_efx_rate(); - set_efx_depth(); - set_efx_feedback(); - set_efx_delay(); -} - -void EaxChorusEffect::get(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) + static void Get(const EaxCall &call, const EaxProps &all) { - case EAXCHORUS_NONE: + switch(call.get_property_id()) + { + case Traits::eax_none_param_id(): break; - - case EAXCHORUS_ALLPARAMETERS: - eax_call.set_value(eax_); + case Traits::eax_allparameters_param_id(): + call.template set_value(all); break; - - case EAXCHORUS_WAVEFORM: - eax_call.set_value(eax_.ulWaveform); + case Traits::eax_waveform_param_id(): + call.template set_value(all.ulWaveform); break; - - case EAXCHORUS_PHASE: - eax_call.set_value(eax_.lPhase); + case Traits::eax_phase_param_id(): + call.template set_value(all.lPhase); break; - - case EAXCHORUS_RATE: - eax_call.set_value(eax_.flRate); + case Traits::eax_rate_param_id(): + call.template set_value(all.flRate); break; - - case EAXCHORUS_DEPTH: - eax_call.set_value(eax_.flDepth); + case Traits::eax_depth_param_id(): + call.template set_value(all.flDepth); break; - - case EAXCHORUS_FEEDBACK: - eax_call.set_value(eax_.flFeedback); + case Traits::eax_feedback_param_id(): + call.template set_value(all.flFeedback); break; - - case EAXCHORUS_DELAY: - eax_call.set_value(eax_.flDelay); + case Traits::eax_delay_param_id(): + call.template set_value(all.flDelay); break; - default: - throw EaxChorusEffectException{"Unsupported property id."}; - } -} - -void EaxChorusEffect::validate_waveform( - unsigned long ulWaveform) -{ - eax_validate_range( - "Waveform", - ulWaveform, - EAXCHORUS_MINWAVEFORM, - EAXCHORUS_MAXWAVEFORM); -} - -void EaxChorusEffect::validate_phase( - long lPhase) -{ - eax_validate_range( - "Phase", - lPhase, - EAXCHORUS_MINPHASE, - EAXCHORUS_MAXPHASE); -} - -void EaxChorusEffect::validate_rate( - float flRate) -{ - eax_validate_range( - "Rate", - flRate, - EAXCHORUS_MINRATE, - EAXCHORUS_MAXRATE); -} - -void EaxChorusEffect::validate_depth( - float flDepth) -{ - eax_validate_range( - "Depth", - flDepth, - EAXCHORUS_MINDEPTH, - EAXCHORUS_MAXDEPTH); -} - -void EaxChorusEffect::validate_feedback( - float flFeedback) -{ - eax_validate_range( - "Feedback", - flFeedback, - EAXCHORUS_MINFEEDBACK, - EAXCHORUS_MAXFEEDBACK); -} - -void EaxChorusEffect::validate_delay( - float flDelay) -{ - eax_validate_range( - "Delay", - flDelay, - EAXCHORUS_MINDELAY, - EAXCHORUS_MAXDELAY); -} - -void EaxChorusEffect::validate_all( - const EAXCHORUSPROPERTIES& eax_all) -{ - validate_waveform(eax_all.ulWaveform); - validate_phase(eax_all.lPhase); - validate_rate(eax_all.flRate); - validate_depth(eax_all.flDepth); - validate_feedback(eax_all.flFeedback); - validate_delay(eax_all.flDelay); -} - -void EaxChorusEffect::defer_waveform( - unsigned long ulWaveform) -{ - eax_d_.ulWaveform = ulWaveform; - eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); -} - -void EaxChorusEffect::defer_phase( - long lPhase) -{ - eax_d_.lPhase = lPhase; - eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase); -} - -void EaxChorusEffect::defer_rate( - float flRate) -{ - eax_d_.flRate = flRate; - eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); -} - -void EaxChorusEffect::defer_depth( - float flDepth) -{ - eax_d_.flDepth = flDepth; - eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth); -} - -void EaxChorusEffect::defer_feedback( - float flFeedback) -{ - eax_d_.flFeedback = flFeedback; - eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); -} - -void EaxChorusEffect::defer_delay( - float flDelay) -{ - eax_d_.flDelay = flDelay; - eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); -} - -void EaxChorusEffect::defer_all( - const EAXCHORUSPROPERTIES& eax_all) -{ - defer_waveform(eax_all.ulWaveform); - defer_phase(eax_all.lPhase); - defer_rate(eax_all.flRate); - defer_depth(eax_all.flDepth); - defer_feedback(eax_all.flFeedback); - defer_delay(eax_all.flDelay); -} - -void EaxChorusEffect::defer_waveform( - const EaxEaxCall& eax_call) -{ - const auto& waveform = - eax_call.get_value(); - - validate_waveform(waveform); - defer_waveform(waveform); -} - -void EaxChorusEffect::defer_phase( - const EaxEaxCall& eax_call) -{ - const auto& phase = - eax_call.get_value(); - - validate_phase(phase); - defer_phase(phase); -} - -void EaxChorusEffect::defer_rate( - const EaxEaxCall& eax_call) -{ - const auto& rate = - eax_call.get_value(); - - validate_rate(rate); - defer_rate(rate); -} - -void EaxChorusEffect::defer_depth( - const EaxEaxCall& eax_call) -{ - const auto& depth = - eax_call.get_value(); - - validate_depth(depth); - defer_depth(depth); -} - -void EaxChorusEffect::defer_feedback( - const EaxEaxCall& eax_call) -{ - const auto& feedback = - eax_call.get_value(); - - validate_feedback(feedback); - defer_feedback(feedback); -} - -void EaxChorusEffect::defer_delay( - const EaxEaxCall& eax_call) -{ - const auto& delay = - eax_call.get_value(); - - validate_delay(delay); - defer_delay(delay); -} - -void EaxChorusEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto& all = - eax_call.get_value(); - - validate_all(all); - defer_all(all); -} - -// [[nodiscard]] -bool EaxChorusEffect::apply_deferred() -{ - if (eax_dirty_flags_ == EaxChorusEffectDirtyFlags{}) - { - return false; + Committer::fail_unknown_property_id(); + } } - eax_ = eax_d_; - - if (eax_dirty_flags_.ulWaveform) + static void Set(const EaxCall &call, EaxProps &all) { - set_efx_waveform(); - } - - if (eax_dirty_flags_.lPhase) - { - set_efx_phase(); - } - - if (eax_dirty_flags_.flRate) - { - set_efx_rate(); - } - - if (eax_dirty_flags_.flDepth) - { - set_efx_depth(); - } - - if (eax_dirty_flags_.flFeedback) - { - set_efx_feedback(); - } - - if (eax_dirty_flags_.flDelay) - { - set_efx_delay(); - } - - eax_dirty_flags_ = EaxChorusEffectDirtyFlags{}; - - return true; -} - -void EaxChorusEffect::set(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) - { - case EAXCHORUS_NONE: + switch(call.get_property_id()) + { + case Traits::eax_none_param_id(): break; - - case EAXCHORUS_ALLPARAMETERS: - defer_all(eax_call); + case Traits::eax_allparameters_param_id(): + Committer::template defer(call, all); break; - - case EAXCHORUS_WAVEFORM: - defer_waveform(eax_call); + case Traits::eax_waveform_param_id(): + Committer::template defer(call, all.ulWaveform); break; - - case EAXCHORUS_PHASE: - defer_phase(eax_call); + case Traits::eax_phase_param_id(): + Committer::template defer(call, all.lPhase); break; - - case EAXCHORUS_RATE: - defer_rate(eax_call); + case Traits::eax_rate_param_id(): + Committer::template defer(call, all.flRate); break; - - case EAXCHORUS_DEPTH: - defer_depth(eax_call); + case Traits::eax_depth_param_id(): + Committer::template defer(call, all.flDepth); break; - - case EAXCHORUS_FEEDBACK: - defer_feedback(eax_call); + case Traits::eax_feedback_param_id(): + Committer::template defer(call, all.flFeedback); break; - - case EAXCHORUS_DELAY: - defer_delay(eax_call); + case Traits::eax_delay_param_id(): + Committer::template defer(call, all.flDelay); break; - default: - throw EaxChorusEffectException{"Unsupported property id."}; + Committer::fail_unknown_property_id(); + } } -} + static bool Commit(const EaxProps &props, EaxEffectProps &props_, AlProps &al_props_) + { + if(auto *cur = std::get_if(&props_); cur && *cur == props) + return false; + + props_ = props; + + 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; + } +}; // EaxChorusFlangerEffect + + +using ChorusCommitter = EaxCommitter; +using FlangerCommitter = EaxCommitter; } // namespace - -EaxEffectUPtr eax_create_eax_chorus_effect() +template<> +struct ChorusCommitter::Exception : public EaxException { - return std::make_unique<::EaxChorusEffect>(); + explicit Exception(const char *message) : EaxException{"EAX_CHORUS_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void ChorusCommitter::fail(const char *message) +{ + throw Exception{message}; } - -namespace +bool EaxChorusCommitter::commit(const EAXCHORUSPROPERTIES &props) { - - -using EaxFlangerEffectDirtyFlagsValue = std::uint_least8_t; - -struct EaxFlangerEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxFlangerEffectDirtyFlagsValue ulWaveform : 1; - EaxFlangerEffectDirtyFlagsValue lPhase : 1; - EaxFlangerEffectDirtyFlagsValue flRate : 1; - EaxFlangerEffectDirtyFlagsValue flDepth : 1; - EaxFlangerEffectDirtyFlagsValue flFeedback : 1; - EaxFlangerEffectDirtyFlagsValue flDelay : 1; -}; // EaxFlangerEffectDirtyFlags - - -class EaxFlangerEffect final : - public EaxEffect -{ -public: - EaxFlangerEffect(); - - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAXFLANGERPROPERTIES eax_{}; - EAXFLANGERPROPERTIES eax_d_{}; - EaxFlangerEffectDirtyFlags eax_dirty_flags_{}; - - void set_eax_defaults(); - - void set_efx_waveform(); - void set_efx_phase(); - void set_efx_rate(); - void set_efx_depth(); - void set_efx_feedback(); - void set_efx_delay(); - void set_efx_defaults(); - - void get(const EaxEaxCall& eax_call); - - void validate_waveform(unsigned long ulWaveform); - void validate_phase(long lPhase); - void validate_rate(float flRate); - void validate_depth(float flDepth); - void validate_feedback(float flFeedback); - void validate_delay(float flDelay); - void validate_all(const EAXFLANGERPROPERTIES& all); - - void defer_waveform(unsigned long ulWaveform); - void defer_phase(long lPhase); - void defer_rate(float flRate); - void defer_depth(float flDepth); - void defer_feedback(float flFeedback); - void defer_delay(float flDelay); - void defer_all(const EAXFLANGERPROPERTIES& all); - - void defer_waveform(const EaxEaxCall& eax_call); - void defer_phase(const EaxEaxCall& eax_call); - void defer_rate(const EaxEaxCall& eax_call); - void defer_depth(const EaxEaxCall& eax_call); - void defer_feedback(const EaxEaxCall& eax_call); - void defer_delay(const EaxEaxCall& eax_call); - void defer_all(const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxFlangerEffect - - -class EaxFlangerEffectException : - public EaxException -{ -public: - explicit EaxFlangerEffectException( - const char* message) - : - EaxException{"EAX_FLANGER_EFFECT", message} - { - } -}; // EaxFlangerEffectException - - -EaxFlangerEffect::EaxFlangerEffect() - : EaxEffect{AL_EFFECT_FLANGER} -{ - set_eax_defaults(); - set_efx_defaults(); + using Committer = ChorusFlangerEffect; + return Committer::Commit(props, mEaxProps, mAlProps.emplace()); } -void EaxFlangerEffect::dispatch(const EaxEaxCall& eax_call) +void EaxChorusCommitter::SetDefaults(EaxEffectProps &props) { - eax_call.is_get() ? get(eax_call) : set(eax_call); + using Committer = ChorusFlangerEffect; + Committer::SetDefaults(props); } -void EaxFlangerEffect::set_eax_defaults() +void EaxChorusCommitter::Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props) { - eax_.ulWaveform = EAXFLANGER_DEFAULTWAVEFORM; - eax_.lPhase = EAXFLANGER_DEFAULTPHASE; - eax_.flRate = EAXFLANGER_DEFAULTRATE; - eax_.flDepth = EAXFLANGER_DEFAULTDEPTH; - eax_.flFeedback = EAXFLANGER_DEFAULTFEEDBACK; - eax_.flDelay = EAXFLANGER_DEFAULTDELAY; - - eax_d_ = eax_; + using Committer = ChorusFlangerEffect; + Committer::Get(call, props); } -void EaxFlangerEffect::set_efx_waveform() +void EaxChorusCommitter::Set(const EaxCall &call, EAXCHORUSPROPERTIES &props) { - const auto waveform = clamp( - static_cast(eax_.ulWaveform), - AL_FLANGER_MIN_WAVEFORM, - AL_FLANGER_MAX_WAVEFORM); - - eax_set_efx_waveform(waveform, al_effect_props_); + using Committer = ChorusFlangerEffect; + Committer::Set(call, props); } -void EaxFlangerEffect::set_efx_phase() +template<> +struct FlangerCommitter::Exception : public EaxException { - const auto phase = clamp( - static_cast(eax_.lPhase), - AL_FLANGER_MIN_PHASE, - AL_FLANGER_MAX_PHASE); + explicit Exception(const char *message) : EaxException{"EAX_FLANGER_EFFECT", message} + { } +}; - eax_set_efx_phase(phase, al_effect_props_); +template<> +[[noreturn]] void FlangerCommitter::fail(const char *message) +{ + throw Exception{message}; } -void EaxFlangerEffect::set_efx_rate() +bool EaxFlangerCommitter::commit(const EAXFLANGERPROPERTIES &props) { - const auto rate = clamp( - eax_.flRate, - AL_FLANGER_MIN_RATE, - AL_FLANGER_MAX_RATE); - - eax_set_efx_rate(rate, al_effect_props_); + using Committer = ChorusFlangerEffect; + return Committer::Commit(props, mEaxProps, mAlProps.emplace()); } -void EaxFlangerEffect::set_efx_depth() +void EaxFlangerCommitter::SetDefaults(EaxEffectProps &props) { - const auto depth = clamp( - eax_.flDepth, - AL_FLANGER_MIN_DEPTH, - AL_FLANGER_MAX_DEPTH); - - eax_set_efx_depth(depth, al_effect_props_); + using Committer = ChorusFlangerEffect; + Committer::SetDefaults(props); } -void EaxFlangerEffect::set_efx_feedback() +void EaxFlangerCommitter::Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props) { - const auto feedback = clamp( - eax_.flFeedback, - AL_FLANGER_MIN_FEEDBACK, - AL_FLANGER_MAX_FEEDBACK); - - eax_set_efx_feedback(feedback, al_effect_props_); + using Committer = ChorusFlangerEffect; + Committer::Get(call, props); } -void EaxFlangerEffect::set_efx_delay() +void EaxFlangerCommitter::Set(const EaxCall &call, EAXFLANGERPROPERTIES &props) { - const auto delay = clamp( - eax_.flDelay, - AL_FLANGER_MIN_DELAY, - AL_FLANGER_MAX_DELAY); - - eax_set_efx_delay(delay, al_effect_props_); -} - -void EaxFlangerEffect::set_efx_defaults() -{ - set_efx_waveform(); - set_efx_phase(); - set_efx_rate(); - set_efx_depth(); - set_efx_feedback(); - set_efx_delay(); -} - -void EaxFlangerEffect::get(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) - { - case EAXFLANGER_NONE: - break; - - case EAXFLANGER_ALLPARAMETERS: - eax_call.set_value(eax_); - break; - - case EAXFLANGER_WAVEFORM: - eax_call.set_value(eax_.ulWaveform); - break; - - case EAXFLANGER_PHASE: - eax_call.set_value(eax_.lPhase); - break; - - case EAXFLANGER_RATE: - eax_call.set_value(eax_.flRate); - break; - - case EAXFLANGER_DEPTH: - eax_call.set_value(eax_.flDepth); - break; - - case EAXFLANGER_FEEDBACK: - eax_call.set_value(eax_.flFeedback); - break; - - case EAXFLANGER_DELAY: - eax_call.set_value(eax_.flDelay); - break; - - default: - throw EaxFlangerEffectException{"Unsupported property id."}; - } -} - -void EaxFlangerEffect::validate_waveform( - unsigned long ulWaveform) -{ - eax_validate_range( - "Waveform", - ulWaveform, - EAXFLANGER_MINWAVEFORM, - EAXFLANGER_MAXWAVEFORM); -} - -void EaxFlangerEffect::validate_phase( - long lPhase) -{ - eax_validate_range( - "Phase", - lPhase, - EAXFLANGER_MINPHASE, - EAXFLANGER_MAXPHASE); -} - -void EaxFlangerEffect::validate_rate( - float flRate) -{ - eax_validate_range( - "Rate", - flRate, - EAXFLANGER_MINRATE, - EAXFLANGER_MAXRATE); -} - -void EaxFlangerEffect::validate_depth( - float flDepth) -{ - eax_validate_range( - "Depth", - flDepth, - EAXFLANGER_MINDEPTH, - EAXFLANGER_MAXDEPTH); -} - -void EaxFlangerEffect::validate_feedback( - float flFeedback) -{ - eax_validate_range( - "Feedback", - flFeedback, - EAXFLANGER_MINFEEDBACK, - EAXFLANGER_MAXFEEDBACK); -} - -void EaxFlangerEffect::validate_delay( - float flDelay) -{ - eax_validate_range( - "Delay", - flDelay, - EAXFLANGER_MINDELAY, - EAXFLANGER_MAXDELAY); -} - -void EaxFlangerEffect::validate_all( - const EAXFLANGERPROPERTIES& all) -{ - validate_waveform(all.ulWaveform); - validate_phase(all.lPhase); - validate_rate(all.flRate); - validate_depth(all.flDepth); - validate_feedback(all.flDelay); - validate_delay(all.flDelay); -} - -void EaxFlangerEffect::defer_waveform( - unsigned long ulWaveform) -{ - eax_d_.ulWaveform = ulWaveform; - eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); -} - -void EaxFlangerEffect::defer_phase( - long lPhase) -{ - eax_d_.lPhase = lPhase; - eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase); -} - -void EaxFlangerEffect::defer_rate( - float flRate) -{ - eax_d_.flRate = flRate; - eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); -} - -void EaxFlangerEffect::defer_depth( - float flDepth) -{ - eax_d_.flDepth = flDepth; - eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth); -} - -void EaxFlangerEffect::defer_feedback( - float flFeedback) -{ - eax_d_.flFeedback = flFeedback; - eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); -} - -void EaxFlangerEffect::defer_delay( - float flDelay) -{ - eax_d_.flDelay = flDelay; - eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); -} - -void EaxFlangerEffect::defer_all( - const EAXFLANGERPROPERTIES& all) -{ - defer_waveform(all.ulWaveform); - defer_phase(all.lPhase); - defer_rate(all.flRate); - defer_depth(all.flDepth); - defer_feedback(all.flDelay); - defer_delay(all.flDelay); -} - -void EaxFlangerEffect::defer_waveform( - const EaxEaxCall& eax_call) -{ - const auto& waveform = - eax_call.get_value(); - - validate_waveform(waveform); - defer_waveform(waveform); -} - -void EaxFlangerEffect::defer_phase( - const EaxEaxCall& eax_call) -{ - const auto& phase = - eax_call.get_value(); - - validate_phase(phase); - defer_phase(phase); -} - -void EaxFlangerEffect::defer_rate( - const EaxEaxCall& eax_call) -{ - const auto& rate = - eax_call.get_value(); - - validate_rate(rate); - defer_rate(rate); -} - -void EaxFlangerEffect::defer_depth( - const EaxEaxCall& eax_call) -{ - const auto& depth = - eax_call.get_value(); - - validate_depth(depth); - defer_depth(depth); -} - -void EaxFlangerEffect::defer_feedback( - const EaxEaxCall& eax_call) -{ - const auto& feedback = - eax_call.get_value(); - - validate_feedback(feedback); - defer_feedback(feedback); -} - -void EaxFlangerEffect::defer_delay( - const EaxEaxCall& eax_call) -{ - const auto& delay = - eax_call.get_value(); - - validate_delay(delay); - defer_delay(delay); -} - -void EaxFlangerEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto& all = - eax_call.get_value(); - - validate_all(all); - defer_all(all); -} - -// [[nodiscard]] -bool EaxFlangerEffect::apply_deferred() -{ - if (eax_dirty_flags_ == EaxFlangerEffectDirtyFlags{}) - { - return false; - } - - eax_ = eax_d_; - - if (eax_dirty_flags_.ulWaveform) - { - set_efx_waveform(); - } - - if (eax_dirty_flags_.lPhase) - { - set_efx_phase(); - } - - if (eax_dirty_flags_.flRate) - { - set_efx_rate(); - } - - if (eax_dirty_flags_.flDepth) - { - set_efx_depth(); - } - - if (eax_dirty_flags_.flFeedback) - { - set_efx_feedback(); - } - - if (eax_dirty_flags_.flDelay) - { - set_efx_delay(); - } - - eax_dirty_flags_ = EaxFlangerEffectDirtyFlags{}; - - return true; -} - -void EaxFlangerEffect::set(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) - { - case EAXFLANGER_NONE: - break; - - case EAXFLANGER_ALLPARAMETERS: - defer_all(eax_call); - break; - - case EAXFLANGER_WAVEFORM: - defer_waveform(eax_call); - break; - - case EAXFLANGER_PHASE: - defer_phase(eax_call); - break; - - case EAXFLANGER_RATE: - defer_rate(eax_call); - break; - - case EAXFLANGER_DEPTH: - defer_depth(eax_call); - break; - - case EAXFLANGER_FEEDBACK: - defer_feedback(eax_call); - break; - - case EAXFLANGER_DELAY: - defer_delay(eax_call); - break; - - default: - throw EaxFlangerEffectException{"Unsupported property id."}; - } -} - -} // namespace - -EaxEffectUPtr eax_create_eax_flanger_effect() -{ - return std::make_unique(); + using Committer = ChorusFlangerEffect; + Committer::Set(call, props); } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/compressor.cpp b/libs/openal-soft/al/effects/compressor.cpp index bb5dfa3e..a388dcef 100644 --- a/libs/openal-soft/al/effects/compressor.cpp +++ b/libs/openal-soft/al/effects/compressor.cpp @@ -9,22 +9,33 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" - -#include "al/eax_exception.h" -#include "al/eax_utils.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { -void Compressor_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultProps() noexcept +{ + CompressorProps props{}; + props.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; + return props; +} + +} // namespace + +const EffectProps CompressorEffectProps{genDefaultProps()}; + +void EffectHandler::SetParami(CompressorProps &props, ALenum param, int val) { switch(param) { case AL_COMPRESSOR_ONOFF: if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) throw effect_exception{AL_INVALID_VALUE, "Compressor state out of range"}; - props->Compressor.OnOff = (val != AL_FALSE); + props.OnOff = (val != AL_FALSE); break; default: @@ -32,22 +43,22 @@ void Compressor_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Compressor_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Compressor_setParami(props, param, vals[0]); } -void Compressor_setParamf(EffectProps*, ALenum param, float) +void EffectHandler::SetParamiv(CompressorProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void EffectHandler::SetParamf(CompressorProps&, ALenum param, float) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } -void Compressor_setParamfv(EffectProps*, ALenum param, const float*) +void EffectHandler::SetParamfv(CompressorProps&, ALenum param, const float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param}; } -void Compressor_getParami(const EffectProps *props, ALenum param, int *val) +void EffectHandler::GetParami(const CompressorProps &props, ALenum param, int *val) { switch(param) { case AL_COMPRESSOR_ONOFF: - *val = props->Compressor.OnOff; + *val = props.OnOff; break; default: @@ -55,242 +66,91 @@ void Compressor_getParami(const EffectProps *props, ALenum param, int *val) param}; } } -void Compressor_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Compressor_getParami(props, param, vals); } -void Compressor_getParamf(const EffectProps*, ALenum param, float*) +void EffectHandler::GetParamiv(const CompressorProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void EffectHandler::GetParamf(const CompressorProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } -void Compressor_getParamfv(const EffectProps*, ALenum param, float*) +void EffectHandler::GetParamfv(const CompressorProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param}; } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Compressor); - -const EffectProps CompressorEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { -using EaxCompressorEffectDirtyFlagsValue = std::uint_least8_t; +using CompressorCommitter = EaxCommitter; -struct EaxCompressorEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxCompressorEffectDirtyFlagsValue ulOnOff : 1; -}; // EaxCompressorEffectDirtyFlags - - -class EaxCompressorEffect final : - public EaxEffect -{ -public: - EaxCompressorEffect(); - - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAXAGCCOMPRESSORPROPERTIES eax_{}; - EAXAGCCOMPRESSORPROPERTIES eax_d_{}; - EaxCompressorEffectDirtyFlags eax_dirty_flags_{}; - - - void set_eax_defaults(); - - void set_efx_on_off(); - void set_efx_defaults(); - - void get(const EaxEaxCall& eax_call); - - void validate_on_off(unsigned long ulOnOff); - void validate_all(const EAXAGCCOMPRESSORPROPERTIES& eax_all); - - void defer_on_off(unsigned long ulOnOff); - void defer_all(const EAXAGCCOMPRESSORPROPERTIES& eax_all); - - void defer_on_off(const EaxEaxCall& eax_call); - void defer_all(const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxCompressorEffect - - -class EaxCompressorEffectException : - public EaxException -{ -public: - explicit EaxCompressorEffectException( - const char* message) - : - EaxException{"EAX_COMPRESSOR_EFFECT", message} +struct OnOffValidator { + void operator()(unsigned long ulOnOff) const { + eax_validate_range( + "On-Off", + ulOnOff, + EAXAGCCOMPRESSOR_MINONOFF, + EAXAGCCOMPRESSOR_MAXONOFF); } -}; // EaxCompressorEffectException +}; // OnOffValidator - -EaxCompressorEffect::EaxCompressorEffect() - : EaxEffect{AL_EFFECT_COMPRESSOR} -{ - set_eax_defaults(); - set_efx_defaults(); -} - -// [[nodiscard]] -void EaxCompressorEffect::dispatch(const EaxEaxCall& eax_call) -{ - eax_call.is_get() ? get(eax_call) : set(eax_call); -} - -void EaxCompressorEffect::set_eax_defaults() -{ - eax_.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF; - - eax_d_ = eax_; -} - -void EaxCompressorEffect::set_efx_on_off() -{ - const auto on_off = clamp( - static_cast(eax_.ulOnOff), - AL_COMPRESSOR_MIN_ONOFF, - AL_COMPRESSOR_MAX_ONOFF); - - al_effect_props_.Compressor.OnOff = (on_off != AL_FALSE); -} - -void EaxCompressorEffect::set_efx_defaults() -{ - set_efx_on_off(); -} - -void EaxCompressorEffect::get(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) +struct AllValidator { + void operator()(const EAXAGCCOMPRESSORPROPERTIES& all) const { - case EAXAGCCOMPRESSOR_NONE: - break; - - case EAXAGCCOMPRESSOR_ALLPARAMETERS: - eax_call.set_value(eax_); - break; - - case EAXAGCCOMPRESSOR_ONOFF: - eax_call.set_value(eax_.ulOnOff); - break; - - default: - throw EaxCompressorEffectException{"Unsupported property id."}; + OnOffValidator{}(all.ulOnOff); } +}; // AllValidator + +} // namespace + +template<> +struct CompressorCommitter::Exception : public EaxException +{ + explicit Exception(const char *message) : EaxException{"EAX_CHORUS_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void CompressorCommitter::fail(const char *message) +{ + throw Exception{message}; } -void EaxCompressorEffect::validate_on_off( - unsigned long ulOnOff) +bool EaxCompressorCommitter::commit(const EAXAGCCOMPRESSORPROPERTIES &props) { - eax_validate_range( - "On-Off", - ulOnOff, - EAXAGCCOMPRESSOR_MINONOFF, - EAXAGCCOMPRESSOR_MAXONOFF); -} - -void EaxCompressorEffect::validate_all( - const EAXAGCCOMPRESSORPROPERTIES& eax_all) -{ - validate_on_off(eax_all.ulOnOff); -} - -void EaxCompressorEffect::defer_on_off( - unsigned long ulOnOff) -{ - eax_d_.ulOnOff = ulOnOff; - eax_dirty_flags_.ulOnOff = (eax_.ulOnOff != eax_d_.ulOnOff); -} - -void EaxCompressorEffect::defer_all( - const EAXAGCCOMPRESSORPROPERTIES& eax_all) -{ - defer_on_off(eax_all.ulOnOff); -} - -void EaxCompressorEffect::defer_on_off( - const EaxEaxCall& eax_call) -{ - const auto& on_off = - eax_call.get_value(); - - validate_on_off(on_off); - defer_on_off(on_off); -} - -void EaxCompressorEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto& all = - eax_call.get_value(); - - validate_all(all); - defer_all(all); -} - -// [[nodiscard]] -bool EaxCompressorEffect::apply_deferred() -{ - if (eax_dirty_flags_ == EaxCompressorEffectDirtyFlags{}) - { + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; - } - eax_ = eax_d_; - - if (eax_dirty_flags_.ulOnOff) - { - set_efx_on_off(); - } - - eax_dirty_flags_ = EaxCompressorEffectDirtyFlags{}; + mEaxProps = props; + mAlProps = CompressorProps{props.ulOnOff != 0}; return true; } -void EaxCompressorEffect::set(const EaxEaxCall& eax_call) +void EaxCompressorCommitter::SetDefaults(EaxEffectProps &props) { - switch(eax_call.get_property_id()) + props = EAXAGCCOMPRESSORPROPERTIES{EAXAGCCOMPRESSOR_DEFAULTONOFF}; +} + +void EaxCompressorCommitter::Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props) +{ + switch(call.get_property_id()) { - case EAXAGCCOMPRESSOR_NONE: - break; - - case EAXAGCCOMPRESSOR_ALLPARAMETERS: - defer_all(eax_call); - break; - - case EAXAGCCOMPRESSOR_ONOFF: - defer_on_off(eax_call); - break; - - default: - throw EaxCompressorEffectException{"Unsupported property id."}; + case EAXAGCCOMPRESSOR_NONE: break; + case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value(props); break; + case EAXAGCCOMPRESSOR_ONOFF: call.set_value(props.ulOnOff); break; + default: fail_unknown_property_id(); } } -} // namespace - -EaxEffectUPtr eax_create_eax_compressor_effect() +void EaxCompressorCommitter::Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props) { - return std::make_unique(); + switch(call.get_property_id()) + { + case EAXAGCCOMPRESSOR_NONE: break; + case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer(call, props); break; + case EAXAGCCOMPRESSOR_ONOFF: defer(call, props.ulOnOff); break; + default: fail_unknown_property_id(); + } } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/convolution.cpp b/libs/openal-soft/al/effects/convolution.cpp index 8e850fd3..6f31fce3 100644 --- a/libs/openal-soft/al/effects/convolution.cpp +++ b/libs/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 EffectHandler::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 EffectHandler::SetParamiv(ConvolutionProps &props, ALenum param, const int *vals) +{ + switch(param) + { + default: + SetParami(props, param, *vals); + } +} +void EffectHandler::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 EffectHandler::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 EffectHandler::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 EffectHandler::GetParamiv(const ConvolutionProps &props, ALenum param, int *vals) +{ + switch(param) + { + default: + GetParami(props, param, vals); + } +} +void EffectHandler::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 EffectHandler::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/libs/openal-soft/al/effects/dedicated.cpp b/libs/openal-soft/al/effects/dedicated.cpp index db57003c..e6e62f56 100644 --- a/libs/openal-soft/al/effects/dedicated.cpp +++ b/libs/openal-soft/al/effects/dedicated.cpp @@ -12,61 +12,109 @@ namespace { -void Dedicated_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultDialogProps() noexcept +{ + DedicatedDialogProps props{}; + props.Gain = 1.0f; + return props; +} + +constexpr EffectProps genDefaultLfeProps() noexcept +{ + DedicatedLfeProps props{}; + props.Gain = 1.0f; + return props; +} + +} // namespace + +const EffectProps DedicatedDialogEffectProps{genDefaultDialogProps()}; + +void EffectHandler::SetParami(DedicatedDialogProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void Dedicated_setParamiv(EffectProps*, ALenum param, const int*) +void EffectHandler::SetParamiv(DedicatedDialogProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param}; } -void Dedicated_setParamf(EffectProps *props, ALenum param, float val) +void EffectHandler::SetParamf(DedicatedDialogProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: if(!(val >= 0.0f && std::isfinite(val))) throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; - props->Dedicated.Gain = val; + props.Gain = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; } } -void Dedicated_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Dedicated_setParamf(props, param, vals[0]); } +void EffectHandler::SetParamfv(DedicatedDialogProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Dedicated_getParami(const EffectProps*, ALenum param, int*) +void EffectHandler::GetParami(const DedicatedDialogProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void Dedicated_getParamiv(const EffectProps*, ALenum param, int*) +void EffectHandler::GetParamiv(const DedicatedDialogProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param}; } -void Dedicated_getParamf(const EffectProps *props, ALenum param, float *val) +void EffectHandler::GetParamf(const DedicatedDialogProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_DEDICATED_GAIN: *val = props.Gain; break; + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + } +} +void EffectHandler::GetParamfv(const DedicatedDialogProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } + + +const EffectProps DedicatedLfeEffectProps{genDefaultLfeProps()}; + +void EffectHandler::SetParami(DedicatedLfeProps&, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } +void EffectHandler::SetParamiv(DedicatedLfeProps&, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", + param}; +} +void EffectHandler::SetParamf(DedicatedLfeProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: - *val = props->Dedicated.Gain; + if(!(val >= 0.0f && std::isfinite(val))) + throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; + props.Gain = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; } } -void Dedicated_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Dedicated_getParamf(props, param, vals); } +void EffectHandler::SetParamfv(DedicatedLfeProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -EffectProps genDefaultProps() noexcept +void EffectHandler::GetParami(const DedicatedLfeProps&, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } +void EffectHandler::GetParamiv(const DedicatedLfeProps&, ALenum param, int*) { - EffectProps props{}; - props.Dedicated.Gain = 1.0f; - return props; + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", + param}; } - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Dedicated); - -const EffectProps DedicatedEffectProps{genDefaultProps()}; +void EffectHandler::GetParamf(const DedicatedLfeProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_DEDICATED_GAIN: *val = props.Gain; break; + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + } +} +void EffectHandler::GetParamfv(const DedicatedLfeProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } diff --git a/libs/openal-soft/al/effects/distortion.cpp b/libs/openal-soft/al/effects/distortion.cpp index 13b1f23d..eb93d8cd 100644 --- a/libs/openal-soft/al/effects/distortion.cpp +++ b/libs/openal-soft/al/effects/distortion.cpp @@ -9,563 +9,249 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" - -#include "al/eax_exception.h" -#include "al/eax_utils.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { -void Distortion_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultProps() noexcept +{ + DistortionProps props{}; + props.Edge = AL_DISTORTION_DEFAULT_EDGE; + props.Gain = AL_DISTORTION_DEFAULT_GAIN; + props.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF; + props.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER; + props.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH; + return props; +} + +} // namespace + +const EffectProps DistortionEffectProps{genDefaultProps()}; + +void EffectHandler::SetParami(DistortionProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } -void Distortion_setParamiv(EffectProps*, ALenum param, const int*) +void EffectHandler::SetParamiv(DistortionProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param}; } -void Distortion_setParamf(EffectProps *props, ALenum param, float val) +void EffectHandler::SetParamf(DistortionProps &props, ALenum param, float val) { switch(param) { case AL_DISTORTION_EDGE: if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"}; - props->Distortion.Edge = val; + props.Edge = val; break; case AL_DISTORTION_GAIN: if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"}; - props->Distortion.Gain = val; + props.Gain = val; break; case AL_DISTORTION_LOWPASS_CUTOFF: if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) throw effect_exception{AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"}; - props->Distortion.LowpassCutoff = val; + props.LowpassCutoff = val; break; case AL_DISTORTION_EQCENTER: if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"}; - props->Distortion.EQCenter = val; + props.EQCenter = val; break; case AL_DISTORTION_EQBANDWIDTH: if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"}; - props->Distortion.EQBandwidth = val; + props.EQBandwidth = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; } } -void Distortion_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Distortion_setParamf(props, param, vals[0]); } +void EffectHandler::SetParamfv(DistortionProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Distortion_getParami(const EffectProps*, ALenum param, int*) +void EffectHandler::GetParami(const DistortionProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } -void Distortion_getParamiv(const EffectProps*, ALenum param, int*) +void EffectHandler::GetParamiv(const DistortionProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param}; } -void Distortion_getParamf(const EffectProps *props, ALenum param, float *val) +void EffectHandler::GetParamf(const DistortionProps &props, ALenum param, float *val) { switch(param) { - case AL_DISTORTION_EDGE: - *val = props->Distortion.Edge; - break; - - case AL_DISTORTION_GAIN: - *val = props->Distortion.Gain; - break; - - case AL_DISTORTION_LOWPASS_CUTOFF: - *val = props->Distortion.LowpassCutoff; - break; - - case AL_DISTORTION_EQCENTER: - *val = props->Distortion.EQCenter; - break; - - case AL_DISTORTION_EQBANDWIDTH: - *val = props->Distortion.EQBandwidth; - break; + case AL_DISTORTION_EDGE: *val = props.Edge; break; + case AL_DISTORTION_GAIN: *val = props.Gain; break; + case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; break; + case AL_DISTORTION_EQCENTER: *val = props.EQCenter; break; + case AL_DISTORTION_EQBANDWIDTH: *val = props.EQBandwidth; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; } } -void Distortion_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Distortion_getParamf(props, param, vals); } +void EffectHandler::GetParamfv(const DistortionProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE; - props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN; - props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF; - props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER; - props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Distortion); - -const EffectProps DistortionEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { -using EaxDistortionEffectDirtyFlagsValue = std::uint_least8_t; +using DistortionCommitter = EaxCommitter; -struct EaxDistortionEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxDistortionEffectDirtyFlagsValue flEdge : 1; - EaxDistortionEffectDirtyFlagsValue lGain : 1; - EaxDistortionEffectDirtyFlagsValue flLowPassCutOff : 1; - EaxDistortionEffectDirtyFlagsValue flEQCenter : 1; - EaxDistortionEffectDirtyFlagsValue flEQBandwidth : 1; -}; // EaxDistortionEffectDirtyFlags - - -class EaxDistortionEffect final : - public EaxEffect -{ -public: - EaxDistortionEffect(); - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAXDISTORTIONPROPERTIES eax_{}; - EAXDISTORTIONPROPERTIES eax_d_{}; - EaxDistortionEffectDirtyFlags eax_dirty_flags_{}; - - void set_eax_defaults(); - - void set_efx_edge(); - void set_efx_gain(); - void set_efx_lowpass_cutoff(); - void set_efx_eq_center(); - void set_efx_eq_bandwidth(); - void set_efx_defaults(); - - void get(const EaxEaxCall& eax_call); - - void validate_edge(float flEdge); - void validate_gain(long lGain); - void validate_lowpass_cutoff(float flLowPassCutOff); - void validate_eq_center(float flEQCenter); - void validate_eq_bandwidth(float flEQBandwidth); - void validate_all(const EAXDISTORTIONPROPERTIES& eax_all); - - void defer_edge(float flEdge); - void defer_gain(long lGain); - void defer_low_pass_cutoff(float flLowPassCutOff); - void defer_eq_center(float flEQCenter); - void defer_eq_bandwidth(float flEQBandwidth); - void defer_all(const EAXDISTORTIONPROPERTIES& eax_all); - - void defer_edge(const EaxEaxCall& eax_call); - void defer_gain(const EaxEaxCall& eax_call); - void defer_low_pass_cutoff(const EaxEaxCall& eax_call); - void defer_eq_center(const EaxEaxCall& eax_call); - void defer_eq_bandwidth(const EaxEaxCall& eax_call); - void defer_all(const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxDistortionEffect - - -class EaxDistortionEffectException : - public EaxException -{ -public: - explicit EaxDistortionEffectException( - const char* message) - : - EaxException{"EAX_DISTORTION_EFFECT", message} +struct EdgeValidator { + void operator()(float flEdge) const { + eax_validate_range( + "Edge", + flEdge, + EAXDISTORTION_MINEDGE, + EAXDISTORTION_MAXEDGE); } -}; // EaxDistortionEffectException +}; // EdgeValidator - -EaxDistortionEffect::EaxDistortionEffect() - : EaxEffect{AL_EFFECT_DISTORTION} -{ - set_eax_defaults(); - set_efx_defaults(); -} - -void EaxDistortionEffect::dispatch(const EaxEaxCall& eax_call) -{ - eax_call.is_get() ? get(eax_call) : set(eax_call); -} - -void EaxDistortionEffect::set_eax_defaults() -{ - eax_.flEdge = EAXDISTORTION_DEFAULTEDGE; - eax_.lGain = EAXDISTORTION_DEFAULTGAIN; - eax_.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF; - eax_.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER; - eax_.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH; - - eax_d_ = eax_; -} - -void EaxDistortionEffect::set_efx_edge() -{ - const auto edge = clamp( - eax_.flEdge, - AL_DISTORTION_MIN_EDGE, - AL_DISTORTION_MAX_EDGE); - - al_effect_props_.Distortion.Edge = edge; -} - -void EaxDistortionEffect::set_efx_gain() -{ - const auto gain = clamp( - level_mb_to_gain(static_cast(eax_.lGain)), - AL_DISTORTION_MIN_GAIN, - AL_DISTORTION_MAX_GAIN); - - al_effect_props_.Distortion.Gain = gain; -} - -void EaxDistortionEffect::set_efx_lowpass_cutoff() -{ - const auto lowpass_cutoff = clamp( - eax_.flLowPassCutOff, - AL_DISTORTION_MIN_LOWPASS_CUTOFF, - AL_DISTORTION_MAX_LOWPASS_CUTOFF); - - al_effect_props_.Distortion.LowpassCutoff = lowpass_cutoff; -} - -void EaxDistortionEffect::set_efx_eq_center() -{ - const auto eq_center = clamp( - eax_.flEQCenter, - AL_DISTORTION_MIN_EQCENTER, - AL_DISTORTION_MAX_EQCENTER); - - al_effect_props_.Distortion.EQCenter = eq_center; -} - -void EaxDistortionEffect::set_efx_eq_bandwidth() -{ - const auto eq_bandwidth = clamp( - eax_.flEdge, - AL_DISTORTION_MIN_EQBANDWIDTH, - AL_DISTORTION_MAX_EQBANDWIDTH); - - al_effect_props_.Distortion.EQBandwidth = eq_bandwidth; -} - -void EaxDistortionEffect::set_efx_defaults() -{ - set_efx_edge(); - set_efx_gain(); - set_efx_lowpass_cutoff(); - set_efx_eq_center(); - set_efx_eq_bandwidth(); -} - -void EaxDistortionEffect::get(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) +struct GainValidator { + void operator()(long lGain) const { - case EAXDISTORTION_NONE: - break; - - case EAXDISTORTION_ALLPARAMETERS: - eax_call.set_value(eax_); - break; - - case EAXDISTORTION_EDGE: - eax_call.set_value(eax_.flEdge); - break; - - case EAXDISTORTION_GAIN: - eax_call.set_value(eax_.lGain); - break; - - case EAXDISTORTION_LOWPASSCUTOFF: - eax_call.set_value(eax_.flLowPassCutOff); - break; - - case EAXDISTORTION_EQCENTER: - eax_call.set_value(eax_.flEQCenter); - break; - - case EAXDISTORTION_EQBANDWIDTH: - eax_call.set_value(eax_.flEQBandwidth); - break; - - default: - throw EaxDistortionEffectException{"Unsupported property id."}; + eax_validate_range( + "Gain", + lGain, + EAXDISTORTION_MINGAIN, + EAXDISTORTION_MAXGAIN); } -} +}; // GainValidator -void EaxDistortionEffect::validate_edge( - float flEdge) -{ - eax_validate_range( - "Edge", - flEdge, - EAXDISTORTION_MINEDGE, - EAXDISTORTION_MAXEDGE); -} - -void EaxDistortionEffect::validate_gain( - long lGain) -{ - eax_validate_range( - "Gain", - lGain, - EAXDISTORTION_MINGAIN, - EAXDISTORTION_MAXGAIN); -} - -void EaxDistortionEffect::validate_lowpass_cutoff( - float flLowPassCutOff) -{ - eax_validate_range( - "Low-pass Cut-off", - flLowPassCutOff, - EAXDISTORTION_MINLOWPASSCUTOFF, - EAXDISTORTION_MAXLOWPASSCUTOFF); -} - -void EaxDistortionEffect::validate_eq_center( - float flEQCenter) -{ - eax_validate_range( - "EQ Center", - flEQCenter, - EAXDISTORTION_MINEQCENTER, - EAXDISTORTION_MAXEQCENTER); -} - -void EaxDistortionEffect::validate_eq_bandwidth( - float flEQBandwidth) -{ - eax_validate_range( - "EQ Bandwidth", - flEQBandwidth, - EAXDISTORTION_MINEQBANDWIDTH, - EAXDISTORTION_MAXEQBANDWIDTH); -} - -void EaxDistortionEffect::validate_all( - const EAXDISTORTIONPROPERTIES& eax_all) -{ - validate_edge(eax_all.flEdge); - validate_gain(eax_all.lGain); - validate_lowpass_cutoff(eax_all.flLowPassCutOff); - validate_eq_center(eax_all.flEQCenter); - validate_eq_bandwidth(eax_all.flEQBandwidth); -} - -void EaxDistortionEffect::defer_edge( - float flEdge) -{ - eax_d_.flEdge = flEdge; - eax_dirty_flags_.flEdge = (eax_.flEdge != eax_d_.flEdge); -} - -void EaxDistortionEffect::defer_gain( - long lGain) -{ - eax_d_.lGain = lGain; - eax_dirty_flags_.lGain = (eax_.lGain != eax_d_.lGain); -} - -void EaxDistortionEffect::defer_low_pass_cutoff( - float flLowPassCutOff) -{ - eax_d_.flLowPassCutOff = flLowPassCutOff; - eax_dirty_flags_.flLowPassCutOff = (eax_.flLowPassCutOff != eax_d_.flLowPassCutOff); -} - -void EaxDistortionEffect::defer_eq_center( - float flEQCenter) -{ - eax_d_.flEQCenter = flEQCenter; - eax_dirty_flags_.flEQCenter = (eax_.flEQCenter != eax_d_.flEQCenter); -} - -void EaxDistortionEffect::defer_eq_bandwidth( - float flEQBandwidth) -{ - eax_d_.flEQBandwidth = flEQBandwidth; - eax_dirty_flags_.flEQBandwidth = (eax_.flEQBandwidth != eax_d_.flEQBandwidth); -} - -void EaxDistortionEffect::defer_all( - const EAXDISTORTIONPROPERTIES& eax_all) -{ - defer_edge(eax_all.flEdge); - defer_gain(eax_all.lGain); - defer_low_pass_cutoff(eax_all.flLowPassCutOff); - defer_eq_center(eax_all.flEQCenter); - defer_eq_bandwidth(eax_all.flEQBandwidth); -} - -void EaxDistortionEffect::defer_edge( - const EaxEaxCall& eax_call) -{ - const auto& edge = - eax_call.get_value(); - - validate_edge(edge); - defer_edge(edge); -} - -void EaxDistortionEffect::defer_gain( - const EaxEaxCall& eax_call) -{ - const auto& gain = - eax_call.get_value(); - - validate_gain(gain); - defer_gain(gain); -} - -void EaxDistortionEffect::defer_low_pass_cutoff( - const EaxEaxCall& eax_call) -{ - const auto& lowpass_cutoff = - eax_call.get_value(); - - validate_lowpass_cutoff(lowpass_cutoff); - defer_low_pass_cutoff(lowpass_cutoff); -} - -void EaxDistortionEffect::defer_eq_center( - const EaxEaxCall& eax_call) -{ - const auto& eq_center = - eax_call.get_value(); - - validate_eq_center(eq_center); - defer_eq_center(eq_center); -} - -void EaxDistortionEffect::defer_eq_bandwidth( - const EaxEaxCall& eax_call) -{ - const auto& eq_bandwidth = - eax_call.get_value(); - - validate_eq_bandwidth(eq_bandwidth); - defer_eq_bandwidth(eq_bandwidth); -} - -void EaxDistortionEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto& all = - eax_call.get_value(); - - validate_all(all); - defer_all(all); -} - -// [[nodiscard]] -bool EaxDistortionEffect::apply_deferred() -{ - if (eax_dirty_flags_ == EaxDistortionEffectDirtyFlags{}) +struct LowPassCutOffValidator { + void operator()(float flLowPassCutOff) const { + eax_validate_range( + "Low-pass Cut-off", + flLowPassCutOff, + EAXDISTORTION_MINLOWPASSCUTOFF, + EAXDISTORTION_MAXLOWPASSCUTOFF); + } +}; // LowPassCutOffValidator + +struct EqCenterValidator { + void operator()(float flEQCenter) const + { + eax_validate_range( + "EQ Center", + flEQCenter, + EAXDISTORTION_MINEQCENTER, + EAXDISTORTION_MAXEQCENTER); + } +}; // EqCenterValidator + +struct EqBandwidthValidator { + void operator()(float flEQBandwidth) const + { + eax_validate_range( + "EQ Bandwidth", + flEQBandwidth, + EAXDISTORTION_MINEQBANDWIDTH, + EAXDISTORTION_MAXEQBANDWIDTH); + } +}; // EqBandwidthValidator + +struct AllValidator { + void operator()(const EAXDISTORTIONPROPERTIES& all) const + { + EdgeValidator{}(all.flEdge); + GainValidator{}(all.lGain); + LowPassCutOffValidator{}(all.flLowPassCutOff); + EqCenterValidator{}(all.flEQCenter); + EqBandwidthValidator{}(all.flEQBandwidth); + } +}; // AllValidator + +} // namespace + +template<> +struct DistortionCommitter::Exception : public EaxException { + explicit Exception(const char *message) : EaxException{"EAX_DISTORTION_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void DistortionCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +bool EaxDistortionCommitter::commit(const EAXDISTORTIONPROPERTIES &props) +{ + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; - } - eax_ = eax_d_; - - if (eax_dirty_flags_.flEdge) - { - set_efx_edge(); - } - - if (eax_dirty_flags_.lGain) - { - set_efx_gain(); - } - - if (eax_dirty_flags_.flLowPassCutOff) - { - set_efx_lowpass_cutoff(); - } - - if (eax_dirty_flags_.flEQCenter) - { - set_efx_eq_center(); - } - - if (eax_dirty_flags_.flEQBandwidth) - { - set_efx_eq_bandwidth(); - } - - eax_dirty_flags_ = EaxDistortionEffectDirtyFlags{}; + mEaxProps = props; + 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; } -void EaxDistortionEffect::set(const EaxEaxCall& eax_call) +void EaxDistortionCommitter::SetDefaults(EaxEffectProps &props) { - switch(eax_call.get_property_id()) + static constexpr EAXDISTORTIONPROPERTIES defprops{[] { - case EAXDISTORTION_NONE: - break; + 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; +} - case EAXDISTORTION_ALLPARAMETERS: - defer_all(eax_call); - break; - - case EAXDISTORTION_EDGE: - defer_edge(eax_call); - break; - - case EAXDISTORTION_GAIN: - defer_gain(eax_call); - break; - - case EAXDISTORTION_LOWPASSCUTOFF: - defer_low_pass_cutoff(eax_call); - break; - - case EAXDISTORTION_EQCENTER: - defer_eq_center(eax_call); - break; - - case EAXDISTORTION_EQBANDWIDTH: - defer_eq_bandwidth(eax_call); - break; - - default: - throw EaxDistortionEffectException{"Unsupported property id."}; +void EaxDistortionCommitter::Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props) +{ + switch(call.get_property_id()) + { + case EAXDISTORTION_NONE: break; + case EAXDISTORTION_ALLPARAMETERS: call.set_value(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(); } } -} // namespace - -EaxEffectUPtr eax_create_eax_distortion_effect() +void EaxDistortionCommitter::Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props) { - return std::make_unique(); + switch(call.get_property_id()) + { + case EAXDISTORTION_NONE: 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(); + } } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/echo.cpp b/libs/openal-soft/al/effects/echo.cpp index 61adad7f..4605f5b4 100644 --- a/libs/openal-soft/al/effects/echo.cpp +++ b/libs/openal-soft/al/effects/echo.cpp @@ -9,9 +9,9 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" - -#include "al/eax_exception.h" -#include "al/eax_utils.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -20,550 +20,235 @@ namespace { static_assert(EchoMaxDelay >= AL_ECHO_MAX_DELAY, "Echo max delay too short"); static_assert(EchoMaxLRDelay >= AL_ECHO_MAX_LRDELAY, "Echo max left-right delay too short"); -void Echo_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultProps() noexcept +{ + EchoProps props{}; + props.Delay = AL_ECHO_DEFAULT_DELAY; + props.LRDelay = AL_ECHO_DEFAULT_LRDELAY; + props.Damping = AL_ECHO_DEFAULT_DAMPING; + props.Feedback = AL_ECHO_DEFAULT_FEEDBACK; + props.Spread = AL_ECHO_DEFAULT_SPREAD; + return props; +} + +} // namespace + +const EffectProps EchoEffectProps{genDefaultProps()}; + +void EffectHandler::SetParami(EchoProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } -void Echo_setParamiv(EffectProps*, ALenum param, const int*) +void EffectHandler::SetParamiv(EchoProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } -void Echo_setParamf(EffectProps *props, ALenum param, float val) +void EffectHandler::SetParamf(EchoProps &props, ALenum param, float val) { switch(param) { case AL_ECHO_DELAY: if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) throw effect_exception{AL_INVALID_VALUE, "Echo delay out of range"}; - props->Echo.Delay = val; + props.Delay = val; break; case AL_ECHO_LRDELAY: if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) throw effect_exception{AL_INVALID_VALUE, "Echo LR delay out of range"}; - props->Echo.LRDelay = val; + props.LRDelay = val; break; case AL_ECHO_DAMPING: if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) throw effect_exception{AL_INVALID_VALUE, "Echo damping out of range"}; - props->Echo.Damping = val; + props.Damping = val; break; case AL_ECHO_FEEDBACK: if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) throw effect_exception{AL_INVALID_VALUE, "Echo feedback out of range"}; - props->Echo.Feedback = val; + props.Feedback = val; break; case AL_ECHO_SPREAD: if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) throw effect_exception{AL_INVALID_VALUE, "Echo spread out of range"}; - props->Echo.Spread = val; + props.Spread = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; } } -void Echo_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Echo_setParamf(props, param, vals[0]); } +void EffectHandler::SetParamfv(EchoProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Echo_getParami(const EffectProps*, ALenum param, int*) +void EffectHandler::GetParami(const EchoProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } -void Echo_getParamiv(const EffectProps*, ALenum param, int*) +void EffectHandler::GetParamiv(const EchoProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } -void Echo_getParamf(const EffectProps *props, ALenum param, float *val) +void EffectHandler::GetParamf(const EchoProps &props, ALenum param, float *val) { switch(param) { - case AL_ECHO_DELAY: - *val = props->Echo.Delay; - break; - - case AL_ECHO_LRDELAY: - *val = props->Echo.LRDelay; - break; - - case AL_ECHO_DAMPING: - *val = props->Echo.Damping; - break; - - case AL_ECHO_FEEDBACK: - *val = props->Echo.Feedback; - break; - - case AL_ECHO_SPREAD: - *val = props->Echo.Spread; - break; + case AL_ECHO_DELAY: *val = props.Delay; break; + case AL_ECHO_LRDELAY: *val = props.LRDelay; break; + case AL_ECHO_DAMPING: *val = props.Damping; break; + case AL_ECHO_FEEDBACK: *val = props.Feedback; break; + case AL_ECHO_SPREAD: *val = props.Spread; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; } } -void Echo_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Echo_getParamf(props, param, vals); } +void EffectHandler::GetParamfv(const EchoProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Echo.Delay = AL_ECHO_DEFAULT_DELAY; - props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY; - props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING; - props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK; - props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Echo); - -const EffectProps EchoEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { -using EaxEchoEffectDirtyFlagsValue = std::uint_least8_t; +using EchoCommitter = EaxCommitter; -struct EaxEchoEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxEchoEffectDirtyFlagsValue flDelay : 1; - EaxEchoEffectDirtyFlagsValue flLRDelay : 1; - EaxEchoEffectDirtyFlagsValue flDamping : 1; - EaxEchoEffectDirtyFlagsValue flFeedback : 1; - EaxEchoEffectDirtyFlagsValue flSpread : 1; -}; // EaxEchoEffectDirtyFlags - - -class EaxEchoEffect final : - public EaxEffect -{ -public: - EaxEchoEffect(); - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAXECHOPROPERTIES eax_{}; - EAXECHOPROPERTIES eax_d_{}; - EaxEchoEffectDirtyFlags eax_dirty_flags_{}; - - void set_eax_defaults(); - - void set_efx_delay(); - void set_efx_lr_delay(); - void set_efx_damping(); - void set_efx_feedback(); - void set_efx_spread(); - void set_efx_defaults(); - - void get(const EaxEaxCall& eax_call); - - void validate_delay(float flDelay); - void validate_lr_delay(float flLRDelay); - void validate_damping(float flDamping); - void validate_feedback(float flFeedback); - void validate_spread(float flSpread); - void validate_all(const EAXECHOPROPERTIES& all); - - void defer_delay(float flDelay); - void defer_lr_delay(float flLRDelay); - void defer_damping(float flDamping); - void defer_feedback(float flFeedback); - void defer_spread(float flSpread); - void defer_all(const EAXECHOPROPERTIES& all); - - void defer_delay(const EaxEaxCall& eax_call); - void defer_lr_delay(const EaxEaxCall& eax_call); - void defer_damping(const EaxEaxCall& eax_call); - void defer_feedback(const EaxEaxCall& eax_call); - void defer_spread(const EaxEaxCall& eax_call); - void defer_all(const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxEchoEffect - - -class EaxEchoEffectException : - public EaxException -{ -public: - explicit EaxEchoEffectException( - const char* message) - : - EaxException{"EAX_ECHO_EFFECT", message} +struct DelayValidator { + void operator()(float flDelay) const { + eax_validate_range( + "Delay", + flDelay, + EAXECHO_MINDELAY, + EAXECHO_MAXDELAY); } -}; // EaxEchoEffectException +}; // DelayValidator - -EaxEchoEffect::EaxEchoEffect() - : EaxEffect{AL_EFFECT_ECHO} -{ - set_eax_defaults(); - set_efx_defaults(); -} - -void EaxEchoEffect::dispatch( - const EaxEaxCall& eax_call) -{ - eax_call.is_get() ? get(eax_call) : set(eax_call); -} - -void EaxEchoEffect::set_eax_defaults() -{ - eax_.flDelay = EAXECHO_DEFAULTDELAY; - eax_.flLRDelay = EAXECHO_DEFAULTLRDELAY; - eax_.flDamping = EAXECHO_DEFAULTDAMPING; - eax_.flFeedback = EAXECHO_DEFAULTFEEDBACK; - eax_.flSpread = EAXECHO_DEFAULTSPREAD; - - eax_d_ = eax_; -} - -void EaxEchoEffect::set_efx_delay() -{ - const auto delay = clamp( - eax_.flDelay, - AL_ECHO_MIN_DELAY, - AL_ECHO_MAX_DELAY); - - al_effect_props_.Echo.Delay = delay; -} - -void EaxEchoEffect::set_efx_lr_delay() -{ - const auto lr_delay = clamp( - eax_.flLRDelay, - AL_ECHO_MIN_LRDELAY, - AL_ECHO_MAX_LRDELAY); - - al_effect_props_.Echo.LRDelay = lr_delay; -} - -void EaxEchoEffect::set_efx_damping() -{ - const auto damping = clamp( - eax_.flDamping, - AL_ECHO_MIN_DAMPING, - AL_ECHO_MAX_DAMPING); - - al_effect_props_.Echo.Damping = damping; -} - -void EaxEchoEffect::set_efx_feedback() -{ - const auto feedback = clamp( - eax_.flFeedback, - AL_ECHO_MIN_FEEDBACK, - AL_ECHO_MAX_FEEDBACK); - - al_effect_props_.Echo.Feedback = feedback; -} - -void EaxEchoEffect::set_efx_spread() -{ - const auto spread = clamp( - eax_.flSpread, - AL_ECHO_MIN_SPREAD, - AL_ECHO_MAX_SPREAD); - - al_effect_props_.Echo.Spread = spread; -} - -void EaxEchoEffect::set_efx_defaults() -{ - set_efx_delay(); - set_efx_lr_delay(); - set_efx_damping(); - set_efx_feedback(); - set_efx_spread(); -} - -void EaxEchoEffect::get(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) +struct LrDelayValidator { + void operator()(float flLRDelay) const { - case EAXECHO_NONE: - break; - - case EAXECHO_ALLPARAMETERS: - eax_call.set_value(eax_); - break; - - case EAXECHO_DELAY: - eax_call.set_value(eax_.flDelay); - break; - - case EAXECHO_LRDELAY: - eax_call.set_value(eax_.flLRDelay); - break; - - case EAXECHO_DAMPING: - eax_call.set_value(eax_.flDamping); - break; - - case EAXECHO_FEEDBACK: - eax_call.set_value(eax_.flFeedback); - break; - - case EAXECHO_SPREAD: - eax_call.set_value(eax_.flSpread); - break; - - default: - throw EaxEchoEffectException{"Unsupported property id."}; + eax_validate_range( + "LR Delay", + flLRDelay, + EAXECHO_MINLRDELAY, + EAXECHO_MAXLRDELAY); } -} +}; // LrDelayValidator -void EaxEchoEffect::validate_delay( - float flDelay) -{ - eax_validate_range( - "Delay", - flDelay, - EAXECHO_MINDELAY, - EAXECHO_MAXDELAY); -} - -void EaxEchoEffect::validate_lr_delay( - float flLRDelay) -{ - eax_validate_range( - "LR Delay", - flLRDelay, - EAXECHO_MINLRDELAY, - EAXECHO_MAXLRDELAY); -} - -void EaxEchoEffect::validate_damping( - float flDamping) -{ - eax_validate_range( - "Damping", - flDamping, - EAXECHO_MINDAMPING, - EAXECHO_MAXDAMPING); -} - -void EaxEchoEffect::validate_feedback( - float flFeedback) -{ - eax_validate_range( - "Feedback", - flFeedback, - EAXECHO_MINFEEDBACK, - EAXECHO_MAXFEEDBACK); -} - -void EaxEchoEffect::validate_spread( - float flSpread) -{ - eax_validate_range( - "Spread", - flSpread, - EAXECHO_MINSPREAD, - EAXECHO_MAXSPREAD); -} - -void EaxEchoEffect::validate_all( - const EAXECHOPROPERTIES& all) -{ - validate_delay(all.flDelay); - validate_lr_delay(all.flLRDelay); - validate_damping(all.flDamping); - validate_feedback(all.flFeedback); - validate_spread(all.flSpread); -} - -void EaxEchoEffect::defer_delay( - float flDelay) -{ - eax_d_.flDelay = flDelay; - eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); -} - -void EaxEchoEffect::defer_lr_delay( - float flLRDelay) -{ - eax_d_.flLRDelay = flLRDelay; - eax_dirty_flags_.flLRDelay = (eax_.flLRDelay != eax_d_.flLRDelay); -} - -void EaxEchoEffect::defer_damping( - float flDamping) -{ - eax_d_.flDamping = flDamping; - eax_dirty_flags_.flDamping = (eax_.flDamping != eax_d_.flDamping); -} - -void EaxEchoEffect::defer_feedback( - float flFeedback) -{ - eax_d_.flFeedback = flFeedback; - eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); -} - -void EaxEchoEffect::defer_spread( - float flSpread) -{ - eax_d_.flSpread = flSpread; - eax_dirty_flags_.flSpread = (eax_.flSpread != eax_d_.flSpread); -} - -void EaxEchoEffect::defer_all( - const EAXECHOPROPERTIES& all) -{ - defer_delay(all.flDelay); - defer_lr_delay(all.flLRDelay); - defer_damping(all.flDamping); - defer_feedback(all.flFeedback); - defer_spread(all.flSpread); -} - -void EaxEchoEffect::defer_delay( - const EaxEaxCall& eax_call) -{ - const auto& delay = - eax_call.get_value(); - - validate_delay(delay); - defer_delay(delay); -} - -void EaxEchoEffect::defer_lr_delay( - const EaxEaxCall& eax_call) -{ - const auto& lr_delay = - eax_call.get_value(); - - validate_lr_delay(lr_delay); - defer_lr_delay(lr_delay); -} - -void EaxEchoEffect::defer_damping( - const EaxEaxCall& eax_call) -{ - const auto& damping = - eax_call.get_value(); - - validate_damping(damping); - defer_damping(damping); -} - -void EaxEchoEffect::defer_feedback( - const EaxEaxCall& eax_call) -{ - const auto& feedback = - eax_call.get_value(); - - validate_feedback(feedback); - defer_feedback(feedback); -} - -void EaxEchoEffect::defer_spread( - const EaxEaxCall& eax_call) -{ - const auto& spread = - eax_call.get_value(); - - validate_spread(spread); - defer_spread(spread); -} - -void EaxEchoEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto& all = - eax_call.get_value(); - - validate_all(all); - defer_all(all); -} - -// [[nodiscard]] -bool EaxEchoEffect::apply_deferred() -{ - if (eax_dirty_flags_ == EaxEchoEffectDirtyFlags{}) +struct DampingValidator { + void operator()(float flDamping) const { + eax_validate_range( + "Damping", + flDamping, + EAXECHO_MINDAMPING, + EAXECHO_MAXDAMPING); + } +}; // DampingValidator + +struct FeedbackValidator { + void operator()(float flFeedback) const + { + eax_validate_range( + "Feedback", + flFeedback, + EAXECHO_MINFEEDBACK, + EAXECHO_MAXFEEDBACK); + } +}; // FeedbackValidator + +struct SpreadValidator { + void operator()(float flSpread) const + { + eax_validate_range( + "Spread", + flSpread, + EAXECHO_MINSPREAD, + EAXECHO_MAXSPREAD); + } +}; // SpreadValidator + +struct AllValidator { + void operator()(const EAXECHOPROPERTIES& all) const + { + DelayValidator{}(all.flDelay); + LrDelayValidator{}(all.flLRDelay); + DampingValidator{}(all.flDamping); + FeedbackValidator{}(all.flFeedback); + SpreadValidator{}(all.flSpread); + } +}; // AllValidator + +} // namespace + +template<> +struct EchoCommitter::Exception : public EaxException { + explicit Exception(const char* message) : EaxException{"EAX_ECHO_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void EchoCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +bool EaxEchoCommitter::commit(const EAXECHOPROPERTIES &props) +{ + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; - } - eax_ = eax_d_; - - if (eax_dirty_flags_.flDelay) - { - set_efx_delay(); - } - - if (eax_dirty_flags_.flLRDelay) - { - set_efx_lr_delay(); - } - - if (eax_dirty_flags_.flDamping) - { - set_efx_damping(); - } - - if (eax_dirty_flags_.flFeedback) - { - set_efx_feedback(); - } - - if (eax_dirty_flags_.flSpread) - { - set_efx_spread(); - } - - eax_dirty_flags_ = EaxEchoEffectDirtyFlags{}; + mEaxProps = props; + mAlProps = [&]{ + EchoProps ret{}; + ret.Delay = props.flDelay; + ret.LRDelay = props.flLRDelay; + ret.Damping = props.flDamping; + ret.Feedback = props.flFeedback; + ret.Spread = props.flSpread; + return ret; + }(); return true; } -void EaxEchoEffect::set(const EaxEaxCall& eax_call) +void EaxEchoCommitter::SetDefaults(EaxEffectProps &props) { - switch(eax_call.get_property_id()) + static constexpr EAXECHOPROPERTIES defprops{[] { - case EAXECHO_NONE: - break; + 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; +} - case EAXECHO_ALLPARAMETERS: - defer_all(eax_call); - break; - - case EAXECHO_DELAY: - defer_delay(eax_call); - break; - - case EAXECHO_LRDELAY: - defer_lr_delay(eax_call); - break; - - case EAXECHO_DAMPING: - defer_damping(eax_call); - break; - - case EAXECHO_FEEDBACK: - defer_feedback(eax_call); - break; - - case EAXECHO_SPREAD: - defer_spread(eax_call); - break; - - default: - throw EaxEchoEffectException{"Unsupported property id."}; +void EaxEchoCommitter::Get(const EaxCall &call, const EAXECHOPROPERTIES &props) +{ + switch(call.get_property_id()) + { + case EAXECHO_NONE: break; + case EAXECHO_ALLPARAMETERS: call.set_value(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(); } } -} // namespace - -EaxEffectUPtr eax_create_eax_echo_effect() +void EaxEchoCommitter::Set(const EaxCall &call, EAXECHOPROPERTIES &props) { - return std::make_unique(); + switch(call.get_property_id()) + { + case EAXECHO_NONE: 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(); + } } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/effects.cpp b/libs/openal-soft/al/effects/effects.cpp index faf322d2..ace4e706 100644 --- a/libs/openal-soft/al/effects/effects.cpp +++ b/libs/openal-soft/al/effects/effects.cpp @@ -1,66 +1,3 @@ - #include "config.h" -#ifdef ALSOFT_EAX - #include "effects.h" - -#include - -#include "AL/efx.h" - - -EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type) -{ -#define EAX_PREFIX "[EAX_MAKE_EAX_EFFECT] " - - switch (al_effect_type) - { - case AL_EFFECT_NULL: - return eax_create_eax_null_effect(); - - case AL_EFFECT_CHORUS: - return eax_create_eax_chorus_effect(); - - case AL_EFFECT_DISTORTION: - return eax_create_eax_distortion_effect(); - - case AL_EFFECT_ECHO: - return eax_create_eax_echo_effect(); - - case AL_EFFECT_FLANGER: - return eax_create_eax_flanger_effect(); - - case AL_EFFECT_FREQUENCY_SHIFTER: - return eax_create_eax_frequency_shifter_effect(); - - case AL_EFFECT_VOCAL_MORPHER: - return eax_create_eax_vocal_morpher_effect(); - - case AL_EFFECT_PITCH_SHIFTER: - return eax_create_eax_pitch_shifter_effect(); - - case AL_EFFECT_RING_MODULATOR: - return eax_create_eax_ring_modulator_effect(); - - case AL_EFFECT_AUTOWAH: - return eax_create_eax_auto_wah_effect(); - - case AL_EFFECT_COMPRESSOR: - return eax_create_eax_compressor_effect(); - - case AL_EFFECT_EQUALIZER: - return eax_create_eax_equalizer_effect(); - - case AL_EFFECT_EAXREVERB: - return eax_create_eax_reverb_effect(); - - default: - assert(false && "Unsupported AL effect type."); - return nullptr; - } - -#undef EAX_PREFIX -} - -#endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/effects.h b/libs/openal-soft/al/effects/effects.h index 830e7191..8dc9f9bc 100644 --- a/libs/openal-soft/al/effects/effects.h +++ b/libs/openal-soft/al/effects/effects.h @@ -1,51 +1,54 @@ #ifndef AL_EFFECTS_EFFECTS_H #define AL_EFFECTS_EFFECTS_H +#include + #include "AL/al.h" -#include "core/except.h" - -#ifdef ALSOFT_EAX -#include "al/eax_effect.h" -#endif // ALSOFT_EAX - -union EffectProps; +#include "al/error.h" +#include "core/effects/base.h" -class effect_exception final : public al::base_exception { - ALenum mErrorCode; +struct EffectHandler { +#define DECL_HANDLER(T) \ + static void SetParami(T &props, ALenum param, int val); \ + static void SetParamiv(T &props, ALenum param, const int *vals); \ + static void SetParamf(T &props, ALenum param, float val); \ + static void SetParamfv(T &props, ALenum param, const float *vals); \ + static void GetParami(const T &props, ALenum param, int *val); \ + static void GetParamiv(const T &props, ALenum param, int *vals); \ + static void GetParamf(const T &props, ALenum param, float *val); \ + static void GetParamfv(const T &props, ALenum param, float *vals); -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, ...); + DECL_HANDLER(std::monostate) + DECL_HANDLER(ReverbProps) + DECL_HANDLER(ChorusProps) + DECL_HANDLER(AutowahProps) + DECL_HANDLER(CompressorProps) + DECL_HANDLER(ConvolutionProps) + DECL_HANDLER(DedicatedDialogProps) + DECL_HANDLER(DedicatedLfeProps) + DECL_HANDLER(DistortionProps) + DECL_HANDLER(EchoProps) + DECL_HANDLER(EqualizerProps) + DECL_HANDLER(FlangerProps) + DECL_HANDLER(FshifterProps) + DECL_HANDLER(ModulatorProps) + DECL_HANDLER(PshifterProps) + DECL_HANDLER(VmorpherProps) +#undef DECL_HANDLER - ALenum errorCode() const noexcept { return mErrorCode; } + static void StdReverbSetParami(ReverbProps &props, ALenum param, int val); + static void StdReverbSetParamiv(ReverbProps &props, ALenum param, const int *vals); + static void StdReverbSetParamf(ReverbProps &props, ALenum param, float val); + static void StdReverbSetParamfv(ReverbProps &props, ALenum param, const float *vals); + static void StdReverbGetParami(const ReverbProps &props, ALenum param, int *val); + static void StdReverbGetParamiv(const ReverbProps &props, ALenum param, int *vals); + static void StdReverbGetParamf(const ReverbProps &props, ALenum param, float *val); + static void StdReverbGetParamfv(const ReverbProps &props, ALenum param, float *vals); }; - -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. */ @@ -63,30 +66,8 @@ extern const EffectProps FshifterEffectProps; extern const EffectProps ModulatorEffectProps; extern const EffectProps PshifterEffectProps; extern const EffectProps VmorpherEffectProps; -extern const EffectProps DedicatedEffectProps; +extern const EffectProps DedicatedDialogEffectProps; +extern const EffectProps DedicatedLfeEffectProps; extern const EffectProps ConvolutionEffectProps; -/* Vtables to get/set properties for the given effect types. */ -extern const EffectVtable NullEffectVtable; -extern const EffectVtable ReverbEffectVtable; -extern const EffectVtable StdReverbEffectVtable; -extern const EffectVtable AutowahEffectVtable; -extern const EffectVtable ChorusEffectVtable; -extern const EffectVtable CompressorEffectVtable; -extern const EffectVtable DistortionEffectVtable; -extern const EffectVtable EchoEffectVtable; -extern const EffectVtable EqualizerEffectVtable; -extern const EffectVtable FlangerEffectVtable; -extern const EffectVtable FshifterEffectVtable; -extern const EffectVtable ModulatorEffectVtable; -extern const EffectVtable PshifterEffectVtable; -extern const EffectVtable VmorpherEffectVtable; -extern const EffectVtable DedicatedEffectVtable; -extern const EffectVtable ConvolutionEffectVtable; - - -#ifdef ALSOFT_EAX -EaxEffectUPtr eax_create_eax_effect(ALenum al_effect_type); -#endif // ALSOFT_EAX - #endif /* AL_EFFECTS_EFFECTS_H */ diff --git a/libs/openal-soft/al/effects/equalizer.cpp b/libs/openal-soft/al/effects/equalizer.cpp index f829328c..f12ad2da 100644 --- a/libs/openal-soft/al/effects/equalizer.cpp +++ b/libs/openal-soft/al/effects/equalizer.cpp @@ -9,913 +9,369 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" - -#include "al/eax_exception.h" -#include "al/eax_utils.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { -void Equalizer_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultProps() noexcept +{ + EqualizerProps props{}; + props.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF; + props.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN; + props.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER; + props.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN; + props.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH; + props.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER; + props.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN; + props.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH; + props.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF; + props.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN; + return props; +} + +} // namespace + +const EffectProps EqualizerEffectProps{genDefaultProps()}; + +void EffectHandler::SetParami(EqualizerProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } -void Equalizer_setParamiv(EffectProps*, ALenum param, const int*) +void EffectHandler::SetParamiv(EqualizerProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param}; } -void Equalizer_setParamf(EffectProps *props, ALenum param, float val) +void EffectHandler::SetParamf(EqualizerProps &props, ALenum param, float val) { switch(param) { case AL_EQUALIZER_LOW_GAIN: if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band gain out of range"}; - props->Equalizer.LowGain = val; + props.LowGain = val; break; case AL_EQUALIZER_LOW_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"}; - props->Equalizer.LowCutoff = val; + props.LowCutoff = val; break; case AL_EQUALIZER_MID1_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"}; - props->Equalizer.Mid1Gain = val; + props.Mid1Gain = val; break; case AL_EQUALIZER_MID1_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band center out of range"}; - props->Equalizer.Mid1Center = val; + props.Mid1Center = val; break; case AL_EQUALIZER_MID1_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band width out of range"}; - props->Equalizer.Mid1Width = val; + props.Mid1Width = val; break; case AL_EQUALIZER_MID2_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"}; - props->Equalizer.Mid2Gain = val; + props.Mid2Gain = val; break; case AL_EQUALIZER_MID2_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band center out of range"}; - props->Equalizer.Mid2Center = val; + props.Mid2Center = val; break; case AL_EQUALIZER_MID2_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band width out of range"}; - props->Equalizer.Mid2Width = val; + props.Mid2Width = val; break; case AL_EQUALIZER_HIGH_GAIN: if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band gain out of range"}; - props->Equalizer.HighGain = val; + props.HighGain = val; break; case AL_EQUALIZER_HIGH_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"}; - props->Equalizer.HighCutoff = val; + props.HighCutoff = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; } } -void Equalizer_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Equalizer_setParamf(props, param, vals[0]); } +void EffectHandler::SetParamfv(EqualizerProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Equalizer_getParami(const EffectProps*, ALenum param, int*) +void EffectHandler::GetParami(const EqualizerProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } -void Equalizer_getParamiv(const EffectProps*, ALenum param, int*) +void EffectHandler::GetParamiv(const EqualizerProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param}; } -void Equalizer_getParamf(const EffectProps *props, ALenum param, float *val) +void EffectHandler::GetParamf(const EqualizerProps &props, ALenum param, float *val) { switch(param) { - case AL_EQUALIZER_LOW_GAIN: - *val = props->Equalizer.LowGain; - break; - - case AL_EQUALIZER_LOW_CUTOFF: - *val = props->Equalizer.LowCutoff; - break; - - case AL_EQUALIZER_MID1_GAIN: - *val = props->Equalizer.Mid1Gain; - break; - - case AL_EQUALIZER_MID1_CENTER: - *val = props->Equalizer.Mid1Center; - break; - - case AL_EQUALIZER_MID1_WIDTH: - *val = props->Equalizer.Mid1Width; - break; - - case AL_EQUALIZER_MID2_GAIN: - *val = props->Equalizer.Mid2Gain; - break; - - case AL_EQUALIZER_MID2_CENTER: - *val = props->Equalizer.Mid2Center; - break; - - case AL_EQUALIZER_MID2_WIDTH: - *val = props->Equalizer.Mid2Width; - break; - - case AL_EQUALIZER_HIGH_GAIN: - *val = props->Equalizer.HighGain; - break; - - case AL_EQUALIZER_HIGH_CUTOFF: - *val = props->Equalizer.HighCutoff; - break; + case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; break; + case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; break; + case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; break; + case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; break; + case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; break; + case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; break; + case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; break; + case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; break; + case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; break; + case AL_EQUALIZER_HIGH_CUTOFF: *val = props.HighCutoff; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; } } -void Equalizer_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Equalizer_getParamf(props, param, vals); } +void EffectHandler::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 { -using EaxEqualizerEffectDirtyFlagsValue = std::uint_least16_t; +using EqualizerCommitter = EaxCommitter; -struct EaxEqualizerEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxEqualizerEffectDirtyFlagsValue lLowGain : 1; - EaxEqualizerEffectDirtyFlagsValue flLowCutOff : 1; - EaxEqualizerEffectDirtyFlagsValue lMid1Gain : 1; - EaxEqualizerEffectDirtyFlagsValue flMid1Center : 1; - EaxEqualizerEffectDirtyFlagsValue flMid1Width : 1; - EaxEqualizerEffectDirtyFlagsValue lMid2Gain : 1; - EaxEqualizerEffectDirtyFlagsValue flMid2Center : 1; - EaxEqualizerEffectDirtyFlagsValue flMid2Width : 1; - EaxEqualizerEffectDirtyFlagsValue lHighGain : 1; - EaxEqualizerEffectDirtyFlagsValue flHighCutOff : 1; -}; // EaxEqualizerEffectDirtyFlags - - -class EaxEqualizerEffect final : - public EaxEffect -{ -public: - EaxEqualizerEffect(); - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAXEQUALIZERPROPERTIES eax_{}; - EAXEQUALIZERPROPERTIES eax_d_{}; - EaxEqualizerEffectDirtyFlags eax_dirty_flags_{}; - - void set_eax_defaults(); - - void set_efx_low_gain(); - void set_efx_low_cutoff(); - void set_efx_mid1_gain(); - void set_efx_mid1_center(); - void set_efx_mid1_width(); - void set_efx_mid2_gain(); - void set_efx_mid2_center(); - void set_efx_mid2_width(); - void set_efx_high_gain(); - void set_efx_high_cutoff(); - void set_efx_defaults(); - - void get(const EaxEaxCall& eax_call); - - void validate_low_gain(long lLowGain); - void validate_low_cutoff(float flLowCutOff); - void validate_mid1_gain(long lMid1Gain); - void validate_mid1_center(float flMid1Center); - void validate_mid1_width(float flMid1Width); - void validate_mid2_gain(long lMid2Gain); - void validate_mid2_center(float flMid2Center); - void validate_mid2_width(float flMid2Width); - void validate_high_gain(long lHighGain); - void validate_high_cutoff(float flHighCutOff); - void validate_all(const EAXEQUALIZERPROPERTIES& all); - - void defer_low_gain(long lLowGain); - void defer_low_cutoff(float flLowCutOff); - void defer_mid1_gain(long lMid1Gain); - void defer_mid1_center(float flMid1Center); - void defer_mid1_width(float flMid1Width); - void defer_mid2_gain(long lMid2Gain); - void defer_mid2_center(float flMid2Center); - void defer_mid2_width(float flMid2Width); - void defer_high_gain(long lHighGain); - void defer_high_cutoff(float flHighCutOff); - void defer_all(const EAXEQUALIZERPROPERTIES& all); - - void defer_low_gain(const EaxEaxCall& eax_call); - void defer_low_cutoff(const EaxEaxCall& eax_call); - void defer_mid1_gain(const EaxEaxCall& eax_call); - void defer_mid1_center(const EaxEaxCall& eax_call); - void defer_mid1_width(const EaxEaxCall& eax_call); - void defer_mid2_gain(const EaxEaxCall& eax_call); - void defer_mid2_center(const EaxEaxCall& eax_call); - void defer_mid2_width(const EaxEaxCall& eax_call); - void defer_high_gain(const EaxEaxCall& eax_call); - void defer_high_cutoff(const EaxEaxCall& eax_call); - void defer_all(const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxEqualizerEffect - - -class EaxEqualizerEffectException : - public EaxException -{ -public: - explicit EaxEqualizerEffectException( - const char* message) - : - EaxException{"EAX_EQUALIZER_EFFECT", message} +struct LowGainValidator { + void operator()(long lLowGain) const { + eax_validate_range( + "Low Gain", + lLowGain, + EAXEQUALIZER_MINLOWGAIN, + EAXEQUALIZER_MAXLOWGAIN); } -}; // EaxEqualizerEffectException +}; // LowGainValidator - -EaxEqualizerEffect::EaxEqualizerEffect() - : EaxEffect{AL_EFFECT_EQUALIZER} -{ - set_eax_defaults(); - set_efx_defaults(); -} - -void EaxEqualizerEffect::dispatch(const EaxEaxCall& eax_call) -{ - eax_call.is_get() ? get(eax_call) : set(eax_call); -} - -void EaxEqualizerEffect::set_eax_defaults() -{ - eax_.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN; - eax_.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF; - eax_.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN; - eax_.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER; - eax_.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH; - eax_.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN; - eax_.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER; - eax_.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH; - eax_.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN; - eax_.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF; - - eax_d_ = eax_; -} - -void EaxEqualizerEffect::set_efx_low_gain() -{ - const auto low_gain = clamp( - level_mb_to_gain(static_cast(eax_.lLowGain)), - AL_EQUALIZER_MIN_LOW_GAIN, - AL_EQUALIZER_MAX_LOW_GAIN); - - al_effect_props_.Equalizer.LowGain = low_gain; -} - -void EaxEqualizerEffect::set_efx_low_cutoff() -{ - const auto low_cutoff = clamp( - eax_.flLowCutOff, - AL_EQUALIZER_MIN_LOW_CUTOFF, - AL_EQUALIZER_MAX_LOW_CUTOFF); - - al_effect_props_.Equalizer.LowCutoff = low_cutoff; -} - -void EaxEqualizerEffect::set_efx_mid1_gain() -{ - const auto mid1_gain = clamp( - level_mb_to_gain(static_cast(eax_.lMid1Gain)), - AL_EQUALIZER_MIN_MID1_GAIN, - AL_EQUALIZER_MAX_MID1_GAIN); - - al_effect_props_.Equalizer.Mid1Gain = mid1_gain; -} - -void EaxEqualizerEffect::set_efx_mid1_center() -{ - const auto mid1_center = clamp( - eax_.flMid1Center, - AL_EQUALIZER_MIN_MID1_CENTER, - AL_EQUALIZER_MAX_MID1_CENTER); - - al_effect_props_.Equalizer.Mid1Center = mid1_center; -} - -void EaxEqualizerEffect::set_efx_mid1_width() -{ - const auto mid1_width = clamp( - eax_.flMid1Width, - AL_EQUALIZER_MIN_MID1_WIDTH, - AL_EQUALIZER_MAX_MID1_WIDTH); - - al_effect_props_.Equalizer.Mid1Width = mid1_width; -} - -void EaxEqualizerEffect::set_efx_mid2_gain() -{ - const auto mid2_gain = clamp( - level_mb_to_gain(static_cast(eax_.lMid2Gain)), - AL_EQUALIZER_MIN_MID2_GAIN, - AL_EQUALIZER_MAX_MID2_GAIN); - - al_effect_props_.Equalizer.Mid2Gain = mid2_gain; -} - -void EaxEqualizerEffect::set_efx_mid2_center() -{ - const auto mid2_center = clamp( - eax_.flMid2Center, - AL_EQUALIZER_MIN_MID2_CENTER, - AL_EQUALIZER_MAX_MID2_CENTER); - - al_effect_props_.Equalizer.Mid2Center = mid2_center; -} - -void EaxEqualizerEffect::set_efx_mid2_width() -{ - const auto mid2_width = clamp( - eax_.flMid2Width, - AL_EQUALIZER_MIN_MID2_WIDTH, - AL_EQUALIZER_MAX_MID2_WIDTH); - - al_effect_props_.Equalizer.Mid2Width = mid2_width; -} - -void EaxEqualizerEffect::set_efx_high_gain() -{ - const auto high_gain = clamp( - level_mb_to_gain(static_cast(eax_.lHighGain)), - AL_EQUALIZER_MIN_HIGH_GAIN, - AL_EQUALIZER_MAX_HIGH_GAIN); - - al_effect_props_.Equalizer.HighGain = high_gain; -} - -void EaxEqualizerEffect::set_efx_high_cutoff() -{ - const auto high_cutoff = clamp( - eax_.flHighCutOff, - AL_EQUALIZER_MIN_HIGH_CUTOFF, - AL_EQUALIZER_MAX_HIGH_CUTOFF); - - al_effect_props_.Equalizer.HighCutoff = high_cutoff; -} - -void EaxEqualizerEffect::set_efx_defaults() -{ - set_efx_low_gain(); - set_efx_low_cutoff(); - set_efx_mid1_gain(); - set_efx_mid1_center(); - set_efx_mid1_width(); - set_efx_mid2_gain(); - set_efx_mid2_center(); - set_efx_mid2_width(); - set_efx_high_gain(); - set_efx_high_cutoff(); -} - -void EaxEqualizerEffect::get(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) +struct LowCutOffValidator { + void operator()(float flLowCutOff) const { - case EAXEQUALIZER_NONE: - break; - - case EAXEQUALIZER_ALLPARAMETERS: - eax_call.set_value(eax_); - break; - - case EAXEQUALIZER_LOWGAIN: - eax_call.set_value(eax_.lLowGain); - break; - - case EAXEQUALIZER_LOWCUTOFF: - eax_call.set_value(eax_.flLowCutOff); - break; - - case EAXEQUALIZER_MID1GAIN: - eax_call.set_value(eax_.lMid1Gain); - break; - - case EAXEQUALIZER_MID1CENTER: - eax_call.set_value(eax_.flMid1Center); - break; - - case EAXEQUALIZER_MID1WIDTH: - eax_call.set_value(eax_.flMid1Width); - break; - - case EAXEQUALIZER_MID2GAIN: - eax_call.set_value(eax_.lMid2Gain); - break; - - case EAXEQUALIZER_MID2CENTER: - eax_call.set_value(eax_.flMid2Center); - break; - - case EAXEQUALIZER_MID2WIDTH: - eax_call.set_value(eax_.flMid2Width); - break; - - case EAXEQUALIZER_HIGHGAIN: - eax_call.set_value(eax_.lHighGain); - break; - - case EAXEQUALIZER_HIGHCUTOFF: - eax_call.set_value(eax_.flHighCutOff); - break; - - default: - throw EaxEqualizerEffectException{"Unsupported property id."}; + eax_validate_range( + "Low Cutoff", + flLowCutOff, + EAXEQUALIZER_MINLOWCUTOFF, + EAXEQUALIZER_MAXLOWCUTOFF); } -} +}; // LowCutOffValidator -void EaxEqualizerEffect::validate_low_gain( - long lLowGain) -{ - eax_validate_range( - "Low Gain", - lLowGain, - EAXEQUALIZER_MINLOWGAIN, - EAXEQUALIZER_MAXLOWGAIN); -} - -void EaxEqualizerEffect::validate_low_cutoff( - float flLowCutOff) -{ - eax_validate_range( - "Low Cutoff", - flLowCutOff, - EAXEQUALIZER_MINLOWCUTOFF, - EAXEQUALIZER_MAXLOWCUTOFF); -} - -void EaxEqualizerEffect::validate_mid1_gain( - long lMid1Gain) -{ - eax_validate_range( - "Mid1 Gain", - lMid1Gain, - EAXEQUALIZER_MINMID1GAIN, - EAXEQUALIZER_MAXMID1GAIN); -} - -void EaxEqualizerEffect::validate_mid1_center( - float flMid1Center) -{ - eax_validate_range( - "Mid1 Center", - flMid1Center, - EAXEQUALIZER_MINMID1CENTER, - EAXEQUALIZER_MAXMID1CENTER); -} - -void EaxEqualizerEffect::validate_mid1_width( - float flMid1Width) -{ - eax_validate_range( - "Mid1 Width", - flMid1Width, - EAXEQUALIZER_MINMID1WIDTH, - EAXEQUALIZER_MAXMID1WIDTH); -} - -void EaxEqualizerEffect::validate_mid2_gain( - long lMid2Gain) -{ - eax_validate_range( - "Mid2 Gain", - lMid2Gain, - EAXEQUALIZER_MINMID2GAIN, - EAXEQUALIZER_MAXMID2GAIN); -} - -void EaxEqualizerEffect::validate_mid2_center( - float flMid2Center) -{ - eax_validate_range( - "Mid2 Center", - flMid2Center, - EAXEQUALIZER_MINMID2CENTER, - EAXEQUALIZER_MAXMID2CENTER); -} - -void EaxEqualizerEffect::validate_mid2_width( - float flMid2Width) -{ - eax_validate_range( - "Mid2 Width", - flMid2Width, - EAXEQUALIZER_MINMID2WIDTH, - EAXEQUALIZER_MAXMID2WIDTH); -} - -void EaxEqualizerEffect::validate_high_gain( - long lHighGain) -{ - eax_validate_range( - "High Gain", - lHighGain, - EAXEQUALIZER_MINHIGHGAIN, - EAXEQUALIZER_MAXHIGHGAIN); -} - -void EaxEqualizerEffect::validate_high_cutoff( - float flHighCutOff) -{ - eax_validate_range( - "High Cutoff", - flHighCutOff, - EAXEQUALIZER_MINHIGHCUTOFF, - EAXEQUALIZER_MAXHIGHCUTOFF); -} - -void EaxEqualizerEffect::validate_all( - const EAXEQUALIZERPROPERTIES& all) -{ - validate_low_gain(all.lLowGain); - validate_low_cutoff(all.flLowCutOff); - validate_mid1_gain(all.lMid1Gain); - validate_mid1_center(all.flMid1Center); - validate_mid1_width(all.flMid1Width); - validate_mid2_gain(all.lMid2Gain); - validate_mid2_center(all.flMid2Center); - validate_mid2_width(all.flMid2Width); - validate_high_gain(all.lHighGain); - validate_high_cutoff(all.flHighCutOff); -} - -void EaxEqualizerEffect::defer_low_gain( - long lLowGain) -{ - eax_d_.lLowGain = lLowGain; - eax_dirty_flags_.lLowGain = (eax_.lLowGain != eax_d_.lLowGain); -} - -void EaxEqualizerEffect::defer_low_cutoff( - float flLowCutOff) -{ - eax_d_.flLowCutOff = flLowCutOff; - eax_dirty_flags_.flLowCutOff = (eax_.flLowCutOff != eax_d_.flLowCutOff); -} - -void EaxEqualizerEffect::defer_mid1_gain( - long lMid1Gain) -{ - eax_d_.lMid1Gain = lMid1Gain; - eax_dirty_flags_.lMid1Gain = (eax_.lMid1Gain != eax_d_.lMid1Gain); -} - -void EaxEqualizerEffect::defer_mid1_center( - float flMid1Center) -{ - eax_d_.flMid1Center = flMid1Center; - eax_dirty_flags_.flMid1Center = (eax_.flMid1Center != eax_d_.flMid1Center); -} - -void EaxEqualizerEffect::defer_mid1_width( - float flMid1Width) -{ - eax_d_.flMid1Width = flMid1Width; - eax_dirty_flags_.flMid1Width = (eax_.flMid1Width != eax_d_.flMid1Width); -} - -void EaxEqualizerEffect::defer_mid2_gain( - long lMid2Gain) -{ - eax_d_.lMid2Gain = lMid2Gain; - eax_dirty_flags_.lMid2Gain = (eax_.lMid2Gain != eax_d_.lMid2Gain); -} - -void EaxEqualizerEffect::defer_mid2_center( - float flMid2Center) -{ - eax_d_.flMid2Center = flMid2Center; - eax_dirty_flags_.flMid2Center = (eax_.flMid2Center != eax_d_.flMid2Center); -} - -void EaxEqualizerEffect::defer_mid2_width( - float flMid2Width) -{ - eax_d_.flMid2Width = flMid2Width; - eax_dirty_flags_.flMid2Width = (eax_.flMid2Width != eax_d_.flMid2Width); -} - -void EaxEqualizerEffect::defer_high_gain( - long lHighGain) -{ - eax_d_.lHighGain = lHighGain; - eax_dirty_flags_.lHighGain = (eax_.lHighGain != eax_d_.lHighGain); -} - -void EaxEqualizerEffect::defer_high_cutoff( - float flHighCutOff) -{ - eax_d_.flHighCutOff = flHighCutOff; - eax_dirty_flags_.flHighCutOff = (eax_.flHighCutOff != eax_d_.flHighCutOff); -} - -void EaxEqualizerEffect::defer_all( - const EAXEQUALIZERPROPERTIES& all) -{ - defer_low_gain(all.lLowGain); - defer_low_cutoff(all.flLowCutOff); - defer_mid1_gain(all.lMid1Gain); - defer_mid1_center(all.flMid1Center); - defer_mid1_width(all.flMid1Width); - defer_mid2_gain(all.lMid2Gain); - defer_mid2_center(all.flMid2Center); - defer_mid2_width(all.flMid2Width); - defer_high_gain(all.lHighGain); - defer_high_cutoff(all.flHighCutOff); -} - -void EaxEqualizerEffect::defer_low_gain( - const EaxEaxCall& eax_call) -{ - const auto& low_gain = - eax_call.get_value(); - - validate_low_gain(low_gain); - defer_low_gain(low_gain); -} - -void EaxEqualizerEffect::defer_low_cutoff( - const EaxEaxCall& eax_call) -{ - const auto& low_cutoff = - eax_call.get_value(); - - validate_low_cutoff(low_cutoff); - defer_low_cutoff(low_cutoff); -} - -void EaxEqualizerEffect::defer_mid1_gain( - const EaxEaxCall& eax_call) -{ - const auto& mid1_gain = - eax_call.get_value(); - - validate_mid1_gain(mid1_gain); - defer_mid1_gain(mid1_gain); -} - -void EaxEqualizerEffect::defer_mid1_center( - const EaxEaxCall& eax_call) -{ - const auto& mid1_center = - eax_call.get_value(); - - validate_mid1_center(mid1_center); - defer_mid1_center(mid1_center); -} - -void EaxEqualizerEffect::defer_mid1_width( - const EaxEaxCall& eax_call) -{ - const auto& mid1_width = - eax_call.get_value(); - - validate_mid1_width(mid1_width); - defer_mid1_width(mid1_width); -} - -void EaxEqualizerEffect::defer_mid2_gain( - const EaxEaxCall& eax_call) -{ - const auto& mid2_gain = - eax_call.get_value(); - - validate_mid2_gain(mid2_gain); - defer_mid2_gain(mid2_gain); -} - -void EaxEqualizerEffect::defer_mid2_center( - const EaxEaxCall& eax_call) -{ - const auto& mid2_center = - eax_call.get_value(); - - validate_mid2_center(mid2_center); - defer_mid2_center(mid2_center); -} - -void EaxEqualizerEffect::defer_mid2_width( - const EaxEaxCall& eax_call) -{ - const auto& mid2_width = - eax_call.get_value(); - - validate_mid2_width(mid2_width); - defer_mid2_width(mid2_width); -} - -void EaxEqualizerEffect::defer_high_gain( - const EaxEaxCall& eax_call) -{ - const auto& high_gain = - eax_call.get_value(); - - validate_high_gain(high_gain); - defer_high_gain(high_gain); -} - -void EaxEqualizerEffect::defer_high_cutoff( - const EaxEaxCall& eax_call) -{ - const auto& high_cutoff = - eax_call.get_value(); - - validate_high_cutoff(high_cutoff); - defer_high_cutoff(high_cutoff); -} - -void EaxEqualizerEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto& all = - eax_call.get_value(); - - validate_all(all); - defer_all(all); -} - -// [[nodiscard]] -bool EaxEqualizerEffect::apply_deferred() -{ - if (eax_dirty_flags_ == EaxEqualizerEffectDirtyFlags{}) +struct Mid1GainValidator { + void operator()(long lMid1Gain) const { + eax_validate_range( + "Mid1 Gain", + lMid1Gain, + EAXEQUALIZER_MINMID1GAIN, + EAXEQUALIZER_MAXMID1GAIN); + } +}; // Mid1GainValidator + +struct Mid1CenterValidator { + void operator()(float flMid1Center) const + { + eax_validate_range( + "Mid1 Center", + flMid1Center, + EAXEQUALIZER_MINMID1CENTER, + EAXEQUALIZER_MAXMID1CENTER); + } +}; // Mid1CenterValidator + +struct Mid1WidthValidator { + void operator()(float flMid1Width) const + { + eax_validate_range( + "Mid1 Width", + flMid1Width, + EAXEQUALIZER_MINMID1WIDTH, + EAXEQUALIZER_MAXMID1WIDTH); + } +}; // Mid1WidthValidator + +struct Mid2GainValidator { + void operator()(long lMid2Gain) const + { + eax_validate_range( + "Mid2 Gain", + lMid2Gain, + EAXEQUALIZER_MINMID2GAIN, + EAXEQUALIZER_MAXMID2GAIN); + } +}; // Mid2GainValidator + +struct Mid2CenterValidator { + void operator()(float flMid2Center) const + { + eax_validate_range( + "Mid2 Center", + flMid2Center, + EAXEQUALIZER_MINMID2CENTER, + EAXEQUALIZER_MAXMID2CENTER); + } +}; // Mid2CenterValidator + +struct Mid2WidthValidator { + void operator()(float flMid2Width) const + { + eax_validate_range( + "Mid2 Width", + flMid2Width, + EAXEQUALIZER_MINMID2WIDTH, + EAXEQUALIZER_MAXMID2WIDTH); + } +}; // Mid2WidthValidator + +struct HighGainValidator { + void operator()(long lHighGain) const + { + eax_validate_range( + "High Gain", + lHighGain, + EAXEQUALIZER_MINHIGHGAIN, + EAXEQUALIZER_MAXHIGHGAIN); + } +}; // HighGainValidator + +struct HighCutOffValidator { + void operator()(float flHighCutOff) const + { + eax_validate_range( + "High Cutoff", + flHighCutOff, + EAXEQUALIZER_MINHIGHCUTOFF, + EAXEQUALIZER_MAXHIGHCUTOFF); + } +}; // HighCutOffValidator + +struct AllValidator { + void operator()(const EAXEQUALIZERPROPERTIES& all) const + { + LowGainValidator{}(all.lLowGain); + LowCutOffValidator{}(all.flLowCutOff); + Mid1GainValidator{}(all.lMid1Gain); + Mid1CenterValidator{}(all.flMid1Center); + Mid1WidthValidator{}(all.flMid1Width); + Mid2GainValidator{}(all.lMid2Gain); + Mid2CenterValidator{}(all.flMid2Center); + Mid2WidthValidator{}(all.flMid2Width); + HighGainValidator{}(all.lHighGain); + HighCutOffValidator{}(all.flHighCutOff); + } +}; // AllValidator + +} // namespace + +template<> +struct EqualizerCommitter::Exception : public EaxException { + explicit Exception(const char* message) : EaxException{"EAX_EQUALIZER_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void EqualizerCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +bool EaxEqualizerCommitter::commit(const EAXEQUALIZERPROPERTIES &props) +{ + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; - } - eax_ = eax_d_; - - if (eax_dirty_flags_.lLowGain) - { - set_efx_low_gain(); - } - - if (eax_dirty_flags_.flLowCutOff) - { - set_efx_low_cutoff(); - } - - if (eax_dirty_flags_.lMid1Gain) - { - set_efx_mid1_gain(); - } - - if (eax_dirty_flags_.flMid1Center) - { - set_efx_mid1_center(); - } - - if (eax_dirty_flags_.flMid1Width) - { - set_efx_mid1_width(); - } - - if (eax_dirty_flags_.lMid2Gain) - { - set_efx_mid2_gain(); - } - - if (eax_dirty_flags_.flMid2Center) - { - set_efx_mid2_center(); - } - - if (eax_dirty_flags_.flMid2Width) - { - set_efx_mid2_width(); - } - - if (eax_dirty_flags_.lHighGain) - { - set_efx_high_gain(); - } - - if (eax_dirty_flags_.flHighCutOff) - { - set_efx_high_cutoff(); - } - - eax_dirty_flags_ = EaxEqualizerEffectDirtyFlags{}; + mEaxProps = props; + 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; } -void EaxEqualizerEffect::set(const EaxEaxCall& eax_call) +void EaxEqualizerCommitter::SetDefaults(EaxEffectProps &props) { - switch(eax_call.get_property_id()) + static constexpr EAXEQUALIZERPROPERTIES defprops{[] { - case EAXEQUALIZER_NONE: - break; + 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; +} - case EAXEQUALIZER_ALLPARAMETERS: - defer_all(eax_call); - break; - - case EAXEQUALIZER_LOWGAIN: - defer_low_gain(eax_call); - break; - - case EAXEQUALIZER_LOWCUTOFF: - defer_low_cutoff(eax_call); - break; - - case EAXEQUALIZER_MID1GAIN: - defer_mid1_gain(eax_call); - break; - - case EAXEQUALIZER_MID1CENTER: - defer_mid1_center(eax_call); - break; - - case EAXEQUALIZER_MID1WIDTH: - defer_mid1_width(eax_call); - break; - - case EAXEQUALIZER_MID2GAIN: - defer_mid2_gain(eax_call); - break; - - case EAXEQUALIZER_MID2CENTER: - defer_mid2_center(eax_call); - break; - - case EAXEQUALIZER_MID2WIDTH: - defer_mid2_width(eax_call); - break; - - case EAXEQUALIZER_HIGHGAIN: - defer_high_gain(eax_call); - break; - - case EAXEQUALIZER_HIGHCUTOFF: - defer_high_cutoff(eax_call); - break; - - default: - throw EaxEqualizerEffectException{"Unsupported property id."}; +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); 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(); } } -} // namespace - -EaxEffectUPtr eax_create_eax_equalizer_effect() +void EaxEqualizerCommitter::Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props) { - return std::make_unique(); + switch(call.get_property_id()) + { + case EAXEQUALIZER_NONE: 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(); + } } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/fshifter.cpp b/libs/openal-soft/al/effects/fshifter.cpp index d334890b..bae00d18 100644 --- a/libs/openal-soft/al/effects/fshifter.cpp +++ b/libs/openal-soft/al/effects/fshifter.cpp @@ -1,38 +1,37 @@ #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_exception.h" -#include "al/eax_utils.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { -al::optional DirectionFromEmum(ALenum value) +constexpr std::optional DirectionFromEmum(ALenum value) noexcept { switch(value) { - case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: return al::make_optional(FShifterDirection::Down); - case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return al::make_optional(FShifterDirection::Up); - case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return al::make_optional(FShifterDirection::Off); + case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: return FShifterDirection::Down; + case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return FShifterDirection::Up; + case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return FShifterDirection::Off; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromDirection(FShifterDirection dir) +constexpr ALenum EnumFromDirection(FShifterDirection dir) { switch(dir) { @@ -43,31 +42,26 @@ ALenum EnumFromDirection(FShifterDirection dir) throw std::runtime_error{"Invalid direction: "+std::to_string(static_cast(dir))}; } -void Fshifter_setParamf(EffectProps *props, ALenum param, float val) +constexpr EffectProps genDefaultProps() noexcept { - switch(param) - { - case AL_FREQUENCY_SHIFTER_FREQUENCY: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) - throw effect_exception{AL_INVALID_VALUE, "Frequency shifter frequency out of range"}; - props->Fshifter.Frequency = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", - param}; - } + FshifterProps props{}; + props.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY; + props.LeftDirection = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION).value(); + props.RightDirection = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION).value(); + return props; } -void Fshifter_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Fshifter_setParamf(props, param, vals[0]); } -void Fshifter_setParami(EffectProps *props, ALenum param, int val) +} // namespace + +const EffectProps FshifterEffectProps{genDefaultProps()}; + +void EffectHandler::SetParami(FshifterProps &props, ALenum param, int val) { switch(param) { case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: if(auto diropt = DirectionFromEmum(val)) - props->Fshifter.LeftDirection = *diropt; + props.LeftDirection = *diropt; else throw effect_exception{AL_INVALID_VALUE, "Unsupported frequency shifter left direction: 0x%04x", val}; @@ -75,7 +69,7 @@ void Fshifter_setParami(EffectProps *props, ALenum param, int val) case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: if(auto diropt = DirectionFromEmum(val)) - props->Fshifter.RightDirection = *diropt; + props.RightDirection = *diropt; else throw effect_exception{AL_INVALID_VALUE, "Unsupported frequency shifter right direction: 0x%04x", val}; @@ -86,33 +80,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 EffectHandler::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 EffectHandler::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: @@ -120,360 +98,170 @@ 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 EffectHandler::SetParamfv(FshifterProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -EffectProps genDefaultProps() noexcept +void EffectHandler::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 EffectHandler::GetParamiv(const FshifterProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } -} // namespace +void EffectHandler::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 EffectHandler::GetParamfv(const FshifterProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -const EffectProps FshifterEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { -using EaxFrequencyShifterEffectDirtyFlagsValue = std::uint_least8_t; +using FrequencyShifterCommitter = EaxCommitter; -struct EaxFrequencyShifterEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxFrequencyShifterEffectDirtyFlagsValue flFrequency : 1; - EaxFrequencyShifterEffectDirtyFlagsValue ulLeftDirection : 1; - EaxFrequencyShifterEffectDirtyFlagsValue ulRightDirection : 1; -}; // EaxFrequencyShifterEffectDirtyFlags - - -class EaxFrequencyShifterEffect final : - public EaxEffect -{ -public: - EaxFrequencyShifterEffect(); - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAXFREQUENCYSHIFTERPROPERTIES eax_{}; - EAXFREQUENCYSHIFTERPROPERTIES eax_d_{}; - EaxFrequencyShifterEffectDirtyFlags eax_dirty_flags_{}; - - void set_eax_defaults(); - - void set_efx_frequency(); - void set_efx_left_direction(); - void set_efx_right_direction(); - void set_efx_defaults(); - - void get(const EaxEaxCall& eax_call); - - void validate_frequency(float flFrequency); - void validate_left_direction(unsigned long ulLeftDirection); - void validate_right_direction(unsigned long ulRightDirection); - void validate_all(const EAXFREQUENCYSHIFTERPROPERTIES& all); - - void defer_frequency(float flFrequency); - void defer_left_direction(unsigned long ulLeftDirection); - void defer_right_direction(unsigned long ulRightDirection); - void defer_all(const EAXFREQUENCYSHIFTERPROPERTIES& all); - - void defer_frequency(const EaxEaxCall& eax_call); - void defer_left_direction(const EaxEaxCall& eax_call); - void defer_right_direction(const EaxEaxCall& eax_call); - void defer_all(const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxFrequencyShifterEffect - - -class EaxFrequencyShifterEffectException : - public EaxException -{ -public: - explicit EaxFrequencyShifterEffectException( - const char* message) - : - EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message} +struct FrequencyValidator { + void operator()(float flFrequency) const { + eax_validate_range( + "Frequency", + flFrequency, + EAXFREQUENCYSHIFTER_MINFREQUENCY, + EAXFREQUENCYSHIFTER_MAXFREQUENCY); } -}; // EaxFrequencyShifterEffectException +}; // FrequencyValidator - -EaxFrequencyShifterEffect::EaxFrequencyShifterEffect() - : EaxEffect{AL_EFFECT_FREQUENCY_SHIFTER} -{ - set_eax_defaults(); - set_efx_defaults(); -} - -void EaxFrequencyShifterEffect::dispatch(const EaxEaxCall& eax_call) -{ - eax_call.is_get() ? get(eax_call) : set(eax_call); -} - -void EaxFrequencyShifterEffect::set_eax_defaults() -{ - eax_.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY; - eax_.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION; - eax_.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION; - - eax_d_ = eax_; -} - -void EaxFrequencyShifterEffect::set_efx_frequency() -{ - const auto frequency = clamp( - eax_.flFrequency, - AL_FREQUENCY_SHIFTER_MIN_FREQUENCY, - AL_FREQUENCY_SHIFTER_MAX_FREQUENCY); - - al_effect_props_.Fshifter.Frequency = frequency; -} - -void EaxFrequencyShifterEffect::set_efx_left_direction() -{ - const auto left_direction = clamp( - static_cast(eax_.ulLeftDirection), - AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION, - AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION); - - const auto efx_left_direction = DirectionFromEmum(left_direction); - assert(efx_left_direction.has_value()); - al_effect_props_.Fshifter.LeftDirection = *efx_left_direction; -} - -void EaxFrequencyShifterEffect::set_efx_right_direction() -{ - const auto right_direction = clamp( - static_cast(eax_.ulRightDirection), - AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION, - AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION); - - const auto efx_right_direction = DirectionFromEmum(right_direction); - assert(efx_right_direction.has_value()); - al_effect_props_.Fshifter.RightDirection = *efx_right_direction; -} - -void EaxFrequencyShifterEffect::set_efx_defaults() -{ - set_efx_frequency(); - set_efx_left_direction(); - set_efx_right_direction(); -} - -void EaxFrequencyShifterEffect::get(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) +struct LeftDirectionValidator { + void operator()(unsigned long ulLeftDirection) const { - case EAXFREQUENCYSHIFTER_NONE: - break; - - case EAXFREQUENCYSHIFTER_ALLPARAMETERS: - eax_call.set_value(eax_); - break; - - case EAXFREQUENCYSHIFTER_FREQUENCY: - eax_call.set_value(eax_.flFrequency); - break; - - case EAXFREQUENCYSHIFTER_LEFTDIRECTION: - eax_call.set_value(eax_.ulLeftDirection); - break; - - case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: - eax_call.set_value(eax_.ulRightDirection); - break; - - default: - throw EaxFrequencyShifterEffectException{"Unsupported property id."}; + eax_validate_range( + "Left Direction", + ulLeftDirection, + EAXFREQUENCYSHIFTER_MINLEFTDIRECTION, + EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION); } -} +}; // LeftDirectionValidator -void EaxFrequencyShifterEffect::validate_frequency( - float flFrequency) -{ - eax_validate_range( - "Frequency", - flFrequency, - EAXFREQUENCYSHIFTER_MINFREQUENCY, - EAXFREQUENCYSHIFTER_MAXFREQUENCY); -} - -void EaxFrequencyShifterEffect::validate_left_direction( - unsigned long ulLeftDirection) -{ - eax_validate_range( - "Left Direction", - ulLeftDirection, - EAXFREQUENCYSHIFTER_MINLEFTDIRECTION, - EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION); -} - -void EaxFrequencyShifterEffect::validate_right_direction( - unsigned long ulRightDirection) -{ - eax_validate_range( - "Right Direction", - ulRightDirection, - EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION, - EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION); -} - -void EaxFrequencyShifterEffect::validate_all( - const EAXFREQUENCYSHIFTERPROPERTIES& all) -{ - validate_frequency(all.flFrequency); - validate_left_direction(all.ulLeftDirection); - validate_right_direction(all.ulRightDirection); -} - -void EaxFrequencyShifterEffect::defer_frequency( - float flFrequency) -{ - eax_d_.flFrequency = flFrequency; - eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency); -} - -void EaxFrequencyShifterEffect::defer_left_direction( - unsigned long ulLeftDirection) -{ - eax_d_.ulLeftDirection = ulLeftDirection; - eax_dirty_flags_.ulLeftDirection = (eax_.ulLeftDirection != eax_d_.ulLeftDirection); -} - -void EaxFrequencyShifterEffect::defer_right_direction( - unsigned long ulRightDirection) -{ - eax_d_.ulRightDirection = ulRightDirection; - eax_dirty_flags_.ulRightDirection = (eax_.ulRightDirection != eax_d_.ulRightDirection); -} - -void EaxFrequencyShifterEffect::defer_all( - const EAXFREQUENCYSHIFTERPROPERTIES& all) -{ - defer_frequency(all.flFrequency); - defer_left_direction(all.ulLeftDirection); - defer_right_direction(all.ulRightDirection); -} - -void EaxFrequencyShifterEffect::defer_frequency( - const EaxEaxCall& eax_call) -{ - const auto& frequency = - eax_call.get_value< - EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::flFrequency)>(); - - validate_frequency(frequency); - defer_frequency(frequency); -} - -void EaxFrequencyShifterEffect::defer_left_direction( - const EaxEaxCall& eax_call) -{ - const auto& left_direction = - eax_call.get_value< - EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulLeftDirection)>(); - - validate_left_direction(left_direction); - defer_left_direction(left_direction); -} - -void EaxFrequencyShifterEffect::defer_right_direction( - const EaxEaxCall& eax_call) -{ - const auto& right_direction = - eax_call.get_value< - EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulRightDirection)>(); - - validate_right_direction(right_direction); - defer_right_direction(right_direction); -} - -void EaxFrequencyShifterEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto& all = - eax_call.get_value< - EaxFrequencyShifterEffectException, const EAXFREQUENCYSHIFTERPROPERTIES>(); - - validate_all(all); - defer_all(all); -} - -// [[nodiscard]] -bool EaxFrequencyShifterEffect::apply_deferred() -{ - if (eax_dirty_flags_ == EaxFrequencyShifterEffectDirtyFlags{}) +struct RightDirectionValidator { + void operator()(unsigned long ulRightDirection) const { + eax_validate_range( + "Right Direction", + ulRightDirection, + EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION, + EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION); + } +}; // RightDirectionValidator + +struct AllValidator { + void operator()(const EAXFREQUENCYSHIFTERPROPERTIES& all) const + { + FrequencyValidator{}(all.flFrequency); + LeftDirectionValidator{}(all.ulLeftDirection); + RightDirectionValidator{}(all.ulRightDirection); + } +}; // AllValidator + +} // namespace + +template<> +struct FrequencyShifterCommitter::Exception : public EaxException { + explicit Exception(const char *message) : EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void FrequencyShifterCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +bool EaxFrequencyShifterCommitter::commit(const EAXFREQUENCYSHIFTERPROPERTIES &props) +{ + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; - } - eax_ = eax_d_; + mEaxProps = props; - if (eax_dirty_flags_.flFrequency) + auto get_direction = [](unsigned long dir) noexcept { - set_efx_frequency(); - } + if(dir == EAX_FREQUENCYSHIFTER_DOWN) + return FShifterDirection::Down; + if(dir == EAX_FREQUENCYSHIFTER_UP) + return FShifterDirection::Up; + return FShifterDirection::Off; + }; - if (eax_dirty_flags_.ulLeftDirection) - { - set_efx_left_direction(); - } - - if (eax_dirty_flags_.ulRightDirection) - { - set_efx_right_direction(); - } - - eax_dirty_flags_ = EaxFrequencyShifterEffectDirtyFlags{}; + mAlProps = [&]{ + FshifterProps ret{}; + ret.Frequency = props.flFrequency; + ret.LeftDirection = get_direction(props.ulLeftDirection); + ret.RightDirection = get_direction(props.ulRightDirection); + return ret; + }(); return true; } -void EaxFrequencyShifterEffect::set(const EaxEaxCall& eax_call) +void EaxFrequencyShifterCommitter::SetDefaults(EaxEffectProps &props) { - switch(eax_call.get_property_id()) + static constexpr EAXFREQUENCYSHIFTERPROPERTIES defprops{[] { - case EAXFREQUENCYSHIFTER_NONE: - break; + EAXFREQUENCYSHIFTERPROPERTIES ret{}; + ret.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY; + ret.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION; + ret.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION; + return ret; + }()}; + props = defprops; +} - case EAXFREQUENCYSHIFTER_ALLPARAMETERS: - defer_all(eax_call); - break; - - case EAXFREQUENCYSHIFTER_FREQUENCY: - defer_frequency(eax_call); - break; - - case EAXFREQUENCYSHIFTER_LEFTDIRECTION: - defer_left_direction(eax_call); - break; - - case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: - defer_right_direction(eax_call); - break; - - default: - throw EaxFrequencyShifterEffectException{"Unsupported property id."}; +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); 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(); } } -} // namespace - -EaxEffectUPtr eax_create_eax_frequency_shifter_effect() +void EaxFrequencyShifterCommitter::Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props) { - return std::make_unique(); + switch(call.get_property_id()) + { + case EAXFREQUENCYSHIFTER_NONE: 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(); + } } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/modulator.cpp b/libs/openal-soft/al/effects/modulator.cpp index 800b892d..a80b59af 100644 --- a/libs/openal-soft/al/effects/modulator.cpp +++ b/libs/openal-soft/al/effects/modulator.cpp @@ -1,38 +1,37 @@ #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_exception.h" -#include "al/eax_utils.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { -al::optional WaveformFromEmum(ALenum value) +constexpr std::optional WaveformFromEmum(ALenum value) noexcept { switch(value) { - case AL_RING_MODULATOR_SINUSOID: return al::make_optional(ModulatorWaveform::Sinusoid); - case AL_RING_MODULATOR_SAWTOOTH: return al::make_optional(ModulatorWaveform::Sawtooth); - case AL_RING_MODULATOR_SQUARE: return al::make_optional(ModulatorWaveform::Square); + case AL_RING_MODULATOR_SINUSOID: return ModulatorWaveform::Sinusoid; + case AL_RING_MODULATOR_SAWTOOTH: return ModulatorWaveform::Sawtooth; + case AL_RING_MODULATOR_SQUARE: return ModulatorWaveform::Square; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromWaveform(ModulatorWaveform type) +constexpr ALenum EnumFromWaveform(ModulatorWaveform type) { switch(type) { @@ -44,40 +43,31 @@ ALenum EnumFromWaveform(ModulatorWaveform type) std::to_string(static_cast(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 EffectHandler::SetParami(ModulatorProps &props, ALenum param, int val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - Modulator_setParamf(props, param, static_cast(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; @@ -87,396 +77,189 @@ void Modulator_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Modulator_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Modulator_setParami(props, param, vals[0]); } +void EffectHandler::SetParamiv(ModulatorProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } -void Modulator_getParami(const EffectProps *props, ALenum param, int *val) +void EffectHandler::SetParamf(ModulatorProps &props, ALenum param, float val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: - *val = static_cast(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 EffectHandler::SetParamfv(ModulatorProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -EffectProps genDefaultProps() noexcept +void EffectHandler::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 EffectHandler::GetParamiv(const ModulatorProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void EffectHandler::GetParamf(const ModulatorProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: *val = props.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 EffectHandler::GetParamfv(const ModulatorProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -DEFINE_ALEFFECT_VTABLE(Modulator); - -const EffectProps ModulatorEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { -using EaxRingModulatorEffectDirtyFlagsValue = std::uint_least8_t; +using ModulatorCommitter = EaxCommitter; -struct EaxRingModulatorEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxRingModulatorEffectDirtyFlagsValue flFrequency : 1; - EaxRingModulatorEffectDirtyFlagsValue flHighPassCutOff : 1; - EaxRingModulatorEffectDirtyFlagsValue ulWaveform : 1; -}; // EaxPitchShifterEffectDirtyFlags - - -class EaxRingModulatorEffect final : - public EaxEffect -{ -public: - EaxRingModulatorEffect(); - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAXRINGMODULATORPROPERTIES eax_{}; - EAXRINGMODULATORPROPERTIES eax_d_{}; - EaxRingModulatorEffectDirtyFlags eax_dirty_flags_{}; - - void set_eax_defaults(); - - void set_efx_frequency(); - void set_efx_high_pass_cutoff(); - void set_efx_waveform(); - void set_efx_defaults(); - - void get(const EaxEaxCall& eax_call); - - void validate_frequency(float flFrequency); - void validate_high_pass_cutoff(float flHighPassCutOff); - void validate_waveform(unsigned long ulWaveform); - void validate_all(const EAXRINGMODULATORPROPERTIES& all); - - void defer_frequency(float flFrequency); - void defer_high_pass_cutoff(float flHighPassCutOff); - void defer_waveform(unsigned long ulWaveform); - void defer_all(const EAXRINGMODULATORPROPERTIES& all); - - void defer_frequency(const EaxEaxCall& eax_call); - void defer_high_pass_cutoff(const EaxEaxCall& eax_call); - void defer_waveform(const EaxEaxCall& eax_call); - void defer_all(const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxRingModulatorEffect - - -class EaxRingModulatorEffectException : - public EaxException -{ -public: - explicit EaxRingModulatorEffectException( - const char* message) - : - EaxException{"EAX_RING_MODULATOR_EFFECT", message} +struct FrequencyValidator { + void operator()(float flFrequency) const { + eax_validate_range( + "Frequency", + flFrequency, + EAXRINGMODULATOR_MINFREQUENCY, + EAXRINGMODULATOR_MAXFREQUENCY); } -}; // EaxRingModulatorEffectException +}; // FrequencyValidator - -EaxRingModulatorEffect::EaxRingModulatorEffect() - : EaxEffect{AL_EFFECT_RING_MODULATOR} -{ - set_eax_defaults(); - set_efx_defaults(); -} - -void EaxRingModulatorEffect::dispatch(const EaxEaxCall& eax_call) -{ - eax_call.is_get() ? get(eax_call) : set(eax_call); -} - -void EaxRingModulatorEffect::set_eax_defaults() -{ - eax_.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY; - eax_.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF; - eax_.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM; - - eax_d_ = eax_; -} - -void EaxRingModulatorEffect::set_efx_frequency() -{ - const auto frequency = clamp( - eax_.flFrequency, - AL_RING_MODULATOR_MIN_FREQUENCY, - AL_RING_MODULATOR_MAX_FREQUENCY); - - al_effect_props_.Modulator.Frequency = frequency; -} - -void EaxRingModulatorEffect::set_efx_high_pass_cutoff() -{ - const auto high_pass_cutoff = clamp( - eax_.flHighPassCutOff, - AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF, - AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF); - - al_effect_props_.Modulator.HighPassCutoff = high_pass_cutoff; -} - -void EaxRingModulatorEffect::set_efx_waveform() -{ - const auto waveform = clamp( - static_cast(eax_.ulWaveform), - AL_RING_MODULATOR_MIN_WAVEFORM, - AL_RING_MODULATOR_MAX_WAVEFORM); - - const auto efx_waveform = WaveformFromEmum(waveform); - assert(efx_waveform.has_value()); - al_effect_props_.Modulator.Waveform = *efx_waveform; -} - -void EaxRingModulatorEffect::set_efx_defaults() -{ - set_efx_frequency(); - set_efx_high_pass_cutoff(); - set_efx_waveform(); -} - -void EaxRingModulatorEffect::get(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) +struct HighPassCutOffValidator { + void operator()(float flHighPassCutOff) const { - case EAXRINGMODULATOR_NONE: - break; - - case EAXRINGMODULATOR_ALLPARAMETERS: - eax_call.set_value(eax_); - break; - - case EAXRINGMODULATOR_FREQUENCY: - eax_call.set_value(eax_.flFrequency); - break; - - case EAXRINGMODULATOR_HIGHPASSCUTOFF: - eax_call.set_value(eax_.flHighPassCutOff); - break; - - case EAXRINGMODULATOR_WAVEFORM: - eax_call.set_value(eax_.ulWaveform); - break; - - default: - throw EaxRingModulatorEffectException{"Unsupported property id."}; + eax_validate_range( + "High-Pass Cutoff", + flHighPassCutOff, + EAXRINGMODULATOR_MINHIGHPASSCUTOFF, + EAXRINGMODULATOR_MAXHIGHPASSCUTOFF); } -} +}; // HighPassCutOffValidator -void EaxRingModulatorEffect::validate_frequency( - float flFrequency) -{ - eax_validate_range( - "Frequency", - flFrequency, - EAXRINGMODULATOR_MINFREQUENCY, - EAXRINGMODULATOR_MAXFREQUENCY); -} - -void EaxRingModulatorEffect::validate_high_pass_cutoff( - float flHighPassCutOff) -{ - eax_validate_range( - "High-Pass Cutoff", - flHighPassCutOff, - EAXRINGMODULATOR_MINHIGHPASSCUTOFF, - EAXRINGMODULATOR_MAXHIGHPASSCUTOFF); -} - -void EaxRingModulatorEffect::validate_waveform( - unsigned long ulWaveform) -{ - eax_validate_range( - "Waveform", - ulWaveform, - EAXRINGMODULATOR_MINWAVEFORM, - EAXRINGMODULATOR_MAXWAVEFORM); -} - -void EaxRingModulatorEffect::validate_all( - const EAXRINGMODULATORPROPERTIES& all) -{ - validate_frequency(all.flFrequency); - validate_high_pass_cutoff(all.flHighPassCutOff); - validate_waveform(all.ulWaveform); -} - -void EaxRingModulatorEffect::defer_frequency( - float flFrequency) -{ - eax_d_.flFrequency = flFrequency; - eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency); -} - -void EaxRingModulatorEffect::defer_high_pass_cutoff( - float flHighPassCutOff) -{ - eax_d_.flHighPassCutOff = flHighPassCutOff; - eax_dirty_flags_.flHighPassCutOff = (eax_.flHighPassCutOff != eax_d_.flHighPassCutOff); -} - -void EaxRingModulatorEffect::defer_waveform( - unsigned long ulWaveform) -{ - eax_d_.ulWaveform = ulWaveform; - eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); -} - -void EaxRingModulatorEffect::defer_all( - const EAXRINGMODULATORPROPERTIES& all) -{ - defer_frequency(all.flFrequency); - defer_high_pass_cutoff(all.flHighPassCutOff); - defer_waveform(all.ulWaveform); -} - -void EaxRingModulatorEffect::defer_frequency( - const EaxEaxCall& eax_call) -{ - const auto& frequency = - eax_call.get_value< - EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flFrequency)>(); - - validate_frequency(frequency); - defer_frequency(frequency); -} - -void EaxRingModulatorEffect::defer_high_pass_cutoff( - const EaxEaxCall& eax_call) -{ - const auto& high_pass_cutoff = - eax_call.get_value< - EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flHighPassCutOff)>(); - - validate_high_pass_cutoff(high_pass_cutoff); - defer_high_pass_cutoff(high_pass_cutoff); -} - -void EaxRingModulatorEffect::defer_waveform( - const EaxEaxCall& eax_call) -{ - const auto& waveform = - eax_call.get_value< - EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::ulWaveform)>(); - - validate_waveform(waveform); - defer_waveform(waveform); -} - -void EaxRingModulatorEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto& all = - eax_call.get_value(); - - validate_all(all); - defer_all(all); -} - -// [[nodiscard]] -bool EaxRingModulatorEffect::apply_deferred() -{ - if (eax_dirty_flags_ == EaxRingModulatorEffectDirtyFlags{}) +struct WaveformValidator { + void operator()(unsigned long ulWaveform) const { + eax_validate_range( + "Waveform", + ulWaveform, + EAXRINGMODULATOR_MINWAVEFORM, + EAXRINGMODULATOR_MAXWAVEFORM); + } +}; // WaveformValidator + +struct AllValidator { + void operator()(const EAXRINGMODULATORPROPERTIES& all) const + { + FrequencyValidator{}(all.flFrequency); + HighPassCutOffValidator{}(all.flHighPassCutOff); + WaveformValidator{}(all.ulWaveform); + } +}; // AllValidator + +} // namespace + +template<> +struct ModulatorCommitter::Exception : public EaxException { + explicit Exception(const char *message) : EaxException{"EAX_RING_MODULATOR_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void ModulatorCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +bool EaxModulatorCommitter::commit(const EAXRINGMODULATORPROPERTIES &props) +{ + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; - } - eax_ = eax_d_; + mEaxProps = props; - if (eax_dirty_flags_.flFrequency) + auto get_waveform = [](unsigned long form) { - set_efx_frequency(); - } + if(form == EAX_RINGMODULATOR_SINUSOID) + return ModulatorWaveform::Sinusoid; + if(form == EAX_RINGMODULATOR_SAWTOOTH) + return ModulatorWaveform::Sawtooth; + if(form == EAX_RINGMODULATOR_SQUARE) + return ModulatorWaveform::Square; + return ModulatorWaveform::Sinusoid; + }; - if (eax_dirty_flags_.flHighPassCutOff) - { - set_efx_high_pass_cutoff(); - } - - if (eax_dirty_flags_.ulWaveform) - { - set_efx_waveform(); - } - - eax_dirty_flags_ = EaxRingModulatorEffectDirtyFlags{}; + mAlProps = [&]{ + ModulatorProps ret{}; + ret.Frequency = props.flFrequency; + ret.HighPassCutoff = props.flHighPassCutOff; + ret.Waveform = get_waveform(props.ulWaveform); + return ret; + }(); return true; } -void EaxRingModulatorEffect::set(const EaxEaxCall& eax_call) +void EaxModulatorCommitter::SetDefaults(EaxEffectProps &props) { - switch (eax_call.get_property_id()) + static constexpr EAXRINGMODULATORPROPERTIES defprops{[] { - case EAXRINGMODULATOR_NONE: - break; + EAXRINGMODULATORPROPERTIES ret{}; + ret.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY; + ret.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF; + ret.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM; + return ret; + }()}; + props = defprops; +} - case EAXRINGMODULATOR_ALLPARAMETERS: - defer_all(eax_call); - break; - - case EAXRINGMODULATOR_FREQUENCY: - defer_frequency(eax_call); - break; - - case EAXRINGMODULATOR_HIGHPASSCUTOFF: - defer_high_pass_cutoff(eax_call); - break; - - case EAXRINGMODULATOR_WAVEFORM: - defer_waveform(eax_call); - break; - - default: - throw EaxRingModulatorEffectException{"Unsupported property id."}; +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); 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(); } } -} // namespace - -EaxEffectUPtr eax_create_eax_ring_modulator_effect() +void EaxModulatorCommitter::Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props) { - return std::make_unique(); + switch(call.get_property_id()) + { + case EAXRINGMODULATOR_NONE: 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(); + } } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/null.cpp b/libs/openal-soft/al/effects/null.cpp index a0eb2247..02d24175 100644 --- a/libs/openal-soft/al/effects/null.cpp +++ b/libs/openal-soft/al/effects/null.cpp @@ -8,145 +8,136 @@ #include "effects.h" #ifdef ALSOFT_EAX -#include "al/eax_exception.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" #endif // ALSOFT_EAX namespace { -void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/) +constexpr EffectProps genDefaultProps() noexcept { - 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 EffectHandler::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 EffectHandler::SetParamiv(std::monostate &props, ALenum param, const int *vals) +{ + switch(param) + { + default: + SetParami(props, param, *vals); + } +} +void EffectHandler::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 EffectHandler::SetParamfv(std::monostate &props, ALenum param, const float *vals) +{ + switch(param) + { + default: + SetParamf(props, param, *vals); + } +} + +void EffectHandler::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 EffectHandler::GetParamiv(const std::monostate &props, ALenum param, int *vals) +{ + switch(param) + { + default: + GetParami(props, param, vals); + } +} +void EffectHandler::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 EffectHandler::GetParamfv(const std::monostate &props, ALenum param, float *vals) +{ + switch(param) + { + default: + GetParamf(props, param, vals); + } +} + #ifdef ALSOFT_EAX namespace { -class EaxNullEffect final : - public EaxEffect -{ -public: - EaxNullEffect(); - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; -}; // EaxNullEffect - - -class EaxNullEffectException : - public EaxException -{ -public: - explicit EaxNullEffectException( - const char* message) - : - EaxException{"EAX_NULL_EFFECT", message} - { - } -}; // EaxNullEffectException - - -EaxNullEffect::EaxNullEffect() - : EaxEffect{AL_EFFECT_NULL} -{ -} - -void EaxNullEffect::dispatch(const EaxEaxCall& eax_call) -{ - if(eax_call.get_property_id() != 0) - throw EaxNullEffectException{"Unsupported property id."}; -} - -bool EaxNullEffect::apply_deferred() -{ - return false; -} +using NullCommitter = EaxCommitter; } // namespace -EaxEffectUPtr eax_create_eax_null_effect() +template<> +struct NullCommitter::Exception : public EaxException { - return std::make_unique(); + explicit Exception(const char *message) : EaxException{"EAX_NULL_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void NullCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +bool EaxNullCommitter::commit(const std::monostate &props) +{ + const bool ret{std::holds_alternative(mEaxProps)}; + mEaxProps = props; + mAlProps = std::monostate{}; + return ret; +} + +void EaxNullCommitter::SetDefaults(EaxEffectProps &props) +{ + props = std::monostate{}; +} + +void EaxNullCommitter::Get(const EaxCall &call, const std::monostate&) +{ + if(call.get_property_id() != 0) + fail_unknown_property_id(); +} + +void EaxNullCommitter::Set(const EaxCall &call, std::monostate&) +{ + if(call.get_property_id() != 0) + fail_unknown_property_id(); } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/pshifter.cpp b/libs/openal-soft/al/effects/pshifter.cpp index 1b2dcff0..7b91dd1f 100644 --- a/libs/openal-soft/al/effects/pshifter.cpp +++ b/libs/openal-soft/al/effects/pshifter.cpp @@ -9,36 +9,40 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" - -#include "al/eax_exception.h" -#include "al/eax_utils.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { -void Pshifter_setParamf(EffectProps*, ALenum param, float) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } -void Pshifter_setParamfv(EffectProps*, ALenum param, const float*) +constexpr EffectProps genDefaultProps() noexcept { - throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", - param}; + PshifterProps props{}; + props.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; + props.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE; + return props; } -void Pshifter_setParami(EffectProps *props, ALenum param, int val) +} // namespace + +const EffectProps PshifterEffectProps{genDefaultProps()}; + +void EffectHandler::SetParami(PshifterProps &props, ALenum param, int val) { switch(param) { case AL_PITCH_SHIFTER_COARSE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) throw effect_exception{AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"}; - props->Pshifter.CoarseTune = val; + props.CoarseTune = val; break; case AL_PITCH_SHIFTER_FINE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) throw effect_exception{AL_INVALID_VALUE, "Pitch shifter fine tune out of range"}; - props->Pshifter.FineTune = val; + props.FineTune = val; break; default: @@ -46,319 +50,134 @@ void Pshifter_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Pshifter_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Pshifter_setParami(props, param, vals[0]); } +void EffectHandler::SetParamiv(PshifterProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } -void Pshifter_getParami(const EffectProps *props, ALenum param, int *val) +void EffectHandler::SetParamf(PshifterProps&, ALenum param, float) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } +void EffectHandler::SetParamfv(PshifterProps&, ALenum param, const float*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", + param}; +} + +void EffectHandler::GetParami(const PshifterProps &props, ALenum param, int *val) { switch(param) { - case AL_PITCH_SHIFTER_COARSE_TUNE: - *val = props->Pshifter.CoarseTune; - break; - case AL_PITCH_SHIFTER_FINE_TUNE: - *val = props->Pshifter.FineTune; - break; + case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; break; + case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.FineTune; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param}; } } -void Pshifter_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Pshifter_getParami(props, param, vals); } +void EffectHandler::GetParamiv(const PshifterProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } -void Pshifter_getParamf(const EffectProps*, ALenum param, float*) +void EffectHandler::GetParamf(const PshifterProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } -void Pshifter_getParamfv(const EffectProps*, ALenum param, float*) +void EffectHandler::GetParamfv(const PshifterProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param}; } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; - props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Pshifter); - -const EffectProps PshifterEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { -using EaxPitchShifterEffectDirtyFlagsValue = std::uint_least8_t; +using PitchShifterCommitter = EaxCommitter; -struct EaxPitchShifterEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxPitchShifterEffectDirtyFlagsValue lCoarseTune : 1; - EaxPitchShifterEffectDirtyFlagsValue lFineTune : 1; -}; // EaxPitchShifterEffectDirtyFlags - - -class EaxPitchShifterEffect final : - public EaxEffect -{ -public: - EaxPitchShifterEffect(); - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAXPITCHSHIFTERPROPERTIES eax_{}; - EAXPITCHSHIFTERPROPERTIES eax_d_{}; - EaxPitchShifterEffectDirtyFlags eax_dirty_flags_{}; - - void set_eax_defaults(); - - void set_efx_coarse_tune(); - void set_efx_fine_tune(); - void set_efx_defaults(); - - void get(const EaxEaxCall& eax_call); - - void validate_coarse_tune(long lCoarseTune); - void validate_fine_tune(long lFineTune); - void validate_all(const EAXPITCHSHIFTERPROPERTIES& all); - - void defer_coarse_tune(long lCoarseTune); - void defer_fine_tune(long lFineTune); - void defer_all(const EAXPITCHSHIFTERPROPERTIES& all); - - void defer_coarse_tune(const EaxEaxCall& eax_call); - void defer_fine_tune(const EaxEaxCall& eax_call); - void defer_all(const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxPitchShifterEffect - - -class EaxPitchShifterEffectException : - public EaxException -{ -public: - explicit EaxPitchShifterEffectException( - const char* message) - : - EaxException{"EAX_PITCH_SHIFTER_EFFECT", message} +struct CoarseTuneValidator { + void operator()(long lCoarseTune) const { + eax_validate_range( + "Coarse Tune", + lCoarseTune, + EAXPITCHSHIFTER_MINCOARSETUNE, + EAXPITCHSHIFTER_MAXCOARSETUNE); } -}; // EaxPitchShifterEffectException +}; // CoarseTuneValidator - -EaxPitchShifterEffect::EaxPitchShifterEffect() - : EaxEffect{AL_EFFECT_PITCH_SHIFTER} -{ - set_eax_defaults(); - set_efx_defaults(); -} - -void EaxPitchShifterEffect::dispatch(const EaxEaxCall& eax_call) -{ - eax_call.is_get() ? get(eax_call) : set(eax_call); -} - -void EaxPitchShifterEffect::set_eax_defaults() -{ - eax_.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE; - eax_.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE; - - eax_d_ = eax_; -} - -void EaxPitchShifterEffect::set_efx_coarse_tune() -{ - const auto coarse_tune = clamp( - static_cast(eax_.lCoarseTune), - AL_PITCH_SHIFTER_MIN_COARSE_TUNE, - AL_PITCH_SHIFTER_MAX_COARSE_TUNE); - - al_effect_props_.Pshifter.CoarseTune = coarse_tune; -} - -void EaxPitchShifterEffect::set_efx_fine_tune() -{ - const auto fine_tune = clamp( - static_cast(eax_.lFineTune), - AL_PITCH_SHIFTER_MIN_FINE_TUNE, - AL_PITCH_SHIFTER_MAX_FINE_TUNE); - - al_effect_props_.Pshifter.FineTune = fine_tune; -} - -void EaxPitchShifterEffect::set_efx_defaults() -{ - set_efx_coarse_tune(); - set_efx_fine_tune(); -} - -void EaxPitchShifterEffect::get(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) +struct FineTuneValidator { + void operator()(long lFineTune) const { - case EAXPITCHSHIFTER_NONE: - break; - - case EAXPITCHSHIFTER_ALLPARAMETERS: - eax_call.set_value(eax_); - break; - - case EAXPITCHSHIFTER_COARSETUNE: - eax_call.set_value(eax_.lCoarseTune); - break; - - case EAXPITCHSHIFTER_FINETUNE: - eax_call.set_value(eax_.lFineTune); - break; - - default: - throw EaxPitchShifterEffectException{"Unsupported property id."}; + eax_validate_range( + "Fine Tune", + lFineTune, + EAXPITCHSHIFTER_MINFINETUNE, + EAXPITCHSHIFTER_MAXFINETUNE); } -} +}; // FineTuneValidator -void EaxPitchShifterEffect::validate_coarse_tune( - long lCoarseTune) -{ - eax_validate_range( - "Coarse Tune", - lCoarseTune, - EAXPITCHSHIFTER_MINCOARSETUNE, - EAXPITCHSHIFTER_MAXCOARSETUNE); -} - -void EaxPitchShifterEffect::validate_fine_tune( - long lFineTune) -{ - eax_validate_range( - "Fine Tune", - lFineTune, - EAXPITCHSHIFTER_MINFINETUNE, - EAXPITCHSHIFTER_MAXFINETUNE); -} - -void EaxPitchShifterEffect::validate_all( - const EAXPITCHSHIFTERPROPERTIES& all) -{ - validate_coarse_tune(all.lCoarseTune); - validate_fine_tune(all.lFineTune); -} - -void EaxPitchShifterEffect::defer_coarse_tune( - long lCoarseTune) -{ - eax_d_.lCoarseTune = lCoarseTune; - eax_dirty_flags_.lCoarseTune = (eax_.lCoarseTune != eax_d_.lCoarseTune); -} - -void EaxPitchShifterEffect::defer_fine_tune( - long lFineTune) -{ - eax_d_.lFineTune = lFineTune; - eax_dirty_flags_.lFineTune = (eax_.lFineTune != eax_d_.lFineTune); -} - -void EaxPitchShifterEffect::defer_all( - const EAXPITCHSHIFTERPROPERTIES& all) -{ - defer_coarse_tune(all.lCoarseTune); - defer_fine_tune(all.lFineTune); -} - -void EaxPitchShifterEffect::defer_coarse_tune( - const EaxEaxCall& eax_call) -{ - const auto& coarse_tune = - eax_call.get_value(); - - validate_coarse_tune(coarse_tune); - defer_coarse_tune(coarse_tune); -} - -void EaxPitchShifterEffect::defer_fine_tune( - const EaxEaxCall& eax_call) -{ - const auto& fine_tune = - eax_call.get_value(); - - validate_fine_tune(fine_tune); - defer_fine_tune(fine_tune); -} - -void EaxPitchShifterEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto& all = - eax_call.get_value(); - - validate_all(all); - defer_all(all); -} - -// [[nodiscard]] -bool EaxPitchShifterEffect::apply_deferred() -{ - if (eax_dirty_flags_ == EaxPitchShifterEffectDirtyFlags{}) +struct AllValidator { + void operator()(const EAXPITCHSHIFTERPROPERTIES& all) const { + CoarseTuneValidator{}(all.lCoarseTune); + FineTuneValidator{}(all.lFineTune); + } +}; // AllValidator + +} // namespace + +template<> +struct PitchShifterCommitter::Exception : public EaxException { + explicit Exception(const char *message) : EaxException{"EAX_PITCH_SHIFTER_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void PitchShifterCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +bool EaxPitchShifterCommitter::commit(const EAXPITCHSHIFTERPROPERTIES &props) +{ + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; - } - eax_ = eax_d_; - - if (eax_dirty_flags_.lCoarseTune) - { - set_efx_coarse_tune(); - } - - if (eax_dirty_flags_.lFineTune) - { - set_efx_fine_tune(); - } - - eax_dirty_flags_ = EaxPitchShifterEffectDirtyFlags{}; + mEaxProps = props; + mAlProps = [&]{ + PshifterProps ret{}; + ret.CoarseTune = static_cast(props.lCoarseTune); + ret.FineTune = static_cast(props.lFineTune); + return ret; + }(); return true; } -void EaxPitchShifterEffect::set(const EaxEaxCall& eax_call) +void EaxPitchShifterCommitter::SetDefaults(EaxEffectProps &props) { - switch(eax_call.get_property_id()) + props = EAXPITCHSHIFTERPROPERTIES{EAXPITCHSHIFTER_DEFAULTCOARSETUNE, + EAXPITCHSHIFTER_DEFAULTFINETUNE}; +} + +void EaxPitchShifterCommitter::Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props) +{ + switch(call.get_property_id()) { - case EAXPITCHSHIFTER_NONE: - break; - - case EAXPITCHSHIFTER_ALLPARAMETERS: - defer_all(eax_call); - break; - - case EAXPITCHSHIFTER_COARSETUNE: - defer_coarse_tune(eax_call); - break; - - case EAXPITCHSHIFTER_FINETUNE: - defer_fine_tune(eax_call); - break; - - default: - throw EaxPitchShifterEffectException{"Unsupported property id."}; + case EAXPITCHSHIFTER_NONE: break; + case EAXPITCHSHIFTER_ALLPARAMETERS: 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(); } } -} // namespace - -EaxEffectUPtr eax_create_eax_pitch_shifter_effect() +void EaxPitchShifterCommitter::Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props) { - return std::make_unique(); + switch(call.get_property_id()) + { + case EAXPITCHSHIFTER_NONE: 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(); + } } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/reverb.cpp b/libs/openal-soft/al/effects/reverb.cpp index 197ea500..5030e9e9 100644 --- a/libs/openal-soft/al/effects/reverb.cpp +++ b/libs/openal-soft/al/effects/reverb.cpp @@ -1,33 +1,105 @@ #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_exception.h" -#include "al/eax_utils.h" +#include +#include "al/eax/api.h" +#include "al/eax/call.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { -void Reverb_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultProps() noexcept +{ + ReverbProps props{}; + props.Density = AL_EAXREVERB_DEFAULT_DENSITY; + props.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; + props.Gain = AL_EAXREVERB_DEFAULT_GAIN; + props.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; + props.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; + props.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; + props.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; + props.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; + props.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; + props.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; + props.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; + props.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; + props.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; + props.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; + props.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; + props.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; + props.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; + props.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; + props.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; + props.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; + props.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; + return props; +} + +constexpr EffectProps genDefaultStdProps() noexcept +{ + ReverbProps props{}; + props.Density = AL_REVERB_DEFAULT_DENSITY; + props.Diffusion = AL_REVERB_DEFAULT_DIFFUSION; + props.Gain = AL_REVERB_DEFAULT_GAIN; + props.GainHF = AL_REVERB_DEFAULT_GAINHF; + props.GainLF = 1.0f; + props.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME; + props.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO; + props.DecayLFRatio = 1.0f; + props.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN; + props.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY; + props.ReflectionsPan = {0.0f, 0.0f, 0.0f}; + props.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN; + props.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY; + props.LateReverbPan = {0.0f, 0.0f, 0.0f}; + props.EchoTime = 0.25f; + props.EchoDepth = 0.0f; + props.ModulationTime = 0.25f; + props.ModulationDepth = 0.0f; + props.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF; + props.HFReference = 5000.0f; + props.LFReference = 250.0f; + props.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; + props.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT; + return props; +} + +} // namespace + +const EffectProps ReverbEffectProps{genDefaultProps()}; + +void EffectHandler::SetParami(ReverbProps &props, ALenum param, int val) { switch(param) { case AL_EAXREVERB_DECAY_HFLIMIT: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; - props->Reverb.DecayHFLimit = val != AL_FALSE; + props.DecayHFLimit = val != AL_FALSE; break; default: @@ -35,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 EffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void EffectHandler::SetParamf(ReverbProps &props, ALenum param, float val) { switch(param) { case AL_EAXREVERB_DENSITY: if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; - props->Reverb.Density = val; + props.Density = val; break; case AL_EAXREVERB_DIFFUSION: if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; - props->Reverb.Diffusion = val; + props.Diffusion = val; break; case AL_EAXREVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; - props->Reverb.Gain = val; + props.Gain = val; break; case AL_EAXREVERB_GAINHF: if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; - props->Reverb.GainHF = val; + props.GainHF = val; break; case AL_EAXREVERB_GAINLF: if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainlf out of range"}; - props->Reverb.GainLF = val; + props.GainLF = val; break; case AL_EAXREVERB_DECAY_TIME: if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; - props->Reverb.DecayTime = val; + props.DecayTime = val; break; case AL_EAXREVERB_DECAY_HFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; - props->Reverb.DecayHFRatio = val; + props.DecayHFRatio = val; break; case AL_EAXREVERB_DECAY_LFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"}; - props->Reverb.DecayLFRatio = val; + props.DecayLFRatio = val; break; case AL_EAXREVERB_REFLECTIONS_GAIN: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; - props->Reverb.ReflectionsGain = val; + props.ReflectionsGain = val; break; case AL_EAXREVERB_REFLECTIONS_DELAY: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; - props->Reverb.ReflectionsDelay = val; + props.ReflectionsDelay = val; break; case AL_EAXREVERB_LATE_REVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; - props->Reverb.LateReverbGain = val; + props.LateReverbGain = val; break; case AL_EAXREVERB_LATE_REVERB_DELAY: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; - props->Reverb.LateReverbDelay = val; - break; - - case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: - if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; - props->Reverb.AirAbsorptionGainHF = val; + props.LateReverbDelay = val; break; case AL_EAXREVERB_ECHO_TIME: if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo time out of range"}; - props->Reverb.EchoTime = val; + props.EchoTime = val; break; case AL_EAXREVERB_ECHO_DEPTH: if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo depth out of range"}; - props->Reverb.EchoDepth = val; + props.EchoDepth = val; break; case AL_EAXREVERB_MODULATION_TIME: if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation time out of range"}; - props->Reverb.ModulationTime = val; + props.ModulationTime = val; break; case AL_EAXREVERB_MODULATION_DEPTH: if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"}; - props->Reverb.ModulationDepth = val; + props.ModulationDepth = val; + break; + + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: + if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; + props.AirAbsorptionGainHF = val; break; case AL_EAXREVERB_HFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb hfreference out of range"}; - props->Reverb.HFReference = val; + props.HFReference = val; break; case AL_EAXREVERB_LFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb lfreference out of range"}; - props->Reverb.LFReference = val; + props.LFReference = val; break; case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; - props->Reverb.RoomRolloffFactor = val; + props.RoomRolloffFactor = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; } } -void Reverb_setParamfv(EffectProps *props, ALenum param, const float *vals) +void EffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *vals) { + static constexpr auto finite_checker = [](float f) -> bool { return std::isfinite(f); }; + al::span values; switch(param) { case AL_EAXREVERB_REFLECTIONS_PAN: - if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) + values = {vals, 3_uz}; + if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"}; - props->Reverb.ReflectionsPan[0] = vals[0]; - props->Reverb.ReflectionsPan[1] = vals[1]; - props->Reverb.ReflectionsPan[2] = vals[2]; + std::copy(values.cbegin(), values.cend(), props.ReflectionsPan.begin()); break; case AL_EAXREVERB_LATE_REVERB_PAN: - if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) + values = {vals, 3_uz}; + if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"}; - props->Reverb.LateReverbPan[0] = vals[0]; - props->Reverb.LateReverbPan[1] = vals[1]; - props->Reverb.LateReverbPan[2] = vals[2]; + std::copy(values.cbegin(), values.cend(), props.LateReverbPan.begin()); break; default: - Reverb_setParamf(props, param, vals[0]); + SetParamf(props, param, *vals); break; } } -void Reverb_getParami(const EffectProps *props, ALenum param, int *val) +void EffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val) { switch(param) { - case AL_EAXREVERB_DECAY_HFLIMIT: - *val = props->Reverb.DecayHFLimit; + 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 EffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void EffectHandler::GetParamf(const ReverbProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_EAXREVERB_DENSITY: *val = props.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 EffectHandler::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 EffectHandler::StdReverbSetParami(ReverbProps &props, ALenum param, int val) +{ + switch(param) + { + case AL_REVERB_DECAY_HFLIMIT: + if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; + props.DecayHFLimit = val != AL_FALSE; break; default: @@ -203,2340 +341,912 @@ 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 EffectHandler::StdReverbSetParamiv(ReverbProps &props, ALenum param, const int *vals) +{ StdReverbSetParami(props, param, *vals); } +void EffectHandler::StdReverbSetParamf(ReverbProps &props, ALenum param, float val) { switch(param) { - case AL_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_AIR_ABSORPTION_GAINHF: - *val = props->Reverb.AirAbsorptionGainHF; - break; - - case AL_EAXREVERB_ECHO_TIME: - *val = props->Reverb.EchoTime; - break; - - case AL_EAXREVERB_ECHO_DEPTH: - *val = props->Reverb.EchoDepth; - break; - - case AL_EAXREVERB_MODULATION_TIME: - *val = props->Reverb.ModulationTime; - break; - - case AL_EAXREVERB_MODULATION_DEPTH: - *val = props->Reverb.ModulationDepth; - break; - - case AL_EAXREVERB_HFREFERENCE: - *val = props->Reverb.HFReference; - break; - - case AL_EAXREVERB_LFREFERENCE: - *val = props->Reverb.LFReference; - break; - - case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: - *val = props->Reverb.RoomRolloffFactor; + 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 EffectHandler::StdReverbSetParamfv(ReverbProps &props, ALenum param, const float *vals) { switch(param) { - case AL_EAXREVERB_REFLECTIONS_PAN: - vals[0] = props->Reverb.ReflectionsPan[0]; - vals[1] = props->Reverb.ReflectionsPan[1]; - vals[2] = props->Reverb.ReflectionsPan[2]; - break; - case AL_EAXREVERB_LATE_REVERB_PAN: - vals[0] = props->Reverb.LateReverbPan[0]; - vals[1] = props->Reverb.LateReverbPan[1]; - vals[2] = props->Reverb.LateReverbPan[2]; - break; - default: - Reverb_getParamf(props, param, vals); + StdReverbSetParamf(props, param, *vals); break; } } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY; - props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; - props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN; - props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; - props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; - props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; - props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; - props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; - props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; - props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; - props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; - props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; - props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; - props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; - props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; - props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; - props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; - props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; - props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; - props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; - props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; - return props; -} - - -void StdReverb_setParami(EffectProps *props, ALenum param, int val) +void EffectHandler::StdReverbGetParami(const ReverbProps &props, ALenum param, int *val) { switch(param) { - case AL_REVERB_DECAY_HFLIMIT: - if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) - throw effect_exception{AL_INVALID_VALUE, "Reverb decay hflimit out of range"}; - props->Reverb.DecayHFLimit = val != AL_FALSE; - break; - + case AL_REVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break; default: - throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param}; + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", + param}; } } -void StdReverb_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ StdReverb_setParami(props, param, vals[0]); } -void StdReverb_setParamf(EffectProps *props, ALenum param, float val) +void EffectHandler::StdReverbGetParamiv(const ReverbProps &props, ALenum param, int *vals) +{ StdReverbGetParami(props, param, vals); } +void EffectHandler::StdReverbGetParamf(const ReverbProps &props, ALenum param, float *val) { switch(param) { - case AL_REVERB_DENSITY: - 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; - - 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; + 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 float property 0x%04x", param}; + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; } } -void StdReverb_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ StdReverb_setParamf(props, param, vals[0]); } - -void StdReverb_getParami(const EffectProps *props, ALenum param, int *val) +void EffectHandler::StdReverbGetParamfv(const ReverbProps &props, ALenum param, float *vals) { 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}; + StdReverbGetParamf(props, param, vals); + break; } } -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 { -extern const EFXEAXREVERBPROPERTIES eax_efx_reverb_presets[]; - -using EaxReverbEffectDirtyFlagsValue = std::uint_least32_t; - -struct EaxReverbEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxReverbEffectDirtyFlagsValue ulEnvironment : 1; - EaxReverbEffectDirtyFlagsValue flEnvironmentSize : 1; - EaxReverbEffectDirtyFlagsValue flEnvironmentDiffusion : 1; - EaxReverbEffectDirtyFlagsValue lRoom : 1; - EaxReverbEffectDirtyFlagsValue lRoomHF : 1; - EaxReverbEffectDirtyFlagsValue lRoomLF : 1; - EaxReverbEffectDirtyFlagsValue flDecayTime : 1; - EaxReverbEffectDirtyFlagsValue flDecayHFRatio : 1; - EaxReverbEffectDirtyFlagsValue flDecayLFRatio : 1; - EaxReverbEffectDirtyFlagsValue lReflections : 1; - EaxReverbEffectDirtyFlagsValue flReflectionsDelay : 1; - EaxReverbEffectDirtyFlagsValue vReflectionsPan : 1; - EaxReverbEffectDirtyFlagsValue lReverb : 1; - EaxReverbEffectDirtyFlagsValue flReverbDelay : 1; - EaxReverbEffectDirtyFlagsValue vReverbPan : 1; - EaxReverbEffectDirtyFlagsValue flEchoTime : 1; - EaxReverbEffectDirtyFlagsValue flEchoDepth : 1; - EaxReverbEffectDirtyFlagsValue flModulationTime : 1; - EaxReverbEffectDirtyFlagsValue flModulationDepth : 1; - EaxReverbEffectDirtyFlagsValue flAirAbsorptionHF : 1; - EaxReverbEffectDirtyFlagsValue flHFReference : 1; - EaxReverbEffectDirtyFlagsValue flLFReference : 1; - EaxReverbEffectDirtyFlagsValue flRoomRolloffFactor : 1; - EaxReverbEffectDirtyFlagsValue ulFlags : 1; -}; // EaxReverbEffectDirtyFlags - -struct Eax1ReverbEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxReverbEffectDirtyFlagsValue ulEnvironment : 1; - EaxReverbEffectDirtyFlagsValue flVolume : 1; - EaxReverbEffectDirtyFlagsValue flDecayTime : 1; - EaxReverbEffectDirtyFlagsValue flDamping : 1; -}; // Eax1ReverbEffectDirtyFlags - -class EaxReverbEffect final : - public EaxEffect +class EaxReverbEffectException : public EaxException { public: - EaxReverbEffect(); - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAX_REVERBPROPERTIES eax1_{}; - EAX_REVERBPROPERTIES eax1_d_{}; - Eax1ReverbEffectDirtyFlags eax1_dirty_flags_{}; - EAXREVERBPROPERTIES eax_{}; - EAXREVERBPROPERTIES eax_d_{}; - EaxReverbEffectDirtyFlags eax_dirty_flags_{}; - - [[noreturn]] static void eax_fail(const char* message); - - void set_eax_defaults(); - - void set_efx_density_from_environment_size(); - void set_efx_diffusion(); - void set_efx_gain(); - void set_efx_gain_hf(); - void set_efx_gain_lf(); - void set_efx_decay_time(); - void set_efx_decay_hf_ratio(); - void set_efx_decay_lf_ratio(); - void set_efx_reflections_gain(); - void set_efx_reflections_delay(); - void set_efx_reflections_pan(); - void set_efx_late_reverb_gain(); - void set_efx_late_reverb_delay(); - void set_efx_late_reverb_pan(); - void set_efx_echo_time(); - void set_efx_echo_depth(); - void set_efx_modulation_time(); - void set_efx_modulation_depth(); - void set_efx_air_absorption_gain_hf(); - void set_efx_hf_reference(); - void set_efx_lf_reference(); - void set_efx_room_rolloff_factor(); - void set_efx_flags(); - void set_efx_defaults(); - - void v1_get(const EaxEaxCall& eax_call) const; - - void get_all(const EaxEaxCall& eax_call) const; - - void get(const EaxEaxCall& eax_call) const; - - static void v1_validate_environment(unsigned long environment); - static void v1_validate_volume(float volume); - static void v1_validate_decay_time(float decay_time); - static void v1_validate_damping(float damping); - static void v1_validate_all(const EAX_REVERBPROPERTIES& all); - - void v1_defer_environment(unsigned long environment); - void v1_defer_volume(float volume); - void v1_defer_decay_time(float decay_time); - void v1_defer_damping(float damping); - void v1_defer_all(const EAX_REVERBPROPERTIES& all); - - void v1_defer_environment(const EaxEaxCall& eax_call); - void v1_defer_volume(const EaxEaxCall& eax_call); - void v1_defer_decay_time(const EaxEaxCall& eax_call); - void v1_defer_damping(const EaxEaxCall& eax_call); - void v1_defer_all(const EaxEaxCall& eax_call); - void v1_defer(const EaxEaxCall& eax_call); - - void v1_set_efx(); - - static void validate_environment(unsigned long ulEnvironment, int version, bool is_standalone); - static void validate_environment_size(float flEnvironmentSize); - static void validate_environment_diffusion(float flEnvironmentDiffusion); - static void validate_room(long lRoom); - static void validate_room_hf(long lRoomHF); - static void validate_room_lf(long lRoomLF); - static void validate_decay_time(float flDecayTime); - static void validate_decay_hf_ratio(float flDecayHFRatio); - static void validate_decay_lf_ratio(float flDecayLFRatio); - static void validate_reflections(long lReflections); - static void validate_reflections_delay(float flReflectionsDelay); - static void validate_reflections_pan(const EAXVECTOR& vReflectionsPan); - static void validate_reverb(long lReverb); - static void validate_reverb_delay(float flReverbDelay); - static void validate_reverb_pan(const EAXVECTOR& vReverbPan); - static void validate_echo_time(float flEchoTime); - static void validate_echo_depth(float flEchoDepth); - static void validate_modulation_time(float flModulationTime); - static void validate_modulation_depth(float flModulationDepth); - static void validate_air_absorbtion_hf(float air_absorbtion_hf); - static void validate_hf_reference(float flHFReference); - static void validate_lf_reference(float flLFReference); - static void validate_room_rolloff_factor(float flRoomRolloffFactor); - static void validate_flags(unsigned long ulFlags); - static void validate_all(const EAX20LISTENERPROPERTIES& all, int version); - static void validate_all(const EAXREVERBPROPERTIES& all, int version); - - void defer_environment(unsigned long ulEnvironment); - void defer_environment_size(float flEnvironmentSize); - void defer_environment_diffusion(float flEnvironmentDiffusion); - void defer_room(long lRoom); - void defer_room_hf(long lRoomHF); - void defer_room_lf(long lRoomLF); - void defer_decay_time(float flDecayTime); - void defer_decay_hf_ratio(float flDecayHFRatio); - void defer_decay_lf_ratio(float flDecayLFRatio); - void defer_reflections(long lReflections); - void defer_reflections_delay(float flReflectionsDelay); - void defer_reflections_pan(const EAXVECTOR& vReflectionsPan); - void defer_reverb(long lReverb); - void defer_reverb_delay(float flReverbDelay); - void defer_reverb_pan(const EAXVECTOR& vReverbPan); - void defer_echo_time(float flEchoTime); - void defer_echo_depth(float flEchoDepth); - void defer_modulation_time(float flModulationTime); - void defer_modulation_depth(float flModulationDepth); - void defer_air_absorbtion_hf(float flAirAbsorptionHF); - void defer_hf_reference(float flHFReference); - void defer_lf_reference(float flLFReference); - void defer_room_rolloff_factor(float flRoomRolloffFactor); - void defer_flags(unsigned long ulFlags); - void defer_all(const EAX20LISTENERPROPERTIES& all); - void defer_all(const EAXREVERBPROPERTIES& all); - - void defer_environment(const EaxEaxCall& eax_call); - void defer_environment_size(const EaxEaxCall& eax_call); - void defer_environment_diffusion(const EaxEaxCall& eax_call); - void defer_room(const EaxEaxCall& eax_call); - void defer_room_hf(const EaxEaxCall& eax_call); - void defer_room_lf(const EaxEaxCall& eax_call); - void defer_decay_time(const EaxEaxCall& eax_call); - void defer_decay_hf_ratio(const EaxEaxCall& eax_call); - void defer_decay_lf_ratio(const EaxEaxCall& eax_call); - void defer_reflections(const EaxEaxCall& eax_call); - void defer_reflections_delay(const EaxEaxCall& eax_call); - void defer_reflections_pan(const EaxEaxCall& eax_call); - void defer_reverb(const EaxEaxCall& eax_call); - void defer_reverb_delay(const EaxEaxCall& eax_call); - void defer_reverb_pan(const EaxEaxCall& eax_call); - void defer_echo_time(const EaxEaxCall& eax_call); - void defer_echo_depth(const EaxEaxCall& eax_call); - void defer_modulation_time(const EaxEaxCall& eax_call); - void defer_modulation_depth(const EaxEaxCall& eax_call); - void defer_air_absorbtion_hf(const EaxEaxCall& eax_call); - void defer_hf_reference(const EaxEaxCall& eax_call); - void defer_lf_reference(const EaxEaxCall& eax_call); - void defer_room_rolloff_factor(const EaxEaxCall& eax_call); - void defer_flags(const EaxEaxCall& eax_call); - void defer_all(const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxReverbEffect - - -class EaxReverbEffectException : - public EaxException -{ -public: - explicit EaxReverbEffectException( - const char* message) - : - EaxException{"EAX_REVERB_EFFECT", message} - { - } + explicit EaxReverbEffectException(const char* message) + : EaxException{"EAX_REVERB_EFFECT", message} + {} }; // EaxReverbEffectException - -EaxReverbEffect::EaxReverbEffect() - : EaxEffect{AL_EFFECT_EAXREVERB} -{ - set_eax_defaults(); - set_efx_defaults(); -} - -void EaxReverbEffect::dispatch(const EaxEaxCall& eax_call) -{ - eax_call.is_get() ? get(eax_call) : set(eax_call); -} - -[[noreturn]] void EaxReverbEffect::eax_fail(const char* message) -{ - throw EaxReverbEffectException{message}; -} - -void EaxReverbEffect::set_eax_defaults() -{ - eax1_ = EAX1REVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; - eax1_d_ = eax1_; - eax_ = EAXREVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; - /* HACK: EAX2 has a default room volume of -10,000dB (silence), although - * newer versions use -1,000dB. What should be happening is properties for - * each EAX version is tracked separately, with the last version used for - * the properties to apply (presumably v2 or nothing being the default). - */ - eax_.lRoom = EAXREVERB_MINROOM; - eax_d_ = eax_; -} - -void EaxReverbEffect::set_efx_density_from_environment_size() -{ - const auto eax_environment_size = eax_.flEnvironmentSize; - - const auto efx_density = clamp( - (eax_environment_size * eax_environment_size * eax_environment_size) / 16.0F, - AL_EAXREVERB_MIN_DENSITY, - AL_EAXREVERB_MAX_DENSITY); - - al_effect_props_.Reverb.Density = efx_density; -} - -void EaxReverbEffect::set_efx_diffusion() -{ - const auto efx_diffusion = clamp( - eax_.flEnvironmentDiffusion, - AL_EAXREVERB_MIN_DIFFUSION, - AL_EAXREVERB_MAX_DIFFUSION); - - al_effect_props_.Reverb.Diffusion = efx_diffusion; -} - -void EaxReverbEffect::set_efx_gain() -{ - const auto efx_gain = clamp( - level_mb_to_gain(static_cast(eax_.lRoom)), - AL_EAXREVERB_MIN_GAIN, - AL_EAXREVERB_MAX_GAIN); - - al_effect_props_.Reverb.Gain = efx_gain; -} - -void EaxReverbEffect::set_efx_gain_hf() -{ - const auto efx_gain_hf = clamp( - level_mb_to_gain(static_cast(eax_.lRoomHF)), - AL_EAXREVERB_MIN_GAINHF, - AL_EAXREVERB_MAX_GAINHF); - - al_effect_props_.Reverb.GainHF = efx_gain_hf; -} - -void EaxReverbEffect::set_efx_gain_lf() -{ - const auto efx_gain_lf = clamp( - level_mb_to_gain(static_cast(eax_.lRoomLF)), - AL_EAXREVERB_MIN_GAINLF, - AL_EAXREVERB_MAX_GAINLF); - - al_effect_props_.Reverb.GainLF = efx_gain_lf; -} - -void EaxReverbEffect::set_efx_decay_time() -{ - const auto efx_decay_time = clamp( - eax_.flDecayTime, - AL_EAXREVERB_MIN_DECAY_TIME, - AL_EAXREVERB_MAX_DECAY_TIME); - - al_effect_props_.Reverb.DecayTime = efx_decay_time; -} - -void EaxReverbEffect::set_efx_decay_hf_ratio() -{ - const auto efx_decay_hf_ratio = clamp( - eax_.flDecayHFRatio, - AL_EAXREVERB_MIN_DECAY_HFRATIO, - AL_EAXREVERB_MAX_DECAY_HFRATIO); - - al_effect_props_.Reverb.DecayHFRatio = efx_decay_hf_ratio; -} - -void EaxReverbEffect::set_efx_decay_lf_ratio() -{ - const auto efx_decay_lf_ratio = clamp( - eax_.flDecayLFRatio, - AL_EAXREVERB_MIN_DECAY_LFRATIO, - AL_EAXREVERB_MAX_DECAY_LFRATIO); - - al_effect_props_.Reverb.DecayLFRatio = efx_decay_lf_ratio; -} - -void EaxReverbEffect::set_efx_reflections_gain() -{ - const auto efx_reflections_gain = clamp( - level_mb_to_gain(static_cast(eax_.lReflections)), - AL_EAXREVERB_MIN_REFLECTIONS_GAIN, - AL_EAXREVERB_MAX_REFLECTIONS_GAIN); - - al_effect_props_.Reverb.ReflectionsGain = efx_reflections_gain; -} - -void EaxReverbEffect::set_efx_reflections_delay() -{ - const auto efx_reflections_delay = clamp( - eax_.flReflectionsDelay, - AL_EAXREVERB_MIN_REFLECTIONS_DELAY, - AL_EAXREVERB_MAX_REFLECTIONS_DELAY); - - al_effect_props_.Reverb.ReflectionsDelay = efx_reflections_delay; -} - -void EaxReverbEffect::set_efx_reflections_pan() -{ - al_effect_props_.Reverb.ReflectionsPan[0] = eax_.vReflectionsPan.x; - al_effect_props_.Reverb.ReflectionsPan[1] = eax_.vReflectionsPan.y; - al_effect_props_.Reverb.ReflectionsPan[2] = eax_.vReflectionsPan.z; -} - -void EaxReverbEffect::set_efx_late_reverb_gain() -{ - const auto efx_late_reverb_gain = clamp( - level_mb_to_gain(static_cast(eax_.lReverb)), - AL_EAXREVERB_MIN_LATE_REVERB_GAIN, - AL_EAXREVERB_MAX_LATE_REVERB_GAIN); - - al_effect_props_.Reverb.LateReverbGain = efx_late_reverb_gain; -} - -void EaxReverbEffect::set_efx_late_reverb_delay() -{ - const auto efx_late_reverb_delay = clamp( - eax_.flReverbDelay, - AL_EAXREVERB_MIN_LATE_REVERB_DELAY, - AL_EAXREVERB_MAX_LATE_REVERB_DELAY); - - al_effect_props_.Reverb.LateReverbDelay = efx_late_reverb_delay; -} - -void EaxReverbEffect::set_efx_late_reverb_pan() -{ - al_effect_props_.Reverb.LateReverbPan[0] = eax_.vReverbPan.x; - al_effect_props_.Reverb.LateReverbPan[1] = eax_.vReverbPan.y; - al_effect_props_.Reverb.LateReverbPan[2] = eax_.vReverbPan.z; -} - -void EaxReverbEffect::set_efx_echo_time() -{ - const auto efx_echo_time = clamp( - eax_.flEchoTime, - AL_EAXREVERB_MIN_ECHO_TIME, - AL_EAXREVERB_MAX_ECHO_TIME); - - al_effect_props_.Reverb.EchoTime = efx_echo_time; -} - -void EaxReverbEffect::set_efx_echo_depth() -{ - const auto efx_echo_depth = clamp( - eax_.flEchoDepth, - AL_EAXREVERB_MIN_ECHO_DEPTH, - AL_EAXREVERB_MAX_ECHO_DEPTH); - - al_effect_props_.Reverb.EchoDepth = efx_echo_depth; -} - -void EaxReverbEffect::set_efx_modulation_time() -{ - const auto efx_modulation_time = clamp( - eax_.flModulationTime, - AL_EAXREVERB_MIN_MODULATION_TIME, - AL_EAXREVERB_MAX_MODULATION_TIME); - - al_effect_props_.Reverb.ModulationTime = efx_modulation_time; -} - -void EaxReverbEffect::set_efx_modulation_depth() -{ - const auto efx_modulation_depth = clamp( - eax_.flModulationDepth, - AL_EAXREVERB_MIN_MODULATION_DEPTH, - AL_EAXREVERB_MAX_MODULATION_DEPTH); - - al_effect_props_.Reverb.ModulationDepth = efx_modulation_depth; -} - -void EaxReverbEffect::set_efx_air_absorption_gain_hf() -{ - const auto efx_air_absorption_hf = clamp( - level_mb_to_gain(eax_.flAirAbsorptionHF), - AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF, - AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF); - - al_effect_props_.Reverb.AirAbsorptionGainHF = efx_air_absorption_hf; -} - -void EaxReverbEffect::set_efx_hf_reference() -{ - const auto efx_hf_reference = clamp( - eax_.flHFReference, - AL_EAXREVERB_MIN_HFREFERENCE, - AL_EAXREVERB_MAX_HFREFERENCE); - - al_effect_props_.Reverb.HFReference = efx_hf_reference; -} - -void EaxReverbEffect::set_efx_lf_reference() -{ - const auto efx_lf_reference = clamp( - eax_.flLFReference, - AL_EAXREVERB_MIN_LFREFERENCE, - AL_EAXREVERB_MAX_LFREFERENCE); - - al_effect_props_.Reverb.LFReference = efx_lf_reference; -} - -void EaxReverbEffect::set_efx_room_rolloff_factor() -{ - const auto efx_room_rolloff_factor = clamp( - eax_.flRoomRolloffFactor, - AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR, - AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR); - - al_effect_props_.Reverb.RoomRolloffFactor = efx_room_rolloff_factor; -} - -void EaxReverbEffect::set_efx_flags() -{ - al_effect_props_.Reverb.DecayHFLimit = ((eax_.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); -} - -void EaxReverbEffect::set_efx_defaults() -{ - set_efx_density_from_environment_size(); - set_efx_diffusion(); - set_efx_gain(); - set_efx_gain_hf(); - set_efx_gain_lf(); - set_efx_decay_time(); - set_efx_decay_hf_ratio(); - set_efx_decay_lf_ratio(); - set_efx_reflections_gain(); - set_efx_reflections_delay(); - set_efx_reflections_pan(); - set_efx_late_reverb_gain(); - set_efx_late_reverb_delay(); - set_efx_late_reverb_pan(); - set_efx_echo_time(); - set_efx_echo_depth(); - set_efx_modulation_time(); - set_efx_modulation_depth(); - set_efx_air_absorption_gain_hf(); - set_efx_hf_reference(); - set_efx_lf_reference(); - set_efx_room_rolloff_factor(); - set_efx_flags(); -} - -void EaxReverbEffect::v1_get(const EaxEaxCall& eax_call) const -{ - switch(eax_call.get_property_id()) +struct EnvironmentValidator1 { + void operator()(unsigned long ulEnvironment) const { - case DSPROPERTY_EAX_ALL: - eax_call.set_value(eax1_); - break; - - case DSPROPERTY_EAX_ENVIRONMENT: - eax_call.set_value(eax1_.environment); - break; - - case DSPROPERTY_EAX_VOLUME: - eax_call.set_value(eax1_.fVolume); - break; - - case DSPROPERTY_EAX_DECAYTIME: - eax_call.set_value(eax1_.fDecayTime_sec); - break; - - case DSPROPERTY_EAX_DAMPING: - eax_call.set_value(eax1_.fDamping); - break; - - default: - eax_fail("Unsupported property id."); + eax_validate_range( + "Environment", + ulEnvironment, + EAXREVERB_MINENVIRONMENT, + EAX1REVERB_MAXENVIRONMENT); } -} +}; // EnvironmentValidator1 -void EaxReverbEffect::get_all( - const EaxEaxCall& eax_call) const -{ - if (eax_call.get_version() == 2) +struct VolumeValidator { + void operator()(float volume) const { - auto& eax_reverb = eax_call.get_value(); - eax_reverb.lRoom = eax_.lRoom; - eax_reverb.lRoomHF = eax_.lRoomHF; - eax_reverb.flRoomRolloffFactor = eax_.flRoomRolloffFactor; - eax_reverb.flDecayTime = eax_.flDecayTime; - eax_reverb.flDecayHFRatio = eax_.flDecayHFRatio; - eax_reverb.lReflections = eax_.lReflections; - eax_reverb.flReflectionsDelay = eax_.flReflectionsDelay; - eax_reverb.lReverb = eax_.lReverb; - eax_reverb.flReverbDelay = eax_.flReverbDelay; - eax_reverb.dwEnvironment = eax_.ulEnvironment; - eax_reverb.flEnvironmentSize = eax_.flEnvironmentSize; - eax_reverb.flEnvironmentDiffusion = eax_.flEnvironmentDiffusion; - eax_reverb.flAirAbsorptionHF = eax_.flAirAbsorptionHF; - eax_reverb.dwFlags = eax_.ulFlags; + eax_validate_range( + "Volume", + volume, + EAX1REVERB_MINVOLUME, + EAX1REVERB_MAXVOLUME); } - else +}; // VolumeValidator + +struct DecayTimeValidator { + void operator()(float flDecayTime) const { - eax_call.set_value(eax_); - } -} - -void EaxReverbEffect::get(const EaxEaxCall& eax_call) const -{ - if(eax_call.get_version() == 1) - v1_get(eax_call); - else switch(eax_call.get_property_id()) - { - case EAXREVERB_NONE: - break; - - case EAXREVERB_ALLPARAMETERS: - get_all(eax_call); - break; - - case EAXREVERB_ENVIRONMENT: - eax_call.set_value(eax_.ulEnvironment); - break; - - case EAXREVERB_ENVIRONMENTSIZE: - eax_call.set_value(eax_.flEnvironmentSize); - break; - - case EAXREVERB_ENVIRONMENTDIFFUSION: - eax_call.set_value(eax_.flEnvironmentDiffusion); - break; - - case EAXREVERB_ROOM: - eax_call.set_value(eax_.lRoom); - break; - - case EAXREVERB_ROOMHF: - eax_call.set_value(eax_.lRoomHF); - break; - - case EAXREVERB_ROOMLF: - eax_call.set_value(eax_.lRoomLF); - break; - - case EAXREVERB_DECAYTIME: - eax_call.set_value(eax_.flDecayTime); - break; - - case EAXREVERB_DECAYHFRATIO: - eax_call.set_value(eax_.flDecayHFRatio); - break; - - case EAXREVERB_DECAYLFRATIO: - eax_call.set_value(eax_.flDecayLFRatio); - break; - - case EAXREVERB_REFLECTIONS: - eax_call.set_value(eax_.lReflections); - break; - - case EAXREVERB_REFLECTIONSDELAY: - eax_call.set_value(eax_.flReflectionsDelay); - break; - - case EAXREVERB_REFLECTIONSPAN: - eax_call.set_value(eax_.vReflectionsPan); - break; - - case EAXREVERB_REVERB: - eax_call.set_value(eax_.lReverb); - break; - - case EAXREVERB_REVERBDELAY: - eax_call.set_value(eax_.flReverbDelay); - break; - - case EAXREVERB_REVERBPAN: - eax_call.set_value(eax_.vReverbPan); - break; - - case EAXREVERB_ECHOTIME: - eax_call.set_value(eax_.flEchoTime); - break; - - case EAXREVERB_ECHODEPTH: - eax_call.set_value(eax_.flEchoDepth); - break; - - case EAXREVERB_MODULATIONTIME: - eax_call.set_value(eax_.flModulationTime); - break; - - case EAXREVERB_MODULATIONDEPTH: - eax_call.set_value(eax_.flModulationDepth); - break; - - case EAXREVERB_AIRABSORPTIONHF: - eax_call.set_value(eax_.flAirAbsorptionHF); - break; - - case EAXREVERB_HFREFERENCE: - eax_call.set_value(eax_.flHFReference); - break; - - case EAXREVERB_LFREFERENCE: - eax_call.set_value(eax_.flLFReference); - break; - - case EAXREVERB_ROOMROLLOFFFACTOR: - eax_call.set_value(eax_.flRoomRolloffFactor); - break; - - case EAXREVERB_FLAGS: - eax_call.set_value(eax_.ulFlags); - break; - - default: - eax_fail("Unsupported property id."); - } -} - -void EaxReverbEffect::v1_validate_environment(unsigned long environment) -{ - validate_environment(environment, 1, true); -} - -void EaxReverbEffect::v1_validate_volume(float volume) -{ - eax_validate_range("Volume", volume, EAX1REVERB_MINVOLUME, EAX1REVERB_MAXVOLUME); -} - -void EaxReverbEffect::v1_validate_decay_time(float decay_time) -{ - validate_decay_time(decay_time); -} - -void EaxReverbEffect::v1_validate_damping(float damping) -{ - eax_validate_range("Damping", damping, EAX1REVERB_MINDAMPING, EAX1REVERB_MAXDAMPING); -} - -void EaxReverbEffect::v1_validate_all(const EAX_REVERBPROPERTIES& all) -{ - v1_validate_environment(all.environment); - v1_validate_volume(all.fVolume); - v1_validate_decay_time(all.fDecayTime_sec); - v1_validate_damping(all.fDamping); -} - -void EaxReverbEffect::validate_environment( - unsigned long ulEnvironment, - int version, - bool is_standalone) -{ - eax_validate_range( - "Environment", - ulEnvironment, - EAXREVERB_MINENVIRONMENT, - (version <= 2 || is_standalone) ? EAX1REVERB_MAXENVIRONMENT : EAX30REVERB_MAXENVIRONMENT); -} - -void EaxReverbEffect::validate_environment_size( - float flEnvironmentSize) -{ - eax_validate_range( - "Environment Size", - flEnvironmentSize, - EAXREVERB_MINENVIRONMENTSIZE, - EAXREVERB_MAXENVIRONMENTSIZE); -} - -void EaxReverbEffect::validate_environment_diffusion( - float flEnvironmentDiffusion) -{ - eax_validate_range( - "Environment Diffusion", - flEnvironmentDiffusion, - EAXREVERB_MINENVIRONMENTDIFFUSION, - EAXREVERB_MAXENVIRONMENTDIFFUSION); -} - -void EaxReverbEffect::validate_room( - long lRoom) -{ - eax_validate_range( - "Room", - lRoom, - EAXREVERB_MINROOM, - EAXREVERB_MAXROOM); -} - -void EaxReverbEffect::validate_room_hf( - long lRoomHF) -{ - eax_validate_range( - "Room HF", - lRoomHF, - EAXREVERB_MINROOMHF, - EAXREVERB_MAXROOMHF); -} - -void EaxReverbEffect::validate_room_lf( - long lRoomLF) -{ - eax_validate_range( - "Room LF", - lRoomLF, - EAXREVERB_MINROOMLF, - EAXREVERB_MAXROOMLF); -} - -void EaxReverbEffect::validate_decay_time( - float flDecayTime) -{ - eax_validate_range( - "Decay Time", - flDecayTime, - EAXREVERB_MINDECAYTIME, - EAXREVERB_MAXDECAYTIME); -} - -void EaxReverbEffect::validate_decay_hf_ratio( - float flDecayHFRatio) -{ - eax_validate_range( - "Decay HF Ratio", - flDecayHFRatio, - EAXREVERB_MINDECAYHFRATIO, - EAXREVERB_MAXDECAYHFRATIO); -} - -void EaxReverbEffect::validate_decay_lf_ratio( - float flDecayLFRatio) -{ - eax_validate_range( - "Decay LF Ratio", - flDecayLFRatio, - EAXREVERB_MINDECAYLFRATIO, - EAXREVERB_MAXDECAYLFRATIO); -} - -void EaxReverbEffect::validate_reflections( - long lReflections) -{ - eax_validate_range( - "Reflections", - lReflections, - EAXREVERB_MINREFLECTIONS, - EAXREVERB_MAXREFLECTIONS); -} - -void EaxReverbEffect::validate_reflections_delay( - float flReflectionsDelay) -{ - eax_validate_range( - "Reflections Delay", - flReflectionsDelay, - EAXREVERB_MINREFLECTIONSDELAY, - EAXREVERB_MAXREFLECTIONSDELAY); -} - -void EaxReverbEffect::validate_reflections_pan( - const EAXVECTOR& vReflectionsPan) -{ - std::ignore = vReflectionsPan; -} - -void EaxReverbEffect::validate_reverb( - long lReverb) -{ - eax_validate_range( - "Reverb", - lReverb, - EAXREVERB_MINREVERB, - EAXREVERB_MAXREVERB); -} - -void EaxReverbEffect::validate_reverb_delay( - float flReverbDelay) -{ - eax_validate_range( - "Reverb Delay", - flReverbDelay, - EAXREVERB_MINREVERBDELAY, - EAXREVERB_MAXREVERBDELAY); -} - -void EaxReverbEffect::validate_reverb_pan( - const EAXVECTOR& vReverbPan) -{ - std::ignore = vReverbPan; -} - -void EaxReverbEffect::validate_echo_time( - float flEchoTime) -{ - eax_validate_range( - "Echo Time", - flEchoTime, - EAXREVERB_MINECHOTIME, - EAXREVERB_MAXECHOTIME); -} - -void EaxReverbEffect::validate_echo_depth( - float flEchoDepth) -{ - eax_validate_range( - "Echo Depth", - flEchoDepth, - EAXREVERB_MINECHODEPTH, - EAXREVERB_MAXECHODEPTH); -} - -void EaxReverbEffect::validate_modulation_time( - float flModulationTime) -{ - eax_validate_range( - "Modulation Time", - flModulationTime, - EAXREVERB_MINMODULATIONTIME, - EAXREVERB_MAXMODULATIONTIME); -} - -void EaxReverbEffect::validate_modulation_depth( - float flModulationDepth) -{ - eax_validate_range( - "Modulation Depth", - flModulationDepth, - EAXREVERB_MINMODULATIONDEPTH, - EAXREVERB_MAXMODULATIONDEPTH); -} - -void EaxReverbEffect::validate_air_absorbtion_hf( - float air_absorbtion_hf) -{ - eax_validate_range( - "Air Absorbtion HF", - air_absorbtion_hf, - EAXREVERB_MINAIRABSORPTIONHF, - EAXREVERB_MAXAIRABSORPTIONHF); -} - -void EaxReverbEffect::validate_hf_reference( - float flHFReference) -{ - eax_validate_range( - "HF Reference", - flHFReference, - EAXREVERB_MINHFREFERENCE, - EAXREVERB_MAXHFREFERENCE); -} - -void EaxReverbEffect::validate_lf_reference( - float flLFReference) -{ - eax_validate_range( - "LF Reference", - flLFReference, - EAXREVERB_MINLFREFERENCE, - EAXREVERB_MAXLFREFERENCE); -} - -void EaxReverbEffect::validate_room_rolloff_factor( - float flRoomRolloffFactor) -{ - eax_validate_range( - "Room Rolloff Factor", - flRoomRolloffFactor, - EAXREVERB_MINROOMROLLOFFFACTOR, - EAXREVERB_MAXROOMROLLOFFFACTOR); -} - -void EaxReverbEffect::validate_flags( - unsigned long ulFlags) -{ - eax_validate_range( - "Flags", - ulFlags, - 0UL, - ~EAXREVERBFLAGS_RESERVED); -} - -void EaxReverbEffect::validate_all( - const EAX20LISTENERPROPERTIES& listener, - int version) -{ - validate_room(listener.lRoom); - validate_room_hf(listener.lRoomHF); - validate_room_rolloff_factor(listener.flRoomRolloffFactor); - validate_decay_time(listener.flDecayTime); - validate_decay_hf_ratio(listener.flDecayHFRatio); - validate_reflections(listener.lReflections); - validate_reflections_delay(listener.flReflectionsDelay); - validate_reverb(listener.lReverb); - validate_reverb_delay(listener.flReverbDelay); - validate_environment(listener.dwEnvironment, version, false); - validate_environment_size(listener.flEnvironmentSize); - validate_environment_diffusion(listener.flEnvironmentDiffusion); - validate_air_absorbtion_hf(listener.flAirAbsorptionHF); - validate_flags(listener.dwFlags); -} - -void EaxReverbEffect::validate_all( - const EAXREVERBPROPERTIES& lReverb, - int version) -{ - validate_environment(lReverb.ulEnvironment, version, false); - validate_environment_size(lReverb.flEnvironmentSize); - validate_environment_diffusion(lReverb.flEnvironmentDiffusion); - validate_room(lReverb.lRoom); - validate_room_hf(lReverb.lRoomHF); - validate_room_lf(lReverb.lRoomLF); - validate_decay_time(lReverb.flDecayTime); - validate_decay_hf_ratio(lReverb.flDecayHFRatio); - validate_decay_lf_ratio(lReverb.flDecayLFRatio); - validate_reflections(lReverb.lReflections); - validate_reflections_delay(lReverb.flReflectionsDelay); - validate_reverb(lReverb.lReverb); - validate_reverb_delay(lReverb.flReverbDelay); - validate_echo_time(lReverb.flEchoTime); - validate_echo_depth(lReverb.flEchoDepth); - validate_modulation_time(lReverb.flModulationTime); - validate_modulation_depth(lReverb.flModulationDepth); - validate_air_absorbtion_hf(lReverb.flAirAbsorptionHF); - validate_hf_reference(lReverb.flHFReference); - validate_lf_reference(lReverb.flLFReference); - validate_room_rolloff_factor(lReverb.flRoomRolloffFactor); - validate_flags(lReverb.ulFlags); -} - -void EaxReverbEffect::v1_defer_environment(unsigned long environment) -{ - eax1_d_ = EAX1REVERB_PRESETS[environment]; - eax1_dirty_flags_.ulEnvironment = true; -} - -void EaxReverbEffect::v1_defer_volume(float volume) -{ - eax1_d_.fVolume = volume; - eax1_dirty_flags_.flVolume = (eax1_.fVolume != eax1_d_.fVolume); -} - -void EaxReverbEffect::v1_defer_decay_time(float decay_time) -{ - eax1_d_.fDecayTime_sec = decay_time; - eax1_dirty_flags_.flDecayTime = (eax1_.fDecayTime_sec != eax1_d_.fDecayTime_sec); -} - -void EaxReverbEffect::v1_defer_damping(float damping) -{ - eax1_d_.fDamping = damping; - eax1_dirty_flags_.flDamping = (eax1_.fDamping != eax1_d_.fDamping); -} - -void EaxReverbEffect::v1_defer_all(const EAX_REVERBPROPERTIES& lReverb) -{ - v1_defer_environment(lReverb.environment); - v1_defer_volume(lReverb.fVolume); - v1_defer_decay_time(lReverb.fDecayTime_sec); - v1_defer_damping(lReverb.fDamping); -} - - -void EaxReverbEffect::v1_set_efx() -{ - auto efx_props = eax_efx_reverb_presets[eax1_.environment]; - efx_props.flGain = eax1_.fVolume; - efx_props.flDecayTime = eax1_.fDecayTime_sec; - efx_props.flDecayHFRatio = clamp(eax1_.fDamping, AL_EAXREVERB_MIN_DECAY_HFRATIO, AL_EAXREVERB_MAX_DECAY_HFRATIO); - - al_effect_props_.Reverb.Density = efx_props.flDensity; - al_effect_props_.Reverb.Diffusion = efx_props.flDiffusion; - al_effect_props_.Reverb.Gain = efx_props.flGain; - al_effect_props_.Reverb.GainHF = efx_props.flGainHF; - al_effect_props_.Reverb.GainLF = efx_props.flGainLF; - al_effect_props_.Reverb.DecayTime = efx_props.flDecayTime; - al_effect_props_.Reverb.DecayHFRatio = efx_props.flDecayHFRatio; - al_effect_props_.Reverb.DecayLFRatio = efx_props.flDecayLFRatio; - al_effect_props_.Reverb.ReflectionsGain = efx_props.flReflectionsGain; - al_effect_props_.Reverb.ReflectionsDelay = efx_props.flReflectionsDelay; - al_effect_props_.Reverb.ReflectionsPan[0] = efx_props.flReflectionsPan[0]; - al_effect_props_.Reverb.ReflectionsPan[1] = efx_props.flReflectionsPan[1]; - al_effect_props_.Reverb.ReflectionsPan[2] = efx_props.flReflectionsPan[2]; - al_effect_props_.Reverb.LateReverbGain = efx_props.flLateReverbGain; - al_effect_props_.Reverb.LateReverbDelay = efx_props.flLateReverbDelay; - al_effect_props_.Reverb.LateReverbPan[0] = efx_props.flLateReverbPan[0]; - al_effect_props_.Reverb.LateReverbPan[1] = efx_props.flLateReverbPan[1]; - al_effect_props_.Reverb.LateReverbPan[2] = efx_props.flLateReverbPan[2]; - al_effect_props_.Reverb.EchoTime = efx_props.flEchoTime; - al_effect_props_.Reverb.EchoDepth = efx_props.flEchoDepth; - al_effect_props_.Reverb.ModulationTime = efx_props.flModulationTime; - al_effect_props_.Reverb.ModulationDepth = efx_props.flModulationDepth; - al_effect_props_.Reverb.HFReference = efx_props.flHFReference; - al_effect_props_.Reverb.LFReference = efx_props.flLFReference; - al_effect_props_.Reverb.RoomRolloffFactor = efx_props.flRoomRolloffFactor; - al_effect_props_.Reverb.AirAbsorptionGainHF = efx_props.flAirAbsorptionGainHF; - al_effect_props_.Reverb.DecayHFLimit = false; -} - -void EaxReverbEffect::defer_environment( - unsigned long ulEnvironment) -{ - eax_d_.ulEnvironment = ulEnvironment; - eax_dirty_flags_.ulEnvironment = (eax_.ulEnvironment != eax_d_.ulEnvironment); -} - -void EaxReverbEffect::defer_environment_size( - float flEnvironmentSize) -{ - eax_d_.flEnvironmentSize = flEnvironmentSize; - eax_dirty_flags_.flEnvironmentSize = (eax_.flEnvironmentSize != eax_d_.flEnvironmentSize); -} - -void EaxReverbEffect::defer_environment_diffusion( - float flEnvironmentDiffusion) -{ - eax_d_.flEnvironmentDiffusion = flEnvironmentDiffusion; - eax_dirty_flags_.flEnvironmentDiffusion = (eax_.flEnvironmentDiffusion != eax_d_.flEnvironmentDiffusion); -} - -void EaxReverbEffect::defer_room( - long lRoom) -{ - eax_d_.lRoom = lRoom; - eax_dirty_flags_.lRoom = (eax_.lRoom != eax_d_.lRoom); -} - -void EaxReverbEffect::defer_room_hf( - long lRoomHF) -{ - eax_d_.lRoomHF = lRoomHF; - eax_dirty_flags_.lRoomHF = (eax_.lRoomHF != eax_d_.lRoomHF); -} - -void EaxReverbEffect::defer_room_lf( - long lRoomLF) -{ - eax_d_.lRoomLF = lRoomLF; - eax_dirty_flags_.lRoomLF = (eax_.lRoomLF != eax_d_.lRoomLF); -} - -void EaxReverbEffect::defer_decay_time( - float flDecayTime) -{ - eax_d_.flDecayTime = flDecayTime; - eax_dirty_flags_.flDecayTime = (eax_.flDecayTime != eax_d_.flDecayTime); -} - -void EaxReverbEffect::defer_decay_hf_ratio( - float flDecayHFRatio) -{ - eax_d_.flDecayHFRatio = flDecayHFRatio; - eax_dirty_flags_.flDecayHFRatio = (eax_.flDecayHFRatio != eax_d_.flDecayHFRatio); -} - -void EaxReverbEffect::defer_decay_lf_ratio( - float flDecayLFRatio) -{ - eax_d_.flDecayLFRatio = flDecayLFRatio; - eax_dirty_flags_.flDecayLFRatio = (eax_.flDecayLFRatio != eax_d_.flDecayLFRatio); -} - -void EaxReverbEffect::defer_reflections( - long lReflections) -{ - eax_d_.lReflections = lReflections; - eax_dirty_flags_.lReflections = (eax_.lReflections != eax_d_.lReflections); -} - -void EaxReverbEffect::defer_reflections_delay( - float flReflectionsDelay) -{ - eax_d_.flReflectionsDelay = flReflectionsDelay; - eax_dirty_flags_.flReflectionsDelay = (eax_.flReflectionsDelay != eax_d_.flReflectionsDelay); -} - -void EaxReverbEffect::defer_reflections_pan( - const EAXVECTOR& vReflectionsPan) -{ - eax_d_.vReflectionsPan = vReflectionsPan; - eax_dirty_flags_.vReflectionsPan = (eax_.vReflectionsPan != eax_d_.vReflectionsPan); -} - -void EaxReverbEffect::defer_reverb( - long lReverb) -{ - eax_d_.lReverb = lReverb; - eax_dirty_flags_.lReverb = (eax_.lReverb != eax_d_.lReverb); -} - -void EaxReverbEffect::defer_reverb_delay( - float flReverbDelay) -{ - eax_d_.flReverbDelay = flReverbDelay; - eax_dirty_flags_.flReverbDelay = (eax_.flReverbDelay != eax_d_.flReverbDelay); -} - -void EaxReverbEffect::defer_reverb_pan( - const EAXVECTOR& vReverbPan) -{ - eax_d_.vReverbPan = vReverbPan; - eax_dirty_flags_.vReverbPan = (eax_.vReverbPan != eax_d_.vReverbPan); -} - -void EaxReverbEffect::defer_echo_time( - float flEchoTime) -{ - eax_d_.flEchoTime = flEchoTime; - eax_dirty_flags_.flEchoTime = (eax_.flEchoTime != eax_d_.flEchoTime); -} - -void EaxReverbEffect::defer_echo_depth( - float flEchoDepth) -{ - eax_d_.flEchoDepth = flEchoDepth; - eax_dirty_flags_.flEchoDepth = (eax_.flEchoDepth != eax_d_.flEchoDepth); -} - -void EaxReverbEffect::defer_modulation_time( - float flModulationTime) -{ - eax_d_.flModulationTime = flModulationTime; - eax_dirty_flags_.flModulationTime = (eax_.flModulationTime != eax_d_.flModulationTime); -} - -void EaxReverbEffect::defer_modulation_depth( - float flModulationDepth) -{ - eax_d_.flModulationDepth = flModulationDepth; - eax_dirty_flags_.flModulationDepth = (eax_.flModulationDepth != eax_d_.flModulationDepth); -} - -void EaxReverbEffect::defer_air_absorbtion_hf( - float flAirAbsorptionHF) -{ - eax_d_.flAirAbsorptionHF = flAirAbsorptionHF; - eax_dirty_flags_.flAirAbsorptionHF = (eax_.flAirAbsorptionHF != eax_d_.flAirAbsorptionHF); -} - -void EaxReverbEffect::defer_hf_reference( - float flHFReference) -{ - eax_d_.flHFReference = flHFReference; - eax_dirty_flags_.flHFReference = (eax_.flHFReference != eax_d_.flHFReference); -} - -void EaxReverbEffect::defer_lf_reference( - float flLFReference) -{ - eax_d_.flLFReference = flLFReference; - eax_dirty_flags_.flLFReference = (eax_.flLFReference != eax_d_.flLFReference); -} - -void EaxReverbEffect::defer_room_rolloff_factor( - float flRoomRolloffFactor) -{ - eax_d_.flRoomRolloffFactor = flRoomRolloffFactor; - eax_dirty_flags_.flRoomRolloffFactor = (eax_.flRoomRolloffFactor != eax_d_.flRoomRolloffFactor); -} - -void EaxReverbEffect::defer_flags( - unsigned long ulFlags) -{ - eax_d_.ulFlags = ulFlags; - eax_dirty_flags_.ulFlags = (eax_.ulFlags != eax_d_.ulFlags); -} - -void EaxReverbEffect::defer_all( - const EAX20LISTENERPROPERTIES& listener) -{ - defer_room(listener.lRoom); - defer_room_hf(listener.lRoomHF); - defer_room_rolloff_factor(listener.flRoomRolloffFactor); - defer_decay_time(listener.flDecayTime); - defer_decay_hf_ratio(listener.flDecayHFRatio); - defer_reflections(listener.lReflections); - defer_reflections_delay(listener.flReflectionsDelay); - defer_reverb(listener.lReverb); - defer_reverb_delay(listener.flReverbDelay); - defer_environment(listener.dwEnvironment); - defer_environment_size(listener.flEnvironmentSize); - defer_environment_diffusion(listener.flEnvironmentDiffusion); - defer_air_absorbtion_hf(listener.flAirAbsorptionHF); - defer_flags(listener.dwFlags); -} - -void EaxReverbEffect::defer_all( - const EAXREVERBPROPERTIES& lReverb) -{ - defer_environment(lReverb.ulEnvironment); - defer_environment_size(lReverb.flEnvironmentSize); - defer_environment_diffusion(lReverb.flEnvironmentDiffusion); - defer_room(lReverb.lRoom); - defer_room_hf(lReverb.lRoomHF); - defer_room_lf(lReverb.lRoomLF); - defer_decay_time(lReverb.flDecayTime); - defer_decay_hf_ratio(lReverb.flDecayHFRatio); - defer_decay_lf_ratio(lReverb.flDecayLFRatio); - defer_reflections(lReverb.lReflections); - defer_reflections_delay(lReverb.flReflectionsDelay); - defer_reflections_pan(lReverb.vReflectionsPan); - defer_reverb(lReverb.lReverb); - defer_reverb_delay(lReverb.flReverbDelay); - defer_reverb_pan(lReverb.vReverbPan); - defer_echo_time(lReverb.flEchoTime); - defer_echo_depth(lReverb.flEchoDepth); - defer_modulation_time(lReverb.flModulationTime); - defer_modulation_depth(lReverb.flModulationDepth); - defer_air_absorbtion_hf(lReverb.flAirAbsorptionHF); - defer_hf_reference(lReverb.flHFReference); - defer_lf_reference(lReverb.flLFReference); - defer_room_rolloff_factor(lReverb.flRoomRolloffFactor); - defer_flags(lReverb.ulFlags); -} - - -void EaxReverbEffect::v1_defer_environment(const EaxEaxCall& eax_call) -{ - const auto& environment = eax_call.get_value(); - - validate_environment(environment, 1, true); - - const auto& reverb_preset = EAX1REVERB_PRESETS[environment]; - v1_defer_all(reverb_preset); -} - -void EaxReverbEffect::v1_defer_volume(const EaxEaxCall& eax_call) -{ - const auto& volume = eax_call.get_value(); - - v1_validate_volume(volume); - v1_defer_volume(volume); -} - -void EaxReverbEffect::v1_defer_decay_time(const EaxEaxCall& eax_call) -{ - const auto& decay_time = eax_call.get_value(); - - v1_validate_decay_time(decay_time); - v1_defer_decay_time(decay_time); -} - -void EaxReverbEffect::v1_defer_damping(const EaxEaxCall& eax_call) -{ - const auto& damping = eax_call.get_value(); - - v1_validate_damping(damping); - v1_defer_damping(damping); -} - -void EaxReverbEffect::v1_defer_all(const EaxEaxCall& eax_call) -{ - const auto& reverb_all = eax_call.get_value(); - - v1_validate_all(reverb_all); - v1_defer_all(reverb_all); -} - - -void EaxReverbEffect::defer_environment( - const EaxEaxCall& eax_call) -{ - const auto& ulEnvironment = - eax_call.get_value(); - - validate_environment(ulEnvironment, eax_call.get_version(), true); - - if (eax_d_.ulEnvironment == ulEnvironment) - { - return; - } - - const auto& reverb_preset = EAXREVERB_PRESETS[ulEnvironment]; - - defer_all(reverb_preset); -} - -void EaxReverbEffect::defer_environment_size( - const EaxEaxCall& eax_call) -{ - const auto& flEnvironmentSize = - eax_call.get_value(); - - validate_environment_size(flEnvironmentSize); - - if (eax_d_.flEnvironmentSize == flEnvironmentSize) - { - return; - } - - const auto scale = flEnvironmentSize / eax_d_.flEnvironmentSize; - - defer_environment_size(flEnvironmentSize); - - if ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) - { - const auto flDecayTime = clamp( - scale * eax_d_.flDecayTime, + eax_validate_range( + "Decay Time", + flDecayTime, EAXREVERB_MINDECAYTIME, EAXREVERB_MAXDECAYTIME); - - defer_decay_time(flDecayTime); } +}; // DecayTimeValidator - if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0) +struct DampingValidator { + void operator()(float damping) const { - if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) - { - const auto lReflections = clamp( - eax_d_.lReflections - static_cast(gain_to_level_mb(scale)), - EAXREVERB_MINREFLECTIONS, - EAXREVERB_MAXREFLECTIONS); - - defer_reflections(lReflections); - } + eax_validate_range( + "Damping", + damping, + EAX1REVERB_MINDAMPING, + EAX1REVERB_MAXDAMPING); } +}; // DampingValidator - if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) +struct AllValidator1 { + void operator()(const EAX_REVERBPROPERTIES& all) const { - const auto flReflectionsDelay = clamp( - eax_d_.flReflectionsDelay * scale, + EnvironmentValidator1{}(all.environment); + VolumeValidator{}(all.fVolume); + DecayTimeValidator{}(all.fDecayTime_sec); + DampingValidator{}(all.fDamping); + } +}; // AllValidator1 + +struct RoomValidator { + void operator()(long lRoom) const + { + eax_validate_range( + "Room", + lRoom, + EAXREVERB_MINROOM, + EAXREVERB_MAXROOM); + } +}; // RoomValidator + +struct RoomHFValidator { + void operator()(long lRoomHF) const + { + eax_validate_range( + "Room HF", + lRoomHF, + EAXREVERB_MINROOMHF, + EAXREVERB_MAXROOMHF); + } +}; // RoomHFValidator + +struct RoomRolloffFactorValidator { + void operator()(float flRoomRolloffFactor) const + { + eax_validate_range( + "Room Rolloff Factor", + flRoomRolloffFactor, + EAXREVERB_MINROOMROLLOFFFACTOR, + EAXREVERB_MAXROOMROLLOFFFACTOR); + } +}; // RoomRolloffFactorValidator + +struct DecayHFRatioValidator { + void operator()(float flDecayHFRatio) const + { + eax_validate_range( + "Decay HF Ratio", + flDecayHFRatio, + EAXREVERB_MINDECAYHFRATIO, + EAXREVERB_MAXDECAYHFRATIO); + } +}; // DecayHFRatioValidator + +struct ReflectionsValidator { + void operator()(long lReflections) const + { + eax_validate_range( + "Reflections", + lReflections, + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); + } +}; // ReflectionsValidator + +struct ReflectionsDelayValidator { + void operator()(float flReflectionsDelay) const + { + eax_validate_range( + "Reflections Delay", + flReflectionsDelay, EAXREVERB_MINREFLECTIONSDELAY, EAXREVERB_MAXREFLECTIONSDELAY); - - defer_reflections_delay(flReflectionsDelay); } +}; // ReflectionsDelayValidator - if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0) +struct ReverbValidator { + void operator()(long lReverb) const { - const auto log_scalar = ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; - - const auto lReverb = clamp( - eax_d_.lReverb - static_cast(std::log10(scale) * log_scalar), + eax_validate_range( + "Reverb", + lReverb, EAXREVERB_MINREVERB, EAXREVERB_MAXREVERB); - - defer_reverb(lReverb); } +}; // ReverbValidator - if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0) +struct ReverbDelayValidator { + void operator()(float flReverbDelay) const { - const auto flReverbDelay = clamp( - scale * eax_d_.flReverbDelay, + eax_validate_range( + "Reverb Delay", + flReverbDelay, EAXREVERB_MINREVERBDELAY, EAXREVERB_MAXREVERBDELAY); - - defer_reverb_delay(flReverbDelay); } +}; // ReverbDelayValidator - if ((eax_d_.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0) +struct EnvironmentSizeValidator { + void operator()(float flEnvironmentSize) const { - const auto flEchoTime = clamp( - eax_d_.flEchoTime * scale, + eax_validate_range( + "Environment Size", + flEnvironmentSize, + EAXREVERB_MINENVIRONMENTSIZE, + EAXREVERB_MAXENVIRONMENTSIZE); + } +}; // EnvironmentSizeValidator + +struct EnvironmentDiffusionValidator { + void operator()(float flEnvironmentDiffusion) const + { + eax_validate_range( + "Environment Diffusion", + flEnvironmentDiffusion, + EAXREVERB_MINENVIRONMENTDIFFUSION, + EAXREVERB_MAXENVIRONMENTDIFFUSION); + } +}; // EnvironmentDiffusionValidator + +struct AirAbsorptionHFValidator { + void operator()(float flAirAbsorptionHF) const + { + eax_validate_range( + "Air Absorbtion HF", + flAirAbsorptionHF, + EAXREVERB_MINAIRABSORPTIONHF, + EAXREVERB_MAXAIRABSORPTIONHF); + } +}; // AirAbsorptionHFValidator + +struct FlagsValidator2 { + void operator()(unsigned long ulFlags) const + { + eax_validate_range( + "Flags", + ulFlags, + 0UL, + ~EAX2LISTENERFLAGS_RESERVED); + } +}; // FlagsValidator2 + +struct AllValidator2 { + void operator()(const EAX20LISTENERPROPERTIES& all) const + { + RoomValidator{}(all.lRoom); + RoomHFValidator{}(all.lRoomHF); + RoomRolloffFactorValidator{}(all.flRoomRolloffFactor); + DecayTimeValidator{}(all.flDecayTime); + DecayHFRatioValidator{}(all.flDecayHFRatio); + ReflectionsValidator{}(all.lReflections); + ReflectionsDelayValidator{}(all.flReflectionsDelay); + ReverbValidator{}(all.lReverb); + ReverbDelayValidator{}(all.flReverbDelay); + EnvironmentValidator1{}(all.dwEnvironment); + EnvironmentSizeValidator{}(all.flEnvironmentSize); + EnvironmentDiffusionValidator{}(all.flEnvironmentDiffusion); + AirAbsorptionHFValidator{}(all.flAirAbsorptionHF); + FlagsValidator2{}(all.dwFlags); + } +}; // AllValidator2 + +struct EnvironmentValidator3 { + void operator()(unsigned long ulEnvironment) const + { + eax_validate_range( + "Environment", + ulEnvironment, + EAXREVERB_MINENVIRONMENT, + EAX30REVERB_MAXENVIRONMENT); + } +}; // EnvironmentValidator1 + +struct RoomLFValidator { + void operator()(long lRoomLF) const + { + eax_validate_range( + "Room LF", + lRoomLF, + EAXREVERB_MINROOMLF, + EAXREVERB_MAXROOMLF); + } +}; // RoomLFValidator + +struct DecayLFRatioValidator { + void operator()(float flDecayLFRatio) const + { + eax_validate_range( + "Decay LF Ratio", + flDecayLFRatio, + EAXREVERB_MINDECAYLFRATIO, + EAXREVERB_MAXDECAYLFRATIO); + } +}; // DecayLFRatioValidator + +struct VectorValidator { + void operator()(const EAXVECTOR&) const + {} +}; // VectorValidator + +struct EchoTimeValidator { + void operator()(float flEchoTime) const + { + eax_validate_range( + "Echo Time", + flEchoTime, EAXREVERB_MINECHOTIME, EAXREVERB_MAXECHOTIME); - - defer_echo_time(flEchoTime); } +}; // EchoTimeValidator - if ((eax_d_.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0) +struct EchoDepthValidator { + void operator()(float flEchoDepth) const { - const auto flModulationTime = clamp( - scale * eax_d_.flModulationTime, + eax_validate_range( + "Echo Depth", + flEchoDepth, + EAXREVERB_MINECHODEPTH, + EAXREVERB_MAXECHODEPTH); + } +}; // EchoDepthValidator + +struct ModulationTimeValidator { + void operator()(float flModulationTime) const + { + eax_validate_range( + "Modulation Time", + flModulationTime, EAXREVERB_MINMODULATIONTIME, EAXREVERB_MAXMODULATIONTIME); - - defer_modulation_time(flModulationTime); } -} +}; // ModulationTimeValidator -void EaxReverbEffect::defer_environment_diffusion( - const EaxEaxCall& eax_call) -{ - const auto& flEnvironmentDiffusion = - eax_call.get_value(); - - validate_environment_diffusion(flEnvironmentDiffusion); - defer_environment_diffusion(flEnvironmentDiffusion); -} - -void EaxReverbEffect::defer_room( - const EaxEaxCall& eax_call) -{ - const auto& lRoom = - eax_call.get_value(); - - validate_room(lRoom); - defer_room(lRoom); -} - -void EaxReverbEffect::defer_room_hf( - const EaxEaxCall& eax_call) -{ - const auto& lRoomHF = - eax_call.get_value(); - - validate_room_hf(lRoomHF); - defer_room_hf(lRoomHF); -} - -void EaxReverbEffect::defer_room_lf( - const EaxEaxCall& eax_call) -{ - const auto& lRoomLF = - eax_call.get_value(); - - validate_room_lf(lRoomLF); - defer_room_lf(lRoomLF); -} - -void EaxReverbEffect::defer_decay_time( - const EaxEaxCall& eax_call) -{ - const auto& flDecayTime = - eax_call.get_value(); - - validate_decay_time(flDecayTime); - defer_decay_time(flDecayTime); -} - -void EaxReverbEffect::defer_decay_hf_ratio( - const EaxEaxCall& eax_call) -{ - const auto& flDecayHFRatio = - eax_call.get_value(); - - validate_decay_hf_ratio(flDecayHFRatio); - defer_decay_hf_ratio(flDecayHFRatio); -} - -void EaxReverbEffect::defer_decay_lf_ratio( - const EaxEaxCall& eax_call) -{ - const auto& flDecayLFRatio = - eax_call.get_value(); - - validate_decay_lf_ratio(flDecayLFRatio); - defer_decay_lf_ratio(flDecayLFRatio); -} - -void EaxReverbEffect::defer_reflections( - const EaxEaxCall& eax_call) -{ - const auto& lReflections = - eax_call.get_value(); - - validate_reflections(lReflections); - defer_reflections(lReflections); -} - -void EaxReverbEffect::defer_reflections_delay( - const EaxEaxCall& eax_call) -{ - const auto& flReflectionsDelay = - eax_call.get_value(); - - validate_reflections_delay(flReflectionsDelay); - defer_reflections_delay(flReflectionsDelay); -} - -void EaxReverbEffect::defer_reflections_pan( - const EaxEaxCall& eax_call) -{ - const auto& vReflectionsPan = - eax_call.get_value(); - - validate_reflections_pan(vReflectionsPan); - defer_reflections_pan(vReflectionsPan); -} - -void EaxReverbEffect::defer_reverb( - const EaxEaxCall& eax_call) -{ - const auto& lReverb = - eax_call.get_value(); - - validate_reverb(lReverb); - defer_reverb(lReverb); -} - -void EaxReverbEffect::defer_reverb_delay( - const EaxEaxCall& eax_call) -{ - const auto& flReverbDelay = - eax_call.get_value(); - - validate_reverb_delay(flReverbDelay); - defer_reverb_delay(flReverbDelay); -} - -void EaxReverbEffect::defer_reverb_pan( - const EaxEaxCall& eax_call) -{ - const auto& vReverbPan = - eax_call.get_value(); - - validate_reverb_pan(vReverbPan); - defer_reverb_pan(vReverbPan); -} - -void EaxReverbEffect::defer_echo_time( - const EaxEaxCall& eax_call) -{ - const auto& flEchoTime = - eax_call.get_value(); - - validate_echo_time(flEchoTime); - defer_echo_time(flEchoTime); -} - -void EaxReverbEffect::defer_echo_depth( - const EaxEaxCall& eax_call) -{ - const auto& flEchoDepth = - eax_call.get_value(); - - validate_echo_depth(flEchoDepth); - defer_echo_depth(flEchoDepth); -} - -void EaxReverbEffect::defer_modulation_time( - const EaxEaxCall& eax_call) -{ - const auto& flModulationTime = - eax_call.get_value(); - - validate_modulation_time(flModulationTime); - defer_modulation_time(flModulationTime); -} - -void EaxReverbEffect::defer_modulation_depth( - const EaxEaxCall& eax_call) -{ - const auto& flModulationDepth = - eax_call.get_value(); - - validate_modulation_depth(flModulationDepth); - defer_modulation_depth(flModulationDepth); -} - -void EaxReverbEffect::defer_air_absorbtion_hf( - const EaxEaxCall& eax_call) -{ - const auto& air_absorbtion_hf = - eax_call.get_value(); - - validate_air_absorbtion_hf(air_absorbtion_hf); - defer_air_absorbtion_hf(air_absorbtion_hf); -} - -void EaxReverbEffect::defer_hf_reference( - const EaxEaxCall& eax_call) -{ - const auto& flHFReference = - eax_call.get_value(); - - validate_hf_reference(flHFReference); - defer_hf_reference(flHFReference); -} - -void EaxReverbEffect::defer_lf_reference( - const EaxEaxCall& eax_call) -{ - const auto& flLFReference = - eax_call.get_value(); - - validate_lf_reference(flLFReference); - defer_lf_reference(flLFReference); -} - -void EaxReverbEffect::defer_room_rolloff_factor( - const EaxEaxCall& eax_call) -{ - const auto& flRoomRolloffFactor = - eax_call.get_value(); - - validate_room_rolloff_factor(flRoomRolloffFactor); - defer_room_rolloff_factor(flRoomRolloffFactor); -} - -void EaxReverbEffect::defer_flags( - const EaxEaxCall& eax_call) -{ - const auto& ulFlags = - eax_call.get_value(); - - validate_flags(ulFlags); - defer_flags(ulFlags); -} - -void EaxReverbEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto eax_version = eax_call.get_version(); - - if (eax_version == 2) +struct ModulationDepthValidator { + void operator()(float flModulationDepth) const { - const auto& listener = - eax_call.get_value(); - - validate_all(listener, eax_version); - defer_all(listener); + eax_validate_range( + "Modulation Depth", + flModulationDepth, + EAXREVERB_MINMODULATIONDEPTH, + EAXREVERB_MAXMODULATIONDEPTH); } - else +}; // ModulationDepthValidator + +struct HFReferenceValidator { + void operator()(float flHFReference) const { - const auto& reverb_all = - eax_call.get_value(); - - validate_all(reverb_all, eax_version); - defer_all(reverb_all); + eax_validate_range( + "HF Reference", + flHFReference, + EAXREVERB_MINHFREFERENCE, + EAXREVERB_MAXHFREFERENCE); } +}; // HFReferenceValidator + +struct LFReferenceValidator { + void operator()(float flLFReference) const + { + eax_validate_range( + "LF Reference", + flLFReference, + EAXREVERB_MINLFREFERENCE, + EAXREVERB_MAXLFREFERENCE); + } +}; // LFReferenceValidator + +struct FlagsValidator3 { + void operator()(unsigned long ulFlags) const + { + eax_validate_range( + "Flags", + ulFlags, + 0UL, + ~EAXREVERBFLAGS_RESERVED); + } +}; // FlagsValidator3 + +struct AllValidator3 { + void operator()(const EAXREVERBPROPERTIES& all) const + { + EnvironmentValidator3{}(all.ulEnvironment); + EnvironmentSizeValidator{}(all.flEnvironmentSize); + EnvironmentDiffusionValidator{}(all.flEnvironmentDiffusion); + RoomValidator{}(all.lRoom); + RoomHFValidator{}(all.lRoomHF); + RoomLFValidator{}(all.lRoomLF); + DecayTimeValidator{}(all.flDecayTime); + DecayHFRatioValidator{}(all.flDecayHFRatio); + DecayLFRatioValidator{}(all.flDecayLFRatio); + ReflectionsValidator{}(all.lReflections); + ReflectionsDelayValidator{}(all.flReflectionsDelay); + VectorValidator{}(all.vReflectionsPan); + ReverbValidator{}(all.lReverb); + ReverbDelayValidator{}(all.flReverbDelay); + VectorValidator{}(all.vReverbPan); + EchoTimeValidator{}(all.flEchoTime); + EchoDepthValidator{}(all.flEchoDepth); + ModulationTimeValidator{}(all.flModulationTime); + ModulationDepthValidator{}(all.flModulationDepth); + AirAbsorptionHFValidator{}(all.flAirAbsorptionHF); + HFReferenceValidator{}(all.flHFReference); + LFReferenceValidator{}(all.flLFReference); + RoomRolloffFactorValidator{}(all.flRoomRolloffFactor); + FlagsValidator3{}(all.ulFlags); + } +}; // AllValidator3 + +struct EnvironmentDeferrer2 { + void operator()(EAX20LISTENERPROPERTIES& props, unsigned long dwEnvironment) const + { + props = EAX2REVERB_PRESETS[dwEnvironment]; + } +}; // EnvironmentDeferrer2 + +struct EnvironmentSizeDeferrer2 { + void operator()(EAX20LISTENERPROPERTIES& props, float flEnvironmentSize) const + { + if (props.flEnvironmentSize == flEnvironmentSize) + { + return; + } + + const auto scale = flEnvironmentSize / props.flEnvironmentSize; + props.flEnvironmentSize = flEnvironmentSize; + + if ((props.dwFlags & EAX2LISTENERFLAGS_DECAYTIMESCALE) != 0) + { + props.flDecayTime = std::clamp( + props.flDecayTime * scale, + EAXREVERB_MINDECAYTIME, + EAXREVERB_MAXDECAYTIME); + } + + if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSSCALE) != 0 && + (props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + props.lReflections = std::clamp( + props.lReflections - static_cast(gain_to_level_mb(scale)), + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); + } + + if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + props.flReflectionsDelay = std::clamp( + props.flReflectionsDelay * scale, + EAXREVERB_MINREFLECTIONSDELAY, + EAXREVERB_MAXREFLECTIONSDELAY); + } + + if ((props.dwFlags & EAX2LISTENERFLAGS_REVERBSCALE) != 0) + { + const auto log_scalar = ((props.dwFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; + + props.lReverb = std::clamp( + props.lReverb - static_cast(std::log10(scale) * log_scalar), + EAXREVERB_MINREVERB, + EAXREVERB_MAXREVERB); + } + + if ((props.dwFlags & EAX2LISTENERFLAGS_REVERBDELAYSCALE) != 0) + { + props.flReverbDelay = std::clamp( + props.flReverbDelay * scale, + EAXREVERB_MINREVERBDELAY, + EAXREVERB_MAXREVERBDELAY); + } + } +}; // EnvironmentSizeDeferrer2 + +struct EnvironmentDeferrer3 { + void operator()(EAXREVERBPROPERTIES& props, unsigned long ulEnvironment) const + { + if (ulEnvironment == EAX_ENVIRONMENT_UNDEFINED) + { + props.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED; + return; + } + + props = EAXREVERB_PRESETS[ulEnvironment]; + } +}; // EnvironmentDeferrer3 + +struct EnvironmentSizeDeferrer3 { + void operator()(EAXREVERBPROPERTIES& props, float flEnvironmentSize) const + { + if (props.flEnvironmentSize == flEnvironmentSize) + { + return; + } + + const auto scale = flEnvironmentSize / props.flEnvironmentSize; + props.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED; + props.flEnvironmentSize = flEnvironmentSize; + + if ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) + { + props.flDecayTime = std::clamp( + props.flDecayTime * scale, + EAXREVERB_MINDECAYTIME, + EAXREVERB_MAXDECAYTIME); + } + + if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0 && + (props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + props.lReflections = std::clamp( + props.lReflections - static_cast(gain_to_level_mb(scale)), + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); + } + + if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + props.flReflectionsDelay = std::clamp( + props.flReflectionsDelay * scale, + EAXREVERB_MINREFLECTIONSDELAY, + EAXREVERB_MAXREFLECTIONSDELAY); + } + + if ((props.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0) + { + const auto log_scalar = ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; + props.lReverb = std::clamp( + props.lReverb - static_cast(std::log10(scale) * log_scalar), + EAXREVERB_MINREVERB, + EAXREVERB_MAXREVERB); + } + + if ((props.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0) + { + props.flReverbDelay = std::clamp( + props.flReverbDelay * scale, + EAXREVERB_MINREVERBDELAY, + EAXREVERB_MAXREVERBDELAY); + } + + if ((props.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0) + { + props.flEchoTime = std::clamp( + props.flEchoTime * scale, + EAXREVERB_MINECHOTIME, + EAXREVERB_MAXECHOTIME); + } + + if ((props.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0) + { + props.flModulationTime = std::clamp( + props.flModulationTime * scale, + EAXREVERB_MINMODULATIONTIME, + EAXREVERB_MAXMODULATIONTIME); + } + } +}; // EnvironmentSizeDeferrer3 + +} // namespace + + +struct EaxReverbCommitter::Exception : public EaxReverbEffectException +{ + using EaxReverbEffectException::EaxReverbEffectException; +}; + +[[noreturn]] void EaxReverbCommitter::fail(const char* message) +{ + throw Exception{message}; } - -void EaxReverbEffect::v1_defer(const EaxEaxCall& eax_call) +void EaxReverbCommitter::translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept { - switch (eax_call.get_property_id()) - { - case DSPROPERTY_EAX_ALL: return v1_defer_all(eax_call); - case DSPROPERTY_EAX_ENVIRONMENT: return v1_defer_environment(eax_call); - case DSPROPERTY_EAX_VOLUME: return v1_defer_volume(eax_call); - case DSPROPERTY_EAX_DECAYTIME: return v1_defer_decay_time(eax_call); - case DSPROPERTY_EAX_DAMPING: return v1_defer_damping(eax_call); - default: eax_fail("Unsupported property id."); - } + assert(src.environment <= EAX1REVERB_MAXENVIRONMENT); + 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)); } -// [[nodiscard]] -bool EaxReverbEffect::apply_deferred() +void EaxReverbCommitter::translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept { - bool ret{false}; + assert(src.dwEnvironment <= EAX1REVERB_MAXENVIRONMENT); + dst = EAXREVERB_PRESETS[src.dwEnvironment]; + dst.ulEnvironment = src.dwEnvironment; + dst.flEnvironmentSize = src.flEnvironmentSize; + dst.flEnvironmentDiffusion = src.flEnvironmentDiffusion; + dst.lRoom = src.lRoom; + dst.lRoomHF = src.lRoomHF; + dst.flDecayTime = src.flDecayTime; + dst.flDecayHFRatio = src.flDecayHFRatio; + dst.lReflections = src.lReflections; + dst.flReflectionsDelay = src.flReflectionsDelay; + dst.lReverb = src.lReverb; + dst.flReverbDelay = src.flReverbDelay; + dst.flAirAbsorptionHF = src.flAirAbsorptionHF; + dst.flRoomRolloffFactor = src.flRoomRolloffFactor; + dst.ulFlags = src.dwFlags; +} - if(unlikely(eax1_dirty_flags_ != Eax1ReverbEffectDirtyFlags{})) - { - eax1_ = eax1_d_; +bool EaxReverbCommitter::commit(const EAX_REVERBPROPERTIES &props) +{ + EAXREVERBPROPERTIES dst{}; + translate(props, dst); + return commit(dst); +} - v1_set_efx(); +bool EaxReverbCommitter::commit(const EAX20LISTENERPROPERTIES &props) +{ + EAXREVERBPROPERTIES dst{}; + translate(props, dst); + return commit(dst); +} - eax1_dirty_flags_ = Eax1ReverbEffectDirtyFlags{}; +bool EaxReverbCommitter::commit(const EAXREVERBPROPERTIES &props) +{ + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) + return false; - ret = true; - } + mEaxProps = props; - if(eax_dirty_flags_ == EaxReverbEffectDirtyFlags{}) + 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; - - eax_ = eax_d_; - - if (eax_dirty_flags_.ulEnvironment) - { - } - - if (eax_dirty_flags_.flEnvironmentSize) - { - set_efx_density_from_environment_size(); - } - - if (eax_dirty_flags_.flEnvironmentDiffusion) - { - set_efx_diffusion(); - } - - if (eax_dirty_flags_.lRoom) - { - set_efx_gain(); - } - - if (eax_dirty_flags_.lRoomHF) - { - set_efx_gain_hf(); - } - - if (eax_dirty_flags_.lRoomLF) - { - set_efx_gain_lf(); - } - - if (eax_dirty_flags_.flDecayTime) - { - set_efx_decay_time(); - } - - if (eax_dirty_flags_.flDecayHFRatio) - { - set_efx_decay_hf_ratio(); - } - - if (eax_dirty_flags_.flDecayLFRatio) - { - set_efx_decay_lf_ratio(); - } - - if (eax_dirty_flags_.lReflections) - { - set_efx_reflections_gain(); - } - - if (eax_dirty_flags_.flReflectionsDelay) - { - set_efx_reflections_delay(); - } - - if (eax_dirty_flags_.vReflectionsPan) - { - set_efx_reflections_pan(); - } - - if (eax_dirty_flags_.lReverb) - { - set_efx_late_reverb_gain(); - } - - if (eax_dirty_flags_.flReverbDelay) - { - set_efx_late_reverb_delay(); - } - - if (eax_dirty_flags_.vReverbPan) - { - set_efx_late_reverb_pan(); - } - - if (eax_dirty_flags_.flEchoTime) - { - set_efx_echo_time(); - } - - if (eax_dirty_flags_.flEchoDepth) - { - set_efx_echo_depth(); - } - - if (eax_dirty_flags_.flModulationTime) - { - set_efx_modulation_time(); - } - - if (eax_dirty_flags_.flModulationDepth) - { - set_efx_modulation_depth(); - } - - if (eax_dirty_flags_.flAirAbsorptionHF) - { - set_efx_air_absorption_gain_hf(); - } - - if (eax_dirty_flags_.flHFReference) - { - set_efx_hf_reference(); - } - - if (eax_dirty_flags_.flLFReference) - { - set_efx_lf_reference(); - } - - if (eax_dirty_flags_.flRoomRolloffFactor) - { - set_efx_room_rolloff_factor(); - } - - if (eax_dirty_flags_.ulFlags) - { - set_efx_flags(); - } - - eax_dirty_flags_ = EaxReverbEffectDirtyFlags{}; + }(); return true; } -void EaxReverbEffect::set(const EaxEaxCall& eax_call) +void EaxReverbCommitter::SetDefaults(EAX_REVERBPROPERTIES &props) { - if(eax_call.get_version() == 1) - v1_defer(eax_call); - else switch(eax_call.get_property_id()) + props = EAX1REVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; +} + +void EaxReverbCommitter::SetDefaults(EAX20LISTENERPROPERTIES &props) +{ + props = EAX2REVERB_PRESETS[EAX2_ENVIRONMENT_GENERIC]; + props.lRoom = -10'000L; +} + +void EaxReverbCommitter::SetDefaults(EAXREVERBPROPERTIES &props) +{ + props = EAXREVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; +} + +void EaxReverbCommitter::SetDefaults(EaxEffectProps &props) +{ + SetDefaults(props.emplace()); +} + + +void EaxReverbCommitter::Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props) +{ + switch(call.get_property_id()) { - case EAXREVERB_NONE: - break; - - case EAXREVERB_ALLPARAMETERS: - defer_all(eax_call); - break; - - case EAXREVERB_ENVIRONMENT: - defer_environment(eax_call); - break; - - case EAXREVERB_ENVIRONMENTSIZE: - defer_environment_size(eax_call); - break; - - case EAXREVERB_ENVIRONMENTDIFFUSION: - defer_environment_diffusion(eax_call); - break; - - case EAXREVERB_ROOM: - defer_room(eax_call); - break; - - case EAXREVERB_ROOMHF: - defer_room_hf(eax_call); - break; - - case EAXREVERB_ROOMLF: - defer_room_lf(eax_call); - break; - - case EAXREVERB_DECAYTIME: - defer_decay_time(eax_call); - break; - - case EAXREVERB_DECAYHFRATIO: - defer_decay_hf_ratio(eax_call); - break; - - case EAXREVERB_DECAYLFRATIO: - defer_decay_lf_ratio(eax_call); - break; - - case EAXREVERB_REFLECTIONS: - defer_reflections(eax_call); - break; - - case EAXREVERB_REFLECTIONSDELAY: - defer_reflections_delay(eax_call); - break; - - case EAXREVERB_REFLECTIONSPAN: - defer_reflections_pan(eax_call); - break; - - case EAXREVERB_REVERB: - defer_reverb(eax_call); - break; - - case EAXREVERB_REVERBDELAY: - defer_reverb_delay(eax_call); - break; - - case EAXREVERB_REVERBPAN: - defer_reverb_pan(eax_call); - break; - - case EAXREVERB_ECHOTIME: - defer_echo_time(eax_call); - break; - - case EAXREVERB_ECHODEPTH: - defer_echo_depth(eax_call); - break; - - case EAXREVERB_MODULATIONTIME: - defer_modulation_time(eax_call); - break; - - case EAXREVERB_MODULATIONDEPTH: - defer_modulation_depth(eax_call); - break; - - case EAXREVERB_AIRABSORPTIONHF: - defer_air_absorbtion_hf(eax_call); - break; - - case EAXREVERB_HFREFERENCE: - defer_hf_reference(eax_call); - break; - - case EAXREVERB_LFREFERENCE: - defer_lf_reference(eax_call); - break; - - case EAXREVERB_ROOMROLLOFFFACTOR: - defer_room_rolloff_factor(eax_call); - break; - - case EAXREVERB_FLAGS: - defer_flags(eax_call); - break; - - default: - eax_fail("Unsupported property id."); + case DSPROPERTY_EAX_ALL: call.set_value(props); break; + case DSPROPERTY_EAX_ENVIRONMENT: call.set_value(props.environment); break; + case DSPROPERTY_EAX_VOLUME: call.set_value(props.fVolume); break; + case DSPROPERTY_EAX_DECAYTIME: call.set_value(props.fDecayTime_sec); break; + case DSPROPERTY_EAX_DAMPING: call.set_value(props.fDamping); break; + default: fail_unknown_property_id(); } } -const EFXEAXREVERBPROPERTIES eax_efx_reverb_presets[EAX1_ENVIRONMENT_COUNT] = +void EaxReverbCommitter::Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props) { - EFX_REVERB_PRESET_GENERIC, - EFX_REVERB_PRESET_PADDEDCELL, - EFX_REVERB_PRESET_ROOM, - EFX_REVERB_PRESET_BATHROOM, - EFX_REVERB_PRESET_LIVINGROOM, - EFX_REVERB_PRESET_STONEROOM, - EFX_REVERB_PRESET_AUDITORIUM, - EFX_REVERB_PRESET_CONCERTHALL, - EFX_REVERB_PRESET_CAVE, - EFX_REVERB_PRESET_ARENA, - EFX_REVERB_PRESET_HANGAR, - EFX_REVERB_PRESET_CARPETEDHALLWAY, - EFX_REVERB_PRESET_HALLWAY, - EFX_REVERB_PRESET_STONECORRIDOR, - EFX_REVERB_PRESET_ALLEY, - EFX_REVERB_PRESET_FOREST, - EFX_REVERB_PRESET_CITY, - EFX_REVERB_PRESET_MOUNTAINS, - EFX_REVERB_PRESET_QUARRY, - EFX_REVERB_PRESET_PLAIN, - EFX_REVERB_PRESET_PARKINGLOT, - EFX_REVERB_PRESET_SEWERPIPE, - EFX_REVERB_PRESET_UNDERWATER, - EFX_REVERB_PRESET_DRUGGED, - EFX_REVERB_PRESET_DIZZY, - EFX_REVERB_PRESET_PSYCHOTIC, -}; // EFXEAXREVERBPROPERTIES + switch(call.get_property_id()) + { + case DSPROPERTY_EAX20LISTENER_NONE: break; + case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: call.set_value(props); break; + case DSPROPERTY_EAX20LISTENER_ROOM: call.set_value(props.lRoom); break; + case DSPROPERTY_EAX20LISTENER_ROOMHF: call.set_value(props.lRoomHF); break; + case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: call.set_value(props.flRoomRolloffFactor); break; + case DSPROPERTY_EAX20LISTENER_DECAYTIME: call.set_value(props.flDecayTime); break; + case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: call.set_value(props.flDecayHFRatio); break; + case DSPROPERTY_EAX20LISTENER_REFLECTIONS: call.set_value(props.lReflections); break; + case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: call.set_value(props.flReflectionsDelay); break; + case DSPROPERTY_EAX20LISTENER_REVERB: call.set_value(props.lReverb); break; + case DSPROPERTY_EAX20LISTENER_REVERBDELAY: call.set_value(props.flReverbDelay); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: call.set_value(props.dwEnvironment); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: call.set_value(props.flEnvironmentSize); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: call.set_value(props.flEnvironmentDiffusion); break; + case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: call.set_value(props.flAirAbsorptionHF); break; + case DSPROPERTY_EAX20LISTENER_FLAGS: call.set_value(props.dwFlags); break; + default: fail_unknown_property_id(); + } +} -} // namespace - -EaxEffectUPtr eax_create_eax_reverb_effect() +void EaxReverbCommitter::Get(const EaxCall &call, const EAXREVERBPROPERTIES &props) { - return std::make_unique(); + switch(call.get_property_id()) + { + case EAXREVERB_NONE: break; + case EAXREVERB_ALLPARAMETERS: call.set_value(props); break; + case EAXREVERB_ENVIRONMENT: call.set_value(props.ulEnvironment); break; + case EAXREVERB_ENVIRONMENTSIZE: call.set_value(props.flEnvironmentSize); break; + case EAXREVERB_ENVIRONMENTDIFFUSION: call.set_value(props.flEnvironmentDiffusion); break; + case EAXREVERB_ROOM: call.set_value(props.lRoom); break; + case EAXREVERB_ROOMHF: call.set_value(props.lRoomHF); break; + case EAXREVERB_ROOMLF: call.set_value(props.lRoomLF); break; + case EAXREVERB_DECAYTIME: call.set_value(props.flDecayTime); break; + case EAXREVERB_DECAYHFRATIO: call.set_value(props.flDecayHFRatio); break; + case EAXREVERB_DECAYLFRATIO: call.set_value(props.flDecayLFRatio); break; + case EAXREVERB_REFLECTIONS: call.set_value(props.lReflections); break; + case EAXREVERB_REFLECTIONSDELAY: call.set_value(props.flReflectionsDelay); break; + case EAXREVERB_REFLECTIONSPAN: call.set_value(props.vReflectionsPan); break; + case EAXREVERB_REVERB: call.set_value(props.lReverb); break; + case EAXREVERB_REVERBDELAY: call.set_value(props.flReverbDelay); break; + case EAXREVERB_REVERBPAN: call.set_value(props.vReverbPan); break; + case EAXREVERB_ECHOTIME: call.set_value(props.flEchoTime); break; + case EAXREVERB_ECHODEPTH: call.set_value(props.flEchoDepth); break; + case EAXREVERB_MODULATIONTIME: call.set_value(props.flModulationTime); break; + case EAXREVERB_MODULATIONDEPTH: call.set_value(props.flModulationDepth); break; + case EAXREVERB_AIRABSORPTIONHF: call.set_value(props.flAirAbsorptionHF); break; + case EAXREVERB_HFREFERENCE: call.set_value(props.flHFReference); break; + case EAXREVERB_LFREFERENCE: call.set_value(props.flLFReference); break; + case EAXREVERB_ROOMROLLOFFFACTOR: call.set_value(props.flRoomRolloffFactor); break; + case EAXREVERB_FLAGS: call.set_value(props.ulFlags); break; + default: fail_unknown_property_id(); + } +} + + +void EaxReverbCommitter::Set(const EaxCall &call, EAX_REVERBPROPERTIES &props) +{ + switch(call.get_property_id()) + { + case DSPROPERTY_EAX_ALL: defer(call, props); break; + case DSPROPERTY_EAX_ENVIRONMENT: defer(call, props.environment); break; + case DSPROPERTY_EAX_VOLUME: defer(call, props.fVolume); break; + case DSPROPERTY_EAX_DECAYTIME: defer(call, props.fDecayTime_sec); break; + case DSPROPERTY_EAX_DAMPING: defer(call, props.fDamping); break; + default: fail_unknown_property_id(); + } +} + +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(); + } +} + +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(); + } } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/effects/vmorpher.cpp b/libs/openal-soft/al/effects/vmorpher.cpp index 8c0b3adb..72749f4e 100644 --- a/libs/openal-soft/al/effects/vmorpher.cpp +++ b/libs/openal-soft/al/effects/vmorpher.cpp @@ -1,31 +1,30 @@ #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_exception.h" -#include "al/eax_utils.h" +#include "al/eax/effect.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { -al::optional PhenomeFromEnum(ALenum val) +constexpr std::optional PhenomeFromEnum(ALenum val) noexcept { #define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \ - return al::make_optional(VMorpherPhenome::x) + return VMorpherPhenome::x switch(val) { HANDLE_PHENOME(A); @@ -59,10 +58,10 @@ al::optional PhenomeFromEnum(ALenum val) HANDLE_PHENOME(V); HANDLE_PHENOME(Z); } - return al::nullopt; + return std::nullopt; #undef HANDLE_PHENOME } -ALenum EnumFromPhenome(VMorpherPhenome phenome) +constexpr ALenum EnumFromPhenome(VMorpherPhenome phenome) { #define HANDLE_PHENOME(x) case VMorpherPhenome::x: return AL_VOCAL_MORPHER_PHONEME_ ## x switch(phenome) @@ -102,17 +101,17 @@ ALenum EnumFromPhenome(VMorpherPhenome phenome) #undef HANDLE_PHENOME } -al::optional WaveformFromEmum(ALenum value) +constexpr std::optional WaveformFromEmum(ALenum value) noexcept { switch(value) { - case AL_VOCAL_MORPHER_WAVEFORM_SINUSOID: return al::make_optional(VMorpherWaveform::Sinusoid); - case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return al::make_optional(VMorpherWaveform::Triangle); - case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return al::make_optional(VMorpherWaveform::Sawtooth); + case AL_VOCAL_MORPHER_WAVEFORM_SINUSOID: return VMorpherWaveform::Sinusoid; + case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return VMorpherWaveform::Triangle; + case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return VMorpherWaveform::Sawtooth; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromWaveform(VMorpherWaveform type) +constexpr ALenum EnumFromWaveform(VMorpherWaveform type) { switch(type) { @@ -124,13 +123,29 @@ ALenum EnumFromWaveform(VMorpherWaveform type) std::to_string(static_cast(type))}; } -void Vmorpher_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultProps() noexcept +{ + VmorpherProps props{}; + props.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE; + props.PhonemeA = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA).value(); + props.PhonemeB = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB).value(); + props.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING; + props.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING; + props.Waveform = WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM).value(); + return props; +} + +} // namespace + +const EffectProps VmorpherEffectProps{genDefaultProps()}; + +void EffectHandler::SetParami(VmorpherProps &props, ALenum param, int val) { switch(param) { case AL_VOCAL_MORPHER_PHONEMEA: if(auto phenomeopt = PhenomeFromEnum(val)) - props->Vmorpher.PhonemeA = *phenomeopt; + props.PhonemeA = *phenomeopt; else throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val}; break; @@ -138,12 +153,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val) case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING)) throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"}; - props->Vmorpher.PhonemeACoarseTuning = val; + props.PhonemeACoarseTuning = val; break; case AL_VOCAL_MORPHER_PHONEMEB: if(auto phenomeopt = PhenomeFromEnum(val)) - props->Vmorpher.PhonemeB = *phenomeopt; + props.PhonemeB = *phenomeopt; else throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val}; break; @@ -151,12 +166,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val) case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING)) throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"}; - props->Vmorpher.PhonemeBCoarseTuning = val; + props.PhonemeBCoarseTuning = val; break; case AL_VOCAL_MORPHER_WAVEFORM: if(auto formopt = WaveformFromEmum(val)) - props->Vmorpher.Waveform = *formopt; + props.Waveform = *formopt; else throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val}; break; @@ -166,19 +181,19 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Vmorpher_setParamiv(EffectProps*, ALenum param, const int*) +void EffectHandler::SetParamiv(VmorpherProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param}; } -void Vmorpher_setParamf(EffectProps *props, ALenum param, float val) +void EffectHandler::SetParamf(VmorpherProps &props, ALenum param, float val) { switch(param) { case AL_VOCAL_MORPHER_RATE: if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE)) throw effect_exception{AL_INVALID_VALUE, "Vocal morpher rate out of range"}; - props->Vmorpher.Rate = val; + props.Rate = val; break; default: @@ -186,49 +201,35 @@ void Vmorpher_setParamf(EffectProps *props, ALenum param, float val) param}; } } -void Vmorpher_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Vmorpher_setParamf(props, param, vals[0]); } +void EffectHandler::SetParamfv(VmorpherProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Vmorpher_getParami(const EffectProps *props, ALenum param, int* val) +void EffectHandler::GetParami(const VmorpherProps &props, ALenum param, int* val) { switch(param) { - case AL_VOCAL_MORPHER_PHONEMEA: - *val = EnumFromPhenome(props->Vmorpher.PhonemeA); - break; - - case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: - *val = props->Vmorpher.PhonemeACoarseTuning; - break; - - case AL_VOCAL_MORPHER_PHONEMEB: - *val = EnumFromPhenome(props->Vmorpher.PhonemeB); - break; - - case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: - *val = props->Vmorpher.PhonemeBCoarseTuning; - break; - - case AL_VOCAL_MORPHER_WAVEFORM: - *val = EnumFromWaveform(props->Vmorpher.Waveform); - break; + case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); break; + case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; break; + case AL_VOCAL_MORPHER_PHONEMEB: *val = EnumFromPhenome(props.PhonemeB); break; + case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: *val = props.PhonemeBCoarseTuning; break; + case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", param}; } } -void Vmorpher_getParamiv(const EffectProps*, ALenum param, int*) +void EffectHandler::GetParamiv(const VmorpherProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param}; } -void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val) +void EffectHandler::GetParamf(const VmorpherProps &props, ALenum param, float *val) { switch(param) { case AL_VOCAL_MORPHER_RATE: - *val = props->Vmorpher.Rate; + *val = props.Rate; break; default: @@ -236,551 +237,221 @@ void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val) param}; } } -void Vmorpher_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Vmorpher_getParamf(props, param, vals); } +void EffectHandler::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 { -using EaxVocalMorpherEffectDirtyFlagsValue = std::uint_least8_t; +using VocalMorpherCommitter = EaxCommitter; -struct EaxVocalMorpherEffectDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeA : 1; - EaxVocalMorpherEffectDirtyFlagsValue lPhonemeACoarseTuning : 1; - EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeB : 1; - EaxVocalMorpherEffectDirtyFlagsValue lPhonemeBCoarseTuning : 1; - EaxVocalMorpherEffectDirtyFlagsValue ulWaveform : 1; - EaxVocalMorpherEffectDirtyFlagsValue flRate : 1; -}; // EaxPitchShifterEffectDirtyFlags - - -class EaxVocalMorpherEffect final : - public EaxEffect -{ -public: - EaxVocalMorpherEffect(); - - void dispatch(const EaxEaxCall& eax_call) override; - - // [[nodiscard]] - bool apply_deferred() override; - -private: - EAXVOCALMORPHERPROPERTIES eax_{}; - EAXVOCALMORPHERPROPERTIES eax_d_{}; - EaxVocalMorpherEffectDirtyFlags eax_dirty_flags_{}; - - void set_eax_defaults(); - - void set_efx_phoneme_a(); - void set_efx_phoneme_a_coarse_tuning(); - void set_efx_phoneme_b(); - void set_efx_phoneme_b_coarse_tuning(); - void set_efx_waveform(); - void set_efx_rate(); - void set_efx_defaults(); - - void get(const EaxEaxCall& eax_call); - - void validate_phoneme_a(unsigned long ulPhonemeA); - void validate_phoneme_a_coarse_tuning(long lPhonemeACoarseTuning); - void validate_phoneme_b(unsigned long ulPhonemeB); - void validate_phoneme_b_coarse_tuning(long lPhonemeBCoarseTuning); - void validate_waveform(unsigned long ulWaveform); - void validate_rate(float flRate); - void validate_all(const EAXVOCALMORPHERPROPERTIES& all); - - void defer_phoneme_a(unsigned long ulPhonemeA); - void defer_phoneme_a_coarse_tuning(long lPhonemeACoarseTuning); - void defer_phoneme_b(unsigned long ulPhonemeB); - void defer_phoneme_b_coarse_tuning(long lPhonemeBCoarseTuning); - void defer_waveform(unsigned long ulWaveform); - void defer_rate(float flRate); - void defer_all(const EAXVOCALMORPHERPROPERTIES& all); - - void defer_phoneme_a(const EaxEaxCall& eax_call); - void defer_phoneme_a_coarse_tuning(const EaxEaxCall& eax_call); - void defer_phoneme_b(const EaxEaxCall& eax_call); - void defer_phoneme_b_coarse_tuning(const EaxEaxCall& eax_call); - void defer_waveform(const EaxEaxCall& eax_call); - void defer_rate(const EaxEaxCall& eax_call); - void defer_all(const EaxEaxCall& eax_call); - - void set(const EaxEaxCall& eax_call); -}; // EaxVocalMorpherEffect - - -class EaxVocalMorpherEffectException : - public EaxException -{ -public: - explicit EaxVocalMorpherEffectException( - const char* message) - : - EaxException{"EAX_VOCAL_MORPHER_EFFECT", message} +struct PhonemeAValidator { + void operator()(unsigned long ulPhonemeA) const { + eax_validate_range( + "Phoneme A", + ulPhonemeA, + EAXVOCALMORPHER_MINPHONEMEA, + EAXVOCALMORPHER_MAXPHONEMEA); } -}; // EaxVocalMorpherEffectException +}; // PhonemeAValidator - -EaxVocalMorpherEffect::EaxVocalMorpherEffect() - : EaxEffect{AL_EFFECT_VOCAL_MORPHER} -{ - set_eax_defaults(); - set_efx_defaults(); -} - -void EaxVocalMorpherEffect::dispatch(const EaxEaxCall& eax_call) -{ - eax_call.is_get() ? get(eax_call) : set(eax_call); -} - -void EaxVocalMorpherEffect::set_eax_defaults() -{ - eax_.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA; - eax_.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING; - eax_.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB; - eax_.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING; - eax_.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM; - eax_.flRate = EAXVOCALMORPHER_DEFAULTRATE; - - eax_d_ = eax_; -} - -void EaxVocalMorpherEffect::set_efx_phoneme_a() -{ - const auto phoneme_a = clamp( - static_cast(eax_.ulPhonemeA), - AL_VOCAL_MORPHER_MIN_PHONEMEA, - AL_VOCAL_MORPHER_MAX_PHONEMEA); - - const auto efx_phoneme_a = PhenomeFromEnum(phoneme_a); - assert(efx_phoneme_a.has_value()); - al_effect_props_.Vmorpher.PhonemeA = *efx_phoneme_a; -} - -void EaxVocalMorpherEffect::set_efx_phoneme_a_coarse_tuning() -{ - const auto phoneme_a_coarse_tuning = clamp( - static_cast(eax_.lPhonemeACoarseTuning), - AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING, - AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING); - - al_effect_props_.Vmorpher.PhonemeACoarseTuning = phoneme_a_coarse_tuning; -} - -void EaxVocalMorpherEffect::set_efx_phoneme_b() -{ - const auto phoneme_b = clamp( - static_cast(eax_.ulPhonemeB), - AL_VOCAL_MORPHER_MIN_PHONEMEB, - AL_VOCAL_MORPHER_MAX_PHONEMEB); - - const auto efx_phoneme_b = PhenomeFromEnum(phoneme_b); - assert(efx_phoneme_b.has_value()); - al_effect_props_.Vmorpher.PhonemeB = *efx_phoneme_b; -} - -void EaxVocalMorpherEffect::set_efx_phoneme_b_coarse_tuning() -{ - const auto phoneme_b_coarse_tuning = clamp( - static_cast(eax_.lPhonemeBCoarseTuning), - AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING, - AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING); - - al_effect_props_.Vmorpher.PhonemeBCoarseTuning = phoneme_b_coarse_tuning; -} - -void EaxVocalMorpherEffect::set_efx_waveform() -{ - const auto waveform = clamp( - static_cast(eax_.ulWaveform), - AL_VOCAL_MORPHER_MIN_WAVEFORM, - AL_VOCAL_MORPHER_MAX_WAVEFORM); - - const auto wfx_waveform = WaveformFromEmum(waveform); - assert(wfx_waveform.has_value()); - al_effect_props_.Vmorpher.Waveform = *wfx_waveform; -} - -void EaxVocalMorpherEffect::set_efx_rate() -{ - const auto rate = clamp( - eax_.flRate, - AL_VOCAL_MORPHER_MIN_RATE, - AL_VOCAL_MORPHER_MAX_RATE); - - al_effect_props_.Vmorpher.Rate = rate; -} - -void EaxVocalMorpherEffect::set_efx_defaults() -{ - set_efx_phoneme_a(); - set_efx_phoneme_a_coarse_tuning(); - set_efx_phoneme_b(); - set_efx_phoneme_b_coarse_tuning(); - set_efx_waveform(); - set_efx_rate(); -} - -void EaxVocalMorpherEffect::get(const EaxEaxCall& eax_call) -{ - switch(eax_call.get_property_id()) +struct PhonemeACoarseTuningValidator { + void operator()(long lPhonemeACoarseTuning) const { - case EAXVOCALMORPHER_NONE: - break; - - case EAXVOCALMORPHER_ALLPARAMETERS: - eax_call.set_value(eax_); - break; - - case EAXVOCALMORPHER_PHONEMEA: - eax_call.set_value(eax_.ulPhonemeA); - break; - - case EAXVOCALMORPHER_PHONEMEACOARSETUNING: - eax_call.set_value(eax_.lPhonemeACoarseTuning); - break; - - case EAXVOCALMORPHER_PHONEMEB: - eax_call.set_value(eax_.ulPhonemeB); - break; - - case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: - eax_call.set_value(eax_.lPhonemeBCoarseTuning); - break; - - case EAXVOCALMORPHER_WAVEFORM: - eax_call.set_value(eax_.ulWaveform); - break; - - case EAXVOCALMORPHER_RATE: - eax_call.set_value(eax_.flRate); - break; - - default: - throw EaxVocalMorpherEffectException{"Unsupported property id."}; + eax_validate_range( + "Phoneme A Coarse Tuning", + lPhonemeACoarseTuning, + EAXVOCALMORPHER_MINPHONEMEACOARSETUNING, + EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING); } -} +}; // PhonemeACoarseTuningValidator -void EaxVocalMorpherEffect::validate_phoneme_a( - unsigned long ulPhonemeA) -{ - eax_validate_range( - "Phoneme A", - ulPhonemeA, - EAXVOCALMORPHER_MINPHONEMEA, - EAXVOCALMORPHER_MAXPHONEMEA); -} - -void EaxVocalMorpherEffect::validate_phoneme_a_coarse_tuning( - long lPhonemeACoarseTuning) -{ - eax_validate_range( - "Phoneme A Coarse Tuning", - lPhonemeACoarseTuning, - EAXVOCALMORPHER_MINPHONEMEACOARSETUNING, - EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING); -} - -void EaxVocalMorpherEffect::validate_phoneme_b( - unsigned long ulPhonemeB) -{ - eax_validate_range( - "Phoneme B", - ulPhonemeB, - EAXVOCALMORPHER_MINPHONEMEB, - EAXVOCALMORPHER_MAXPHONEMEB); -} - -void EaxVocalMorpherEffect::validate_phoneme_b_coarse_tuning( - long lPhonemeBCoarseTuning) -{ - eax_validate_range( - "Phoneme B Coarse Tuning", - lPhonemeBCoarseTuning, - EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING, - EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING); -} - -void EaxVocalMorpherEffect::validate_waveform( - unsigned long ulWaveform) -{ - eax_validate_range( - "Waveform", - ulWaveform, - EAXVOCALMORPHER_MINWAVEFORM, - EAXVOCALMORPHER_MAXWAVEFORM); -} - -void EaxVocalMorpherEffect::validate_rate( - float flRate) -{ - eax_validate_range( - "Rate", - flRate, - EAXVOCALMORPHER_MINRATE, - EAXVOCALMORPHER_MAXRATE); -} - -void EaxVocalMorpherEffect::validate_all( - const EAXVOCALMORPHERPROPERTIES& all) -{ - validate_phoneme_a(all.ulPhonemeA); - validate_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning); - validate_phoneme_b(all.ulPhonemeB); - validate_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning); - validate_waveform(all.ulWaveform); - validate_rate(all.flRate); -} - -void EaxVocalMorpherEffect::defer_phoneme_a( - unsigned long ulPhonemeA) -{ - eax_d_.ulPhonemeA = ulPhonemeA; - eax_dirty_flags_.ulPhonemeA = (eax_.ulPhonemeA != eax_d_.ulPhonemeA); -} - -void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning( - long lPhonemeACoarseTuning) -{ - eax_d_.lPhonemeACoarseTuning = lPhonemeACoarseTuning; - eax_dirty_flags_.lPhonemeACoarseTuning = (eax_.lPhonemeACoarseTuning != eax_d_.lPhonemeACoarseTuning); -} - -void EaxVocalMorpherEffect::defer_phoneme_b( - unsigned long ulPhonemeB) -{ - eax_d_.ulPhonemeB = ulPhonemeB; - eax_dirty_flags_.ulPhonemeB = (eax_.ulPhonemeB != eax_d_.ulPhonemeB); -} - -void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning( - long lPhonemeBCoarseTuning) -{ - eax_d_.lPhonemeBCoarseTuning = lPhonemeBCoarseTuning; - eax_dirty_flags_.lPhonemeBCoarseTuning = (eax_.lPhonemeBCoarseTuning != eax_d_.lPhonemeBCoarseTuning); -} - -void EaxVocalMorpherEffect::defer_waveform( - unsigned long ulWaveform) -{ - eax_d_.ulWaveform = ulWaveform; - eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); -} - -void EaxVocalMorpherEffect::defer_rate( - float flRate) -{ - eax_d_.flRate = flRate; - eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); -} - -void EaxVocalMorpherEffect::defer_all( - const EAXVOCALMORPHERPROPERTIES& all) -{ - defer_phoneme_a(all.ulPhonemeA); - defer_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning); - defer_phoneme_b(all.ulPhonemeB); - defer_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning); - defer_waveform(all.ulWaveform); - defer_rate(all.flRate); -} - -void EaxVocalMorpherEffect::defer_phoneme_a( - const EaxEaxCall& eax_call) -{ - const auto& phoneme_a = eax_call.get_value(); - - validate_phoneme_a(phoneme_a); - defer_phoneme_a(phoneme_a); -} - -void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning( - const EaxEaxCall& eax_call) -{ - const auto& phoneme_a_coarse_tuning = eax_call.get_value< - EaxVocalMorpherEffectException, - const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeACoarseTuning) - >(); - - validate_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning); - defer_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning); -} - -void EaxVocalMorpherEffect::defer_phoneme_b( - const EaxEaxCall& eax_call) -{ - const auto& phoneme_b = eax_call.get_value< - EaxVocalMorpherEffectException, - const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeB) - >(); - - validate_phoneme_b(phoneme_b); - defer_phoneme_b(phoneme_b); -} - -void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning( - const EaxEaxCall& eax_call) -{ - const auto& phoneme_b_coarse_tuning = eax_call.get_value< - EaxVocalMorpherEffectException, - const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeBCoarseTuning) - >(); - - validate_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning); - defer_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning); -} - -void EaxVocalMorpherEffect::defer_waveform( - const EaxEaxCall& eax_call) -{ - const auto& waveform = eax_call.get_value< - EaxVocalMorpherEffectException, - const decltype(EAXVOCALMORPHERPROPERTIES::ulWaveform) - >(); - - validate_waveform(waveform); - defer_waveform(waveform); -} - -void EaxVocalMorpherEffect::defer_rate( - const EaxEaxCall& eax_call) -{ - const auto& rate = eax_call.get_value< - EaxVocalMorpherEffectException, - const decltype(EAXVOCALMORPHERPROPERTIES::flRate) - >(); - - validate_rate(rate); - defer_rate(rate); -} - -void EaxVocalMorpherEffect::defer_all( - const EaxEaxCall& eax_call) -{ - const auto& all = eax_call.get_value< - EaxVocalMorpherEffectException, - const EAXVOCALMORPHERPROPERTIES - >(); - - validate_all(all); - defer_all(all); -} - -// [[nodiscard]] -bool EaxVocalMorpherEffect::apply_deferred() -{ - if (eax_dirty_flags_ == EaxVocalMorpherEffectDirtyFlags{}) +struct PhonemeBValidator { + void operator()(unsigned long ulPhonemeB) const { + eax_validate_range( + "Phoneme B", + ulPhonemeB, + EAXVOCALMORPHER_MINPHONEMEB, + EAXVOCALMORPHER_MAXPHONEMEB); + } +}; // PhonemeBValidator + +struct PhonemeBCoarseTuningValidator { + void operator()(long lPhonemeBCoarseTuning) const + { + eax_validate_range( + "Phoneme B Coarse Tuning", + lPhonemeBCoarseTuning, + EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING, + EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING); + } +}; // PhonemeBCoarseTuningValidator + +struct WaveformValidator { + void operator()(unsigned long ulWaveform) const + { + eax_validate_range( + "Waveform", + ulWaveform, + EAXVOCALMORPHER_MINWAVEFORM, + EAXVOCALMORPHER_MAXWAVEFORM); + } +}; // WaveformValidator + +struct RateValidator { + void operator()(float flRate) const + { + eax_validate_range( + "Rate", + flRate, + EAXVOCALMORPHER_MINRATE, + EAXVOCALMORPHER_MAXRATE); + } +}; // RateValidator + +struct AllValidator { + void operator()(const EAXVOCALMORPHERPROPERTIES& all) const + { + PhonemeAValidator{}(all.ulPhonemeA); + PhonemeACoarseTuningValidator{}(all.lPhonemeACoarseTuning); + PhonemeBValidator{}(all.ulPhonemeB); + PhonemeBCoarseTuningValidator{}(all.lPhonemeBCoarseTuning); + WaveformValidator{}(all.ulWaveform); + RateValidator{}(all.flRate); + } +}; // AllValidator + +} // namespace + +template<> +struct VocalMorpherCommitter::Exception : public EaxException { + explicit Exception(const char *message) : EaxException{"EAX_VOCAL_MORPHER_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void VocalMorpherCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +bool EaxVocalMorpherCommitter::commit(const EAXVOCALMORPHERPROPERTIES &props) +{ + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; - } - eax_ = eax_d_; + mEaxProps = props; - if (eax_dirty_flags_.ulPhonemeA) + auto get_phoneme = [](unsigned long phoneme) noexcept { - set_efx_phoneme_a(); - } - - if (eax_dirty_flags_.lPhonemeACoarseTuning) +#define HANDLE_PHENOME(x) case x: return VMorpherPhenome::x + switch(phoneme) + { + HANDLE_PHENOME(A); + HANDLE_PHENOME(E); + HANDLE_PHENOME(I); + HANDLE_PHENOME(O); + HANDLE_PHENOME(U); + HANDLE_PHENOME(AA); + HANDLE_PHENOME(AE); + HANDLE_PHENOME(AH); + HANDLE_PHENOME(AO); + HANDLE_PHENOME(EH); + HANDLE_PHENOME(ER); + HANDLE_PHENOME(IH); + HANDLE_PHENOME(IY); + HANDLE_PHENOME(UH); + HANDLE_PHENOME(UW); + HANDLE_PHENOME(B); + HANDLE_PHENOME(D); + HANDLE_PHENOME(F); + HANDLE_PHENOME(G); + HANDLE_PHENOME(J); + HANDLE_PHENOME(K); + HANDLE_PHENOME(L); + HANDLE_PHENOME(M); + HANDLE_PHENOME(N); + HANDLE_PHENOME(P); + HANDLE_PHENOME(R); + HANDLE_PHENOME(S); + HANDLE_PHENOME(T); + HANDLE_PHENOME(V); + HANDLE_PHENOME(Z); + } + return VMorpherPhenome::A; +#undef HANDLE_PHENOME + }; + auto get_waveform = [](unsigned long form) noexcept { - set_efx_phoneme_a_coarse_tuning(); - } + if(form == EAX_VOCALMORPHER_SINUSOID) return VMorpherWaveform::Sinusoid; + if(form == EAX_VOCALMORPHER_TRIANGLE) return VMorpherWaveform::Triangle; + if(form == EAX_VOCALMORPHER_SAWTOOTH) return VMorpherWaveform::Sawtooth; + return VMorpherWaveform::Sinusoid; + }; - if (eax_dirty_flags_.ulPhonemeB) - { - set_efx_phoneme_b(); - } - - if (eax_dirty_flags_.lPhonemeBCoarseTuning) - { - set_efx_phoneme_b_coarse_tuning(); - } - - if (eax_dirty_flags_.ulWaveform) - { - set_efx_waveform(); - } - - if (eax_dirty_flags_.flRate) - { - set_efx_rate(); - } - - eax_dirty_flags_ = EaxVocalMorpherEffectDirtyFlags{}; + 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; } -void EaxVocalMorpherEffect::set(const EaxEaxCall& eax_call) +void EaxVocalMorpherCommitter::SetDefaults(EaxEffectProps &props) { - switch(eax_call.get_property_id()) + static constexpr EAXVOCALMORPHERPROPERTIES defprops{[] { - case EAXVOCALMORPHER_NONE: - break; + 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; +} - case EAXVOCALMORPHER_ALLPARAMETERS: - defer_all(eax_call); - break; - - case EAXVOCALMORPHER_PHONEMEA: - defer_phoneme_a(eax_call); - break; - - case EAXVOCALMORPHER_PHONEMEACOARSETUNING: - defer_phoneme_a_coarse_tuning(eax_call); - break; - - case EAXVOCALMORPHER_PHONEMEB: - defer_phoneme_b(eax_call); - break; - - case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: - defer_phoneme_b_coarse_tuning(eax_call); - break; - - case EAXVOCALMORPHER_WAVEFORM: - defer_waveform(eax_call); - break; - - case EAXVOCALMORPHER_RATE: - defer_rate(eax_call); - break; - - default: - throw EaxVocalMorpherEffectException{"Unsupported property id."}; +void EaxVocalMorpherCommitter::Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props) +{ + switch(call.get_property_id()) + { + case EAXVOCALMORPHER_NONE: break; + case EAXVOCALMORPHER_ALLPARAMETERS: call.set_value(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(); } } -} // namespace - - -EaxEffectUPtr eax_create_eax_vocal_morpher_effect() +void EaxVocalMorpherCommitter::Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props) { - return std::make_unique(); + switch(call.get_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(); + } } #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/error.cpp b/libs/openal-soft/al/error.cpp index 90671011..24cc51dc 100644 --- a/libs/openal-soft/al/error.cpp +++ b/libs/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(unlikely(!context)) - { - 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/libs/openal-soft/al/error.h b/libs/openal-soft/al/error.h new file mode 100644 index 00000000..c50011a5 --- /dev/null +++ b/libs/openal-soft/al/error.h @@ -0,0 +1,27 @@ +#ifndef AL_ERROR_H +#define AL_ERROR_H + +#include "AL/al.h" + +#include "core/except.h" + +namespace al { + +class context_error final : public al::base_exception { + ALenum mErrorCode{}; + +public: +#ifdef __MINGW32__ + [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] +#else + [[gnu::format(printf, 3, 4)]] +#endif + context_error(ALenum code, const char *msg, ...); + ~context_error() final; + + [[nodiscard]] auto errorCode() const noexcept -> ALenum { return mErrorCode; } +}; + +} /* namespace al */ + +#endif /* AL_ERROR_H */ diff --git a/libs/openal-soft/al/event.cpp b/libs/openal-soft/al/event.cpp index e5923c43..5b8f4458 100644 --- a/libs/openal-soft/al/event.cpp +++ b/libs/openal-soft/al/event.cpp @@ -3,39 +3,54 @@ #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}; - while(likely(!quitnow)) + while(!quitnow) { auto evt_data = ring->getReadVector().first; if(evt_data.len == 0) @@ -44,81 +59,100 @@ static int EventThread(ALCcontext *context) continue; } - std::lock_guard _{context->mEventCbLock}; - do { - auto *evt_ptr = reinterpret_cast(evt_data.buf); - evt_data.buf += sizeof(AsyncEvent); - evt_data.len -= 1; + 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; - AsyncEvent evt{*evt_ptr}; - al::destroy_at(evt_ptr); - ring->readAdvance(1); - - quitnow = evt.EnumType == AsyncEvent::KillThread; - if(unlikely(quitnow)) break; - - if(evt.EnumType == AsyncEvent::ReleaseEffectState) + auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire); + auto proc_killthread = [](AsyncKillThread&) { }; + auto proc_release = [](AsyncEffectReleaseEvent &evt) { - evt.u.mEffectState->release(); - continue; - } - - uint enabledevts{context->mEnabledEvts.load(std::memory_order_acquire)}; - if(!context->mEventCb) continue; - - if(evt.EnumType == AsyncEvent::SourceStateChange) + al::intrusive_ptr{evt.mEffectState}; + }; + auto proc_srcstate = [context,enabledevts](AsyncSourceStateEvent &evt) { - if(!(enabledevts&AsyncEvent::SourceStateChange)) - continue; + if(!context->mEventCb + || !enabledevts.test(al::to_underlying(AsyncEnableBits::SourceState))) + return; + ALuint state{}; - std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)}; + std::string msg{"Source ID " + std::to_string(evt.mId)}; msg += " state has changed to "; - switch(evt.u.srcstate.state) + switch(evt.mState) { - case AsyncEvent::SrcState::Reset: + case AsyncSrcState::Reset: msg += "AL_INITIAL"; state = AL_INITIAL; break; - case AsyncEvent::SrcState::Stop: + case AsyncSrcState::Stop: msg += "AL_STOPPED"; state = AL_STOPPED; break; - case AsyncEvent::SrcState::Play: + case AsyncSrcState::Play: msg += "AL_PLAYING"; state = AL_PLAYING; break; - case AsyncEvent::SrcState::Pause: + case AsyncSrcState::Pause: msg += "AL_PAUSED"; state = AL_PAUSED; break; } - context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id, - state, static_cast(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) { - if(!(enabledevts&AsyncEvent::BufferCompleted)) - continue; - std::string msg{std::to_string(evt.u.bufcomp.count)}; - if(evt.u.bufcomp.count == 1) msg += " buffer completed"; + if(!context->mEventCb + || !enabledevts.test(al::to_underlying(AsyncEnableBits::BufferCompleted))) + return; + + std::string msg{std::to_string(evt.mCount)}; + if(evt.mCount == 1) msg += " buffer completed"; else msg += " buffers completed"; - context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id, - evt.u.bufcomp.count, static_cast(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) { - if(!(enabledevts&AsyncEvent::Disconnected)) - continue; - 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); + const std::string_view message{evt.msg.data()}; + + context->debugMessage(DebugSource::System, DebugType::Error, 0, + DebugSeverity::High, message); + + if(context->mEventCb + && enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected))) + context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, + static_cast(message.length()), message.data(), + context->mEventParam); + }; + + std::visit(overloaded{proc_srcstate, proc_buffercomp, proc_release, proc_disconnect, + proc_killthread}, event); + } + std::destroy(evt_span.begin(), evt_span.end()); + ring->readAdvance(evt_span.size()); } return 0; } +constexpr std::optional GetEventType(ALenum etype) noexcept +{ + switch(etype) + { + case AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT: return AsyncEnableBits::BufferCompleted; + case AL_EVENT_TYPE_DISCONNECTED_SOFT: return AsyncEnableBits::Disconnected; + case AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT: return AsyncEnableBits::SourceState; + } + return std::nullopt; +} + +} // namespace + + void StartEventThrd(ALCcontext *ctx) { try { @@ -143,7 +177,7 @@ void StopEventThrd(ALCcontext *ctx) evt_data = ring->getWriteVector().first; } while(evt_data.len == 0); } - al::construct_at(reinterpret_cast(evt_data.buf), AsyncEvent::KillThread); + std::ignore = InitAsyncEvent(evt_data.buf); ring->writeAdvance(1); ctx->mEventSem.post(); @@ -151,38 +185,29 @@ void StopEventThrd(ALCcontext *ctx) ctx->mEventThread.join(); } -AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(unlikely(!context)) return; +AL_API DECL_FUNCEXT3(void, alEventControl,SOFT, ALsizei,count, const ALenum*,types, ALboolean,enable) +FORCE_ALIGN void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsizei count, + const ALenum *types, ALboolean enable) noexcept +try { + if(count < 0) + throw al::context_error{AL_INVALID_VALUE, "Controlling %d events", count}; + if(count <= 0) UNLIKELY return; - if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count); - if(count <= 0) return; - if(!types) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer"); + if(!types) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; - uint flags{0}; - const ALenum *types_end = types+count; - auto bad_type = std::find_if_not(types, types_end, - [&flags](ALenum type) noexcept -> bool - { - if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) - flags |= AsyncEvent::BufferCompleted; - else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT) - flags |= AsyncEvent::SourceStateChange; - else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT) - flags |= AsyncEvent::Disconnected; - else - return false; - return true; - } - ); - if(bad_type != types_end) - SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid event type 0x%04x", *bad_type); + ContextBase::AsyncEventBitset flags{}; + for(ALenum evttype : al::span{types, static_cast(count)}) + { + auto etype = GetEventType(evttype); + if(!etype) + throw al::context_error{AL_INVALID_ENUM, "Invalid event type 0x%04x", evttype}; + flags.set(al::to_underlying(*etype)); + } if(enable) { - uint enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)}; + auto enabledevts = context->mEnabledEvts.load(std::memory_order_relaxed); while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts|flags, std::memory_order_acq_rel, std::memory_order_acquire) == 0) { @@ -193,7 +218,7 @@ START_API_FUNC } else { - uint enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)}; + auto enabledevts = context->mEnabledEvts.load(std::memory_order_relaxed); while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts&~flags, std::memory_order_acq_rel, std::memory_order_acquire) == 0) { @@ -201,20 +226,18 @@ START_API_FUNC /* Wait to ensure the event handler sees the changed flags before * returning. */ - std::lock_guard _{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(unlikely(!context)) 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/libs/openal-soft/al/extension.cpp b/libs/openal-soft/al/extension.cpp index 5dda2a86..16dc015a 100644 --- a/libs/openal-soft/al/extension.cpp +++ b/libs/openal-soft/al/extension.cpp @@ -20,60 +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(unlikely(!context)) return AL_FALSE; - - if(!extName) - SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer"); - - size_t len{strlen(extName)}; - const char *ptr{context->mExtensionList}; - while(ptr && *ptr) + if(!extName) UNLIKELY { - if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) - return AL_TRUE; + context->setError(AL_INVALID_VALUE, "NULL pointer"); + return AL_FALSE; + } - if((ptr=strchr(ptr, ' ')) != nullptr) - { - do { - ++ptr; - } while(isspace(*ptr)); - } + const std::string_view tofind{extName}; + for(std::string_view ext : context->mExtensions) + { + if(al::case_compare(ext, tofind) == 0) + return AL_TRUE; } 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/libs/openal-soft/al/filter.cpp b/libs/openal-soft/al/filter.cpp index f4b0ac9d..95b9e10c 100644 --- a/libs/openal-soft/al/filter.cpp +++ b/libs/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,247 +41,19 @@ #include "albit.h" #include "alc/context.h" #include "alc/device.h" +#include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" -#include "core/except.h" +#include "alspan.h" +#include "direct_defs.h" +#include "error.h" +#include "intrusive_ptr.h" #include "opthelpers.h" -#include "vector.h" namespace { -class filter_exception final : public al::base_exception { - ALenum mErrorCode; - -public: -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf, 3, 4)]] -#else - [[gnu::format(printf, 3, 4)]] -#endif - filter_exception(ALenum code, const char *msg, ...) : mErrorCode{code} - { - std::va_list args; - va_start(args, msg); - setMessage(msg, args); - va_end(args); - } - ALenum errorCode() const noexcept { return mErrorCode; } -}; - - -#define DEFINE_ALFILTER_VTABLE(T) \ -const ALfilter::Vtable T##_vtable = { \ - T##_setParami, T##_setParamiv, T##_setParamf, T##_setParamfv, \ - T##_getParami, T##_getParamiv, T##_getParamf, T##_getParamfv, \ -} - -void ALlowpass_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } -void ALlowpass_setParamiv(ALfilter*, ALenum param, const int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", - param}; -} -void ALlowpass_setParamf(ALfilter *filter, ALenum param, float val) -{ - switch(param) - { - case AL_LOWPASS_GAIN: - if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN)) - throw filter_exception{AL_INVALID_VALUE, "Low-pass gain %f out of range", val}; - filter->Gain = val; - break; - - case AL_LOWPASS_GAINHF: - if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF)) - throw filter_exception{AL_INVALID_VALUE, "Low-pass gainhf %f out of range", val}; - filter->GainHF = val; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; - } -} -void ALlowpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ ALlowpass_setParamf(filter, param, vals[0]); } - -void ALlowpass_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } -void ALlowpass_getParamiv(const ALfilter*, ALenum param, int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", - param}; -} -void ALlowpass_getParamf(const ALfilter *filter, ALenum param, float *val) -{ - switch(param) - { - case AL_LOWPASS_GAIN: - *val = filter->Gain; - break; - - case AL_LOWPASS_GAINHF: - *val = filter->GainHF; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; - } -} -void ALlowpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ ALlowpass_getParamf(filter, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALlowpass); - - -void ALhighpass_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } -void ALhighpass_setParamiv(ALfilter*, ALenum param, const int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", - param}; -} -void ALhighpass_setParamf(ALfilter *filter, ALenum param, float val) -{ - switch(param) - { - case AL_HIGHPASS_GAIN: - if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN)) - throw filter_exception{AL_INVALID_VALUE, "High-pass gain %f out of range", val}; - filter->Gain = val; - break; - - case AL_HIGHPASS_GAINLF: - if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF)) - throw filter_exception{AL_INVALID_VALUE, "High-pass gainlf %f out of range", val}; - filter->GainLF = val; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; - } -} -void ALhighpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ ALhighpass_setParamf(filter, param, vals[0]); } - -void ALhighpass_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } -void ALhighpass_getParamiv(const ALfilter*, ALenum param, int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", - param}; -} -void ALhighpass_getParamf(const ALfilter *filter, ALenum param, float *val) -{ - switch(param) - { - case AL_HIGHPASS_GAIN: - *val = filter->Gain; - break; - - case AL_HIGHPASS_GAINLF: - *val = filter->GainLF; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; - } -} -void ALhighpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ ALhighpass_getParamf(filter, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALhighpass); - - -void ALbandpass_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } -void ALbandpass_setParamiv(ALfilter*, ALenum param, const int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", - param}; -} -void ALbandpass_setParamf(ALfilter *filter, ALenum param, float val) -{ - switch(param) - { - case AL_BANDPASS_GAIN: - if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN)) - throw filter_exception{AL_INVALID_VALUE, "Band-pass gain %f out of range", val}; - filter->Gain = val; - break; - - case AL_BANDPASS_GAINHF: - if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF)) - throw filter_exception{AL_INVALID_VALUE, "Band-pass gainhf %f out of range", val}; - filter->GainHF = val; - break; - - case AL_BANDPASS_GAINLF: - if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF)) - throw filter_exception{AL_INVALID_VALUE, "Band-pass gainlf %f out of range", val}; - filter->GainLF = val; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; - } -} -void ALbandpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ ALbandpass_setParamf(filter, param, vals[0]); } - -void ALbandpass_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } -void ALbandpass_getParamiv(const ALfilter*, ALenum param, int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", - param}; -} -void ALbandpass_getParamf(const ALfilter *filter, ALenum param, float *val) -{ - switch(param) - { - case AL_BANDPASS_GAIN: - *val = filter->Gain; - break; - - case AL_BANDPASS_GAINHF: - *val = filter->GainHF; - break; - - case AL_BANDPASS_GAINLF: - *val = filter->GainLF; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; - } -} -void ALbandpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ ALbandpass_getParamf(filter, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALbandpass); - - -void ALnullfilter_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_setParamiv(ALfilter*, ALenum param, const int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_setParamf(ALfilter*, ALenum param, float) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_setParamfv(ALfilter*, ALenum param, const float*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } - -void ALnullfilter_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_getParamiv(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_getParamf(const ALfilter*, ALenum param, float*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_getParamfv(const ALfilter*, ALenum param, float*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } - -DEFINE_ALFILTER_VTABLE(ALnullfilter); +using SubListAllocator = al::allocator>; void InitFilterParams(ALfilter *filter, ALenum type) @@ -288,68 +62,66 @@ void InitFilterParams(ALfilter *filter, ALenum type) { filter->Gain = AL_LOWPASS_DEFAULT_GAIN; filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF; - filter->HFReference = LOWPASSFREQREF; + filter->HFReference = LowPassFreqRef; filter->GainLF = 1.0f; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALlowpass_vtable; + filter->LFReference = HighPassFreqRef; + filter->mTypeVariant.emplace(); } 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)); })}; while(needed > count) { - if UNLIKELY(device->FilterList.size() >= 1<<25) + if(device->FilterList.size() >= 1<<25) UNLIKELY return false; - device->FilterList.emplace_back(); - auto sublist = device->FilterList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Filters = static_cast(al_calloc(alignof(ALfilter), sizeof(ALfilter)*64)); - if UNLIKELY(!sublist->Filters) - { - device->FilterList.pop_back(); - return false; - } - count += 64; + FilterSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.Filters = SubListAllocator{}.allocate(1); + device->FilterList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } return true; } +catch(...) { + return false; +} -ALfilter *AllocFilter(ALCdevice *device) +ALfilter *AllocFilter(ALCdevice *device) noexcept { auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(), [](const FilterSubList &entry) noexcept -> bool @@ -358,7 +130,7 @@ ALfilter *AllocFilter(ALCdevice *device) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALfilter *filter{al::construct_at(sublist->Filters + slidx)}; + ALfilter *filter{al::construct_at(al::to_address(sublist->Filters->begin() + slidx))}; InitFilterParams(filter, AL_FILTER_NULL); /* Add 1 to avoid filter ID 0. */ @@ -371,347 +143,488 @@ ALfilter *AllocFilter(ALCdevice *device) void FreeFilter(ALCdevice *device, ALfilter *filter) { + device->mFilterNames.erase(filter->id); + const ALuint id{filter->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; - al::destroy_at(filter); + std::destroy_at(filter); device->FilterList[lidx].FreeMask |= 1_u64 << slidx; } -inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) +inline auto LookupFilter(ALCdevice *device, ALuint id) noexcept -> ALfilter* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= device->FilterList.size()) + if(lidx >= device->FilterList.size()) UNLIKELY return nullptr; FilterSubList &sublist = device->FilterList[lidx]; - if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Filters + slidx; + return al::to_address(sublist.Filters->begin() + slidx); } } // namespace -AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters) -START_API_FUNC +/* Null filter parameter handlers */ +template<> +void FilterTable::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 UNLIKELY(!context) return; - - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Generating %d filters", n); - if UNLIKELY(n <= 0) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{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 LIKELY(n == 1) - { - /* Special handling for the easy and normal case. */ - ALfilter *filter{AllocFilter(device)}; - if(filter) filters[0] = filter->id; - } - else - { - /* Store the allocated buffer IDs in a separate local list, to avoid - * modifying the user storage in case of failure. - */ - al::vector 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 UNLIKELY(!context) 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 UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Deleting %d filters", n); - if UNLIKELY(n <= 0) return; +/* 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 UNLIKELY(invflt != filters_end) - { - 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 LIKELY(context) - { - 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 UNLIKELY(!context) return; - +AL_API DECL_FUNC3(void, alFilteri, ALuint,filter, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, + ALint value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; + std::lock_guard filterlock{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - if(param == AL_FILTER_TYPE) - { - if(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS - || value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS) - InitFilterParams(alfilt, value); - else - context->setError(AL_INVALID_VALUE, "Invalid filter type 0x%04x", value); - } - else 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 UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->setParamiv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + /* 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 UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->setParamf(param, value); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } +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 UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->setParamfv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - const ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - if(param == AL_FILTER_TYPE) - *value = alfilt->type; - else 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 UNLIKELY(!context) return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; + 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 UNLIKELY(!alfilt) - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->getParamiv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC + if(!alfilt) + 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 + 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 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 + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + /* Call the appropriate handler */ + std::visit([alfilt,param,value](auto&& thunk){thunk.getParamf(alfilt, param, value);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetFilterfv, ALuint,filter, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, + ALfloat *values) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard 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 UNLIKELY(!context) return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; + std::lock_guard filterlock{device->FilterLock}; - const ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->getParamf(param, value); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + 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 UNLIKELY(!context) return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - const ALfilter *alfilt{LookupFilter(device, filter)}; - if UNLIKELY(!alfilt) - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->getParamfv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -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/libs/openal-soft/al/filter.h b/libs/openal-soft/al/filter.h index 65a9e30f..562ec52e 100644 --- a/libs/openal-soft/al/filter.h +++ b/libs/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/libs/openal-soft/al/listener.cpp b/libs/openal-soft/al/listener.cpp index 9484d9b1..30ceed04 100644 --- a/libs/openal-soft/al/listener.cpp +++ b/libs/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" @@ -48,402 +50,353 @@ inline void UpdateProps(ALCcontext *context) context->mPropsDirty = true; } -#ifdef ALSOFT_EAX inline void CommitAndUpdateProps(ALCcontext *context) { if(!context->mDeferUpdates) { - if(context->has_eax()) +#ifdef ALSOFT_EAX + if(context->eaxNeedsCommit()) { - context->mHoldUpdates.store(true, std::memory_order_release); - while((context->mUpdateCount.load(std::memory_order_acquire)&1) != 0) { - /* busy-wait */ - } - - context->eax_commit_and_update_sources(); + context->mPropsDirty = true; + context->applyAllUpdates(); + return; } +#endif UpdateContextProps(context); - context->mHoldUpdates.store(false, std::memory_order_release); return; } context->mPropsDirty = true; } -#else - -inline void CommitAndUpdateProps(ALCcontext *context) -{ UpdateProps(context); } -#endif - } // namespace -AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - +AL_API DECL_FUNC2(void, alListenerf, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alListenerfDirect(ALCcontext *context, ALenum param, ALfloat value) noexcept +try { ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_GAIN: if(!(value >= 0.0f && std::isfinite(value))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener gain out of range"}; listener.Gain = value; - UpdateProps(context.get()); - break; + UpdateProps(context); + return; case AL_METERS_PER_UNIT: if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT)) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener meters per unit out of range"}; listener.mMetersPerUnit = value; - UpdateProps(context.get()); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener float property"); + UpdateProps(context); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; +AL_API DECL_FUNC4(void, alListener3f, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) +FORCE_ALIGN void AL_APIENTRY alListener3fDirect(ALCcontext *context, ALenum param, ALfloat value1, + ALfloat value2, ALfloat value3) noexcept +try { ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_POSITION: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener position out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener position out of range"}; listener.Position[0] = value1; listener.Position[1] = value2; listener.Position[2] = value3; - CommitAndUpdateProps(context.get()); - break; + CommitAndUpdateProps(context); + return; case AL_VELOCITY: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener velocity out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener velocity out of range"}; listener.Velocity[0] = value1; listener.Velocity[1] = value2; listener.Velocity[2] = value3; - CommitAndUpdateProps(context.get()); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property"); + CommitAndUpdateProps(context); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values) -START_API_FUNC -{ - if(values) +AL_API DECL_FUNC2(void, alListenerfv, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum param, + const ALfloat *values) noexcept +try { + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { - switch(param) - { - case AL_GAIN: - case AL_METERS_PER_UNIT: - alListenerf(param, values[0]); - return; + case AL_GAIN: + case AL_METERS_PER_UNIT: + alListenerfDirect(context, param, *values); + return; - case AL_POSITION: - case AL_VELOCITY: - alListener3f(param, values[0], values[1], values[2]); - return; - } + case AL_POSITION: + case AL_VELOCITY: + auto vals = al::span{values, 3_uz}; + alListener3fDirect(context, param, vals[0], vals[1], vals[2]); + return; } - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; - if(!values) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer"); + 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]))) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener orientation out of range"); + 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property"); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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/libs/openal-soft/al/listener.h b/libs/openal-soft/al/listener.h index 81532877..7a4ff530 100644 --- a/libs/openal-soft/al/listener.h +++ b/libs/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/libs/openal-soft/al/source.cpp b/libs/openal-soft/al/source.cpp index d2ccfe08..d8c9b47f 100644 --- a/libs/openal-soft/al/source.cpp +++ b/libs/openal-soft/al/source.cpp @@ -25,21 +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 "AL/al.h" #include "AL/alc.h" @@ -47,39 +54,39 @@ #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 "eax_exception.h" -#endif // ALSOFT_EAX +#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; Voice *GetSourceVoice(ALsource *source, ALCcontext *context) { @@ -92,7 +99,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; } @@ -109,8 +116,8 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context VoicePropsItem *next; do { next = props->next.load(std::memory_order_relaxed); - } while(unlikely(context->mFreeVoiceProps.compare_exchange_weak(props, next, - std::memory_order_acq_rel, std::memory_order_acquire) == false)); + } while(context->mFreeVoiceProps.compare_exchange_weak(props, next, + std::memory_order_acq_rel, std::memory_order_acquire) == false); props->Pitch = source->Pitch; props->Gain = source->Gain; @@ -150,6 +157,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; @@ -160,7 +168,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context auto copy_send = [](const ALsource::SendData &srcsend) noexcept -> VoiceProps::SendData { VoiceProps::SendData ret{}; - ret.Slot = srcsend.Slot ? &srcsend.Slot->mSlot : nullptr; + ret.Slot = srcsend.Slot ? srcsend.Slot->mSlot : nullptr; ret.Gain = srcsend.Gain; ret.GainHF = srcsend.GainHF; ret.HFReference = srcsend.HFReference; @@ -168,9 +176,9 @@ 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; + props->Send[0].Slot = context->mDefaultSlot->mSlot; /* Set the new container for updating internal parameters. */ props = voice->mUpdate.exchange(props, std::memory_order_acq_rel); @@ -193,24 +201,23 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds { ALCdevice *device{context->mALDevice.get()}; const VoiceBufferItem *Current{}; - uint64_t readPos{}; - ALuint refcount; - Voice *voice; + int64_t readPos{}; + uint refcount{}; + Voice *voice{}; do { refcount = device->waitForMix(); - *clocktime = GetDeviceClockTime(device); + *clocktime = device->getClockTime(); voice = GetSourceVoice(Source, context); if(voice) { Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); - readPos = uint64_t{voice->mPosition.load(std::memory_order_relaxed)} << 32; - readPos |= uint64_t{voice->mPositionFrac.load(std::memory_order_relaxed)} << - (32-MixerFracBits); + readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits; + 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; @@ -218,9 +225,11 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds for(auto &item : Source->mQueue) { if(&item == Current) break; - readPos += uint64_t{item.mSampleLen} << 32; + readPos += int64_t{item.mSampleLen} << MixerFracBits; } - return static_cast(minu64(readPos, 0x7fffffffffffffff_u64)); + if(readPos > std::numeric_limits::max() >> (32-MixerFracBits)) + return std::numeric_limits::max(); + return readPos << (32-MixerFracBits); } /* GetSourceSecOffset @@ -232,33 +241,33 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl { ALCdevice *device{context->mALDevice.get()}; const VoiceBufferItem *Current{}; - uint64_t readPos{}; - ALuint refcount; - Voice *voice; + int64_t readPos{}; + uint refcount{}; + Voice *voice{}; do { refcount = device->waitForMix(); - *clocktime = GetDeviceClockTime(device); + *clocktime = device->getClockTime(); voice = GetSourceVoice(Source, context); if(voice) { Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); - readPos = uint64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits; - readPos |= voice->mPositionFrac.load(std::memory_order_relaxed); + readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits; + 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; const ALbuffer *BufferFmt{nullptr}; auto BufferList = Source->mQueue.cbegin(); - while(BufferList != Source->mQueue.cend() && std::addressof(*BufferList) != Current) + while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current) { if(!BufferFmt) BufferFmt = BufferList->mBuffer; - readPos += uint64_t{BufferList->mSampleLen} << MixerFracBits; + readPos += int64_t{BufferList->mSampleLen} << MixerFracBits; ++BufferList; } while(BufferList != Source->mQueue.cend() && !BufferFmt) @@ -277,13 +286,14 @@ 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{}; - ALuint readPos{}; - ALuint readPosFrac{}; - ALuint refcount; + int64_t readPos{}; + uint readPosFrac{}; + uint refcount; Voice *voice; do { @@ -297,14 +307,14 @@ 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(); - while(BufferList != Source->mQueue.cend() && std::addressof(*BufferList) != Current) + while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current) { if(!BufferFmt) BufferFmt = BufferList->mBuffer; readPos += BufferList->mSampleLen; @@ -317,40 +327,47 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) } ASSUME(BufferFmt != nullptr); - double offset{}; + T offset{}; switch(name) { case AL_SEC_OFFSET: - offset = (readPos + readPosFrac/double{MixerFracOne}) / BufferFmt->mSampleRate; - break; - - case AL_SAMPLE_OFFSET: - offset = readPos + readPosFrac/double{MixerFracOne}; - break; - - case AL_BYTE_OFFSET: - if(BufferFmt->OriginalType == UserFmtIMA4) + if constexpr(std::is_floating_point_v) { - ALuint FrameBlockSize{BufferFmt->OriginalAlign}; - ALuint align{(BufferFmt->OriginalAlign-1)/2 + 4}; - ALuint BlockSize{align * BufferFmt->channelsFromFmt()}; - - /* Round down to nearest ADPCM block */ - offset = static_cast(readPos / FrameBlockSize * BlockSize); - } - else if(BufferFmt->OriginalType == UserFmtMSADPCM) - { - ALuint FrameBlockSize{BufferFmt->OriginalAlign}; - ALuint align{(FrameBlockSize-2)/2 + 7}; - ALuint BlockSize{align * BufferFmt->channelsFromFmt()}; - - /* Round down to nearest ADPCM block */ - offset = static_cast(readPos / FrameBlockSize * BlockSize); + offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; + offset /= static_cast(BufferFmt->mSampleRate); } else { - const ALuint FrameSize{BufferFmt->frameSizeFromFmt()}; - offset = static_cast(readPos * FrameSize); + readPos /= BufferFmt->mSampleRate; + offset = static_cast(std::clamp(readPos, std::numeric_limits::min(), + std::numeric_limits::max())); + } + break; + + case AL_SAMPLE_OFFSET: + 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. */ + 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; } @@ -362,7 +379,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}; @@ -373,44 +391,46 @@ 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: - if(BufferFmt->OriginalType == UserFmtIMA4) - { - ALuint FrameBlockSize{BufferFmt->OriginalAlign}; - ALuint align{(BufferFmt->OriginalAlign-1)/2 + 4}; - ALuint BlockSize{align * BufferFmt->channelsFromFmt()}; + const ALuint BlockSamples{BufferFmt->mBlockAlign}; + const ALuint BlockSize{BufferFmt->blockSizeFromFmt()}; + /* Round down to the block boundary. */ + length = length / BlockSamples * BlockSize; - /* Round down to nearest ADPCM block */ - return static_cast(length / FrameBlockSize) * BlockSize; - } - else if(BufferFmt->OriginalType == UserFmtMSADPCM) + if constexpr(std::is_floating_point_v) + return static_cast(length); + else { - ALuint FrameBlockSize{BufferFmt->OriginalAlign}; - ALuint align{(FrameBlockSize-2)/2 + 7}; - ALuint BlockSize{align * BufferFmt->channelsFromFmt()}; - - /* Round down to nearest ADPCM block */ - return static_cast(length / FrameBlockSize) * BlockSize; + if(length > std::numeric_limits::max()) + return RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); + return static_cast(length); } - return static_cast(length) * BufferFmt->frameSizeFromFmt(); } - return 0.0; + return T{0}; } struct VoicePos { - ALuint pos, frac; + int pos; + uint frac; ALbufferQueueItem *bufferitem; }; @@ -418,11 +438,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}; @@ -431,49 +451,61 @@ al::optional GetSampleOffset(al::deque &BufferList, BufferFmt = item.mBuffer; if(BufferFmt) break; } - if(!BufferFmt || BufferFmt->mCallback) - return al::nullopt; + if(!BufferFmt) UNLIKELY + return std::nullopt; /* Get sample frame offset */ - ALuint offset{0u}, frac{0u}; + int64_t offset{}; + uint frac{}; double dbloff, dblfrac; switch(OffsetType) { case AL_SEC_OFFSET: dblfrac = std::modf(Offset*BufferFmt->mSampleRate, &dbloff); - offset = static_cast(mind(dbloff, std::numeric_limits::max())); - frac = static_cast(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); + if(dblfrac < 0.0) + { + /* If there's a negative fraction, reduce the offset to "floor" it, + * and convert the fraction to a percentage to the next value (e.g. + * -2.75 -> -3 + 0.25). + */ + dbloff -= 1.0; + dblfrac += 1.0; + } + offset = static_cast(dbloff); + frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); break; case AL_SAMPLE_OFFSET: dblfrac = std::modf(Offset, &dbloff); - offset = static_cast(mind(dbloff, std::numeric_limits::max())); - frac = static_cast(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); + if(dblfrac < 0.0) + { + dbloff -= 1.0; + dblfrac += 1.0; + } + offset = static_cast(dbloff); + frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); break; case AL_BYTE_OFFSET: /* Determine the ByteOffset (and ensure it is block aligned) */ - offset = static_cast(Offset); - if(BufferFmt->OriginalType == UserFmtIMA4) - { - const ALuint align{(BufferFmt->OriginalAlign-1)/2 + 4}; - offset /= align * BufferFmt->channelsFromFmt(); - offset *= BufferFmt->OriginalAlign; - } - else if(BufferFmt->OriginalType == UserFmtMSADPCM) - { - const ALuint align{(BufferFmt->OriginalAlign-2)/2 + 7}; - offset /= align * BufferFmt->channelsFromFmt(); - offset *= BufferFmt->OriginalAlign; - } - else - offset /= BufferFmt->frameSizeFromFmt(); + Offset = std::floor(Offset / BufferFmt->blockSizeFromFmt()); + offset = static_cast(Offset) * BufferFmt->mBlockAlign; frac = 0; break; } /* Find the bufferlist item this offset belongs to. */ - ALuint totalBufferLen{0u}; + if(offset < 0) + { + if(offset < std::numeric_limits::min()) + return std::nullopt; + return VoicePos{static_cast(offset), frac, &BufferList.front()}; + } + + if(BufferFmt->mCallback) + return std::nullopt; + + int64_t totalBufferLen{0}; for(auto &item : BufferList) { if(totalBufferLen > offset) @@ -481,13 +513,13 @@ al::optional GetSampleOffset(al::deque &BufferList, if(item.mSampleLen > offset-totalBufferLen) { /* Offset is in this buffer */ - return VoicePos{offset-totalBufferLen, frac, &item}; + return VoicePos{static_cast(offset-totalBufferLen), frac, &item}; } totalBufferLen += item.mSampleLen; } /* Offset is out of range of the queue */ - return al::nullopt; + return std::nullopt; } @@ -499,19 +531,24 @@ 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->mFrameSize = buffer->frameSizeFromFmt(); + voice->mBytesPerBlock = buffer->blockSizeFromFmt(); + voice->mSamplesPerBlock = buffer->mBlockAlign; voice->mAmbiLayout = IsUHJ(voice->mFmtChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout; voice->mAmbiScaling = IsUHJ(voice->mFmtChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling; voice->mAmbiOrder = (voice->mFmtChannels == FmtSuperStereo) ? 1 : buffer->mAmbiOrder; if(buffer->mCallback) voice->mFlags.set(VoiceIsCallback); else if(source->SourceType == AL_STATIC) voice->mFlags.set(VoiceIsStatic); - voice->mNumCallbackSamples = 0; + voice->mNumCallbackBlocks = 0; + voice->mCallbackBlockBase = 0; voice->prepare(device); @@ -525,7 +562,7 @@ void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, AL VoiceChange *GetVoiceChanger(ALCcontext *ctx) { VoiceChange *vchg{ctx->mVoiceChangeTail}; - if UNLIKELY(vchg == ctx->mCurrentVoiceChange.load(std::memory_order_acquire)) + if(vchg == ctx->mCurrentVoiceChange.load(std::memory_order_acquire)) UNLIKELY { ctx->allocVoiceChanges(); vchg = ctx->mVoiceChangeTail; @@ -546,8 +583,8 @@ 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(); - if UNLIKELY(!connected) + std::ignore = device->waitForMix(); + if(!connected) UNLIKELY { if(ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) { @@ -585,7 +622,7 @@ bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALC } ++vidx; } - if(unlikely(!newvoice)) + if(!newvoice) UNLIKELY { auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); if(allvoices.size() == voicelist.size()) @@ -619,8 +656,10 @@ bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALC newvoice->mPosition.store(vpos.pos, std::memory_order_relaxed); newvoice->mPositionFrac.store(vpos.frac, std::memory_order_relaxed); newvoice->mCurrentBuffer.store(vpos.bufferitem, std::memory_order_relaxed); + newvoice->mStartTime = oldvoice->mStartTime; newvoice->mFlags.reset(); - if(vpos.pos > 0 || vpos.frac > 0 || vpos.bufferitem != &source->mQueue.front()) + if(vpos.pos > 0 || (vpos.pos == 0 && vpos.frac > 0) + || vpos.bufferitem != &source->mQueue.front()) newvoice->mFlags.set(VoiceIsFading); InitVoice(newvoice, source, vpos.bufferitem, context, device); source->VoiceIdx = vidx; @@ -640,7 +679,7 @@ bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALC /* If the old voice still has a sourceID, it's still active and the change- * over will work on the next update. */ - if LIKELY(oldvoice->mSourceID.load(std::memory_order_acquire) != 0u) + if(oldvoice->mSourceID.load(std::memory_order_acquire) != 0u) LIKELY return true; /* Otherwise, if the new voice's state is not pending, the change-over @@ -650,7 +689,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 @@ -686,31 +725,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 UNLIKELY(context->mSourceList.size() >= 1<<25) - return false; - - context->mSourceList.emplace_back(); - auto sublist = context->mSourceList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Sources = static_cast(al_calloc(alignof(ALsource), sizeof(ALsource)*64)); - if UNLIKELY(!sublist->Sources) + 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 @@ -719,7 +757,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; @@ -732,6 +773,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}; @@ -748,7 +791,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--; @@ -760,64 +803,63 @@ inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; - if UNLIKELY(lidx >= context->mSourceList.size()) + if(lidx >= context->mSourceList.size()) UNLIKELY return nullptr; SourceSubList &sublist{context->mSourceList[lidx]}; - if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + 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 UNLIKELY(lidx >= device->BufferList.size()) + if(lidx >= device->BufferList.size()) UNLIKELY return nullptr; - BufferSubList &sublist = device->BufferList[lidx]; - if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + 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 UNLIKELY(lidx >= device->FilterList.size()) + if(lidx >= device->FilterList.size()) UNLIKELY return nullptr; - FilterSubList &sublist = device->FilterList[lidx]; - if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + 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 UNLIKELY(lidx >= context->mEffectSlotList.size()) + if(lidx >= context->mEffectSlotList.size()) UNLIKELY return nullptr; - EffectSlotSubList &sublist{context->mEffectSlotList[lidx]}; - if UNLIKELY(sublist.FreeMask & (1_u64 << slidx)) + 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 al::make_optional(SourceStereo::Normal); - case AL_SUPER_STEREO_SOFT: return al::make_optional(SourceStereo::Enhanced); + 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) @@ -828,17 +870,16 @@ 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) { - case AL_FALSE: return al::make_optional(SpatializeMode::Off); - case AL_TRUE: return al::make_optional(SpatializeMode::On); - case AL_AUTO_SOFT: return al::make_optional(SpatializeMode::Auto); + case AL_FALSE: return SpatializeMode::Off; + 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) @@ -850,17 +891,16 @@ 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) { - case AL_FALSE: return al::make_optional(DirectMode::Off); - case AL_DROP_UNMATCHED_SOFT: return al::make_optional(DirectMode::DropMismatch); - case AL_REMIX_UNMATCHED_SOFT: return al::make_optional(DirectMode::RemixMismatch); + case AL_FALSE: return DirectMode::Off; + 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) @@ -872,20 +912,20 @@ 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) { - case AL_NONE: return al::make_optional(DistanceModel::Disable); - case AL_INVERSE_DISTANCE: return al::make_optional(DistanceModel::Inverse); - case AL_INVERSE_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::InverseClamped); - case AL_LINEAR_DISTANCE: return al::make_optional(DistanceModel::Linear); - case AL_LINEAR_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::LinearClamped); - case AL_EXPONENT_DISTANCE: return al::make_optional(DistanceModel::Exponent); - case AL_EXPONENT_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::ExponentClamped); + case AL_NONE: return DistanceModel::Disable; + case AL_INVERSE_DISTANCE: return DistanceModel::Inverse; + case AL_INVERSE_DISTANCE_CLAMPED: return DistanceModel::InverseClamped; + case AL_LINEAR_DISTANCE: return DistanceModel::Linear; + case AL_LINEAR_DISTANCE_CLAMPED: return DistanceModel::LinearClamped; + 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) @@ -976,12 +1016,177 @@ enum SourceProp : ALenum { /* AL_SOFT_UHJ */ srcStereoMode = AL_STEREO_MODE_SOFT, srcSuperStereoWidth = AL_SUPER_STEREO_WIDTH_SOFT, + + /* 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)) + { + 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: + case AL_SOURCE_RELATIVE: + case AL_LOOPING: + case AL_BUFFER: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + case AL_DIRECT_FILTER: + 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: + case AL_PANNING_ENABLED_SOFT: + case AL_PAN_SOFT: + return 1; -ALuint FloatValsByProp(ALenum prop) + case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ + if(sBufferSubDataCompat) + return 2; + /*fall-through*/ + 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_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: + return 1; /* 1x float */ + + case AL_SAMPLE_RW_OFFSETS_SOFT: + if(sBufferSubDataCompat) + return 2; + break; + + case AL_AUXILIARY_SEND_FILTER: + return 3; + + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + return 3; /* 3x float */ + + case AL_ORIENTATION: + return 6; /* 6x float */ + + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + case AL_STEREO_ANGLES: + break; /* i64 only */ + case AL_SEC_OFFSET_LATENCY_SOFT: + case AL_SEC_OFFSET_CLOCK_SOFT: + break; /* double only */ + } + + return 0; +} + +constexpr ALuint Int64ValsByProp(ALenum prop) +{ + switch(static_cast(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: + case AL_SOURCE_RELATIVE: + case AL_LOOPING: + case AL_BUFFER: + case AL_SAMPLE_OFFSET: + case AL_BYTE_OFFSET: + case AL_DIRECT_FILTER: + 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: + case AL_PANNING_ENABLED_SOFT: + case AL_PAN_SOFT: + return 1; + + case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ + if(sBufferSubDataCompat) + return 2; + /*fall-through*/ + 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_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: + return 1; /* 1x float */ + + case AL_SAMPLE_RW_OFFSETS_SOFT: + if(sBufferSubDataCompat) + return 2; + break; + + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + case AL_STEREO_ANGLES: + return 2; + + case AL_AUXILIARY_SEND_FILTER: + return 3; + + case AL_POSITION: + case AL_VELOCITY: + case AL_DIRECTION: + return 3; /* 3x float */ + + case AL_ORIENTATION: + return 6; /* 6x float */ + + case AL_SEC_OFFSET_LATENCY_SOFT: + case AL_SEC_OFFSET_CLOCK_SOFT: + break; /* double only */ + } + + return 0; +} + +constexpr ALuint FloatValsByProp(ALenum prop) { switch(static_cast(prop)) { @@ -1013,7 +1218,6 @@ ALuint FloatValsByProp(ALenum prop) case AL_BUFFERS_QUEUED: case AL_BUFFERS_PROCESSED: case AL_SOURCE_TYPE: - case AL_SOURCE_RADIUS: case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_BYTE_LENGTH_SOFT: @@ -1021,8 +1225,17 @@ 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:*/ + if(!sBufferSubDataCompat) + return 1; + /*fall-through*/ + case AL_SAMPLE_RW_OFFSETS_SOFT: + break; + case AL_STEREO_ANGLES: return 2; @@ -1048,7 +1261,7 @@ ALuint FloatValsByProp(ALenum prop) } return 0; } -ALuint DoubleValsByProp(ALenum prop) +constexpr ALuint DoubleValsByProp(ALenum prop) { switch(static_cast(prop)) { @@ -1080,7 +1293,6 @@ ALuint DoubleValsByProp(ALenum prop) case AL_BUFFERS_QUEUED: case AL_BUFFERS_PROCESSED: case AL_SOURCE_TYPE: - case AL_SOURCE_RADIUS: case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_BYTE_LENGTH_SOFT: @@ -1088,8 +1300,17 @@ 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:*/ + if(!sBufferSubDataCompat) + return 1; + /*fall-through*/ + case AL_SAMPLE_RW_OFFSETS_SOFT: + break; + case AL_SEC_OFFSET_LATENCY_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: case AL_STEREO_ANGLES: @@ -1115,23 +1336,6 @@ ALuint DoubleValsByProp(ALenum prop) } -void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); -void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); -void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); - -#define CHECKSIZE(v, s) do { \ - if LIKELY((v).size() == (s) || (v).size() == MaxValues) break; \ - Context->setError(AL_INVALID_ENUM, \ - "Property 0x%04x expects %d value(s), got %zu", prop, (s), \ - (v).size()); \ - return; \ -} while(0) -#define CHECKVAL(x) do { \ - if LIKELY(x) break; \ - Context->setError(AL_INVALID_VALUE, "Value out of range"); \ - return; \ -} while(0) - void UpdateSourceProps(ALsource *source, ALCcontext *context) { if(!context->mDeferUpdates) @@ -1149,8 +1353,8 @@ void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) { if(!context->mDeferUpdates) { - if(source->eax_is_initialized()) - source->eax_commit(); + if(context->hasEax()) + source->eaxCommit(); if(Voice *voice{GetSourceVoice(source, context)}) { UpdateSourceProps(source, voice, context); @@ -1167,1882 +1371,1131 @@ inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) #endif -void SetSourcefv(ALsource *Source, ALCcontext *Context, SourceProp prop, - const al::span values) +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 setter. + * + * 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. + * + * The second lambda tests the validity of the value check, setting the proper + * context error and throwing a check_value_exception if it failed. + */ +template +auto GetCheckers(const SourceProp prop, const al::span values) { - int ival; + return std::make_pair( + [=](size_t expect) -> void + { + 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()}; + }, + [](bool passed) -> void + { + if(passed) return; + throw al::context_error{AL_INVALID_VALUE, "Value out of range"}; + } + ); +} + +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 */ - SETERR_RETURN(Context, 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(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + 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(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 360.0f); + CheckSize(1); + 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(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 360.0f); + CheckSize(1); + 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(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + CheckValue(values[0] >= T{0}); - Source->Gain = values[0]; + Source->Gain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_DISTANCE: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + CheckValue(values[0] >= T{0}); - Source->MaxDistance = values[0]; + Source->MaxDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_ROLLOFF_FACTOR: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + CheckValue(values[0] >= T{0}); - Source->RolloffFactor = values[0]; + Source->RolloffFactor = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_REFERENCE_DISTANCE: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + CheckValue(values[0] >= T{0}); - Source->RefDistance = values[0]; + Source->RefDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_MIN_GAIN: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + CheckValue(values[0] >= T{0}); - Source->MinGain = values[0]; + Source->MinGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_GAIN: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + 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(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); + CheckSize(1); + 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(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); + CheckSize(1); + 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(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 10.0f); + CheckSize(1); + 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(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 10.0f); + CheckSize(1); + 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(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); + CheckSize(1); + 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(values, 1); - CHECKVAL(values[0] >= 0.0f); + CheckSize(1); + 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) SETERR_RETURN(Context, 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_SOURCE_RADIUS: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f && std::isfinite(values[0])); - - Source->Radius = values[0]; - return UpdateSourceProps(Source, Context); - - case AL_SUPER_STEREO_WIDTH_SOFT: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0.0f && values[0] <= 1.0f); - - Source->EnhWidth = values[0]; - return UpdateSourceProps(Source, Context); - - case AL_STEREO_ANGLES: - CHECKSIZE(values, 2); - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1])); - - Source->StereoPan[0] = values[0]; - Source->StereoPan[1] = values[1]; - return UpdateSourceProps(Source, Context); - - - case AL_POSITION: - CHECKSIZE(values, 3); - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); - - Source->Position[0] = values[0]; - Source->Position[1] = values[1]; - Source->Position[2] = values[2]; - return CommitAndUpdateSourceProps(Source, Context); - - case AL_VELOCITY: - CHECKSIZE(values, 3); - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); - - Source->Velocity[0] = values[0]; - Source->Velocity[1] = values[1]; - Source->Velocity[2] = values[2]; - return CommitAndUpdateSourceProps(Source, Context); - - case AL_DIRECTION: - CHECKSIZE(values, 3); - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); - - Source->Direction[0] = values[0]; - Source->Direction[1] = values[1]; - Source->Direction[2] = values[2]; - return CommitAndUpdateSourceProps(Source, Context); - - case AL_ORIENTATION: - CHECKSIZE(values, 6); - CHECKVAL(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) - && std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])); - - Source->OrientAt[0] = values[0]; - Source->OrientAt[1] = values[1]; - Source->OrientAt[2] = values[2]; - Source->OrientUp[0] = values[3]; - Source->OrientUp[1] = values[4]; - Source->OrientUp[2] = values[5]; - return UpdateSourceProps(Source, Context); - - - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SOURCE_STATE: - case AL_SOURCE_TYPE: - case AL_DISTANCE_MODEL: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_STEREO_MODE_SOFT: - CHECKSIZE(values, 1); - ival = static_cast(values[0]); - return SetSourceiv(Source, Context, prop, {&ival, 1u}); - - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - CHECKSIZE(values, 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); - return; -} - -void SetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, - const al::span values) -{ - 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 */ - SETERR_RETURN(Context, AL_INVALID_OPERATION,, - "Setting read-only source property 0x%04x", prop); - - case AL_SOURCE_RELATIVE: - CHECKSIZE(values, 1); - CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->HeadRelative = values[0] != AL_FALSE; - return CommitAndUpdateSourceProps(Source, Context); - - case AL_LOOPING: - CHECKSIZE(values, 1); - CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->Looping = values[0] != AL_FALSE; - if(Voice *voice{GetSourceVoice(Source, Context)}) + case AL_SAMPLE_RW_OFFSETS_SOFT: + if(sBufferSubDataCompat) { - 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. - */ - device->waitForMix(); - } - return; - - case AL_BUFFER: - CHECKSIZE(values, 1); - { - const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; - if(state == AL_PLAYING || state == AL_PAUSED) - SETERR_RETURN(Context, AL_INVALID_OPERATION,, - "Setting buffer on playing or paused source %u", Source->id); - } - if(values[0]) - { - std::lock_guard _{device->BufferLock}; - ALbuffer *buffer{LookupBuffer(device, static_cast(values[0]))}; - if(!buffer) - SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid buffer ID %u", - static_cast(values[0])); - if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - SETERR_RETURN(Context, AL_INVALID_OPERATION,, - "Setting non-persistently mapped buffer %u", buffer->id); - if(buffer->mCallback && ReadRef(buffer->ref) != 0) - SETERR_RETURN(Context, AL_INVALID_OPERATION,, - "Setting already-set callback buffer %u", buffer->id); - - /* 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().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); - } - 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; - - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - CHECKSIZE(values, 1); - CHECKVAL(values[0] >= 0); - - if(Voice *voice{GetSourceVoice(Source, Context)}) - { - auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]); - if(!vpos) SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid source offset"); - - if(SetVoiceOffset(voice, *vpos, Source, Context, device)) - return; - } - Source->OffsetType = prop; - Source->Offset = values[0]; - return; - - case AL_DIRECT_FILTER: - CHECKSIZE(values, 1); - if(values[0]) - { - std::lock_guard _{device->FilterLock}; - ALfilter *filter{LookupFilter(device, static_cast(values[0]))}; - if(!filter) - SETERR_RETURN(Context, 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); - - case AL_DIRECT_FILTER_GAINHF_AUTO: - CHECKSIZE(values, 1); - CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->DryGainHFAuto = values[0] != AL_FALSE; - return UpdateSourceProps(Source, Context); - - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - CHECKSIZE(values, 1); - CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->WetGainAuto = values[0] != AL_FALSE; - return UpdateSourceProps(Source, Context); - - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - CHECKSIZE(values, 1); - CHECKVAL(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->WetGainHFAuto = values[0] != AL_FALSE; - return UpdateSourceProps(Source, Context); - - case AL_DIRECT_CHANNELS_SOFT: - CHECKSIZE(values, 1); - if(auto mode = DirectModeFromEnum(values[0])) - { - Source->DirectChannels = *mode; - return UpdateSourceProps(Source, Context); - } - Context->setError(AL_INVALID_VALUE, "Unsupported AL_DIRECT_CHANNELS_SOFT: 0x%04x\n", - values[0]); - return; - - case AL_DISTANCE_MODEL: - CHECKSIZE(values, 1); - if(auto model = DistanceModelFromALenum(values[0])) - { - Source->mDistanceModel = *model; - if(Context->mSourceDistanceModel) - 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(values, 1); - CHECKVAL(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(values, 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(values, 1); - { - const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; - if(state == AL_PLAYING || state == AL_PAUSED) - SETERR_RETURN(Context, 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(values, 3); - slotlock = std::unique_lock{Context->mEffectSlotLock}; - if(values[0] && (slot=LookupEffectSlot(Context, static_cast(values[0]))) == nullptr) - SETERR_RETURN(Context, AL_INVALID_VALUE,, "Invalid effect ID %u", values[0]); - if(static_cast(values[1]) >= device->NumAuxSends) - SETERR_RETURN(Context, 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) - SETERR_RETURN(Context, 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; - } - - if(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; - - /* We must force an update if the auxiliary slot changed on an - * active source, in case the slot is about to be deleted. - */ - 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; - - - /* 1x float */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_MAX_DISTANCE: - case AL_DOPPLER_FACTOR: - case AL_CONE_OUTER_GAINHF: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_SOURCE_RADIUS: - case AL_SEC_LENGTH_SOFT: - case AL_SUPER_STEREO_WIDTH_SOFT: - CHECKSIZE(values, 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(values, 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(values, 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); - return; -} - -void SetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, - const al::span values) -{ - 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 */ - SETERR_RETURN(Context, 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(values, 1); - CHECKVAL(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(values, 1); - CHECKVAL(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(values, 3); - CHECKVAL(values[0] <= UINT_MAX && values[0] >= 0 && values[1] <= UINT_MAX && values[1] >= 0 - && values[2] <= UINT_MAX && values[2] >= 0); - - ivals[0] = static_cast(values[0]); - ivals[1] = static_cast(values[1]); - ivals[2] = static_cast(values[2]); - return SetSourceiv(Source, Context, prop, {ivals, 3u}); - - /* 1x float */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_MAX_DISTANCE: - case AL_DOPPLER_FACTOR: - case AL_CONE_OUTER_GAINHF: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_SOURCE_RADIUS: - case AL_SEC_LENGTH_SOFT: - case AL_SUPER_STEREO_WIDTH_SOFT: - CHECKSIZE(values, 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(values, 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(values, 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); - return; -} - -#undef CHECKVAL -#undef CHECKSIZE - -#define CHECKSIZE(v, s) do { \ - if LIKELY((v).size() == (s) || (v).size() == MaxValues) break; \ - Context->setError(AL_INVALID_ENUM, \ - "Property 0x%04x expects %d value(s), got %zu", prop, (s), \ - (v).size()); \ - return false; \ -} while(0) - -bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); -bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); -bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values); - -bool GetSourcedv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) -{ - ALCdevice *device{Context->mALDevice.get()}; - ClockLatency clocktime; - nanoseconds srcclock; - int ivals[MaxValues]; - bool err; - - switch(prop) - { - case AL_GAIN: - CHECKSIZE(values, 1); - values[0] = Source->Gain; - return true; - - case AL_PITCH: - CHECKSIZE(values, 1); - values[0] = Source->Pitch; - return true; - - case AL_MAX_DISTANCE: - CHECKSIZE(values, 1); - values[0] = Source->MaxDistance; - return true; - - case AL_ROLLOFF_FACTOR: - CHECKSIZE(values, 1); - values[0] = Source->RolloffFactor; - return true; - - case AL_REFERENCE_DISTANCE: - CHECKSIZE(values, 1); - values[0] = Source->RefDistance; - return true; - - case AL_CONE_INNER_ANGLE: - CHECKSIZE(values, 1); - values[0] = Source->InnerAngle; - return true; - - case AL_CONE_OUTER_ANGLE: - CHECKSIZE(values, 1); - values[0] = Source->OuterAngle; - return true; - - case AL_MIN_GAIN: - CHECKSIZE(values, 1); - values[0] = Source->MinGain; - return true; - - case AL_MAX_GAIN: - CHECKSIZE(values, 1); - values[0] = Source->MaxGain; - return true; - - case AL_CONE_OUTER_GAIN: - CHECKSIZE(values, 1); - values[0] = Source->OuterGain; - return true; - - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - CHECKSIZE(values, 1); - values[0] = GetSourceOffset(Source, prop, Context); - return true; - - case AL_CONE_OUTER_GAINHF: - CHECKSIZE(values, 1); - values[0] = Source->OuterGainHF; - return true; - - case AL_AIR_ABSORPTION_FACTOR: - CHECKSIZE(values, 1); - values[0] = Source->AirAbsorptionFactor; - return true; - - case AL_ROOM_ROLLOFF_FACTOR: - CHECKSIZE(values, 1); - values[0] = Source->RoomRolloffFactor; - return true; - - case AL_DOPPLER_FACTOR: - CHECKSIZE(values, 1); - values[0] = Source->DopplerFactor; - return true; - - case AL_SOURCE_RADIUS: - CHECKSIZE(values, 1); - values[0] = Source->Radius; - return true; - - case AL_SUPER_STEREO_WIDTH_SOFT: - CHECKSIZE(values, 1); - values[0] = Source->EnhWidth; - return true; - - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_SEC_LENGTH_SOFT: - CHECKSIZE(values, 1); - values[0] = GetSourceLength(Source, prop); - return true; - - case AL_STEREO_ANGLES: - CHECKSIZE(values, 2); - values[0] = Source->StereoPan[0]; - values[1] = Source->StereoPan[1]; - return true; - - case AL_SEC_OFFSET_LATENCY_SOFT: - CHECKSIZE(values, 2); - /* Get the source offset with the clock time first. Then get the clock - * time with the device latency. Order is important. - */ - values[0] = GetSourceSecOffset(Source, Context, &srcclock); - { - std::lock_guard _{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. - */ - const nanoseconds diff{clocktime.ClockTime - srcclock}; - const nanoseconds latency{clocktime.Latency - std::min(clocktime.Latency, diff)}; - values[1] = static_cast(latency.count()) / 1000000000.0; - } - return true; - - case AL_SEC_OFFSET_CLOCK_SOFT: - CHECKSIZE(values, 2); - values[0] = GetSourceSecOffset(Source, Context, &srcclock); - values[1] = static_cast(srcclock.count()) / 1000000000.0; - return true; - - case AL_POSITION: - CHECKSIZE(values, 3); - values[0] = Source->Position[0]; - values[1] = Source->Position[1]; - values[2] = Source->Position[2]; - return true; - - case AL_VELOCITY: - CHECKSIZE(values, 3); - values[0] = Source->Velocity[0]; - values[1] = Source->Velocity[1]; - values[2] = Source->Velocity[2]; - return true; - - case AL_DIRECTION: - CHECKSIZE(values, 3); - values[0] = Source->Direction[0]; - values[1] = Source->Direction[1]; - values[2] = Source->Direction[2]; - return true; - - case AL_ORIENTATION: - CHECKSIZE(values, 6); - values[0] = Source->OrientAt[0]; - values[1] = Source->OrientAt[1]; - values[2] = Source->OrientAt[2]; - values[3] = Source->OrientUp[0]; - values[4] = Source->OrientUp[1]; - values[5] = Source->OrientUp[2]; - return true; - - /* 1x int */ - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SOURCE_STATE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_TYPE: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_DISTANCE_MODEL: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - case AL_STEREO_MODE_SOFT: - CHECKSIZE(values, 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; -} - -bool GetSourceiv(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) -{ - double dvals[MaxValues]; - bool err; - - switch(prop) - { - case AL_SOURCE_RELATIVE: - CHECKSIZE(values, 1); - values[0] = Source->HeadRelative; - return true; - - case AL_LOOPING: - CHECKSIZE(values, 1); - values[0] = Source->Looping; - return true; - - case AL_BUFFER: - CHECKSIZE(values, 1); - { - ALbufferQueueItem *BufferList{(Source->SourceType == AL_STATIC) - ? &Source->mQueue.front() : nullptr}; - ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr}; - values[0] = buffer ? static_cast(buffer->id) : 0; - } - return true; - - case AL_SOURCE_STATE: - CHECKSIZE(values, 1); - values[0] = GetSourceState(Source, GetSourceVoice(Source, Context)); - return true; - - case AL_BUFFERS_QUEUED: - CHECKSIZE(values, 1); - values[0] = static_cast(Source->mQueue.size()); - return true; - - case AL_BUFFERS_PROCESSED: - CHECKSIZE(values, 1); - if(Source->Looping || Source->SourceType != AL_STREAMING) - { - /* Buffers on a looping source are in a perpetual state of PENDING, - * so don't report any as PROCESSED - */ - values[0] = 0; - } - else - { - int played{0}; - if(Source->state != AL_INITIAL) + if constexpr(std::is_integral_v) { - 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; - } + /* Query only */ + throw al::context_error{AL_INVALID_OPERATION, + "Setting read-only source property 0x%04x", prop}; } - values[0] = played; } - return true; + break; - case AL_SOURCE_TYPE: - CHECKSIZE(values, 1); - values[0] = Source->SourceType; - return true; + 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); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); - case AL_DIRECT_FILTER_GAINHF_AUTO: - CHECKSIZE(values, 1); - values[0] = Source->DryGainHFAuto; - return true; + Source->Radius = static_cast(values[0]); + return UpdateSourceProps(Source, Context); - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - CHECKSIZE(values, 1); - values[0] = Source->WetGainAuto; - return true; - - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - CHECKSIZE(values, 1); - values[0] = Source->WetGainHFAuto; - return true; - - case AL_DIRECT_CHANNELS_SOFT: - CHECKSIZE(values, 1); - values[0] = EnumFromDirectMode(Source->DirectChannels); - return true; - - case AL_DISTANCE_MODEL: - CHECKSIZE(values, 1); - values[0] = ALenumFromDistanceModel(Source->mDistanceModel); - return true; - - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_SEC_LENGTH_SOFT: - CHECKSIZE(values, 1); - values[0] = static_cast(mind(GetSourceLength(Source, prop), - std::numeric_limits::max())); - return true; - - case AL_SOURCE_RESAMPLER_SOFT: - CHECKSIZE(values, 1); - values[0] = static_cast(Source->mResampler); - return true; - - case AL_SOURCE_SPATIALIZE_SOFT: - CHECKSIZE(values, 1); - values[0] = EnumFromSpatializeMode(Source->mSpatialize); - return true; - - case AL_STEREO_MODE_SOFT: - CHECKSIZE(values, 1); - values[0] = EnumFromStereoMode(Source->mStereoMode); - return true; - - /* 1x float/double */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_MAX_DISTANCE: - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - case AL_DOPPLER_FACTOR: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAINHF: - case AL_SOURCE_RADIUS: case AL_SUPER_STEREO_WIDTH_SOFT: - CHECKSIZE(values, 1); - if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) - values[0] = static_cast(dvals[0]); - return err; + CheckSize(1); + CheckValue(values[0] >= T{0} && values[0] <= T{1}); - /* 3x float/double */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - CHECKSIZE(values, 3); - if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false) - { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); - } - return err; + Source->EnhWidth = static_cast(values[0]); + return UpdateSourceProps(Source, Context); - /* 6x float/double */ - case AL_ORIENTATION: - CHECKSIZE(values, 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; + 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_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 */ + CheckSize(2); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(static_cast(values[0])) + && std::isfinite(static_cast(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); + 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] = 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); + 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] = 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); + 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] = 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); + 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] = 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_DIRECT_FILTER: + if constexpr(std::is_integral_v) + { + 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 + { + 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); + } + break; + + case AL_DIRECT_FILTER_GAINHF_AUTO: + if constexpr(std::is_integral_v) + { + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->DryGainHFAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); + } + break; + + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + if constexpr(std::is_integral_v) + { + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->WetGainAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); + } + break; + + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + if constexpr(std::is_integral_v) + { + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->WetGainHFAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); + } + break; + + case AL_DIRECT_CHANNELS_SOFT: + if constexpr(std::is_integral_v) + { + 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()}; + } + break; + + case AL_DISTANCE_MODEL: + if constexpr(std::is_integral_v) + { + 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()}; + } + break; + + case AL_SOURCE_RESAMPLER_SOFT: + if constexpr(std::is_integral_v) + { + CheckSize(1); + CheckValue(values[0] >= 0 && values[0] <= static_cast(Resampler::Max)); + + Source->mResampler = static_cast(values[0]); + return UpdateSourceProps(Source, Context); + } + break; + + case AL_SOURCE_SPATIALIZE_SOFT: + if constexpr(std::is_integral_v) + { + 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: - break; /* ??? */ + 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; + } + break; } - ERR("Unexpected property: 0x%04x\n", prop); - Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop); - return false; + 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}; } -bool GetSourcei64v(ALsource *Source, ALCcontext *Context, SourceProp prop, const al::span values) + +template +auto GetSizeChecker(const SourceProp prop, const al::span values) { + return [=](size_t expect) -> void + { + if(values.size() == expect) LIKELY return; + throw al::context_error{AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu", + prop, expect, values.size()}; + }; +} + +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; - 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(values, 1); - values[0] = static_cast(GetSourceLength(Source, prop)); - return true; - - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - CHECKSIZE(values, 2); - /* Get the source offset with the clock time first. Then get the clock - * time with the device latency. Order is important. - */ - values[0] = GetSourceSampleOffset(Source, Context, &srcclock); - { - std::lock_guard _{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(values, 2); - values[0] = GetSourceSampleOffset(Source, Context, &srcclock); - values[1] = srcclock.count(); - return true; - - /* 1x float/double */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: + CheckSize(1); + values[0] = static_cast(Source->Gain); + return; + + case AL_PITCH: + CheckSize(1); + values[0] = static_cast(Source->Pitch); + return; + case AL_MAX_DISTANCE: + CheckSize(1); + values[0] = static_cast(Source->MaxDistance); + return; + + case AL_ROLLOFF_FACTOR: + CheckSize(1); + values[0] = static_cast(Source->RolloffFactor); + return; + + case AL_REFERENCE_DISTANCE: + CheckSize(1); + values[0] = static_cast(Source->RefDistance); + return; + + case AL_CONE_INNER_ANGLE: + CheckSize(1); + values[0] = static_cast(Source->InnerAngle); + return; + + case AL_CONE_OUTER_ANGLE: + CheckSize(1); + values[0] = static_cast(Source->OuterAngle); + return; + + case AL_MIN_GAIN: + CheckSize(1); + values[0] = static_cast(Source->MinGain); + return; + + case AL_MAX_GAIN: + CheckSize(1); + values[0] = static_cast(Source->MaxGain); + return; + + case AL_CONE_OUTER_GAIN: + CheckSize(1); + values[0] = static_cast(Source->OuterGain); + return; + 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: + CheckSize(1); + values[0] = GetSourceOffset(Source, prop, Context); + return; + case AL_CONE_OUTER_GAINHF: - case AL_SOURCE_RADIUS: + CheckSize(1); + values[0] = static_cast(Source->OuterGainHF); + return; + + case AL_AIR_ABSORPTION_FACTOR: + CheckSize(1); + values[0] = static_cast(Source->AirAbsorptionFactor); + return; + + case AL_ROOM_ROLLOFF_FACTOR: + CheckSize(1); + values[0] = static_cast(Source->RoomRolloffFactor); + return; + + case AL_DOPPLER_FACTOR: + CheckSize(1); + 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 constexpr(std::is_floating_point_v) + { + if(sBufferSubDataCompat) + break; + + 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(values, 1); - if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) - values[0] = static_cast(dvals[0]); - return err; + CheckSize(1); + values[0] = static_cast(Source->EnhWidth); + return; - /* 3x float/double */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - CHECKSIZE(values, 3); - if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false) + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: + case AL_SEC_LENGTH_SOFT: + CheckSize(1); + 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: + if constexpr(std::is_floating_point_v) { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); + CheckSize(2); + values[0] = static_cast(Source->StereoPan[0]); + values[1] = static_cast(Source->StereoPan[1]); + return; } - return err; + break; - /* 6x float/double */ - case AL_ORIENTATION: - CHECKSIZE(values, 6); - if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false) + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + if constexpr(std::is_same_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(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; } - return err; + break; - /* 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(values, 1); - if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) - values[0] = ivals[0]; - return err; - - /* 1x uint */ - case AL_BUFFER: - case AL_DIRECT_FILTER: - CHECKSIZE(values, 1); - if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) - values[0] = static_cast(ivals[0]); - return err; - - /* 3x uint */ - case AL_AUXILIARY_SEND_FILTER: - CHECKSIZE(values, 3); - if((err=GetSourceiv(Source, Context, prop, {ivals, 3u})) != false) + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + if constexpr(std::is_same_v) { - values[0] = static_cast(ivals[0]); - values[1] = static_cast(ivals[1]); - values[2] = static_cast(ivals[2]); + CheckSize(2); + nanoseconds srcclock{}; + values[0] = GetSourceSampleOffset(Source, Context, &srcclock); + values[1] = srcclock.count(); + return; } - return err; + break; case AL_SEC_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] = 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; + } + break; + case AL_SEC_OFFSET_CLOCK_SOFT: - break; /* Double only */ - case AL_STEREO_ANGLES: - break; /* Float/double only */ - } + if constexpr(std::is_same_v) + { + CheckSize(2); + nanoseconds srcclock{}; + values[0] = GetSourceSecOffset(Source, Context, &srcclock); + values[1] = duration_cast(srcclock).count(); + return; + } + break; - ERR("Unexpected property: 0x%04x\n", prop); - Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop); - return false; -} - -} // namespace - -AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Generating %d sources", n); - if UNLIKELY(n <= 0) return; - -#ifdef ALSOFT_EAX - const bool has_eax{context->has_eax()}; - std::unique_lock proplock{}; - if(has_eax) - proplock = std::unique_lock{context->mPropLock}; -#endif - 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); + case AL_POSITION: + CheckSize(3); + values[0] = static_cast(Source->Position[0]); + values[1] = static_cast(Source->Position[1]); + values[2] = static_cast(Source->Position[2]); return; - } - if(!EnsureSources(context.get(), static_cast(n))) - { - context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n, (n==1)?"":"s"); + + case AL_VELOCITY: + CheckSize(3); + values[0] = static_cast(Source->Velocity[0]); + values[1] = static_cast(Source->Velocity[1]); + values[2] = static_cast(Source->Velocity[2]); return; - } - if(n == 1) - { - ALsource *source{AllocSource(context.get())}; - sources[0] = source->id; - -#ifdef ALSOFT_EAX - if(has_eax) - source->eax_initialize(context.get()); -#endif // ALSOFT_EAX - } - else - { -#ifdef ALSOFT_EAX - auto eax_sources = al::vector{}; - if(has_eax) - eax_sources.reserve(static_cast(n)); -#endif // ALSOFT_EAX - - al::vector ids; - ids.reserve(static_cast(n)); - do { - ALsource *source{AllocSource(context.get())}; - ids.emplace_back(source->id); - -#ifdef ALSOFT_EAX - if(has_eax) - eax_sources.emplace_back(source); -#endif // ALSOFT_EAX - } while(--n); - std::copy(ids.cbegin(), ids.cend(), sources); - -#ifdef ALSOFT_EAX - for(auto& eax_source : eax_sources) - eax_source->eax_initialize(context.get()); -#endif // ALSOFT_EAX - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - if UNLIKELY(n < 0) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Deleting %d sources", n); - - std::lock_guard _{context->mSourceLock}; - - /* Check that all Sources are valid */ - auto validate_source = [&context](const ALuint sid) -> bool - { return LookupSource(context.get(), sid) != nullptr; }; - - const ALuint *sources_end = sources + n; - auto invsrc = std::find_if_not(sources, sources_end, validate_source); - if UNLIKELY(invsrc != sources_end) - { - context->setError(AL_INVALID_NAME, "Invalid source ID %u", *invsrc); + case AL_DIRECTION: + CheckSize(3); + values[0] = static_cast(Source->Direction[0]); + values[1] = static_cast(Source->Direction[1]); + values[2] = static_cast(Source->Direction[2]); return; - } - /* All good. Delete source IDs. */ - auto delete_source = [&context](const ALuint sid) -> void - { - ALsource *src{LookupSource(context.get(), sid)}; - if(src) FreeSource(context.get(), src); - }; - std::for_each(sources, sources_end, delete_source); -} -END_API_FUNC - -AL_API ALboolean AL_APIENTRY alIsSource(ALuint source) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if LIKELY(context) - { - std::lock_guard _{context->mSourceLock}; - if(LookupSource(context.get(), source) != nullptr) - return AL_TRUE; - } - return AL_FALSE; -} -END_API_FUNC + case AL_ORIENTATION: + CheckSize(6); + 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; -AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - SetSourcefv(Source, context.get(), static_cast(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 UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) - 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); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - SetSourcefv(Source, context.get(), static_cast(param), {values, MaxValues}); -} -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) - 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); - } -} -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 UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) - 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); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - const ALuint count{DoubleValsByProp(param)}; - float fvals[MaxValues]; - for(ALuint i{0};i < count;i++) - fvals[i] = static_cast(values[i]); - SetSourcefv(Source, context.get(), static_cast(param), {fvals, count}); - } -} -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - SetSourceiv(Source, context.get(), static_cast(param), {&value, 1u}); -} -END_API_FUNC - -AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) - 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); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - SetSourceiv(Source, context.get(), static_cast(param), {values, MaxValues}); -} -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - SetSourcei64v(Source, context.get(), static_cast(param), {&value, 1u}); -} -END_API_FUNC - -AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - 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); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - SetSourcei64v(Source, context.get(), static_cast(param), {values, MaxValues}); -} -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!value) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - double dval[1]; - if(GetSourcedv(Source, context.get(), static_cast(param), dval)) - *value = static_cast(dval[0]); - } -} -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 UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!(value1 && value2 && value3)) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - double dvals[3]; - if(GetSourcedv(Source, context.get(), static_cast(param), dvals)) + case AL_SOURCE_RELATIVE: + if constexpr(std::is_integral_v) { - *value1 = static_cast(dvals[0]); - *value2 = static_cast(dvals[1]); - *value3 = static_cast(dvals[2]); + CheckSize(1); + values[0] = Source->HeadRelative; + return; } - } -} -END_API_FUNC + break; -AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - const ALuint count{FloatValsByProp(param)}; - double dvals[MaxValues]; - if(GetSourcedv(Source, context.get(), static_cast(param), {dvals, count})) + case AL_LOOPING: + if constexpr(std::is_integral_v) { - for(ALuint i{0};i < count;i++) - values[i] = static_cast(dvals[i]); + CheckSize(1); + values[0] = Source->Looping; + return; } - } -} -END_API_FUNC + break; - -AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!value) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourcedv(Source, context.get(), static_cast(param), {value, 1u}); -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!(value1 && value2 && value3)) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - double dvals[3]; - if(GetSourcedv(Source, context.get(), static_cast(param), dvals)) + case AL_BUFFER: + if constexpr(std::is_integral_v) { - *value1 = dvals[0]; - *value2 = dvals[1]; - *value3 = dvals[2]; + 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 + * when a streaming source changed buffers, so report the current + * buffer's ID when playing. + */ + if(Source->SourceType == AL_STATIC || Source->state == AL_INITIAL) + { + if(!Source->mQueue.empty()) + BufferList = &Source->mQueue.front(); + } + else if(Voice *voice{GetSourceVoice(Source, Context)}) + { + VoiceBufferItem *Current{voice->mCurrentBuffer.load(std::memory_order_relaxed)}; + BufferList = static_cast(Current); + } + ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr}; + values[0] = buffer ? static_cast(buffer->id) : T{0}; + return; } - } -} -END_API_FUNC + break; -AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourcedv(Source, context.get(), static_cast(param), {values, MaxValues}); -} -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!value) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourceiv(Source, context.get(), static_cast(param), {value, 1u}); -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!(value1 && value2 && value3)) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - int ivals[3]; - if(GetSourceiv(Source, context.get(), static_cast(param), ivals)) + case AL_SOURCE_STATE: + if constexpr(std::is_integral_v) { - *value1 = ivals[0]; - *value2 = ivals[1]; - *value3 = ivals[2]; + CheckSize(1); + values[0] = GetSourceState(Source, GetSourceVoice(Source, Context)); + return; } - } -} -END_API_FUNC + break; -AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourceiv(Source, context.get(), static_cast(param), {values, MaxValues}); -} -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!value) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourcei64v(Source, context.get(), static_cast(param), {value, 1u}); -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!(value1 && value2 && value3)) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - int64_t i64vals[3]; - if(GetSourcei64v(Source, context.get(), static_cast(param), i64vals)) + case AL_BUFFERS_QUEUED: + if constexpr(std::is_integral_v) { - *value1 = i64vals[0]; - *value2 = i64vals[1]; - *value3 = i64vals[2]; + CheckSize(1); + values[0] = static_cast(Source->mQueue.size()); + return; } + break; + + case AL_BUFFERS_PROCESSED: + if constexpr(std::is_integral_v) + { + CheckSize(1); + if(Source->Looping || Source->SourceType != AL_STREAMING) + { + /* Buffers on a looping source are in a perpetual state of + * PENDING, so don't report any as PROCESSED + */ + values[0] = 0; + } + else + { + 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_TYPE: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = Source->SourceType; + return; + } + break; + + case AL_DIRECT_FILTER_GAINHF_AUTO: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = Source->DryGainHFAuto; + return; + } + break; + + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = Source->WetGainAuto; + return; + } + break; + + 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 %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}; } -END_API_FUNC -AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values) -START_API_FUNC + +void StartSources(ALCcontext *const context, const al::span srchandles, + const nanoseconds start_time=nanoseconds::min()) { - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if UNLIKELY(!Source) - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if UNLIKELY(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourcei64v(Source, context.get(), static_cast(param), {values, MaxValues}); -} -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcePlay(ALuint source) -START_API_FUNC -{ alSourcePlayv(1, &source); } -END_API_FUNC - -AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Playing %d sources", n); - if UNLIKELY(n <= 0) return; - - al::vector extra_sources; - std::array source_storage; - al::span srchandles; - if LIKELY(static_cast(n) <= source_storage.size()) - srchandles = {source_storage.data(), static_cast(n)}; - else - { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; - } - - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) - { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); - ++sources; - } - ALCdevice *device{context->mALDevice.get()}; /* If the device is disconnected, and voices stop on disconnect, go right * to stopped. */ - if UNLIKELY(!device->Connected.load(std::memory_order_acquire)) + if(!device->Connected.load(std::memory_order_acquire)) UNLIKELY { if(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) { @@ -3068,7 +2521,7 @@ START_API_FUNC if(free_voices == srchandles.size()) break; } - if UNLIKELY(srchandles.size() != free_voices) + if(srchandles.size() != free_voices) UNLIKELY { const size_t inc_amount{srchandles.size() - free_voices}; auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); @@ -3089,15 +2542,12 @@ START_API_FUNC /* Check that there is a queue containing at least one valid, non zero * length buffer. */ - auto BufferList = source->mQueue.begin(); - for(;BufferList != source->mQueue.end();++BufferList) - { - if(BufferList->mSampleLen != 0 || BufferList->mCallback) - break; - } + auto find_buffer = [](ALbufferQueueItem &entry) noexcept + { return entry.mSampleLen != 0 || entry.mCallback != nullptr; }; + auto BufferList = std::find_if(source->mQueue.begin(), source->mQueue.end(), find_buffer); /* If there's nothing to play, go right to stopped. */ - if UNLIKELY(BufferList == source->mQueue.end()) + if(BufferList == source->mQueue.end()) UNLIKELY { /* NOTE: A source without any playable buffers should not have a * Voice since it shouldn't be in a playing or paused state. So @@ -3110,14 +2560,14 @@ START_API_FUNC } 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 *voice{GetSourceVoice(source, context.get())}; + Voice *voice{GetSourceVoice(source, context)}; switch(GetSourceState(source, voice)) { case AL_PAUSED: @@ -3131,8 +2581,8 @@ START_API_FUNC cur->mState = VChangeState::Play; source->state = AL_PLAYING; #ifdef ALSOFT_EAX - if(source->eax_is_initialized()) - source->eax_commit(); + if(context->hasEax()) + source->eaxCommit(); #endif // ALSOFT_EAX continue; @@ -3151,8 +2601,8 @@ START_API_FUNC assert(voice == nullptr); cur->mOldVoice = nullptr; #ifdef ALSOFT_EAX - if(source->eax_is_initialized()) - source->eax_commit(); + if(context->hasEax()) + source->eaxCommit(); #endif // ALSOFT_EAX break; } @@ -3171,9 +2621,10 @@ START_API_FUNC } ASSUME(voice != nullptr); - voice->mPosition.store(0u, std::memory_order_relaxed); + voice->mPosition.store(0, std::memory_order_relaxed); voice->mPositionFrac.store(0, std::memory_order_relaxed); voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed); + voice->mStartTime = start_time; voice->mFlags.reset(); /* A source that's not playing or paused has any offset applied when it * starts playing. @@ -3188,11 +2639,12 @@ START_API_FUNC voice->mPosition.store(vpos->pos, std::memory_order_relaxed); voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed); voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed); - if(vpos->pos!=0 || vpos->frac!=0 || vpos->bufferitem!=&source->mQueue.front()) + if(vpos->pos > 0 || (vpos->pos == 0 && vpos->frac > 0) + || vpos->bufferitem != &source->mQueue.front()) voice->mFlags.set(VoiceIsFading); } } - InitVoice(voice, source, std::addressof(*BufferList), context.get(), device); + InitVoice(voice, source, al::to_address(BufferList), context, device); source->VoiceIdx = vidx; source->state = AL_PLAYING; @@ -3201,47 +2653,655 @@ START_API_FUNC cur->mSourceID = source->id; cur->mState = VChangeState::Play; } - if LIKELY(tail) - SendVoiceChanges(context.get(), tail); + if(tail) LIKELY + SendVoiceChanges(context, tail); } -END_API_FUNC +} // namespace -AL_API void AL_APIENTRY alSourcePause(ALuint source) -START_API_FUNC -{ alSourcePausev(1, &source); } -END_API_FUNC +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; -AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources) -START_API_FUNC + std::unique_lock srclock{context->mSourceLock}; + ALCdevice *device{context->mALDevice.get()}; + + 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"}; + + std::generate(sids.begin(), sids.end(), [context]{ return AllocSource(context)->id; }); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +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 srclock{context->mSourceLock}; + + /* Check that all Sources are valid */ + auto validate_source = [context](const ALuint sid) -> bool + { return LookupSource(context, sid) != nullptr; }; + + 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 + { + if(ALsource *src{LookupSource(context, sid)}) + FreeSource(context, src); + }; + std::for_each(sids.begin(), sids.end(), delete_source); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC1(ALboolean, alIsSource, ALuint,source) +FORCE_ALIGN ALboolean AL_APIENTRY alIsSourceDirect(ALCcontext *context, ALuint source) noexcept { - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; + std::lock_guard srclock{context->mSourceLock}; + if(LookupSource(context, source) != nullptr) + return AL_TRUE; + return AL_FALSE; +} - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Pausing %d sources", n); - if UNLIKELY(n <= 0) return; - al::vector extra_sources; +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}; + + SetProperty(Source, context, static_cast(param), {&value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +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}; + + 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)}; + SetProperty(Source, context, static_cast(param), al::span{values, count}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +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()); +} + +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}; + + 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)}; + SetProperty(Source, context, static_cast(param), al::span{values, count}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +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()); +} + +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}; + + 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)}; + SetProperty(Source, context, static_cast(param), al::span{values, count}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +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()); +} + +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}; + + 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)}; + SetProperty(Source, context, static_cast(param), al::span{values, count}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +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()); +} + +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::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)}; + GetProperty(Source, context, static_cast(param), al::span{values, count}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +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()); +} + +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::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)}; + GetProperty(Source, context, static_cast(param), al::span{values, count}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +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()); +} + +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()); +} + +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)}; + GetProperty(Source, context, static_cast(param), al::span{values, count}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +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()); +} + +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::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)}; + GetProperty(Source, context, static_cast(param), al::span{values, count}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +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}; + + StartSources(context, {&Source, 1}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +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}; + + 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::span sids{sources, static_cast(n)}; + std::vector extra_sources; std::array source_storage; al::span srchandles; - if LIKELY(static_cast(n) <= source_storage.size()) - srchandles = {source_storage.data(), static_cast(n)}; + if(sids.size() <= source_storage.size()) LIKELY + srchandles = al::span{source_storage}.first(sids.size()); else { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; + extra_sources.resize(sids.size()); + srchandles = extra_sources; } - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) + std::lock_guard sourcelock{context->mSourceLock}; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), + [context](const ALuint sid) -> ALsource* + { + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }); + + StartSources(context, srchandles); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +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) + throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time}; + + al::span sids{sources, static_cast(n)}; + std::vector extra_sources; + std::array source_storage; + al::span srchandles; + if(sids.size() <= source_storage.size()) LIKELY + srchandles = al::span{source_storage}.first(sids.size()); + else { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); - ++sources; + extra_sources.resize(sids.size()); + srchandles = extra_sources; } + std::lock_guard sourcelock{context->mSourceLock}; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), + [context](const ALuint sid) -> ALsource* + { + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }); + + StartSources(context, srchandles, nanoseconds{start_time}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +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 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::span sids{sources, static_cast(n)}; + std::vector extra_sources; + std::array source_storage; + al::span srchandles; + if(sids.size() <= source_storage.size()) LIKELY + srchandles = al::span{source_storage}.first(sids.size()); + else + { + extra_sources.resize(sids.size()); + srchandles = extra_sources; + } + + std::lock_guard sourcelock{context->mSourceLock}; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), + [context](const ALuint sid) -> ALsource* + { + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }); + /* Pausing has to be done in two steps. First, for each source that's * detected to be playing, chamge the voice (asynchronously) to * stopping/paused. @@ -3249,14 +3309,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; @@ -3264,9 +3324,9 @@ START_API_FUNC cur->mState = VChangeState::Pause; } } - if LIKELY(tail) + 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 @@ -3274,60 +3334,60 @@ 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 UNLIKELY(!context) return; +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; - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Stopping %d sources", n); - if UNLIKELY(n <= 0) return; - - al::vector extra_sources; + al::span sids{sources, static_cast(n)}; + std::vector extra_sources; std::array source_storage; al::span srchandles; - if LIKELY(static_cast(n) <= source_storage.size()) - srchandles = {source_storage.data(), static_cast(n)}; + if(sids.size() <= source_storage.size()) LIKELY + srchandles = al::span{source_storage}.first(sids.size()); else { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; + extra_sources.resize(sids.size()); + srchandles = extra_sources; } - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) - { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); - ++sources; - } + std::lock_guard sourcelock{context->mSourceLock}; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), + [context](const ALuint sid) -> ALsource* + { + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }); 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); @@ -3338,60 +3398,60 @@ START_API_FUNC } source->Offset = 0.0; source->OffsetType = AL_NONE; - source->VoiceIdx = INVALID_VOICE_IDX; + source->VoiceIdx = InvalidVoiceIndex; } - if LIKELY(tail) - SendVoiceChanges(context.get(), tail); + if(tail) LIKELY + 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 UNLIKELY(!context) return; +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; - if UNLIKELY(n < 0) - context->setError(AL_INVALID_VALUE, "Rewinding %d sources", n); - if UNLIKELY(n <= 0) return; - - al::vector extra_sources; + al::span sids{sources, static_cast(n)}; + std::vector extra_sources; std::array source_storage; al::span srchandles; - if LIKELY(static_cast(n) <= source_storage.size()) - srchandles = {source_storage.data(), static_cast(n)}; + if(sids.size() <= source_storage.size()) LIKELY + srchandles = al::span{source_storage}.first(sids.size()); else { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; + extra_sources.resize(sids.size()); + srchandles = extra_sources; } - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) - { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", *sources); - ++sources; - } + std::lock_guard sourcelock{context->mSourceLock}; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), + [context](const ALuint sid) -> ALsource* + { + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }); 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) @@ -3403,32 +3463,32 @@ START_API_FUNC } source->Offset = 0.0; source->OffsetType = AL_NONE; - source->VoiceIdx = INVALID_VOICE_IDX; + source->VoiceIdx = InvalidVoiceIndex; } - if LIKELY(tail) - SendVoiceChanges(context.get(), tail); + if(tail) LIKELY + 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 UNLIKELY(!context) return; +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; - if UNLIKELY(nb < 0) - context->setError(AL_INVALID_VALUE, "Queueing %d buffers", nb); - if UNLIKELY(nb <= 0) return; - - std::lock_guard _{context->mSourceLock}; - ALsource *source{LookupSource(context.get(),src)}; - if UNLIKELY(!source) - SETERR_RETURN(context, 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 UNLIKELY(source->SourceType == AL_STATIC) - SETERR_RETURN(context, 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()}; @@ -3440,77 +3500,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 && buffer->mCallback) - { - context->setError(AL_INVALID_OPERATION, "Queueing callback buffer %u", buffers[i]); - 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->mSampleLen = buffer->mSampleLen; - BufferList->mLoopEnd = buffer->mSampleLen; - BufferList->mSamples = buffer->mData.data(); - BufferList->mBuffer = buffer; - IncrementRef(buffer->ref); - - if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - { - context->setError(AL_INVALID_OPERATION, "Queueing non-persistently mapped buffer %u", - buffer->id); - goto buffer_error; - } - - if(BufferFmt == nullptr) - BufferFmt = buffer; - else - { - fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate; - fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels; - if(BufferFmt->isBFormat()) + if(buffer) { - fmt_mismatch |= BufferFmt->mAmbiLayout != buffer->mAmbiLayout; - fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling; - } - fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder; - fmt_mismatch |= BufferFmt->OriginalType != buffer->OriginalType; - } - if UNLIKELY(fmt_mismatch) - { - context->setError(AL_INVALID_OPERATION, "Queueing buffer with mismatched format"); + 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(); @@ -3521,38 +3589,38 @@ START_API_FUNC if(NewListStart != 0) { auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart); - (iter-1)->mNext.store(std::addressof(*iter), std::memory_order_release); + (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 UNLIKELY(!context) return; +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; - if UNLIKELY(nb < 0) - context->setError(AL_INVALID_VALUE, "Unqueueing %d buffers", nb); - if UNLIKELY(nb <= 0) return; + 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}; - std::lock_guard _{context->mSourceLock}; - ALsource *source{LookupSource(context.get(),src)}; - if UNLIKELY(!source) - SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid source ID %u", src); - - if UNLIKELY(source->SourceType != AL_STREAMING) - SETERR_RETURN(context, AL_INVALID_VALUE,, "Unqueueing from a non-streaming source %u", - src); - if UNLIKELY(source->Looping) - SETERR_RETURN(context, 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}; - if LIKELY(source->state != AL_INITIAL) + 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) { @@ -3561,51 +3629,52 @@ START_API_FUNC ++processed; } } - if UNLIKELY(processed < static_cast(nb)) - SETERR_RETURN(context, 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 UNLIKELY(!context) return; + 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; } } @@ -3624,222 +3693,355 @@ ALsource::~ALsource() void UpdateAllSourceProps(ALCcontext *context) { - std::lock_guard _{context->mSourceLock}; -#ifdef ALSOFT_EAX - if(context->has_eax()) + std::lock_guard srclock{context->mSourceLock}; + auto voicelist = context->getVoicesSpan(); + ALuint vidx{0u}; + for(Voice *voice : voicelist) { - /* If EAX is enabled, we need to go through and commit all sources' EAX - * changes, along with updating its voice, if any. - */ - for(auto &sublist : context->mSourceList) + ALuint sid{voice->mSourceID.load(std::memory_order_acquire)}; + ALsource *source{sid ? LookupSource(context, sid) : nullptr}; + if(source && source->VoiceIdx == vidx) { - uint64_t usemask{~sublist.FreeMask}; - while(usemask) - { - const int idx{al::countr_zero(usemask)}; - usemask &= ~(1_u64 << idx); - - ALsource *source{sublist.Sources + idx}; - source->eax_commit(); - - if(Voice *voice{GetSourceVoice(source, context)}) - { - if(std::exchange(source->mPropsDirty, false)) - UpdateSourceProps(source, voice, context); - } - } - } - } - else -#endif - { - 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; - if(source && source->VoiceIdx == vidx) - { - if(std::exchange(source->mPropsDirty, false)) - UpdateSourceProps(source, voice, context); - } - ++vidx; + if(std::exchange(source->mPropsDirty, false)) + UpdateSourceProps(source, voice, context); } + ++vidx; } } +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 -class EaxSourceException : - public EaxException +void ALsource::eaxInitialize(ALCcontext *context) noexcept { -public: - explicit EaxSourceException( - const char* message) - : - EaxException{"EAX_SOURCE", message} - { - } -}; // EaxSourceException + assert(context != nullptr); + mEaxAlContext = context; - -class EaxSourceActiveFxSlotsException : - public EaxException -{ -public: - explicit EaxSourceActiveFxSlotsException( - const char* message) - : - EaxException{"EAX_SOURCE_ACTIVE_FX_SLOTS", message} - { - } -}; // EaxSourceActiveFxSlotsException - - -class EaxSourceSendException : - public EaxException -{ -public: - explicit EaxSourceSendException( - const char* message) - : - EaxException{"EAX_SOURCE_SEND", message} - { - } -}; // EaxSourceSendException - - -void EaxUpdateSourceVoice(ALsource *source, ALCcontext *context) -{ - if(Voice *voice{GetSourceVoice(source, context)}) - { - if(std::exchange(source->mPropsDirty, false)) - UpdateSourceProps(source, voice, context); - } -} - - -void ALsource::eax_initialize(ALCcontext *context) noexcept -{ - assert(context); - eax_al_context_ = context; + mEaxPrimaryFxSlotId = context->eaxGetPrimaryFxSlotIndex(); eax_set_defaults(); - eax_initialize_fx_slots(); - eax_d_ = eax_; + eax1_translate(mEax1.i, mEax); + mEaxVersion = 1; + mEaxChanged = true; } -void ALsource::eax_update_filters() +void ALsource::eaxDispatch(const EaxCall& call) { - eax_update_filters_internal(); + call.is_get() ? eax_get(call) : eax_set(call); } -void ALsource::eax_update(EaxContextSharedDirtyFlags) -{ - /* NOTE: EaxContextSharedDirtyFlags only has one flag (primary_fx_slot_id), - * which must be true for this to be called. - */ - if(eax_uses_primary_id_) - eax_update_primary_fx_slot_id(); -} - -void ALsource::eax_commit_and_update() -{ - eax_apply_deferred(); - EaxUpdateSourceVoice(this, eax_al_context_); -} - -ALsource* ALsource::eax_lookup_source( - ALCcontext& al_context, - ALuint source_id) noexcept +ALsource* ALsource::EaxLookupSource(ALCcontext& al_context, ALuint source_id) noexcept { return LookupSource(&al_context, source_id); } -[[noreturn]] -void ALsource::eax_fail( - const char* message) +[[noreturn]] void ALsource::eax_fail(const char* message) { - throw EaxSourceException{message}; + throw Exception{message}; } -void ALsource::eax_set_source_defaults() noexcept +[[noreturn]] void ALsource::eax_fail_unknown_property_id() { - eax1_.fMix = EAX_REVERBMIX_USEDISTANCE; - - eax_.source.lDirect = EAXSOURCE_DEFAULTDIRECT; - eax_.source.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; - eax_.source.lRoom = EAXSOURCE_DEFAULTROOM; - eax_.source.lRoomHF = EAXSOURCE_DEFAULTROOMHF; - eax_.source.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION; - eax_.source.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO; - eax_.source.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; - eax_.source.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; - eax_.source.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; - eax_.source.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; - eax_.source.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; - eax_.source.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; - eax_.source.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF; - eax_.source.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR; - eax_.source.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR; - eax_.source.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR; - eax_.source.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR; - eax_.source.ulFlags = EAXSOURCE_DEFAULTFLAGS; - eax_.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; + eax_fail("Unknown property id."); } -void ALsource::eax_set_active_fx_slots_defaults() noexcept +[[noreturn]] void ALsource::eax_fail_unknown_version() { - eax_.active_fx_slots = EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; + eax_fail("Unknown version."); } -void ALsource::eax_set_send_defaults(EAXSOURCEALLSENDPROPERTIES& eax_send) noexcept +[[noreturn]] void ALsource::eax_fail_unknown_active_fx_slot_id() { - eax_send.guidReceivingFXSlotID = EAX_NULL_GUID; - eax_send.lSend = EAXSOURCE_DEFAULTSEND; - eax_send.lSendHF = EAXSOURCE_DEFAULTSENDHF; - eax_send.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; - eax_send.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; - eax_send.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; - eax_send.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; - eax_send.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; - eax_send.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; + eax_fail("Unknown active FX slot ID."); } -void ALsource::eax_set_sends_defaults() noexcept +[[noreturn]] void ALsource::eax_fail_unknown_receiving_fx_slot_id() { - for (auto& eax_send : eax_.sends) + eax_fail("Unknown receiving FX slot ID."); +} + +void ALsource::eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept +{ + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { - eax_set_send_defaults(eax_send); + auto& send = sends[i]; + send.guidReceivingFXSlotID = *(ids[i]); + send.lSend = EAXSOURCE_DEFAULTSEND; + send.lSendHF = EAXSOURCE_DEFAULTSENDHF; + send.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; + send.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; + send.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; + send.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; + send.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; + send.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; } } -void ALsource::eax_set_speaker_levels_defaults() noexcept +void ALsource::eax1_set_defaults(Eax1Props& props) noexcept { - std::fill(eax_.speaker_levels.begin(), eax_.speaker_levels.end(), EAXSOURCE_DEFAULTSPEAKERLEVEL); + props.fMix = EAX_REVERBMIX_USEDISTANCE; +} + +void ALsource::eax1_set_defaults() noexcept +{ + eax1_set_defaults(mEax1.i); + mEax1.d = mEax1.i; +} + +void ALsource::eax2_set_defaults(Eax2Props& props) noexcept +{ + props.lDirect = EAXSOURCE_DEFAULTDIRECT; + props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; + props.lRoom = EAXSOURCE_DEFAULTROOM; + props.lRoomHF = EAXSOURCE_DEFAULTROOMHF; + props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR; + props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION; + props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO; + props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; + props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; + props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; + props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF; + props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR; + props.dwFlags = EAXSOURCE_DEFAULTFLAGS; +} + +void ALsource::eax2_set_defaults() noexcept +{ + eax2_set_defaults(mEax2.i); + mEax2.d = mEax2.i; +} + +void ALsource::eax3_set_defaults(Eax3Props& props) noexcept +{ + props.lDirect = EAXSOURCE_DEFAULTDIRECT; + props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; + props.lRoom = EAXSOURCE_DEFAULTROOM; + props.lRoomHF = EAXSOURCE_DEFAULTROOMHF; + props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION; + props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO; + props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; + props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; + props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; + props.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; + props.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; + props.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; + props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF; + props.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR; + props.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR; + props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR; + props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR; + props.ulFlags = EAXSOURCE_DEFAULTFLAGS; +} + +void ALsource::eax3_set_defaults() noexcept +{ + eax3_set_defaults(mEax3.i); + mEax3.d = mEax3.i; +} + +void ALsource::eax4_set_sends_defaults(EaxSends& sends) noexcept +{ + eax_set_sends_defaults(sends, eax4_fx_slot_ids); +} + +void ALsource::eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept +{ + slots = EAX40SOURCE_DEFAULTACTIVEFXSLOTID; +} + +void ALsource::eax4_set_defaults() noexcept +{ + eax3_set_defaults(mEax4.i.source); + eax4_set_sends_defaults(mEax4.i.sends); + eax4_set_active_fx_slots_defaults(mEax4.i.active_fx_slots); + mEax4.d = mEax4.i; +} + +void ALsource::eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept +{ + eax3_set_defaults(static_cast(props)); + props.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; +} + +void ALsource::eax5_set_sends_defaults(EaxSends& sends) noexcept +{ + eax_set_sends_defaults(sends, eax5_fx_slot_ids); +} + +void ALsource::eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept +{ + slots = EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; +} + +void ALsource::eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept +{ + 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; + } +} + +void ALsource::eax5_set_defaults(Eax5Props& props) noexcept +{ + eax5_set_source_defaults(props.source); + eax5_set_sends_defaults(props.sends); + eax5_set_active_fx_slots_defaults(props.active_fx_slots); + eax5_set_speaker_levels_defaults(props.speaker_levels); +} + +void ALsource::eax5_set_defaults() noexcept +{ + eax5_set_defaults(mEax5.i); + mEax5.d = mEax5.i; } void ALsource::eax_set_defaults() noexcept { - eax_set_source_defaults(); - eax_set_active_fx_slots_defaults(); - eax_set_sends_defaults(); - eax_set_speaker_levels_defaults(); + eax1_set_defaults(); + eax2_set_defaults(); + eax3_set_defaults(); + eax4_set_defaults(); + eax5_set_defaults(); +} + +void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept +{ + eax5_set_defaults(dst); + + if (src.fMix == EAX_REVERBMIX_USEDISTANCE) + { + dst.source.ulFlags |= EAXSOURCEFLAGS_ROOMAUTO; + dst.sends[0].lSend = 0; + } + else + { + dst.source.ulFlags &= ~EAXSOURCEFLAGS_ROOMAUTO; + dst.sends[0].lSend = std::clamp(static_cast(gain_to_level_mb(src.fMix)), + EAXSOURCE_MINSEND, EAXSOURCE_MAXSEND); + } +} + +void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept +{ + // Source. + // + dst.source.lDirect = src.lDirect; + dst.source.lDirectHF = src.lDirectHF; + dst.source.lRoom = src.lRoom; + dst.source.lRoomHF = src.lRoomHF; + dst.source.lObstruction = src.lObstruction; + dst.source.flObstructionLFRatio = src.flObstructionLFRatio; + dst.source.lOcclusion = src.lOcclusion; + dst.source.flOcclusionLFRatio = src.flOcclusionLFRatio; + dst.source.flOcclusionRoomRatio = src.flOcclusionRoomRatio; + dst.source.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; + dst.source.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; + dst.source.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; + dst.source.lOutsideVolumeHF = src.lOutsideVolumeHF; + dst.source.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR; + dst.source.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR; + dst.source.flRoomRolloffFactor = src.flRoomRolloffFactor; + dst.source.flAirAbsorptionFactor = src.flAirAbsorptionFactor; + dst.source.ulFlags = src.dwFlags; + dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; + + // Set everything else to defaults. + // + eax5_set_sends_defaults(dst.sends); + eax5_set_active_fx_slots_defaults(dst.active_fx_slots); + eax5_set_speaker_levels_defaults(dst.speaker_levels); +} + +void ALsource::eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept +{ + // Source. + // + static_cast(dst.source) = src; + dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; + + // Set everything else to defaults. + // + eax5_set_sends_defaults(dst.sends); + eax5_set_active_fx_slots_defaults(dst.active_fx_slots); + eax5_set_speaker_levels_defaults(dst.speaker_levels); +} + +void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept +{ + // Source. + // + static_cast(dst.source) = src.source; + dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; + + // Sends. + // + dst.sends = src.sends; + + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) + dst.sends[i].guidReceivingFXSlotID = *(eax5_fx_slot_ids[i]); + + // Active FX slots. + // + 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) + { + const auto& src_id = src.active_fx_slots.guidActiveFXSlots[i]; + + if(src_id == EAX_NULL_GUID) + dst_id = EAX_NULL_GUID; + else if(src_id == EAX_PrimaryFXSlotID) + dst_id = EAX_PrimaryFXSlotID; + else if(src_id == EAXPROPERTYID_EAX40_FXSlot0) + dst_id = EAXPROPERTYID_EAX50_FXSlot0; + else if(src_id == EAXPROPERTYID_EAX40_FXSlot1) + dst_id = EAXPROPERTYID_EAX50_FXSlot1; + else if(src_id == EAXPROPERTYID_EAX40_FXSlot2) + dst_id = EAXPROPERTYID_EAX50_FXSlot2; + else if(src_id == EAXPROPERTYID_EAX40_FXSlot3) + dst_id = EAXPROPERTYID_EAX50_FXSlot3; + else + assert(false && "Unknown active FX slot ID."); + } + else + dst_id = EAX_NULL_GUID; + } + + // Speaker levels. + // + eax5_set_speaker_levels_defaults(dst.speaker_levels); } float ALsource::eax_calculate_dst_occlusion_mb( @@ -3851,35 +4053,53 @@ float ALsource::eax_calculate_dst_occlusion_mb( const auto ratio_2 = path_ratio * lf_ratio; const auto ratio = (ratio_2 > ratio_1) ? ratio_2 : ratio_1; const auto dst_occlustion_mb = static_cast(src_occlusion_mb) * ratio; - return dst_occlustion_mb; } EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept { auto gain_mb = - static_cast(eax_.source.lDirect) + - - (static_cast(eax_.source.lObstruction) * eax_.source.flObstructionLFRatio) + - + static_cast(mEax.source.lDirect) + + (static_cast(mEax.source.lObstruction) * mEax.source.flObstructionLFRatio) + eax_calculate_dst_occlusion_mb( - eax_.source.lOcclusion, - eax_.source.flOcclusionDirectRatio, - eax_.source.flOcclusionLFRatio); + mEax.source.lOcclusion, + mEax.source.flOcclusionDirectRatio, + mEax.source.flOcclusionLFRatio); + + const auto has_source_occlusion = (mEax.source.lOcclusion != 0); auto gain_hf_mb = - static_cast(eax_.source.lDirectHF) + + static_cast(mEax.source.lDirectHF) + + static_cast(mEax.source.lObstruction); - static_cast(eax_.source.lObstruction) + - - (static_cast(eax_.source.lOcclusion) * eax_.source.flOcclusionDirectRatio); - - for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i) + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { - if (eax_active_fx_slots_[i]) - { - const auto& send = eax_.sends[i]; + if(!mEaxActiveFxSlots[i]) + continue; + 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) + { + gain_mb += eax_calculate_dst_occlusion_mb( + mEax.source.lOcclusion, + mEax.source.flOcclusionDirectRatio, + mEax.source.flOcclusionLFRatio); + + gain_hf_mb += static_cast(mEax.source.lOcclusion) * mEax.source.flOcclusionDirectRatio; + } + } + + const auto& send = mEax.sends[i]; + + if(send.lOcclusion != 0) + { gain_mb += eax_calculate_dst_occlusion_mb( send.lOcclusion, send.flOcclusionDirectRatio, @@ -3889,13 +4109,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( @@ -3903,2149 +4119,818 @@ EaxAlLowPassParam ALsource::eax_create_room_filter_param( const EAXSOURCEALLSENDPROPERTIES& send) const noexcept { 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); const auto gain_mb = - static_cast( - eax_.source.lRoom + - send.lSend) + - - eax_calculate_dst_occlusion_mb( - eax_.source.lOcclusion, - eax_.source.flOcclusionRoomRatio, - eax_.source.flOcclusionLFRatio - ) + - + (static_cast(fx_slot_eax.lOcclusion) * fx_slot_eax.flOcclusionLFRatio) + + static_cast((is_environmental_fx ? mEax.source.lRoom : 0) + send.lSend) + + (is_listener_environment ? + eax_calculate_dst_occlusion_mb( + mEax.source.lOcclusion, + mEax.source.flOcclusionRoomRatio, + mEax.source.flOcclusionLFRatio) : + 0.0f) + eax_calculate_dst_occlusion_mb( send.lOcclusion, send.flOcclusionRoomRatio, - send.flOcclusionLFRatio - ) + - - (static_cast(eax_.source.lExclusion) * eax_.source.flExclusionLFRatio) + - (static_cast(send.lExclusion) * send.flExclusionLFRatio) + - - 0.0F; + send.flOcclusionLFRatio) + + (is_listener_environment ? + (static_cast(mEax.source.lExclusion) * mEax.source.flExclusionLFRatio) : + 0.0f) + + (static_cast(send.lExclusion) * send.flExclusionLFRatio); const auto gain_hf_mb = - static_cast( - eax_.source.lRoomHF + - send.lSendHF) + - - (static_cast(fx_slot_eax.lOcclusion + eax_.source.lOcclusion) * eax_.source.flOcclusionRoomRatio) + + static_cast(fx_slot_eax.lOcclusion) + + static_cast((is_environmental_fx ? mEax.source.lRoomHF : 0) + send.lSendHF) + + (is_listener_environment ? + ((static_cast(mEax.source.lOcclusion) * mEax.source.flOcclusionRoomRatio)) : + 0.0f) + (static_cast(send.lOcclusion) * send.flOcclusionRoomRatio) + + (is_listener_environment ? + static_cast(mEax.source.lExclusion + send.lExclusion) : + 0.0f); - static_cast( - eax_.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_set_fx_slots() -{ - eax_uses_primary_id_ = false; - eax_has_active_fx_slots_ = false; - eax_active_fx_slots_.fill(false); - - for(const auto& eax_active_fx_slot_id : eax_.active_fx_slots.guidActiveFXSlots) - { - auto fx_slot_index = EaxFxSlotIndex{}; - - if(eax_active_fx_slot_id == EAX_PrimaryFXSlotID) - { - eax_uses_primary_id_ = true; - fx_slot_index = eax_al_context_->eax_get_primary_fx_slot_index(); - } - else - { - fx_slot_index = eax_active_fx_slot_id; - } - - if(fx_slot_index.has_value()) - { - eax_has_active_fx_slots_ = true; - eax_active_fx_slots_[*fx_slot_index] = true; - } - } - - for(auto i = 0u;i < eax_active_fx_slots_.size();++i) - { - if(!eax_active_fx_slots_[i]) - eax_set_al_source_send(nullptr, i, EaxAlLowPassParam{1.0f, 1.0f}); - } -} - -void ALsource::eax_initialize_fx_slots() -{ - eax_set_fx_slots(); - eax_update_filters_internal(); -} - -void ALsource::eax_update_direct_filter_internal() +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_internal() +void ALsource::eax_update_room_filters() { - if (!eax_has_active_fx_slots_) + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { - return; - } + if(!mEaxActiveFxSlots[i]) + continue; - for (auto i = 0u; i < EAX_MAX_FXSLOTS; ++i) - { - if (eax_active_fx_slots_[i]) - { - auto& fx_slot = eax_al_context_->eax_get_fx_slot(static_cast(i)); - const auto& send = eax_.sends[i]; - const auto& room_param = eax_create_room_filter_param(fx_slot, send); - - eax_set_al_source_send(&fx_slot, i, room_param); - } - } -} - -void ALsource::eax_update_filters_internal() -{ - eax_update_direct_filter_internal(); - eax_update_room_filters_internal(); -} - -void ALsource::eax_update_primary_fx_slot_id() -{ - const auto& previous_primary_fx_slot_index = eax_al_context_->eax_get_previous_primary_fx_slot_index(); - const auto& primary_fx_slot_index = eax_al_context_->eax_get_primary_fx_slot_index(); - - if (previous_primary_fx_slot_index == primary_fx_slot_index) - { - return; - } - - if (previous_primary_fx_slot_index.has_value()) - { - const auto fx_slot_index = previous_primary_fx_slot_index.value(); - eax_active_fx_slots_[fx_slot_index] = false; - - eax_set_al_source_send(nullptr, fx_slot_index, EaxAlLowPassParam{1.0f, 1.0f}); - } - - if (primary_fx_slot_index.has_value()) - { - const auto fx_slot_index = primary_fx_slot_index.value(); - eax_active_fx_slots_[fx_slot_index] = true; - - auto& fx_slot = eax_al_context_->eax_get_fx_slot(fx_slot_index); - const auto& send = eax_.sends[fx_slot_index]; + auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i); + const auto& send = mEax.sends[i]; const auto& room_param = eax_create_room_filter_param(fx_slot, send); - - eax_set_al_source_send(&fx_slot, fx_slot_index, room_param); - } - - eax_has_active_fx_slots_ = std::any_of( - eax_active_fx_slots_.cbegin(), - eax_active_fx_slots_.cend(), - [](const auto& item) - { - return item; - } - ); -} - -void ALsource::eax_defer_active_fx_slots( - const EaxEaxCall& eax_call) -{ - const auto active_fx_slots_span = - eax_call.get_values(); - - const auto fx_slot_count = active_fx_slots_span.size(); - - if (fx_slot_count <= 0 || fx_slot_count > EAX_MAX_FXSLOTS) - { - throw EaxSourceActiveFxSlotsException{"Count out of range."}; - } - - for (auto i = std::size_t{}; i < fx_slot_count; ++i) - { - const auto& fx_slot_guid = active_fx_slots_span[i]; - - if (fx_slot_guid != EAX_NULL_GUID && - fx_slot_guid != EAX_PrimaryFXSlotID && - fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot0 && - fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot0 && - fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot1 && - fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot1 && - fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot2 && - fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot2 && - fx_slot_guid != EAXPROPERTYID_EAX40_FXSlot3 && - fx_slot_guid != EAXPROPERTYID_EAX50_FXSlot3) - { - throw EaxSourceActiveFxSlotsException{"Unsupported GUID."}; - } - } - - for (auto i = std::size_t{}; i < fx_slot_count; ++i) - { - eax_d_.active_fx_slots.guidActiveFXSlots[i] = active_fx_slots_span[i]; - } - - for (auto i = fx_slot_count; i < EAX_MAX_FXSLOTS; ++i) - { - eax_d_.active_fx_slots.guidActiveFXSlots[i] = EAX_NULL_GUID; - } - - eax_are_active_fx_slots_dirty_ = (eax_d_.active_fx_slots != eax_.active_fx_slots); -} - - -const char* ALsource::eax_get_exclusion_name() noexcept -{ - return "Exclusion"; -} - -const char* ALsource::eax_get_exclusion_lf_ratio_name() noexcept -{ - return "Exclusion LF Ratio"; -} - -const char* ALsource::eax_get_occlusion_name() noexcept -{ - return "Occlusion"; -} - -const char* ALsource::eax_get_occlusion_lf_ratio_name() noexcept -{ - return "Occlusion LF Ratio"; -} - -const char* ALsource::eax_get_occlusion_direct_ratio_name() noexcept -{ - return "Occlusion Direct Ratio"; -} - -const char* ALsource::eax_get_occlusion_room_ratio_name() noexcept -{ - return "Occlusion Room Ratio"; -} - -void ALsource::eax1_validate_reverb_mix(float reverb_mix) -{ - if (reverb_mix == EAX_REVERBMIX_USEDISTANCE) - return; - - eax_validate_range("Reverb Mix", reverb_mix, EAX_BUFFER_MINREVERBMIX, EAX_BUFFER_MAXREVERBMIX); -} - -void ALsource::eax_validate_send_receiving_fx_slot_guid( - const GUID& guidReceivingFXSlotID) -{ - if (guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 && - guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 && - guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 && - guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 && - guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 && - guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 && - guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot3 && - guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot3) - { - throw EaxSourceSendException{"Unsupported receiving FX slot GUID."}; + eax_set_al_source_send(&fx_slot, i, room_param); } } -void ALsource::eax_validate_send_send( - long lSend) +void ALsource::eax_set_efx_outer_gain_hf() { - eax_validate_range( - "Send", - lSend, - EAXSOURCE_MINSEND, - EAXSOURCE_MAXSEND); -} - -void ALsource::eax_validate_send_send_hf( - long lSendHF) -{ - eax_validate_range( - "Send HF", - lSendHF, - EAXSOURCE_MINSENDHF, - EAXSOURCE_MAXSENDHF); -} - -void ALsource::eax_validate_send_occlusion( - long lOcclusion) -{ - eax_validate_range( - eax_get_occlusion_name(), - lOcclusion, - EAXSOURCE_MINOCCLUSION, - EAXSOURCE_MAXOCCLUSION); -} - -void ALsource::eax_validate_send_occlusion_lf_ratio( - float flOcclusionLFRatio) -{ - eax_validate_range( - eax_get_occlusion_lf_ratio_name(), - flOcclusionLFRatio, - EAXSOURCE_MINOCCLUSIONLFRATIO, - EAXSOURCE_MAXOCCLUSIONLFRATIO); -} - -void ALsource::eax_validate_send_occlusion_room_ratio( - float flOcclusionRoomRatio) -{ - eax_validate_range( - eax_get_occlusion_room_ratio_name(), - flOcclusionRoomRatio, - EAXSOURCE_MINOCCLUSIONROOMRATIO, - EAXSOURCE_MAXOCCLUSIONROOMRATIO); -} - -void ALsource::eax_validate_send_occlusion_direct_ratio( - float flOcclusionDirectRatio) -{ - eax_validate_range( - eax_get_occlusion_direct_ratio_name(), - flOcclusionDirectRatio, - EAXSOURCE_MINOCCLUSIONDIRECTRATIO, - EAXSOURCE_MAXOCCLUSIONDIRECTRATIO); -} - -void ALsource::eax_validate_send_exclusion( - long lExclusion) -{ - eax_validate_range( - eax_get_exclusion_name(), - lExclusion, - EAXSOURCE_MINEXCLUSION, - EAXSOURCE_MAXEXCLUSION); -} - -void ALsource::eax_validate_send_exclusion_lf_ratio( - float flExclusionLFRatio) -{ - eax_validate_range( - eax_get_exclusion_lf_ratio_name(), - flExclusionLFRatio, - EAXSOURCE_MINEXCLUSIONLFRATIO, - EAXSOURCE_MAXEXCLUSIONLFRATIO); -} - -void ALsource::eax_validate_send( - const EAXSOURCESENDPROPERTIES& all) -{ - eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); - eax_validate_send_send(all.lSend); - eax_validate_send_send_hf(all.lSendHF); -} - -void ALsource::eax_validate_send_exclusion_all( - const EAXSOURCEEXCLUSIONSENDPROPERTIES& all) -{ - eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); - eax_validate_send_exclusion(all.lExclusion); - eax_validate_send_exclusion_lf_ratio(all.flExclusionLFRatio); -} - -void ALsource::eax_validate_send_occlusion_all( - const EAXSOURCEOCCLUSIONSENDPROPERTIES& all) -{ - eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); - eax_validate_send_occlusion(all.lOcclusion); - eax_validate_send_occlusion_lf_ratio(all.flOcclusionLFRatio); - eax_validate_send_occlusion_room_ratio(all.flOcclusionRoomRatio); - eax_validate_send_occlusion_direct_ratio(all.flOcclusionDirectRatio); -} - -void ALsource::eax_validate_send_all( - const EAXSOURCEALLSENDPROPERTIES& all) -{ - eax_validate_send_receiving_fx_slot_guid(all.guidReceivingFXSlotID); - eax_validate_send_send(all.lSend); - eax_validate_send_send_hf(all.lSendHF); - eax_validate_send_occlusion(all.lOcclusion); - eax_validate_send_occlusion_lf_ratio(all.flOcclusionLFRatio); - eax_validate_send_occlusion_room_ratio(all.flOcclusionRoomRatio); - eax_validate_send_occlusion_direct_ratio(all.flOcclusionDirectRatio); - eax_validate_send_exclusion(all.lExclusion); - eax_validate_send_exclusion_lf_ratio(all.flExclusionLFRatio); -} - -EaxFxSlotIndexValue ALsource::eax_get_send_index( - const GUID& send_guid) -{ - if (false) - { - } - else if (send_guid == EAXPROPERTYID_EAX40_FXSlot0 || send_guid == EAXPROPERTYID_EAX50_FXSlot0) - { - return 0; - } - else if (send_guid == EAXPROPERTYID_EAX40_FXSlot1 || send_guid == EAXPROPERTYID_EAX50_FXSlot1) - { - return 1; - } - else if (send_guid == EAXPROPERTYID_EAX40_FXSlot2 || send_guid == EAXPROPERTYID_EAX50_FXSlot2) - { - return 2; - } - else if (send_guid == EAXPROPERTYID_EAX40_FXSlot3 || send_guid == EAXPROPERTYID_EAX50_FXSlot3) - { - return 3; - } - else - { - throw EaxSourceSendException{"Unsupported receiving FX slot GUID."}; - } -} - -void ALsource::eax_defer_send_send( - long lSend, - EaxFxSlotIndexValue index) -{ - eax_d_.sends[index].lSend = lSend; - - eax_sends_dirty_flags_.sends[index].lSend = - (eax_.sends[index].lSend != eax_d_.sends[index].lSend); -} - -void ALsource::eax_defer_send_send_hf( - long lSendHF, - EaxFxSlotIndexValue index) -{ - eax_d_.sends[index].lSendHF = lSendHF; - - eax_sends_dirty_flags_.sends[index].lSendHF = - (eax_.sends[index].lSendHF != eax_d_.sends[index].lSendHF); -} - -void ALsource::eax_defer_send_occlusion( - long lOcclusion, - EaxFxSlotIndexValue index) -{ - eax_d_.sends[index].lOcclusion = lOcclusion; - - eax_sends_dirty_flags_.sends[index].lOcclusion = - (eax_.sends[index].lOcclusion != eax_d_.sends[index].lOcclusion); -} - -void ALsource::eax_defer_send_occlusion_lf_ratio( - float flOcclusionLFRatio, - EaxFxSlotIndexValue index) -{ - eax_d_.sends[index].flOcclusionLFRatio = flOcclusionLFRatio; - - eax_sends_dirty_flags_.sends[index].flOcclusionLFRatio = - (eax_.sends[index].flOcclusionLFRatio != eax_d_.sends[index].flOcclusionLFRatio); -} - -void ALsource::eax_defer_send_occlusion_room_ratio( - float flOcclusionRoomRatio, - EaxFxSlotIndexValue index) -{ - eax_d_.sends[index].flOcclusionRoomRatio = flOcclusionRoomRatio; - - eax_sends_dirty_flags_.sends[index].flOcclusionRoomRatio = - (eax_.sends[index].flOcclusionRoomRatio != eax_d_.sends[index].flOcclusionRoomRatio); -} - -void ALsource::eax_defer_send_occlusion_direct_ratio( - float flOcclusionDirectRatio, - EaxFxSlotIndexValue index) -{ - eax_d_.sends[index].flOcclusionDirectRatio = flOcclusionDirectRatio; - - eax_sends_dirty_flags_.sends[index].flOcclusionDirectRatio = - (eax_.sends[index].flOcclusionDirectRatio != eax_d_.sends[index].flOcclusionDirectRatio); -} - -void ALsource::eax_defer_send_exclusion( - long lExclusion, - EaxFxSlotIndexValue index) -{ - eax_d_.sends[index].lExclusion = lExclusion; - - eax_sends_dirty_flags_.sends[index].lExclusion = - (eax_.sends[index].lExclusion != eax_d_.sends[index].lExclusion); -} - -void ALsource::eax_defer_send_exclusion_lf_ratio( - float flExclusionLFRatio, - EaxFxSlotIndexValue index) -{ - eax_d_.sends[index].flExclusionLFRatio = flExclusionLFRatio; - - eax_sends_dirty_flags_.sends[index].flExclusionLFRatio = - (eax_.sends[index].flExclusionLFRatio != eax_d_.sends[index].flExclusionLFRatio); -} - -void ALsource::eax_defer_send( - const EAXSOURCESENDPROPERTIES& all, - EaxFxSlotIndexValue index) -{ - eax_defer_send_send(all.lSend, index); - eax_defer_send_send_hf(all.lSendHF, index); -} - -void ALsource::eax_defer_send_exclusion_all( - const EAXSOURCEEXCLUSIONSENDPROPERTIES& all, - EaxFxSlotIndexValue index) -{ - eax_defer_send_exclusion(all.lExclusion, index); - eax_defer_send_exclusion_lf_ratio(all.flExclusionLFRatio, index); -} - -void ALsource::eax_defer_send_occlusion_all( - const EAXSOURCEOCCLUSIONSENDPROPERTIES& all, - EaxFxSlotIndexValue index) -{ - eax_defer_send_occlusion(all.lOcclusion, index); - eax_defer_send_occlusion_lf_ratio(all.flOcclusionLFRatio, index); - eax_defer_send_occlusion_room_ratio(all.flOcclusionRoomRatio, index); - eax_defer_send_occlusion_direct_ratio(all.flOcclusionDirectRatio, index); -} - -void ALsource::eax_defer_send_all( - const EAXSOURCEALLSENDPROPERTIES& all, - EaxFxSlotIndexValue index) -{ - eax_defer_send_send(all.lSend, index); - eax_defer_send_send_hf(all.lSendHF, index); - eax_defer_send_occlusion(all.lOcclusion, index); - eax_defer_send_occlusion_lf_ratio(all.flOcclusionLFRatio, index); - eax_defer_send_occlusion_room_ratio(all.flOcclusionRoomRatio, index); - eax_defer_send_occlusion_direct_ratio(all.flOcclusionDirectRatio, index); - eax_defer_send_exclusion(all.lExclusion, index); - eax_defer_send_exclusion_lf_ratio(all.flExclusionLFRatio, index); -} - -void ALsource::eax_defer_send( - const EaxEaxCall& eax_call) -{ - const auto eax_all_span = - eax_call.get_values(); - - const auto count = eax_all_span.size(); - - if (count <= 0 || count > EAX_MAX_FXSLOTS) - { - throw EaxSourceSendException{"Send count out of range."}; - } - - for (auto i = std::size_t{}; i < count; ++i) - { - const auto& all = eax_all_span[i]; - eax_validate_send(all); - } - - for (auto i = std::size_t{}; i < count; ++i) - { - const auto& all = eax_all_span[i]; - const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); - eax_defer_send(all, send_index); - } -} - -void ALsource::eax_defer_send_exclusion_all( - const EaxEaxCall& eax_call) -{ - const auto eax_all_span = - eax_call.get_values(); - - const auto count = eax_all_span.size(); - - if (count <= 0 || count > EAX_MAX_FXSLOTS) - { - throw EaxSourceSendException{"Send exclusion all count out of range."}; - } - - for (auto i = std::size_t{}; i < count; ++i) - { - const auto& all = eax_all_span[i]; - eax_validate_send_exclusion_all(all); - } - - for (auto i = std::size_t{}; i < count; ++i) - { - const auto& all = eax_all_span[i]; - const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); - eax_defer_send_exclusion_all(all, send_index); - } -} - -void ALsource::eax_defer_send_occlusion_all( - const EaxEaxCall& eax_call) -{ - const auto eax_all_span = - eax_call.get_values(); - - const auto count = eax_all_span.size(); - - if (count <= 0 || count > EAX_MAX_FXSLOTS) - { - throw EaxSourceSendException{"Send occlusion all count out of range."}; - } - - for (auto i = std::size_t{}; i < count; ++i) - { - const auto& all = eax_all_span[i]; - eax_validate_send_occlusion_all(all); - } - - for (auto i = std::size_t{}; i < count; ++i) - { - const auto& all = eax_all_span[i]; - const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); - eax_defer_send_occlusion_all(all, send_index); - } -} - -void ALsource::eax_defer_send_all( - const EaxEaxCall& eax_call) -{ - const auto eax_all_span = - eax_call.get_values(); - - const auto count = eax_all_span.size(); - - if (count <= 0 || count > EAX_MAX_FXSLOTS) - { - throw EaxSourceSendException{"Send all count out of range."}; - } - - for (auto i = std::size_t{}; i < count; ++i) - { - const auto& all = eax_all_span[i]; - eax_validate_send_all(all); - } - - for (auto i = std::size_t{}; i < count; ++i) - { - const auto& all = eax_all_span[i]; - const auto send_index = eax_get_send_index(all.guidReceivingFXSlotID); - eax_defer_send_all(all, send_index); - } -} - - -void ALsource::eax_validate_source_direct( - long direct) -{ - eax_validate_range( - "Direct", - direct, - EAXSOURCE_MINDIRECT, - EAXSOURCE_MAXDIRECT); -} - -void ALsource::eax_validate_source_direct_hf( - long direct_hf) -{ - eax_validate_range( - "Direct HF", - direct_hf, - EAXSOURCE_MINDIRECTHF, - EAXSOURCE_MAXDIRECTHF); -} - -void ALsource::eax_validate_source_room( - long room) -{ - eax_validate_range( - "Room", - room, - EAXSOURCE_MINROOM, - EAXSOURCE_MAXROOM); -} - -void ALsource::eax_validate_source_room_hf( - long room_hf) -{ - eax_validate_range( - "Room HF", - room_hf, - EAXSOURCE_MINROOMHF, - EAXSOURCE_MAXROOMHF); -} - -void ALsource::eax_validate_source_obstruction( - long obstruction) -{ - eax_validate_range( - "Obstruction", - obstruction, - EAXSOURCE_MINOBSTRUCTION, - EAXSOURCE_MAXOBSTRUCTION); -} - -void ALsource::eax_validate_source_obstruction_lf_ratio( - float obstruction_lf_ratio) -{ - eax_validate_range( - "Obstruction LF Ratio", - obstruction_lf_ratio, - EAXSOURCE_MINOBSTRUCTIONLFRATIO, - EAXSOURCE_MAXOBSTRUCTIONLFRATIO); -} - -void ALsource::eax_validate_source_occlusion( - long occlusion) -{ - eax_validate_range( - eax_get_occlusion_name(), - occlusion, - EAXSOURCE_MINOCCLUSION, - EAXSOURCE_MAXOCCLUSION); -} - -void ALsource::eax_validate_source_occlusion_lf_ratio( - float occlusion_lf_ratio) -{ - eax_validate_range( - eax_get_occlusion_lf_ratio_name(), - occlusion_lf_ratio, - EAXSOURCE_MINOCCLUSIONLFRATIO, - EAXSOURCE_MAXOCCLUSIONLFRATIO); -} - -void ALsource::eax_validate_source_occlusion_room_ratio( - float occlusion_room_ratio) -{ - eax_validate_range( - eax_get_occlusion_room_ratio_name(), - occlusion_room_ratio, - EAXSOURCE_MINOCCLUSIONROOMRATIO, - EAXSOURCE_MAXOCCLUSIONROOMRATIO); -} - -void ALsource::eax_validate_source_occlusion_direct_ratio( - float occlusion_direct_ratio) -{ - eax_validate_range( - eax_get_occlusion_direct_ratio_name(), - occlusion_direct_ratio, - EAXSOURCE_MINOCCLUSIONDIRECTRATIO, - EAXSOURCE_MAXOCCLUSIONDIRECTRATIO); -} - -void ALsource::eax_validate_source_exclusion( - long exclusion) -{ - eax_validate_range( - eax_get_exclusion_name(), - exclusion, - EAXSOURCE_MINEXCLUSION, - EAXSOURCE_MAXEXCLUSION); -} - -void ALsource::eax_validate_source_exclusion_lf_ratio( - float exclusion_lf_ratio) -{ - eax_validate_range( - eax_get_exclusion_lf_ratio_name(), - exclusion_lf_ratio, - EAXSOURCE_MINEXCLUSIONLFRATIO, - EAXSOURCE_MAXEXCLUSIONLFRATIO); -} - -void ALsource::eax_validate_source_outside_volume_hf( - long outside_volume_hf) -{ - eax_validate_range( - "Outside Volume HF", - outside_volume_hf, - EAXSOURCE_MINOUTSIDEVOLUMEHF, - EAXSOURCE_MAXOUTSIDEVOLUMEHF); -} - -void ALsource::eax_validate_source_doppler_factor( - float doppler_factor) -{ - eax_validate_range( - "Doppler Factor", - doppler_factor, - EAXSOURCE_MINDOPPLERFACTOR, - EAXSOURCE_MAXDOPPLERFACTOR); -} - -void ALsource::eax_validate_source_rolloff_factor( - float rolloff_factor) -{ - eax_validate_range( - "Rolloff Factor", - rolloff_factor, - EAXSOURCE_MINROLLOFFFACTOR, - EAXSOURCE_MAXROLLOFFFACTOR); -} - -void ALsource::eax_validate_source_room_rolloff_factor( - float room_rolloff_factor) -{ - eax_validate_range( - "Room Rolloff Factor", - room_rolloff_factor, - EAXSOURCE_MINROOMROLLOFFFACTOR, - EAXSOURCE_MAXROOMROLLOFFFACTOR); -} - -void ALsource::eax_validate_source_air_absorption_factor( - float air_absorption_factor) -{ - eax_validate_range( - "Air Absorption Factor", - air_absorption_factor, - EAXSOURCE_MINAIRABSORPTIONFACTOR, - EAXSOURCE_MAXAIRABSORPTIONFACTOR); -} - -void ALsource::eax_validate_source_flags( - unsigned long flags, - int eax_version) -{ - eax_validate_range( - "Flags", - flags, - 0UL, - ~((eax_version == 5) ? EAX50SOURCEFLAGS_RESERVED : EAX20SOURCEFLAGS_RESERVED)); -} - -void ALsource::eax_validate_source_macro_fx_factor( - float macro_fx_factor) -{ - eax_validate_range( - "Macro FX Factor", - macro_fx_factor, - EAXSOURCE_MINMACROFXFACTOR, - EAXSOURCE_MAXMACROFXFACTOR); -} - -void ALsource::eax_validate_source_2d_all( - const EAXSOURCE2DPROPERTIES& all, - int eax_version) -{ - eax_validate_source_direct(all.lDirect); - eax_validate_source_direct_hf(all.lDirectHF); - eax_validate_source_room(all.lRoom); - eax_validate_source_room_hf(all.lRoomHF); - eax_validate_source_flags(all.ulFlags, eax_version); -} - -void ALsource::eax_validate_source_obstruction_all( - const EAXOBSTRUCTIONPROPERTIES& all) -{ - eax_validate_source_obstruction(all.lObstruction); - eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio); -} - -void ALsource::eax_validate_source_exclusion_all( - const EAXEXCLUSIONPROPERTIES& all) -{ - eax_validate_source_exclusion(all.lExclusion); - eax_validate_source_exclusion_lf_ratio(all.flExclusionLFRatio); -} - -void ALsource::eax_validate_source_occlusion_all( - const EAXOCCLUSIONPROPERTIES& all) -{ - eax_validate_source_occlusion(all.lOcclusion); - eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio); - eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio); - eax_validate_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); -} - -void ALsource::eax_validate_source_all( - const EAX20BUFFERPROPERTIES& all, - int eax_version) -{ - eax_validate_source_direct(all.lDirect); - eax_validate_source_direct_hf(all.lDirectHF); - eax_validate_source_room(all.lRoom); - eax_validate_source_room_hf(all.lRoomHF); - eax_validate_source_obstruction(all.lObstruction); - eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio); - eax_validate_source_occlusion(all.lOcclusion); - eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio); - eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio); - eax_validate_source_outside_volume_hf(all.lOutsideVolumeHF); - eax_validate_source_room_rolloff_factor(all.flRoomRolloffFactor); - eax_validate_source_air_absorption_factor(all.flAirAbsorptionFactor); - eax_validate_source_flags(all.dwFlags, eax_version); -} - -void ALsource::eax_validate_source_all( - const EAX30SOURCEPROPERTIES& all, - int eax_version) -{ - eax_validate_source_direct(all.lDirect); - eax_validate_source_direct_hf(all.lDirectHF); - eax_validate_source_room(all.lRoom); - eax_validate_source_room_hf(all.lRoomHF); - eax_validate_source_obstruction(all.lObstruction); - eax_validate_source_obstruction_lf_ratio(all.flObstructionLFRatio); - eax_validate_source_occlusion(all.lOcclusion); - eax_validate_source_occlusion_lf_ratio(all.flOcclusionLFRatio); - eax_validate_source_occlusion_room_ratio(all.flOcclusionRoomRatio); - eax_validate_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); - eax_validate_source_exclusion(all.lExclusion); - eax_validate_source_exclusion_lf_ratio(all.flExclusionLFRatio); - eax_validate_source_outside_volume_hf(all.lOutsideVolumeHF); - eax_validate_source_doppler_factor(all.flDopplerFactor); - eax_validate_source_rolloff_factor(all.flRolloffFactor); - eax_validate_source_room_rolloff_factor(all.flRoomRolloffFactor); - eax_validate_source_air_absorption_factor(all.flAirAbsorptionFactor); - eax_validate_source_flags(all.ulFlags, eax_version); -} - -void ALsource::eax_validate_source_all( - const EAX50SOURCEPROPERTIES& all, - int eax_version) -{ - eax_validate_source_all(static_cast(all), eax_version); - eax_validate_source_macro_fx_factor(all.flMacroFXFactor); -} - -void ALsource::eax_validate_source_speaker_id( - long speaker_id) -{ - eax_validate_range( - "Speaker Id", - speaker_id, - static_cast(EAXSPEAKER_FRONT_LEFT), - static_cast(EAXSPEAKER_LOW_FREQUENCY)); -} - -void ALsource::eax_validate_source_speaker_level( - long speaker_level) -{ - eax_validate_range( - "Speaker Level", - speaker_level, - EAXSOURCE_MINSPEAKERLEVEL, - EAXSOURCE_MAXSPEAKERLEVEL); -} - -void ALsource::eax_validate_source_speaker_level_all( - const EAXSPEAKERLEVELPROPERTIES& all) -{ - eax_validate_source_speaker_id(all.lSpeakerID); - eax_validate_source_speaker_level(all.lLevel); -} - -void ALsource::eax_defer_source_direct( - long lDirect) -{ - eax_d_.source.lDirect = lDirect; - eax_source_dirty_filter_flags_.lDirect = (eax_.source.lDirect != eax_d_.source.lDirect); -} - -void ALsource::eax_defer_source_direct_hf( - long lDirectHF) -{ - eax_d_.source.lDirectHF = lDirectHF; - eax_source_dirty_filter_flags_.lDirectHF = (eax_.source.lDirectHF != eax_d_.source.lDirectHF); -} - -void ALsource::eax_defer_source_room( - long lRoom) -{ - eax_d_.source.lRoom = lRoom; - eax_source_dirty_filter_flags_.lRoom = (eax_.source.lRoom != eax_d_.source.lRoom); -} - -void ALsource::eax_defer_source_room_hf( - long lRoomHF) -{ - eax_d_.source.lRoomHF = lRoomHF; - eax_source_dirty_filter_flags_.lRoomHF = (eax_.source.lRoomHF != eax_d_.source.lRoomHF); -} - -void ALsource::eax_defer_source_obstruction( - long lObstruction) -{ - eax_d_.source.lObstruction = lObstruction; - eax_source_dirty_filter_flags_.lObstruction = (eax_.source.lObstruction != eax_d_.source.lObstruction); -} - -void ALsource::eax_defer_source_obstruction_lf_ratio( - float flObstructionLFRatio) -{ - eax_d_.source.flObstructionLFRatio = flObstructionLFRatio; - eax_source_dirty_filter_flags_.flObstructionLFRatio = (eax_.source.flObstructionLFRatio != eax_d_.source.flObstructionLFRatio); -} - -void ALsource::eax_defer_source_occlusion( - long lOcclusion) -{ - eax_d_.source.lOcclusion = lOcclusion; - eax_source_dirty_filter_flags_.lOcclusion = (eax_.source.lOcclusion != eax_d_.source.lOcclusion); -} - -void ALsource::eax_defer_source_occlusion_lf_ratio( - float flOcclusionLFRatio) -{ - eax_d_.source.flOcclusionLFRatio = flOcclusionLFRatio; - eax_source_dirty_filter_flags_.flOcclusionLFRatio = (eax_.source.flOcclusionLFRatio != eax_d_.source.flOcclusionLFRatio); -} - -void ALsource::eax_defer_source_occlusion_room_ratio( - float flOcclusionRoomRatio) -{ - eax_d_.source.flOcclusionRoomRatio = flOcclusionRoomRatio; - eax_source_dirty_filter_flags_.flOcclusionRoomRatio = (eax_.source.flOcclusionRoomRatio != eax_d_.source.flOcclusionRoomRatio); -} - -void ALsource::eax_defer_source_occlusion_direct_ratio( - float flOcclusionDirectRatio) -{ - eax_d_.source.flOcclusionDirectRatio = flOcclusionDirectRatio; - eax_source_dirty_filter_flags_.flOcclusionDirectRatio = (eax_.source.flOcclusionDirectRatio != eax_d_.source.flOcclusionDirectRatio); -} - -void ALsource::eax_defer_source_exclusion( - long lExclusion) -{ - eax_d_.source.lExclusion = lExclusion; - eax_source_dirty_filter_flags_.lExclusion = (eax_.source.lExclusion != eax_d_.source.lExclusion); -} - -void ALsource::eax_defer_source_exclusion_lf_ratio( - float flExclusionLFRatio) -{ - eax_d_.source.flExclusionLFRatio = flExclusionLFRatio; - eax_source_dirty_filter_flags_.flExclusionLFRatio = (eax_.source.flExclusionLFRatio != eax_d_.source.flExclusionLFRatio); -} - -void ALsource::eax_defer_source_outside_volume_hf( - long lOutsideVolumeHF) -{ - eax_d_.source.lOutsideVolumeHF = lOutsideVolumeHF; - eax_source_dirty_misc_flags_.lOutsideVolumeHF = (eax_.source.lOutsideVolumeHF != eax_d_.source.lOutsideVolumeHF); -} - -void ALsource::eax_defer_source_doppler_factor( - float flDopplerFactor) -{ - eax_d_.source.flDopplerFactor = flDopplerFactor; - eax_source_dirty_misc_flags_.flDopplerFactor = (eax_.source.flDopplerFactor != eax_d_.source.flDopplerFactor); -} - -void ALsource::eax_defer_source_rolloff_factor( - float flRolloffFactor) -{ - eax_d_.source.flRolloffFactor = flRolloffFactor; - eax_source_dirty_misc_flags_.flRolloffFactor = (eax_.source.flRolloffFactor != eax_d_.source.flRolloffFactor); -} - -void ALsource::eax_defer_source_room_rolloff_factor( - float flRoomRolloffFactor) -{ - eax_d_.source.flRoomRolloffFactor = flRoomRolloffFactor; - eax_source_dirty_misc_flags_.flRoomRolloffFactor = (eax_.source.flRoomRolloffFactor != eax_d_.source.flRoomRolloffFactor); -} - -void ALsource::eax_defer_source_air_absorption_factor( - float flAirAbsorptionFactor) -{ - eax_d_.source.flAirAbsorptionFactor = flAirAbsorptionFactor; - eax_source_dirty_misc_flags_.flAirAbsorptionFactor = (eax_.source.flAirAbsorptionFactor != eax_d_.source.flAirAbsorptionFactor); -} - -void ALsource::eax_defer_source_flags( - unsigned long ulFlags) -{ - eax_d_.source.ulFlags = ulFlags; - eax_source_dirty_misc_flags_.ulFlags = (eax_.source.ulFlags != eax_d_.source.ulFlags); -} - -void ALsource::eax_defer_source_macro_fx_factor( - float flMacroFXFactor) -{ - eax_d_.source.flMacroFXFactor = flMacroFXFactor; - eax_source_dirty_misc_flags_.flMacroFXFactor = (eax_.source.flMacroFXFactor != eax_d_.source.flMacroFXFactor); -} - -void ALsource::eax_defer_source_2d_all( - const EAXSOURCE2DPROPERTIES& all) -{ - eax_defer_source_direct(all.lDirect); - eax_defer_source_direct_hf(all.lDirectHF); - eax_defer_source_room(all.lRoom); - eax_defer_source_room_hf(all.lRoomHF); - eax_defer_source_flags(all.ulFlags); -} - -void ALsource::eax_defer_source_obstruction_all( - const EAXOBSTRUCTIONPROPERTIES& all) -{ - eax_defer_source_obstruction(all.lObstruction); - eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio); -} - -void ALsource::eax_defer_source_exclusion_all( - const EAXEXCLUSIONPROPERTIES& all) -{ - eax_defer_source_exclusion(all.lExclusion); - eax_defer_source_exclusion_lf_ratio(all.flExclusionLFRatio); -} - -void ALsource::eax_defer_source_occlusion_all( - const EAXOCCLUSIONPROPERTIES& all) -{ - eax_defer_source_occlusion(all.lOcclusion); - eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio); - eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio); - eax_defer_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); -} - -void ALsource::eax_defer_source_all( - const EAX20BUFFERPROPERTIES& all) -{ - eax_defer_source_direct(all.lDirect); - eax_defer_source_direct_hf(all.lDirectHF); - eax_defer_source_room(all.lRoom); - eax_defer_source_room_hf(all.lRoomHF); - eax_defer_source_obstruction(all.lObstruction); - eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio); - eax_defer_source_occlusion(all.lOcclusion); - eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio); - eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio); - eax_defer_source_outside_volume_hf(all.lOutsideVolumeHF); - eax_defer_source_room_rolloff_factor(all.flRoomRolloffFactor); - eax_defer_source_air_absorption_factor(all.flAirAbsorptionFactor); - eax_defer_source_flags(all.dwFlags); -} - -void ALsource::eax_defer_source_all( - const EAX30SOURCEPROPERTIES& all) -{ - eax_defer_source_direct(all.lDirect); - eax_defer_source_direct_hf(all.lDirectHF); - eax_defer_source_room(all.lRoom); - eax_defer_source_room_hf(all.lRoomHF); - eax_defer_source_obstruction(all.lObstruction); - eax_defer_source_obstruction_lf_ratio(all.flObstructionLFRatio); - eax_defer_source_occlusion(all.lOcclusion); - eax_defer_source_occlusion_lf_ratio(all.flOcclusionLFRatio); - eax_defer_source_occlusion_room_ratio(all.flOcclusionRoomRatio); - eax_defer_source_occlusion_direct_ratio(all.flOcclusionDirectRatio); - eax_defer_source_exclusion(all.lExclusion); - eax_defer_source_exclusion_lf_ratio(all.flExclusionLFRatio); - eax_defer_source_outside_volume_hf(all.lOutsideVolumeHF); - eax_defer_source_doppler_factor(all.flDopplerFactor); - eax_defer_source_rolloff_factor(all.flRolloffFactor); - eax_defer_source_room_rolloff_factor(all.flRoomRolloffFactor); - eax_defer_source_air_absorption_factor(all.flAirAbsorptionFactor); - eax_defer_source_flags(all.ulFlags); -} - -void ALsource::eax_defer_source_all( - const EAX50SOURCEPROPERTIES& all) -{ - eax_defer_source_all(static_cast(all)); - eax_defer_source_macro_fx_factor(all.flMacroFXFactor); -} - -void ALsource::eax_defer_source_speaker_level_all( - const EAXSPEAKERLEVELPROPERTIES& all) -{ - const auto speaker_index = static_cast(all.lSpeakerID - 1); - auto& speaker_level_d = eax_d_.speaker_levels[speaker_index]; - const auto& speaker_level = eax_.speaker_levels[speaker_index]; - - if (speaker_level != speaker_level_d) - { - eax_source_dirty_misc_flags_.speaker_levels = true; - } -} - -void ALsource::eax1_set_efx() -{ - const auto primary_fx_slot_index = eax_al_context_->eax_get_primary_fx_slot_index(); - - if (!primary_fx_slot_index.has_value()) - return; - - WetGainAuto = (eax1_.fMix == EAX_REVERBMIX_USEDISTANCE); - const auto filter_gain = (WetGainAuto ? 1.0F : eax1_.fMix); - auto& fx_slot = eax_al_context_->eax_get_fx_slot(*primary_fx_slot_index); - eax_set_al_source_send(&fx_slot, *primary_fx_slot_index, EaxAlLowPassParam{filter_gain, 1.0F}); - mPropsDirty = true; -} - -void ALsource::eax1_set_reverb_mix(const EaxEaxCall& eax_call) -{ - const auto reverb_mix = eax_call.get_value(); - eax1_validate_reverb_mix(reverb_mix); - - if (eax1_.fMix == reverb_mix) - return; - - eax1_.fMix = reverb_mix; - eax1_set_efx(); -} - -void ALsource::eax_defer_source_direct( - const EaxEaxCall& eax_call) -{ - const auto direct = - eax_call.get_value(); - - eax_validate_source_direct(direct); - eax_defer_source_direct(direct); -} - -void ALsource::eax_defer_source_direct_hf( - const EaxEaxCall& eax_call) -{ - const auto direct_hf = - eax_call.get_value(); - - eax_validate_source_direct_hf(direct_hf); - eax_defer_source_direct_hf(direct_hf); -} - -void ALsource::eax_defer_source_room( - const EaxEaxCall& eax_call) -{ - const auto room = - eax_call.get_value(); - - eax_validate_source_room(room); - eax_defer_source_room(room); -} - -void ALsource::eax_defer_source_room_hf( - const EaxEaxCall& eax_call) -{ - const auto room_hf = - eax_call.get_value(); - - eax_validate_source_room_hf(room_hf); - eax_defer_source_room_hf(room_hf); -} - -void ALsource::eax_defer_source_obstruction( - const EaxEaxCall& eax_call) -{ - const auto obstruction = - eax_call.get_value(); - - eax_validate_source_obstruction(obstruction); - eax_defer_source_obstruction(obstruction); -} - -void ALsource::eax_defer_source_obstruction_lf_ratio( - const EaxEaxCall& eax_call) -{ - const auto obstruction_lf_ratio = - eax_call.get_value(); - - eax_validate_source_obstruction_lf_ratio(obstruction_lf_ratio); - eax_defer_source_obstruction_lf_ratio(obstruction_lf_ratio); -} - -void ALsource::eax_defer_source_occlusion( - const EaxEaxCall& eax_call) -{ - const auto occlusion = - eax_call.get_value(); - - eax_validate_source_occlusion(occlusion); - eax_defer_source_occlusion(occlusion); -} - -void ALsource::eax_defer_source_occlusion_lf_ratio( - const EaxEaxCall& eax_call) -{ - const auto occlusion_lf_ratio = - eax_call.get_value(); - - eax_validate_source_occlusion_lf_ratio(occlusion_lf_ratio); - eax_defer_source_occlusion_lf_ratio(occlusion_lf_ratio); -} - -void ALsource::eax_defer_source_occlusion_room_ratio( - const EaxEaxCall& eax_call) -{ - const auto occlusion_room_ratio = - eax_call.get_value(); - - eax_validate_source_occlusion_room_ratio(occlusion_room_ratio); - eax_defer_source_occlusion_room_ratio(occlusion_room_ratio); -} - -void ALsource::eax_defer_source_occlusion_direct_ratio( - const EaxEaxCall& eax_call) -{ - const auto occlusion_direct_ratio = - eax_call.get_value(); - - eax_validate_source_occlusion_direct_ratio(occlusion_direct_ratio); - eax_defer_source_occlusion_direct_ratio(occlusion_direct_ratio); -} - -void ALsource::eax_defer_source_exclusion( - const EaxEaxCall& eax_call) -{ - const auto exclusion = - eax_call.get_value(); - - eax_validate_source_exclusion(exclusion); - eax_defer_source_exclusion(exclusion); -} - -void ALsource::eax_defer_source_exclusion_lf_ratio( - const EaxEaxCall& eax_call) -{ - const auto exclusion_lf_ratio = - eax_call.get_value(); - - eax_validate_source_exclusion_lf_ratio(exclusion_lf_ratio); - eax_defer_source_exclusion_lf_ratio(exclusion_lf_ratio); -} - -void ALsource::eax_defer_source_outside_volume_hf( - const EaxEaxCall& eax_call) -{ - const auto outside_volume_hf = - eax_call.get_value(); - - eax_validate_source_outside_volume_hf(outside_volume_hf); - eax_defer_source_outside_volume_hf(outside_volume_hf); -} - -void ALsource::eax_defer_source_doppler_factor( - const EaxEaxCall& eax_call) -{ - const auto doppler_factor = - eax_call.get_value(); - - eax_validate_source_doppler_factor(doppler_factor); - eax_defer_source_doppler_factor(doppler_factor); -} - -void ALsource::eax_defer_source_rolloff_factor( - const EaxEaxCall& eax_call) -{ - const auto rolloff_factor = - eax_call.get_value(); - - eax_validate_source_rolloff_factor(rolloff_factor); - eax_defer_source_rolloff_factor(rolloff_factor); -} - -void ALsource::eax_defer_source_room_rolloff_factor( - const EaxEaxCall& eax_call) -{ - const auto room_rolloff_factor = - eax_call.get_value(); - - eax_validate_source_room_rolloff_factor(room_rolloff_factor); - eax_defer_source_room_rolloff_factor(room_rolloff_factor); -} - -void ALsource::eax_defer_source_air_absorption_factor( - const EaxEaxCall& eax_call) -{ - const auto air_absorption_factor = - eax_call.get_value(); - - eax_validate_source_air_absorption_factor(air_absorption_factor); - eax_defer_source_air_absorption_factor(air_absorption_factor); -} - -void ALsource::eax_defer_source_flags( - const EaxEaxCall& eax_call) -{ - const auto flags = - eax_call.get_value(); - - eax_validate_source_flags(flags, eax_call.get_version()); - eax_defer_source_flags(flags); -} - -void ALsource::eax_defer_source_macro_fx_factor( - const EaxEaxCall& eax_call) -{ - const auto macro_fx_factor = - eax_call.get_value(); - - eax_validate_source_macro_fx_factor(macro_fx_factor); - eax_defer_source_macro_fx_factor(macro_fx_factor); -} - -void ALsource::eax_defer_source_2d_all( - const EaxEaxCall& eax_call) -{ - const auto all = eax_call.get_value(); - - eax_validate_source_2d_all(all, eax_call.get_version()); - eax_defer_source_2d_all(all); -} - -void ALsource::eax_defer_source_obstruction_all( - const EaxEaxCall& eax_call) -{ - const auto all = eax_call.get_value(); - - eax_validate_source_obstruction_all(all); - eax_defer_source_obstruction_all(all); -} - -void ALsource::eax_defer_source_exclusion_all( - const EaxEaxCall& eax_call) -{ - const auto all = eax_call.get_value(); - - eax_validate_source_exclusion_all(all); - eax_defer_source_exclusion_all(all); -} - -void ALsource::eax_defer_source_occlusion_all( - const EaxEaxCall& eax_call) -{ - const auto all = eax_call.get_value(); - - eax_validate_source_occlusion_all(all); - eax_defer_source_occlusion_all(all); -} - -void ALsource::eax_defer_source_all( - const EaxEaxCall& eax_call) -{ - const auto eax_version = eax_call.get_version(); - - if (eax_version == 2) - { - const auto all = eax_call.get_value(); - - eax_validate_source_all(all, eax_version); - eax_defer_source_all(all); - } - else if (eax_version < 5) - { - const auto all = eax_call.get_value(); - - eax_validate_source_all(all, eax_version); - eax_defer_source_all(all); - } - else - { - const auto all = eax_call.get_value(); - - eax_validate_source_all(all, eax_version); - eax_defer_source_all(all); - } -} - -void ALsource::eax_defer_source_speaker_level_all( - const EaxEaxCall& eax_call) -{ - const auto speaker_level_properties = eax_call.get_value(); - - eax_validate_source_speaker_level_all(speaker_level_properties); - eax_defer_source_speaker_level_all(speaker_level_properties); -} - -void ALsource::eax_set_outside_volume_hf() -{ - const auto efx_gain_hf = clamp( - level_mb_to_gain(static_cast(eax_.source.lOutsideVolumeHF)), + OuterGainHF = std::clamp( + level_mb_to_gain(static_cast(mEax.source.lOutsideVolumeHF)), AL_MIN_CONE_OUTER_GAINHF, - AL_MAX_CONE_OUTER_GAINHF - ); - - OuterGainHF = efx_gain_hf; + AL_MAX_CONE_OUTER_GAINHF); } -void ALsource::eax_set_doppler_factor() +void ALsource::eax_set_efx_doppler_factor() { - DopplerFactor = eax_.source.flDopplerFactor; + DopplerFactor = mEax.source.flDopplerFactor; } -void ALsource::eax_set_rolloff_factor() +void ALsource::eax_set_efx_rolloff_factor() { - RolloffFactor2 = eax_.source.flRolloffFactor; + RolloffFactor2 = mEax.source.flRolloffFactor; } -void ALsource::eax_set_room_rolloff_factor() +void ALsource::eax_set_efx_room_rolloff_factor() { - RoomRolloffFactor = eax_.source.flRoomRolloffFactor; + RoomRolloffFactor = mEax.source.flRoomRolloffFactor; } -void ALsource::eax_set_air_absorption_factor() +void ALsource::eax_set_efx_air_absorption_factor() { - AirAbsorptionFactor = eax_.source.flAirAbsorptionFactor; + AirAbsorptionFactor = mEax.source.flAirAbsorptionFactor; } -void ALsource::eax_set_direct_hf_auto_flag() +void ALsource::eax_set_efx_dry_gain_hf_auto() { - const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_DIRECTHFAUTO) != 0; - - DryGainHFAuto = is_enable; + DryGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_DIRECTHFAUTO) != 0); } -void ALsource::eax_set_room_auto_flag() +void ALsource::eax_set_efx_wet_gain_auto() { - const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_ROOMAUTO) != 0; - - WetGainAuto = is_enable; + WetGainAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMAUTO) != 0); } -void ALsource::eax_set_room_hf_auto_flag() +void ALsource::eax_set_efx_wet_gain_hf_auto() { - const auto is_enable = (eax_.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0; - - WetGainHFAuto = is_enable; + WetGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0); } -void ALsource::eax_set_flags() +void ALsource::eax1_set(const EaxCall& call, Eax1Props& props) { - eax_set_direct_hf_auto_flag(); - eax_set_room_auto_flag(); - eax_set_room_hf_auto_flag(); - eax_set_speaker_levels(); -} - -void ALsource::eax_set_macro_fx_factor() -{ - // TODO -} - -void ALsource::eax_set_speaker_levels() -{ - // TODO -} - -void ALsource::eax1_set(const EaxEaxCall& eax_call) -{ - switch (eax_call.get_property_id()) - { + switch (call.get_property_id()) { case DSPROPERTY_EAXBUFFER_ALL: + eax_defer(call, props); + break; + case DSPROPERTY_EAXBUFFER_REVERBMIX: - eax1_set_reverb_mix(eax_call); + eax_defer(call, props.fMix); break; default: - eax_fail("Unsupported property id."); + eax_fail_unknown_property_id(); } } -void ALsource::eax_apply_deferred() +void ALsource::eax2_set(const EaxCall& call, Eax2Props& props) { - if (!eax_are_active_fx_slots_dirty_ && - eax_sends_dirty_flags_ == EaxSourceSendsDirtyFlags{} && - eax_source_dirty_filter_flags_ == EaxSourceSourceFilterDirtyFlags{} && - eax_source_dirty_misc_flags_ == EaxSourceSourceMiscDirtyFlags{}) - { - return; + switch (call.get_property_id()) { + case DSPROPERTY_EAX20BUFFER_NONE: + break; + + case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS: + eax_defer(call, props); + break; + + case DSPROPERTY_EAX20BUFFER_DIRECT: + eax_defer(call, props.lDirect); + break; + + case DSPROPERTY_EAX20BUFFER_DIRECTHF: + eax_defer(call, props.lDirectHF); + break; + + case DSPROPERTY_EAX20BUFFER_ROOM: + eax_defer(call, props.lRoom); + break; + + case DSPROPERTY_EAX20BUFFER_ROOMHF: + eax_defer(call, props.lRoomHF); + break; + + case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR: + eax_defer(call, props.flRoomRolloffFactor); + break; + + case DSPROPERTY_EAX20BUFFER_OBSTRUCTION: + eax_defer(call, props.lObstruction); + break; + + case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO: + eax_defer(call, props.flObstructionLFRatio); + break; + + case DSPROPERTY_EAX20BUFFER_OCCLUSION: + eax_defer(call, props.lOcclusion); + break; + + case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO: + eax_defer(call, props.flOcclusionLFRatio); + break; + + case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO: + eax_defer(call, props.flOcclusionRoomRatio); + break; + + case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF: + eax_defer(call, props.lOutsideVolumeHF); + break; + + case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR: + eax_defer(call, props.flAirAbsorptionFactor); + break; + + case DSPROPERTY_EAX20BUFFER_FLAGS: + eax_defer(call, props.dwFlags); + break; + + default: + eax_fail_unknown_property_id(); } - - eax_ = eax_d_; - - if (eax_are_active_fx_slots_dirty_) - { - eax_are_active_fx_slots_dirty_ = false; - eax_set_fx_slots(); - eax_update_filters_internal(); - } - else if (eax_has_active_fx_slots_) - { - if (eax_source_dirty_filter_flags_ != EaxSourceSourceFilterDirtyFlags{}) - { - eax_update_filters_internal(); - } - else if (eax_sends_dirty_flags_ != EaxSourceSendsDirtyFlags{}) - { - for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i) - { - if (eax_active_fx_slots_[i]) - { - if (eax_sends_dirty_flags_.sends[i] != EaxSourceSendDirtyFlags{}) - { - eax_update_filters_internal(); - break; - } - } - } - } - } - - if (eax_source_dirty_misc_flags_ != EaxSourceSourceMiscDirtyFlags{}) - { - if (eax_source_dirty_misc_flags_.lOutsideVolumeHF) - { - eax_set_outside_volume_hf(); - } - - if (eax_source_dirty_misc_flags_.flDopplerFactor) - { - eax_set_doppler_factor(); - } - - if (eax_source_dirty_misc_flags_.flRolloffFactor) - { - eax_set_rolloff_factor(); - } - - if (eax_source_dirty_misc_flags_.flRoomRolloffFactor) - { - eax_set_room_rolloff_factor(); - } - - if (eax_source_dirty_misc_flags_.flAirAbsorptionFactor) - { - eax_set_air_absorption_factor(); - } - - if (eax_source_dirty_misc_flags_.ulFlags) - { - eax_set_flags(); - } - - if (eax_source_dirty_misc_flags_.flMacroFXFactor) - { - eax_set_macro_fx_factor(); - } - - mPropsDirty = true; - - eax_source_dirty_misc_flags_ = EaxSourceSourceMiscDirtyFlags{}; - } - - eax_sends_dirty_flags_ = EaxSourceSendsDirtyFlags{}; - eax_source_dirty_filter_flags_ = EaxSourceSourceFilterDirtyFlags{}; } -void ALsource::eax_set( - const EaxEaxCall& eax_call) +void ALsource::eax3_set(const EaxCall& call, Eax3Props& props) { - if (eax_call.get_version() == 1) - { - eax1_set(eax_call); - return; - } - - switch (eax_call.get_property_id()) - { + switch (call.get_property_id()) { case EAXSOURCE_NONE: break; case EAXSOURCE_ALLPARAMETERS: - eax_defer_source_all(eax_call); + eax_defer(call, props); break; case EAXSOURCE_OBSTRUCTIONPARAMETERS: - eax_defer_source_obstruction_all(eax_call); + eax_defer_sub(call, props.lObstruction); break; case EAXSOURCE_OCCLUSIONPARAMETERS: - eax_defer_source_occlusion_all(eax_call); + eax_defer_sub(call, props.lOcclusion); break; case EAXSOURCE_EXCLUSIONPARAMETERS: - eax_defer_source_exclusion_all(eax_call); + eax_defer_sub(call, props.lExclusion); break; case EAXSOURCE_DIRECT: - eax_defer_source_direct(eax_call); + eax_defer(call, props.lDirect); break; case EAXSOURCE_DIRECTHF: - eax_defer_source_direct_hf(eax_call); + eax_defer(call, props.lDirectHF); break; case EAXSOURCE_ROOM: - eax_defer_source_room(eax_call); + eax_defer(call, props.lRoom); break; case EAXSOURCE_ROOMHF: - eax_defer_source_room_hf(eax_call); + eax_defer(call, props.lRoomHF); break; case EAXSOURCE_OBSTRUCTION: - eax_defer_source_obstruction(eax_call); + eax_defer(call, props.lObstruction); break; case EAXSOURCE_OBSTRUCTIONLFRATIO: - eax_defer_source_obstruction_lf_ratio(eax_call); + eax_defer(call, props.flObstructionLFRatio); break; case EAXSOURCE_OCCLUSION: - eax_defer_source_occlusion(eax_call); + eax_defer(call, props.lOcclusion); break; case EAXSOURCE_OCCLUSIONLFRATIO: - eax_defer_source_occlusion_lf_ratio(eax_call); + eax_defer(call, props.flOcclusionLFRatio); break; case EAXSOURCE_OCCLUSIONROOMRATIO: - eax_defer_source_occlusion_room_ratio(eax_call); + eax_defer(call, props.flOcclusionRoomRatio); break; case EAXSOURCE_OCCLUSIONDIRECTRATIO: - eax_defer_source_occlusion_direct_ratio(eax_call); + eax_defer(call, props.flOcclusionDirectRatio); break; case EAXSOURCE_EXCLUSION: - eax_defer_source_exclusion(eax_call); + eax_defer(call, props.lExclusion); break; case EAXSOURCE_EXCLUSIONLFRATIO: - eax_defer_source_exclusion_lf_ratio(eax_call); + eax_defer(call, props.flExclusionLFRatio); break; case EAXSOURCE_OUTSIDEVOLUMEHF: - eax_defer_source_outside_volume_hf(eax_call); + eax_defer(call, props.lOutsideVolumeHF); break; case EAXSOURCE_DOPPLERFACTOR: - eax_defer_source_doppler_factor(eax_call); + eax_defer(call, props.flDopplerFactor); break; case EAXSOURCE_ROLLOFFFACTOR: - eax_defer_source_rolloff_factor(eax_call); + eax_defer(call, props.flRolloffFactor); break; case EAXSOURCE_ROOMROLLOFFFACTOR: - eax_defer_source_room_rolloff_factor(eax_call); + eax_defer(call, props.flRoomRolloffFactor); break; case EAXSOURCE_AIRABSORPTIONFACTOR: - eax_defer_source_air_absorption_factor(eax_call); + eax_defer(call, props.flAirAbsorptionFactor); break; case EAXSOURCE_FLAGS: - eax_defer_source_flags(eax_call); + eax_defer(call, props.ulFlags); + break; + + default: + eax_fail_unknown_property_id(); + } +} + +void ALsource::eax4_set(const EaxCall& call, Eax4Props& props) +{ + switch (call.get_property_id()) { + case EAXSOURCE_NONE: + case EAXSOURCE_ALLPARAMETERS: + case EAXSOURCE_OBSTRUCTIONPARAMETERS: + case EAXSOURCE_OCCLUSIONPARAMETERS: + case EAXSOURCE_EXCLUSIONPARAMETERS: + case EAXSOURCE_DIRECT: + case EAXSOURCE_DIRECTHF: + case EAXSOURCE_ROOM: + case EAXSOURCE_ROOMHF: + case EAXSOURCE_OBSTRUCTION: + case EAXSOURCE_OBSTRUCTIONLFRATIO: + case EAXSOURCE_OCCLUSION: + case EAXSOURCE_OCCLUSIONLFRATIO: + case EAXSOURCE_OCCLUSIONROOMRATIO: + case EAXSOURCE_OCCLUSIONDIRECTRATIO: + case EAXSOURCE_EXCLUSION: + case EAXSOURCE_EXCLUSIONLFRATIO: + case EAXSOURCE_OUTSIDEVOLUMEHF: + case EAXSOURCE_DOPPLERFACTOR: + case EAXSOURCE_ROLLOFFFACTOR: + case EAXSOURCE_ROOMROLLOFFFACTOR: + case EAXSOURCE_AIRABSORPTIONFACTOR: + case EAXSOURCE_FLAGS: + eax3_set(call, props.source); break; case EAXSOURCE_SENDPARAMETERS: - eax_defer_send(eax_call); + eax4_defer_sends(call, props.sends); break; case EAXSOURCE_ALLSENDPARAMETERS: - eax_defer_send_all(eax_call); + eax4_defer_sends(call, props.sends); break; case EAXSOURCE_OCCLUSIONSENDPARAMETERS: - eax_defer_send_occlusion_all(eax_call); + eax4_defer_sends(call, props.sends); break; case EAXSOURCE_EXCLUSIONSENDPARAMETERS: - eax_defer_send_exclusion_all(eax_call); + eax4_defer_sends(call, props.sends); break; case EAXSOURCE_ACTIVEFXSLOTID: - eax_defer_active_fx_slots(eax_call); - break; - - case EAXSOURCE_MACROFXFACTOR: - eax_defer_source_macro_fx_factor(eax_call); - break; - - case EAXSOURCE_SPEAKERLEVELS: - eax_defer_source_speaker_level_all(eax_call); - break; - - case EAXSOURCE_ALL2DPARAMETERS: - eax_defer_source_2d_all(eax_call); + eax4_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots}); break; default: - eax_fail("Unsupported property id."); + eax_fail_unknown_property_id(); } } -const GUID& ALsource::eax_get_send_fx_slot_guid( - int eax_version, - EaxFxSlotIndexValue fx_slot_index) +void ALsource::eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props) { - switch (eax_version) - { - case 4: - switch (fx_slot_index) - { - case 0: - return EAXPROPERTYID_EAX40_FXSlot0; + const auto& src_props = call.get_value(); + Eax5SourceAll2dValidator{}(src_props); + props.lDirect = src_props.lDirect; + props.lDirectHF = src_props.lDirectHF; + props.lRoom = src_props.lRoom; + props.lRoomHF = src_props.lRoomHF; + props.ulFlags = src_props.ulFlags; +} - case 1: - return EAXPROPERTYID_EAX40_FXSlot1; +void ALsource::eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props) +{ + const auto values = call.get_values(eax_max_speakers); + std::for_each(values.cbegin(), values.cend(), Eax5SpeakerAllValidator{}); - case 2: - return EAXPROPERTYID_EAX40_FXSlot2; - - case 3: - return EAXPROPERTYID_EAX40_FXSlot3; - - default: - eax_fail("FX slot index out of range."); - } - - case 5: - switch (fx_slot_index) - { - case 0: - return EAXPROPERTYID_EAX50_FXSlot0; - - case 1: - return EAXPROPERTYID_EAX50_FXSlot1; - - case 2: - return EAXPROPERTYID_EAX50_FXSlot2; - - case 3: - return EAXPROPERTYID_EAX50_FXSlot3; - - default: - eax_fail("FX slot index out of range."); - } - - default: - eax_fail("Unsupported EAX version."); + for (const auto& value : values) { + const auto index = static_cast(value.lSpeakerID - EAXSPEAKER_FRONT_LEFT); + props[index].lLevel = value.lLevel; } } -void ALsource::eax_copy_send( - const EAXSOURCEALLSENDPROPERTIES& src_send, - EAXSOURCESENDPROPERTIES& dst_send) +void ALsource::eax5_set(const EaxCall& call, Eax5Props& props) { - dst_send.lSend = src_send.lSend; - dst_send.lSendHF = src_send.lSendHF; -} - -void ALsource::eax_copy_send( - const EAXSOURCEALLSENDPROPERTIES& src_send, - EAXSOURCEALLSENDPROPERTIES& dst_send) -{ - dst_send = src_send; -} - -void ALsource::eax_copy_send( - const EAXSOURCEALLSENDPROPERTIES& src_send, - EAXSOURCEOCCLUSIONSENDPROPERTIES& dst_send) -{ - dst_send.lOcclusion = src_send.lOcclusion; - dst_send.flOcclusionLFRatio = src_send.flOcclusionLFRatio; - dst_send.flOcclusionRoomRatio = src_send.flOcclusionRoomRatio; - dst_send.flOcclusionDirectRatio = src_send.flOcclusionDirectRatio; -} - -void ALsource::eax_copy_send( - const EAXSOURCEALLSENDPROPERTIES& src_send, - EAXSOURCEEXCLUSIONSENDPROPERTIES& dst_send) -{ - dst_send.lExclusion = src_send.lExclusion; - dst_send.flExclusionLFRatio = src_send.flExclusionLFRatio; -} - -void ALsource::eax1_get(const EaxEaxCall& eax_call) -{ - switch (eax_call.get_property_id()) - { - case DSPROPERTY_EAXBUFFER_ALL: - case DSPROPERTY_EAXBUFFER_REVERBMIX: - eax_call.set_value(eax1_); - break; - - default: - eax_fail("Unsupported property id."); - } -} - -void ALsource::eax_api_get_source_all_v2( - const EaxEaxCall& eax_call) -{ - auto eax_2_all = EAX20BUFFERPROPERTIES{}; - eax_2_all.lDirect = eax_.source.lDirect; - eax_2_all.lDirectHF = eax_.source.lDirectHF; - eax_2_all.lRoom = eax_.source.lRoom; - eax_2_all.lRoomHF = eax_.source.lRoomHF; - eax_2_all.flRoomRolloffFactor = eax_.source.flRoomRolloffFactor; - eax_2_all.lObstruction = eax_.source.lObstruction; - eax_2_all.flObstructionLFRatio = eax_.source.flObstructionLFRatio; - eax_2_all.lOcclusion = eax_.source.lOcclusion; - eax_2_all.flOcclusionLFRatio = eax_.source.flOcclusionLFRatio; - eax_2_all.flOcclusionRoomRatio = eax_.source.flOcclusionRoomRatio; - eax_2_all.lOutsideVolumeHF = eax_.source.lOutsideVolumeHF; - eax_2_all.flAirAbsorptionFactor = eax_.source.flAirAbsorptionFactor; - eax_2_all.dwFlags = eax_.source.ulFlags; - - eax_call.set_value(eax_2_all); -} - -void ALsource::eax_api_get_source_all_v3( - const EaxEaxCall& eax_call) -{ - eax_call.set_value(static_cast(eax_.source)); -} - -void ALsource::eax_api_get_source_all_v5( - const EaxEaxCall& eax_call) -{ - eax_call.set_value(eax_.source); -} - -void ALsource::eax_api_get_source_all( - const EaxEaxCall& eax_call) -{ - switch (eax_call.get_version()) - { - case 2: - eax_api_get_source_all_v2(eax_call); - break; - - case 3: - case 4: - eax_api_get_source_all_v3(eax_call); - break; - - case 5: - eax_api_get_source_all_v5(eax_call); - break; - - default: - eax_fail("Unsupported EAX version."); - } -} - -void ALsource::eax_api_get_source_all_obstruction( - const EaxEaxCall& eax_call) -{ - auto eax_obstruction_all = EAXOBSTRUCTIONPROPERTIES{}; - eax_obstruction_all.lObstruction = eax_.source.lObstruction; - eax_obstruction_all.flObstructionLFRatio = eax_.source.flObstructionLFRatio; - - eax_call.set_value(eax_obstruction_all); -} - -void ALsource::eax_api_get_source_all_occlusion( - const EaxEaxCall& eax_call) -{ - auto eax_occlusion_all = EAXOCCLUSIONPROPERTIES{}; - eax_occlusion_all.lOcclusion = eax_.source.lOcclusion; - eax_occlusion_all.flOcclusionLFRatio = eax_.source.flOcclusionLFRatio; - eax_occlusion_all.flOcclusionRoomRatio = eax_.source.flOcclusionRoomRatio; - eax_occlusion_all.flOcclusionDirectRatio = eax_.source.flOcclusionDirectRatio; - - eax_call.set_value(eax_occlusion_all); -} - -void ALsource::eax_api_get_source_all_exclusion( - const EaxEaxCall& eax_call) -{ - auto eax_exclusion_all = EAXEXCLUSIONPROPERTIES{}; - eax_exclusion_all.lExclusion = eax_.source.lExclusion; - eax_exclusion_all.flExclusionLFRatio = eax_.source.flExclusionLFRatio; - - eax_call.set_value(eax_exclusion_all); -} - -void ALsource::eax_api_get_source_active_fx_slot_id( - const EaxEaxCall& eax_call) -{ - switch (eax_call.get_version()) - { - case 4: - { - const auto& active_fx_slots = reinterpret_cast(eax_.active_fx_slots); - eax_call.set_value(active_fx_slots); - } - break; - - case 5: - { - const auto& active_fx_slots = reinterpret_cast(eax_.active_fx_slots); - eax_call.set_value(active_fx_slots); - } - break; - - default: - eax_fail("Unsupported EAX version."); - } -} - -void ALsource::eax_api_get_source_all_2d( - const EaxEaxCall& eax_call) -{ - auto eax_2d_all = EAXSOURCE2DPROPERTIES{}; - eax_2d_all.lDirect = eax_.source.lDirect; - eax_2d_all.lDirectHF = eax_.source.lDirectHF; - eax_2d_all.lRoom = eax_.source.lRoom; - eax_2d_all.lRoomHF = eax_.source.lRoomHF; - eax_2d_all.ulFlags = eax_.source.ulFlags; - - eax_call.set_value(eax_2d_all); -} - -void ALsource::eax_api_get_source_speaker_level_all( - const EaxEaxCall& eax_call) -{ - auto& all = eax_call.get_value(); - - eax_validate_source_speaker_id(all.lSpeakerID); - const auto speaker_index = static_cast(all.lSpeakerID - 1); - all.lLevel = eax_.speaker_levels[speaker_index]; -} - -void ALsource::eax_get( - const EaxEaxCall& eax_call) -{ - if (eax_call.get_version() == 1) - { - eax1_get(eax_call); - return; - } - - switch (eax_call.get_property_id()) - { + switch (call.get_property_id()) { case EAXSOURCE_NONE: break; case EAXSOURCE_ALLPARAMETERS: - eax_api_get_source_all(eax_call); + eax_defer(call, props.source); break; case EAXSOURCE_OBSTRUCTIONPARAMETERS: - eax_api_get_source_all_obstruction(eax_call); - break; - case EAXSOURCE_OCCLUSIONPARAMETERS: - eax_api_get_source_all_occlusion(eax_call); - break; - case EAXSOURCE_EXCLUSIONPARAMETERS: - eax_api_get_source_all_exclusion(eax_call); - break; - case EAXSOURCE_DIRECT: - eax_call.set_value(eax_.source.lDirect); - break; - case EAXSOURCE_DIRECTHF: - eax_call.set_value(eax_.source.lDirectHF); - break; - case EAXSOURCE_ROOM: - eax_call.set_value(eax_.source.lRoom); - break; - case EAXSOURCE_ROOMHF: - eax_call.set_value(eax_.source.lRoomHF); - break; - case EAXSOURCE_OBSTRUCTION: - eax_call.set_value(eax_.source.lObstruction); - break; - case EAXSOURCE_OBSTRUCTIONLFRATIO: - eax_call.set_value(eax_.source.flObstructionLFRatio); - break; - case EAXSOURCE_OCCLUSION: - eax_call.set_value(eax_.source.lOcclusion); - break; - case EAXSOURCE_OCCLUSIONLFRATIO: - eax_call.set_value(eax_.source.flOcclusionLFRatio); - break; - case EAXSOURCE_OCCLUSIONROOMRATIO: - eax_call.set_value(eax_.source.flOcclusionRoomRatio); - break; - case EAXSOURCE_OCCLUSIONDIRECTRATIO: - eax_call.set_value(eax_.source.flOcclusionDirectRatio); - break; - case EAXSOURCE_EXCLUSION: - eax_call.set_value(eax_.source.lExclusion); - break; - case EAXSOURCE_EXCLUSIONLFRATIO: - eax_call.set_value(eax_.source.flExclusionLFRatio); - break; - case EAXSOURCE_OUTSIDEVOLUMEHF: - eax_call.set_value(eax_.source.lOutsideVolumeHF); - break; - case EAXSOURCE_DOPPLERFACTOR: - eax_call.set_value(eax_.source.flDopplerFactor); - break; - case EAXSOURCE_ROLLOFFFACTOR: - eax_call.set_value(eax_.source.flRolloffFactor); - break; - case EAXSOURCE_ROOMROLLOFFFACTOR: - eax_call.set_value(eax_.source.flRoomRolloffFactor); - break; - case EAXSOURCE_AIRABSORPTIONFACTOR: - eax_call.set_value(eax_.source.flAirAbsorptionFactor); - break; - case EAXSOURCE_FLAGS: - eax_call.set_value(eax_.source.ulFlags); + eax3_set(call, props.source); break; case EAXSOURCE_SENDPARAMETERS: - eax_api_get_send_properties(eax_call); + eax5_defer_sends(call, props.sends); break; case EAXSOURCE_ALLSENDPARAMETERS: - eax_api_get_send_properties(eax_call); + eax5_defer_sends(call, props.sends); break; case EAXSOURCE_OCCLUSIONSENDPARAMETERS: - eax_api_get_send_properties(eax_call); + eax5_defer_sends(call, props.sends); break; case EAXSOURCE_EXCLUSIONSENDPARAMETERS: - eax_api_get_send_properties(eax_call); + eax5_defer_sends(call, props.sends); break; case EAXSOURCE_ACTIVEFXSLOTID: - eax_api_get_source_active_fx_slot_id(eax_call); + eax5_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots}); break; case EAXSOURCE_MACROFXFACTOR: - eax_call.set_value(eax_.source.flMacroFXFactor); + eax_defer(call, props.source.flMacroFXFactor); break; case EAXSOURCE_SPEAKERLEVELS: - eax_api_get_source_speaker_level_all(eax_call); + eax5_defer_speaker_levels(call, props.speaker_levels); break; case EAXSOURCE_ALL2DPARAMETERS: - eax_api_get_source_all_2d(eax_call); + eax5_defer_all_2d(call, props.source); break; default: - eax_fail("Unsupported property id."); + eax_fail_unknown_property_id(); } } -void ALsource::eax_set_al_source_send( - ALeffectslot *slot, - size_t sendidx, - const EaxAlLowPassParam &filter) +void ALsource::eax_set(const EaxCall& call) +{ + const auto eax_version = call.get_version(); + switch(eax_version) + { + case 1: eax1_set(call, mEax1.d); break; + case 2: eax2_set(call, mEax2.d); break; + case 3: eax3_set(call, mEax3.d); break; + case 4: eax4_set(call, mEax4.d); break; + case 5: eax5_set(call, mEax5.d); break; + default: eax_fail_unknown_property_id(); + } + mEaxChanged = true; + mEaxVersion = eax_version; +} + +void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count) +{ + 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()); +} + +void ALsource::eax1_get(const EaxCall& call, const Eax1Props& props) +{ + switch (call.get_property_id()) { + case DSPROPERTY_EAXBUFFER_ALL: + case DSPROPERTY_EAXBUFFER_REVERBMIX: + call.set_value(props.fMix); + break; + + default: + eax_fail_unknown_property_id(); + } +} + +void ALsource::eax2_get(const EaxCall& call, const Eax2Props& props) +{ + switch (call.get_property_id()) { + case DSPROPERTY_EAX20BUFFER_NONE: + break; + + case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS: + call.set_value(props); + break; + + case DSPROPERTY_EAX20BUFFER_DIRECT: + call.set_value(props.lDirect); + break; + + case DSPROPERTY_EAX20BUFFER_DIRECTHF: + call.set_value(props.lDirectHF); + break; + + case DSPROPERTY_EAX20BUFFER_ROOM: + call.set_value(props.lRoom); + break; + + case DSPROPERTY_EAX20BUFFER_ROOMHF: + call.set_value(props.lRoomHF); + break; + + case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR: + call.set_value(props.flRoomRolloffFactor); + break; + + case DSPROPERTY_EAX20BUFFER_OBSTRUCTION: + call.set_value(props.lObstruction); + break; + + case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO: + call.set_value(props.flObstructionLFRatio); + break; + + case DSPROPERTY_EAX20BUFFER_OCCLUSION: + call.set_value(props.lOcclusion); + break; + + case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO: + call.set_value(props.flOcclusionLFRatio); + break; + + case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO: + call.set_value(props.flOcclusionRoomRatio); + break; + + case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF: + call.set_value(props.lOutsideVolumeHF); + break; + + case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR: + call.set_value(props.flAirAbsorptionFactor); + break; + + case DSPROPERTY_EAX20BUFFER_FLAGS: + call.set_value(props.dwFlags); + break; + + default: + eax_fail_unknown_property_id(); + } +} + +void ALsource::eax3_get_obstruction(const EaxCall& call, const Eax3Props& props) +{ + const auto& subprops = reinterpret_cast(props.lObstruction); + call.set_value(subprops); +} + +void ALsource::eax3_get_occlusion(const EaxCall& call, const Eax3Props& props) +{ + const auto& subprops = reinterpret_cast(props.lOcclusion); + call.set_value(subprops); +} + +void ALsource::eax3_get_exclusion(const EaxCall& call, const Eax3Props& props) +{ + const auto& subprops = reinterpret_cast(props.lExclusion); + call.set_value(subprops); +} + +void ALsource::eax3_get(const EaxCall& call, const Eax3Props& props) +{ + switch (call.get_property_id()) { + case EAXSOURCE_NONE: + break; + + case EAXSOURCE_ALLPARAMETERS: + call.set_value(props); + break; + + case EAXSOURCE_OBSTRUCTIONPARAMETERS: + eax3_get_obstruction(call, props); + break; + + case EAXSOURCE_OCCLUSIONPARAMETERS: + eax3_get_occlusion(call, props); + break; + + case EAXSOURCE_EXCLUSIONPARAMETERS: + eax3_get_exclusion(call, props); + break; + + case EAXSOURCE_DIRECT: + call.set_value(props.lDirect); + break; + + case EAXSOURCE_DIRECTHF: + call.set_value(props.lDirectHF); + break; + + case EAXSOURCE_ROOM: + call.set_value(props.lRoom); + break; + + case EAXSOURCE_ROOMHF: + call.set_value(props.lRoomHF); + break; + + case EAXSOURCE_OBSTRUCTION: + call.set_value(props.lObstruction); + break; + + case EAXSOURCE_OBSTRUCTIONLFRATIO: + call.set_value(props.flObstructionLFRatio); + break; + + case EAXSOURCE_OCCLUSION: + call.set_value(props.lOcclusion); + break; + + case EAXSOURCE_OCCLUSIONLFRATIO: + call.set_value(props.flOcclusionLFRatio); + break; + + case EAXSOURCE_OCCLUSIONROOMRATIO: + call.set_value(props.flOcclusionRoomRatio); + break; + + case EAXSOURCE_OCCLUSIONDIRECTRATIO: + call.set_value(props.flOcclusionDirectRatio); + break; + + case EAXSOURCE_EXCLUSION: + call.set_value(props.lExclusion); + break; + + case EAXSOURCE_EXCLUSIONLFRATIO: + call.set_value(props.flExclusionLFRatio); + break; + + case EAXSOURCE_OUTSIDEVOLUMEHF: + call.set_value(props.lOutsideVolumeHF); + break; + + case EAXSOURCE_DOPPLERFACTOR: + call.set_value(props.flDopplerFactor); + break; + + case EAXSOURCE_ROLLOFFFACTOR: + call.set_value(props.flRolloffFactor); + break; + + case EAXSOURCE_ROOMROLLOFFFACTOR: + call.set_value(props.flRoomRolloffFactor); + break; + + case EAXSOURCE_AIRABSORPTIONFACTOR: + call.set_value(props.flAirAbsorptionFactor); + break; + + case EAXSOURCE_FLAGS: + call.set_value(props.ulFlags); + break; + + default: + eax_fail_unknown_property_id(); + } +} + +void ALsource::eax4_get(const EaxCall& call, const Eax4Props& props) +{ + switch (call.get_property_id()) { + case EAXSOURCE_NONE: + break; + + case EAXSOURCE_ALLPARAMETERS: + case EAXSOURCE_OBSTRUCTIONPARAMETERS: + case EAXSOURCE_OCCLUSIONPARAMETERS: + case EAXSOURCE_EXCLUSIONPARAMETERS: + case EAXSOURCE_DIRECT: + case EAXSOURCE_DIRECTHF: + case EAXSOURCE_ROOM: + case EAXSOURCE_ROOMHF: + case EAXSOURCE_OBSTRUCTION: + case EAXSOURCE_OBSTRUCTIONLFRATIO: + case EAXSOURCE_OCCLUSION: + case EAXSOURCE_OCCLUSIONLFRATIO: + case EAXSOURCE_OCCLUSIONROOMRATIO: + case EAXSOURCE_OCCLUSIONDIRECTRATIO: + case EAXSOURCE_EXCLUSION: + case EAXSOURCE_EXCLUSIONLFRATIO: + case EAXSOURCE_OUTSIDEVOLUMEHF: + case EAXSOURCE_DOPPLERFACTOR: + case EAXSOURCE_ROLLOFFFACTOR: + case EAXSOURCE_ROOMROLLOFFFACTOR: + case EAXSOURCE_AIRABSORPTIONFACTOR: + case EAXSOURCE_FLAGS: + eax3_get(call, props.source); + break; + + case EAXSOURCE_SENDPARAMETERS: + eax_get_sends(call, props.sends); + break; + + case EAXSOURCE_ALLSENDPARAMETERS: + eax_get_sends(call, props.sends); + break; + + case EAXSOURCE_OCCLUSIONSENDPARAMETERS: + eax_get_sends(call, props.sends); + break; + + case EAXSOURCE_EXCLUSIONSENDPARAMETERS: + eax_get_sends(call, props.sends); + break; + + case EAXSOURCE_ACTIVEFXSLOTID: + eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots.data(), + EAX40_MAX_ACTIVE_FXSLOTS); + break; + + default: + eax_fail_unknown_property_id(); + } +} + +void ALsource::eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props) +{ + auto& subprops = call.get_value(); + subprops.lDirect = props.lDirect; + subprops.lDirectHF = props.lDirectHF; + subprops.lRoom = props.lRoom; + subprops.lRoomHF = props.lRoomHF; + subprops.ulFlags = props.ulFlags; +} + +void ALsource::eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props) +{ + const auto subprops = call.get_values(eax_max_speakers); + std::uninitialized_copy_n(props.cbegin(), subprops.size(), subprops.begin()); +} + +void ALsource::eax5_get(const EaxCall& call, const Eax5Props& props) +{ + switch (call.get_property_id()) { + case EAXSOURCE_NONE: + break; + + case EAXSOURCE_ALLPARAMETERS: + case EAXSOURCE_OBSTRUCTIONPARAMETERS: + case EAXSOURCE_OCCLUSIONPARAMETERS: + case EAXSOURCE_EXCLUSIONPARAMETERS: + case EAXSOURCE_DIRECT: + case EAXSOURCE_DIRECTHF: + case EAXSOURCE_ROOM: + case EAXSOURCE_ROOMHF: + case EAXSOURCE_OBSTRUCTION: + case EAXSOURCE_OBSTRUCTIONLFRATIO: + case EAXSOURCE_OCCLUSION: + case EAXSOURCE_OCCLUSIONLFRATIO: + case EAXSOURCE_OCCLUSIONROOMRATIO: + case EAXSOURCE_OCCLUSIONDIRECTRATIO: + case EAXSOURCE_EXCLUSION: + case EAXSOURCE_EXCLUSIONLFRATIO: + case EAXSOURCE_OUTSIDEVOLUMEHF: + case EAXSOURCE_DOPPLERFACTOR: + case EAXSOURCE_ROLLOFFFACTOR: + case EAXSOURCE_ROOMROLLOFFFACTOR: + case EAXSOURCE_AIRABSORPTIONFACTOR: + case EAXSOURCE_FLAGS: + eax3_get(call, props.source); + break; + + case EAXSOURCE_SENDPARAMETERS: + eax_get_sends(call, props.sends); + break; + + case EAXSOURCE_ALLSENDPARAMETERS: + eax_get_sends(call, props.sends); + break; + + case EAXSOURCE_OCCLUSIONSENDPARAMETERS: + eax_get_sends(call, props.sends); + break; + + case EAXSOURCE_EXCLUSIONSENDPARAMETERS: + eax_get_sends(call, props.sends); + break; + + case EAXSOURCE_ACTIVEFXSLOTID: + eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots.data(), + EAX50_MAX_ACTIVE_FXSLOTS); + break; + + case EAXSOURCE_MACROFXFACTOR: + call.set_value(props.source.flMacroFXFactor); + break; + + case EAXSOURCE_SPEAKERLEVELS: + call.set_value(props.speaker_levels); + break; + + case EAXSOURCE_ALL2DPARAMETERS: + eax5_get_all_2d(call, props.source); + break; + + default: + eax_fail_unknown_property_id(); + } +} + +void ALsource::eax_get(const EaxCall& call) +{ + switch (call.get_version()) { + case 1: eax1_get(call, mEax1.i); break; + case 2: eax2_get(call, mEax2.i); break; + case 3: eax3_get(call, mEax3.i); break; + case 4: eax4_get(call, mEax4.i); break; + case 5: eax5_get(call, mEax5.i); break; + default: eax_fail_unknown_version(); + } +} + +void ALsource::eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, const EaxAlLowPassParam &filter) { if(sendidx >= EAX_MAX_FXSLOTS) return; @@ -6053,16 +4938,107 @@ void ALsource::eax_set_al_source_send( 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) IncrementRef(slot->ref); + if(slot != nullptr) + IncrementRef(slot->ref); if(auto *oldslot = send.Slot) DecrementRef(oldslot->ref); - send.Slot = slot; + send.Slot = slot; mPropsDirty = true; } +void ALsource::eax_commit_active_fx_slots() +{ + // Clear all slots to an inactive state. + mEaxActiveFxSlots.fill(false); + + // Mark the set slots as active. + for(const auto& slot_id : mEax.active_fx_slots.guidActiveFXSlots) + { + if(slot_id == EAX_NULL_GUID) + { + } + else if(slot_id == EAX_PrimaryFXSlotID) + { + // Mark primary FX slot as active. + if(mEaxPrimaryFxSlotId.has_value()) + mEaxActiveFxSlots[*mEaxPrimaryFxSlotId] = true; + } + else if(slot_id == EAXPROPERTYID_EAX50_FXSlot0) + mEaxActiveFxSlots[0] = true; + else if(slot_id == EAXPROPERTYID_EAX50_FXSlot1) + mEaxActiveFxSlots[1] = true; + else if(slot_id == EAXPROPERTYID_EAX50_FXSlot2) + mEaxActiveFxSlots[2] = true; + else if(slot_id == EAXPROPERTYID_EAX50_FXSlot3) + mEaxActiveFxSlots[3] = true; + } + + // Deactivate EFX auxiliary effect slots for inactive slots. Active slots + // will be updated with the room filters. + 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}); + } +} + +void ALsource::eax_commit_filters() +{ + eax_update_direct_filter(); + eax_update_room_filters(); +} + +void ALsource::eaxCommit() +{ + const auto primary_fx_slot_id = mEaxAlContext->eaxGetPrimaryFxSlotIndex(); + const auto is_primary_fx_slot_id_changed = (mEaxPrimaryFxSlotId != primary_fx_slot_id); + + if(!mEaxChanged && !is_primary_fx_slot_id_changed) + return; + + mEaxPrimaryFxSlotId = primary_fx_slot_id; + mEaxChanged = false; + + switch(mEaxVersion) + { + case 1: + mEax1.i = mEax1.d; + eax1_translate(mEax1.i, mEax); + break; + case 2: + mEax2.i = mEax2.d; + eax2_translate(mEax2.i, mEax); + break; + case 3: + mEax3.i = mEax3.d; + eax3_translate(mEax3.i, mEax); + break; + case 4: + mEax4.i = mEax4.d; + eax4_translate(mEax4.i, mEax); + break; + case 5: + mEax5.i = mEax5.d; + mEax = mEax5.d; + break; + } + + eax_set_efx_outer_gain_hf(); + eax_set_efx_doppler_factor(); + eax_set_efx_rolloff_factor(); + eax_set_efx_room_rolloff_factor(); + eax_set_efx_air_absorption_factor(); + eax_set_efx_dry_gain_hf_auto(); + eax_set_efx_wet_gain_auto(); + eax_set_efx_wet_gain_hf_auto(); + + eax_commit_active_fx_slots(); + eax_commit_filters(); +} + #endif // ALSOFT_EAX diff --git a/libs/openal-soft/al/source.h b/libs/openal-soft/al/source.h index 6db6bfa7..7c81e864 100644 --- a/libs/openal-soft/al/source.h +++ b/libs/openal-soft/al/source.h @@ -2,113 +2,61 @@ #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_eax_call.h" -#include "eax_fx_slot_index.h" -#include "eax_utils.h" +#include "eax/api.h" +#include "eax/call.h" +#include "eax/exception.h" +#include "eax/fx_slot_index.h" +#include "eax/utils.h" #endif // ALSOFT_EAX struct ALbuffer; struct ALeffectslot; - +enum class Resampler : uint8_t; enum class SourceStereo : bool { Normal = AL_NORMAL_SOFT, Enhanced = AL_SUPER_STEREO_SOFT }; -#define DEFAULT_SENDS 2 +inline constexpr size_t DefaultSendCount{2}; -#define INVALID_VOICE_IDX static_cast(-1) +inline constexpr ALuint InvalidVoiceIndex{std::numeric_limits::max()}; + +inline bool sBufferSubDataCompat{false}; struct ALbufferQueueItem : public VoiceBufferItem { ALbuffer *mBuffer{nullptr}; - DISABLE_ALLOC() + DISABLE_ALLOC }; #ifdef ALSOFT_EAX -using EaxSourceSourceFilterDirtyFlagsValue = std::uint_least16_t; - -struct EaxSourceSourceFilterDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxSourceSourceFilterDirtyFlagsValue lDirect : 1; - EaxSourceSourceFilterDirtyFlagsValue lDirectHF : 1; - EaxSourceSourceFilterDirtyFlagsValue lRoom : 1; - EaxSourceSourceFilterDirtyFlagsValue lRoomHF : 1; - EaxSourceSourceFilterDirtyFlagsValue lObstruction : 1; - EaxSourceSourceFilterDirtyFlagsValue flObstructionLFRatio : 1; - EaxSourceSourceFilterDirtyFlagsValue lOcclusion : 1; - EaxSourceSourceFilterDirtyFlagsValue flOcclusionLFRatio : 1; - EaxSourceSourceFilterDirtyFlagsValue flOcclusionRoomRatio : 1; - EaxSourceSourceFilterDirtyFlagsValue flOcclusionDirectRatio : 1; - EaxSourceSourceFilterDirtyFlagsValue lExclusion : 1; - EaxSourceSourceFilterDirtyFlagsValue flExclusionLFRatio : 1; -}; // EaxSourceSourceFilterDirtyFlags - - -using EaxSourceSourceMiscDirtyFlagsValue = std::uint_least8_t; - -struct EaxSourceSourceMiscDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxSourceSourceMiscDirtyFlagsValue lOutsideVolumeHF : 1; - EaxSourceSourceMiscDirtyFlagsValue flDopplerFactor : 1; - EaxSourceSourceMiscDirtyFlagsValue flRolloffFactor : 1; - EaxSourceSourceMiscDirtyFlagsValue flRoomRolloffFactor : 1; - EaxSourceSourceMiscDirtyFlagsValue flAirAbsorptionFactor : 1; - EaxSourceSourceMiscDirtyFlagsValue ulFlags : 1; - EaxSourceSourceMiscDirtyFlagsValue flMacroFXFactor : 1; - EaxSourceSourceMiscDirtyFlagsValue speaker_levels : 1; -}; // EaxSourceSourceMiscDirtyFlags - - -using EaxSourceSendDirtyFlagsValue = std::uint_least8_t; - -struct EaxSourceSendDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxSourceSendDirtyFlagsValue lSend : 1; - EaxSourceSendDirtyFlagsValue lSendHF : 1; - EaxSourceSendDirtyFlagsValue lOcclusion : 1; - EaxSourceSendDirtyFlagsValue flOcclusionLFRatio : 1; - EaxSourceSendDirtyFlagsValue flOcclusionRoomRatio : 1; - EaxSourceSendDirtyFlagsValue flOcclusionDirectRatio : 1; - EaxSourceSendDirtyFlagsValue lExclusion : 1; - EaxSourceSendDirtyFlagsValue flExclusionLFRatio : 1; -}; // EaxSourceSendDirtyFlags - - -struct EaxSourceSendsDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxSourceSendDirtyFlags sends[EAX_MAX_FXSLOTS]; -}; // EaxSourceSendsDirtyFlags +class EaxSourceException : public EaxException { +public: + explicit EaxSourceException(const char* message) + : EaxException{"EAX_SOURCE", message} + {} +}; #endif // ALSOFT_EAX struct ALsource { @@ -140,6 +88,7 @@ struct ALsource { DirectMode DirectChannels{DirectMode::Off}; SpatializeMode mSpatialize{SpatializeMode::Auto}; SourceStereo mStereoMode{SourceStereo::Normal}; + bool mPanningEnabled{false}; bool DryGainHFAuto{true}; bool WetGainAuto{true}; @@ -157,24 +106,27 @@ struct ALsource { float Radius{0.0f}; float EnhWidth{0.593f}; + float mPan{0.0f}; /** Direct filter and auxiliary send info. */ - struct { - float Gain; - float GainHF; - float HFReference; - float GainLF; - float LFReference; - } Direct; - struct 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 @@ -190,620 +142,913 @@ 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: - void eax_initialize(ALCcontext *context) noexcept; - - - void eax_dispatch(const EaxEaxCall& eax_call) - { eax_call.is_get() ? eax_get(eax_call) : eax_set(eax_call); } - - - void eax_update_filters(); - - void eax_update( - EaxContextSharedDirtyFlags dirty_flags); - - void eax_commit() { eax_apply_deferred(); } - void eax_commit_and_update(); - - bool eax_is_initialized() const noexcept { return eax_al_context_; } - - - static ALsource* eax_lookup_source( - ALCcontext& al_context, - ALuint source_id) noexcept; + void eaxInitialize(ALCcontext *context) noexcept; + void eaxDispatch(const EaxCall& call); + void eaxCommit(); + void eaxMarkAsChanged() noexcept { mEaxChanged = true; } + static ALsource* EaxLookupSource(ALCcontext& al_context, ALuint source_id) noexcept; private: - static constexpr auto eax_max_speakers = 9; + using Exception = EaxSourceException; + static constexpr auto eax_max_speakers{9u}; + + using EaxFxSlotIds = std::array; + + 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{ + &EAXPROPERTYID_EAX50_FXSlot0, + &EAXPROPERTYID_EAX50_FXSlot1, + &EAXPROPERTYID_EAX50_FXSlot2, + &EAXPROPERTYID_EAX50_FXSlot3, + }; using EaxActiveFxSlots = std::array; - using EaxSpeakerLevels = std::array; + using EaxSpeakerLevels = std::array; + using EaxSends = std::array; - struct Eax - { - using Sends = std::array; + using Eax1Props = EAXBUFFER_REVERBPROPERTIES; + struct Eax1State { + Eax1Props i; // Immediate. + Eax1Props d; // Deferred. + }; - EAX50ACTIVEFXSLOTS active_fx_slots{}; - EAX50SOURCEPROPERTIES source{}; - Sends sends{}; - EaxSpeakerLevels speaker_levels{}; - }; // Eax + using Eax2Props = EAX20BUFFERPROPERTIES; + struct Eax2State { + Eax2Props i; // Immediate. + Eax2Props d; // Deferred. + }; + using Eax3Props = EAX30SOURCEPROPERTIES; + struct Eax3State { + Eax3Props i; // Immediate. + Eax3Props d; // Deferred. + }; - bool eax_uses_primary_id_{}; - bool eax_has_active_fx_slots_{}; - bool eax_are_active_fx_slots_dirty_{}; + struct Eax4Props { + Eax3Props source; + EaxSends sends; + EAX40ACTIVEFXSLOTS active_fx_slots; + }; - ALCcontext* eax_al_context_{}; + struct Eax4State { + Eax4Props i; // Immediate. + Eax4Props d; // Deferred. + }; - EAXBUFFER_REVERBPROPERTIES eax1_{}; - Eax eax_{}; - Eax eax_d_{}; - EaxActiveFxSlots eax_active_fx_slots_{}; + struct Eax5Props { + EAX50SOURCEPROPERTIES source; + EaxSends sends; + EAX50ACTIVEFXSLOTS active_fx_slots; + EaxSpeakerLevels speaker_levels; + }; - EaxSourceSendsDirtyFlags eax_sends_dirty_flags_{}; - EaxSourceSourceFilterDirtyFlags eax_source_dirty_filter_flags_{}; - EaxSourceSourceMiscDirtyFlags eax_source_dirty_misc_flags_{}; + struct Eax5State { + Eax5Props i; // Immediate. + Eax5Props d; // Deferred. + }; + ALCcontext* mEaxAlContext{}; + EaxFxSlotIndex mEaxPrimaryFxSlotId{}; + EaxActiveFxSlots mEaxActiveFxSlots{}; + int mEaxVersion{}; + bool mEaxChanged{}; + Eax1State mEax1{}; + Eax2State mEax2{}; + Eax3State mEax3{}; + Eax4State mEax4{}; + Eax5State mEax5{}; + Eax5Props mEax{}; - [[noreturn]] - static void eax_fail( - const char* message); + // ---------------------------------------------------------------------- + // Source validators + struct Eax1SourceReverbMixValidator { + void operator()(float reverb_mix) const + { + if (reverb_mix == EAX_REVERBMIX_USEDISTANCE) + return; - void eax_set_source_defaults() noexcept; - void eax_set_active_fx_slots_defaults() noexcept; - void eax_set_send_defaults(EAXSOURCEALLSENDPROPERTIES& eax_send) noexcept; - void eax_set_sends_defaults() noexcept; - void eax_set_speaker_levels_defaults() noexcept; + eax_validate_range( + "Reverb Mix", + reverb_mix, + EAX_BUFFER_MINREVERBMIX, + EAX_BUFFER_MAXREVERBMIX); + } + }; + + struct Eax2SourceDirectValidator { + void operator()(long lDirect) const + { + eax_validate_range( + "Direct", + lDirect, + EAXSOURCE_MINDIRECT, + EAXSOURCE_MAXDIRECT); + } + }; + + struct Eax2SourceDirectHfValidator { + void operator()(long lDirectHF) const + { + eax_validate_range( + "Direct HF", + lDirectHF, + EAXSOURCE_MINDIRECTHF, + EAXSOURCE_MAXDIRECTHF); + } + }; + + struct Eax2SourceRoomValidator { + void operator()(long lRoom) const + { + eax_validate_range( + "Room", + lRoom, + EAXSOURCE_MINROOM, + EAXSOURCE_MAXROOM); + } + }; + + struct Eax2SourceRoomHfValidator { + void operator()(long lRoomHF) const + { + eax_validate_range( + "Room HF", + lRoomHF, + EAXSOURCE_MINROOMHF, + EAXSOURCE_MAXROOMHF); + } + }; + + struct Eax2SourceRoomRolloffFactorValidator { + void operator()(float flRoomRolloffFactor) const + { + eax_validate_range( + "Room Rolloff Factor", + flRoomRolloffFactor, + EAXSOURCE_MINROOMROLLOFFFACTOR, + EAXSOURCE_MAXROOMROLLOFFFACTOR); + } + }; + + struct Eax2SourceObstructionValidator { + void operator()(long lObstruction) const + { + eax_validate_range( + "Obstruction", + lObstruction, + EAXSOURCE_MINOBSTRUCTION, + EAXSOURCE_MAXOBSTRUCTION); + } + }; + + struct Eax2SourceObstructionLfRatioValidator { + void operator()(float flObstructionLFRatio) const + { + eax_validate_range( + "Obstruction LF Ratio", + flObstructionLFRatio, + EAXSOURCE_MINOBSTRUCTIONLFRATIO, + EAXSOURCE_MAXOBSTRUCTIONLFRATIO); + } + }; + + struct Eax2SourceOcclusionValidator { + void operator()(long lOcclusion) const + { + eax_validate_range( + "Occlusion", + lOcclusion, + EAXSOURCE_MINOCCLUSION, + EAXSOURCE_MAXOCCLUSION); + } + }; + + struct Eax2SourceOcclusionLfRatioValidator { + void operator()(float flOcclusionLFRatio) const + { + eax_validate_range( + "Occlusion LF Ratio", + flOcclusionLFRatio, + EAXSOURCE_MINOCCLUSIONLFRATIO, + EAXSOURCE_MAXOCCLUSIONLFRATIO); + } + }; + + struct Eax2SourceOcclusionRoomRatioValidator { + void operator()(float flOcclusionRoomRatio) const + { + eax_validate_range( + "Occlusion Room Ratio", + flOcclusionRoomRatio, + EAXSOURCE_MINOCCLUSIONROOMRATIO, + EAXSOURCE_MAXOCCLUSIONROOMRATIO); + } + }; + + struct Eax2SourceOutsideVolumeHfValidator { + void operator()(long lOutsideVolumeHF) const + { + eax_validate_range( + "Outside Volume HF", + lOutsideVolumeHF, + EAXSOURCE_MINOUTSIDEVOLUMEHF, + EAXSOURCE_MAXOUTSIDEVOLUMEHF); + } + }; + + struct Eax2SourceAirAbsorptionFactorValidator { + void operator()(float flAirAbsorptionFactor) const + { + eax_validate_range( + "Air Absorption Factor", + flAirAbsorptionFactor, + EAXSOURCE_MINAIRABSORPTIONFACTOR, + EAXSOURCE_MAXAIRABSORPTIONFACTOR); + } + }; + + struct Eax2SourceFlagsValidator { + void operator()(unsigned long dwFlags) const + { + eax_validate_range( + "Flags", + dwFlags, + 0UL, + ~EAX20SOURCEFLAGS_RESERVED); + } + }; + + struct Eax3SourceOcclusionDirectRatioValidator { + void operator()(float flOcclusionDirectRatio) const + { + eax_validate_range( + "Occlusion Direct Ratio", + flOcclusionDirectRatio, + EAXSOURCE_MINOCCLUSIONDIRECTRATIO, + EAXSOURCE_MAXOCCLUSIONDIRECTRATIO); + } + }; + + struct Eax3SourceExclusionValidator { + void operator()(long lExclusion) const + { + eax_validate_range( + "Exclusion", + lExclusion, + EAXSOURCE_MINEXCLUSION, + EAXSOURCE_MAXEXCLUSION); + } + }; + + struct Eax3SourceExclusionLfRatioValidator { + void operator()(float flExclusionLFRatio) const + { + eax_validate_range( + "Exclusion LF Ratio", + flExclusionLFRatio, + EAXSOURCE_MINEXCLUSIONLFRATIO, + EAXSOURCE_MAXEXCLUSIONLFRATIO); + } + }; + + struct Eax3SourceDopplerFactorValidator { + void operator()(float flDopplerFactor) const + { + eax_validate_range( + "Doppler Factor", + flDopplerFactor, + EAXSOURCE_MINDOPPLERFACTOR, + EAXSOURCE_MAXDOPPLERFACTOR); + } + }; + + struct Eax3SourceRolloffFactorValidator { + void operator()(float flRolloffFactor) const + { + eax_validate_range( + "Rolloff Factor", + flRolloffFactor, + EAXSOURCE_MINROLLOFFFACTOR, + EAXSOURCE_MAXROLLOFFFACTOR); + } + }; + + struct Eax5SourceMacroFXFactorValidator { + void operator()(float flMacroFXFactor) const + { + eax_validate_range( + "Macro FX Factor", + flMacroFXFactor, + EAXSOURCE_MINMACROFXFACTOR, + EAXSOURCE_MAXMACROFXFACTOR); + } + }; + + struct Eax5SourceFlagsValidator { + void operator()(unsigned long dwFlags) const + { + eax_validate_range( + "Flags", + dwFlags, + 0UL, + ~EAX50SOURCEFLAGS_RESERVED); + } + }; + + struct Eax1SourceAllValidator { + void operator()(const Eax1Props& props) const + { + Eax1SourceReverbMixValidator{}(props.fMix); + } + }; + + struct Eax2SourceAllValidator { + void operator()(const Eax2Props& props) const + { + Eax2SourceDirectValidator{}(props.lDirect); + Eax2SourceDirectHfValidator{}(props.lDirectHF); + Eax2SourceRoomValidator{}(props.lRoom); + Eax2SourceRoomHfValidator{}(props.lRoomHF); + Eax2SourceRoomRolloffFactorValidator{}(props.flRoomRolloffFactor); + Eax2SourceObstructionValidator{}(props.lObstruction); + Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio); + Eax2SourceOcclusionValidator{}(props.lOcclusion); + Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); + Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); + Eax2SourceOutsideVolumeHfValidator{}(props.lOutsideVolumeHF); + Eax2SourceAirAbsorptionFactorValidator{}(props.flAirAbsorptionFactor); + Eax2SourceFlagsValidator{}(props.dwFlags); + } + }; + + struct Eax3SourceAllValidator { + void operator()(const Eax3Props& props) const + { + Eax2SourceDirectValidator{}(props.lDirect); + Eax2SourceDirectHfValidator{}(props.lDirectHF); + Eax2SourceRoomValidator{}(props.lRoom); + Eax2SourceRoomHfValidator{}(props.lRoomHF); + Eax2SourceObstructionValidator{}(props.lObstruction); + Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio); + Eax2SourceOcclusionValidator{}(props.lOcclusion); + Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); + Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); + Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); + Eax3SourceExclusionValidator{}(props.lExclusion); + Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); + Eax2SourceOutsideVolumeHfValidator{}(props.lOutsideVolumeHF); + Eax3SourceDopplerFactorValidator{}(props.flDopplerFactor); + Eax3SourceRolloffFactorValidator{}(props.flRolloffFactor); + Eax2SourceRoomRolloffFactorValidator{}(props.flRoomRolloffFactor); + Eax2SourceAirAbsorptionFactorValidator{}(props.flAirAbsorptionFactor); + Eax2SourceFlagsValidator{}(props.ulFlags); + } + }; + + struct Eax5SourceAllValidator { + void operator()(const EAX50SOURCEPROPERTIES& props) const + { + Eax3SourceAllValidator{}(static_cast(props)); + Eax5SourceMacroFXFactorValidator{}(props.flMacroFXFactor); + } + }; + + struct Eax5SourceAll2dValidator { + void operator()(const EAXSOURCE2DPROPERTIES& props) const + { + Eax2SourceDirectValidator{}(props.lDirect); + Eax2SourceDirectHfValidator{}(props.lDirectHF); + Eax2SourceRoomValidator{}(props.lRoom); + Eax2SourceRoomHfValidator{}(props.lRoomHF); + Eax5SourceFlagsValidator{}(props.ulFlags); + } + }; + + struct Eax4ObstructionValidator { + void operator()(const EAXOBSTRUCTIONPROPERTIES& props) const + { + Eax2SourceObstructionValidator{}(props.lObstruction); + Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio); + } + }; + + struct Eax4OcclusionValidator { + void operator()(const EAXOCCLUSIONPROPERTIES& props) const + { + Eax2SourceOcclusionValidator{}(props.lOcclusion); + Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); + Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); + Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); + } + }; + + struct Eax4ExclusionValidator { + void operator()(const EAXEXCLUSIONPROPERTIES& props) const + { + Eax3SourceExclusionValidator{}(props.lExclusion); + Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); + } + }; + + // Source validators + // ---------------------------------------------------------------------- + // Send validators + + struct Eax4SendReceivingFxSlotIdValidator { + void operator()(const GUID& guidReceivingFXSlotID) const + { + if (guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot3) + { + eax_fail_unknown_receiving_fx_slot_id(); + } + } + }; + + struct Eax5SendReceivingFxSlotIdValidator { + void operator()(const GUID& guidReceivingFXSlotID) const + { + if (guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 && + guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot3) + { + eax_fail_unknown_receiving_fx_slot_id(); + } + } + }; + + struct Eax4SendSendValidator { + void operator()(long lSend) const + { + eax_validate_range( + "Send", + lSend, + EAXSOURCE_MINSEND, + EAXSOURCE_MAXSEND); + } + }; + + struct Eax4SendSendHfValidator { + void operator()(long lSendHF) const + { + eax_validate_range( + "Send HF", + lSendHF, + EAXSOURCE_MINSENDHF, + EAXSOURCE_MAXSENDHF); + } + }; + + template + struct EaxSendValidator { + void operator()(const EAXSOURCESENDPROPERTIES& props) const + { + TIdValidator{}(props.guidReceivingFXSlotID); + Eax4SendSendValidator{}(props.lSend); + Eax4SendSendHfValidator{}(props.lSendHF); + } + }; + + struct Eax4SendValidator : EaxSendValidator {}; + struct Eax5SendValidator : EaxSendValidator {}; + + template + struct EaxOcclusionSendValidator { + void operator()(const EAXSOURCEOCCLUSIONSENDPROPERTIES& props) const + { + TIdValidator{}(props.guidReceivingFXSlotID); + Eax2SourceOcclusionValidator{}(props.lOcclusion); + Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); + Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); + Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); + } + }; + + struct Eax4OcclusionSendValidator : EaxOcclusionSendValidator {}; + struct Eax5OcclusionSendValidator : EaxOcclusionSendValidator {}; + + template + struct EaxExclusionSendValidator { + void operator()(const EAXSOURCEEXCLUSIONSENDPROPERTIES& props) const + { + TIdValidator{}(props.guidReceivingFXSlotID); + Eax3SourceExclusionValidator{}(props.lExclusion); + Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); + } + }; + + struct Eax4ExclusionSendValidator : EaxExclusionSendValidator {}; + struct Eax5ExclusionSendValidator : EaxExclusionSendValidator {}; + + template + struct EaxAllSendValidator { + void operator()(const EAXSOURCEALLSENDPROPERTIES& props) const + { + TIdValidator{}(props.guidReceivingFXSlotID); + Eax4SendSendValidator{}(props.lSend); + Eax4SendSendHfValidator{}(props.lSendHF); + Eax2SourceOcclusionValidator{}(props.lOcclusion); + Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); + Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); + Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); + Eax3SourceExclusionValidator{}(props.lExclusion); + Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); + } + }; + + struct Eax4AllSendValidator : EaxAllSendValidator {}; + struct Eax5AllSendValidator : EaxAllSendValidator {}; + + // Send validators + // ---------------------------------------------------------------------- + // Active FX slot ID validators + + struct Eax4ActiveFxSlotIdValidator { + void operator()(const GUID &guid) const + { + if(guid != EAX_NULL_GUID && guid != EAX_PrimaryFXSlotID + && guid != EAXPROPERTYID_EAX40_FXSlot0 && guid != EAXPROPERTYID_EAX40_FXSlot1 + && guid != EAXPROPERTYID_EAX40_FXSlot2 && guid != EAXPROPERTYID_EAX40_FXSlot3) + { + eax_fail_unknown_active_fx_slot_id(); + } + } + }; + + struct Eax5ActiveFxSlotIdValidator { + void operator()(const GUID &guid) const + { + if(guid != EAX_NULL_GUID && guid != EAX_PrimaryFXSlotID + && guid != EAXPROPERTYID_EAX50_FXSlot0 && guid != EAXPROPERTYID_EAX50_FXSlot1 + && guid != EAXPROPERTYID_EAX50_FXSlot2 && guid != EAXPROPERTYID_EAX50_FXSlot3) + { + eax_fail_unknown_active_fx_slot_id(); + } + } + }; + + // Active FX slot ID validators + // ---------------------------------------------------------------------- + // Speaker level validators. + + struct Eax5SpeakerIdValidator { + void operator()(long lSpeakerID) const + { + switch (lSpeakerID) { + case EAXSPEAKER_FRONT_LEFT: + case EAXSPEAKER_FRONT_CENTER: + case EAXSPEAKER_FRONT_RIGHT: + case EAXSPEAKER_SIDE_RIGHT: + case EAXSPEAKER_REAR_RIGHT: + case EAXSPEAKER_REAR_CENTER: + case EAXSPEAKER_REAR_LEFT: + case EAXSPEAKER_SIDE_LEFT: + case EAXSPEAKER_LOW_FREQUENCY: + break; + + default: + eax_fail("Unknown speaker ID."); + } + } + }; + + struct Eax5SpeakerLevelValidator { + void operator()(long lLevel) const + { + // TODO Use a range when the feature will be implemented. + if (lLevel != EAXSOURCE_DEFAULTSPEAKERLEVEL) + eax_fail("Speaker level out of range."); + } + }; + + struct Eax5SpeakerAllValidator { + void operator()(const EAXSPEAKERLEVELPROPERTIES& all) const + { + Eax5SpeakerIdValidator{}(all.lSpeakerID); + Eax5SpeakerLevelValidator{}(all.lLevel); + } + }; + + // Speaker level validators. + // ---------------------------------------------------------------------- + + struct Eax4SendIndexGetter { + EaxFxSlotIndexValue operator()(const GUID &guid) const + { + if(guid == EAXPROPERTYID_EAX40_FXSlot0) + return 0; + if(guid == EAXPROPERTYID_EAX40_FXSlot1) + return 1; + if(guid == EAXPROPERTYID_EAX40_FXSlot2) + return 2; + if(guid == EAXPROPERTYID_EAX40_FXSlot3) + return 3; + eax_fail_unknown_receiving_fx_slot_id(); + } + }; + + struct Eax5SendIndexGetter { + EaxFxSlotIndexValue operator()(const GUID &guid) const + { + if(guid == EAXPROPERTYID_EAX50_FXSlot0) + return 0; + if(guid == EAXPROPERTYID_EAX50_FXSlot1) + return 1; + if(guid == EAXPROPERTYID_EAX50_FXSlot2) + return 2; + if(guid == EAXPROPERTYID_EAX50_FXSlot3) + return 3; + eax_fail_unknown_receiving_fx_slot_id(); + } + }; + + [[noreturn]] static void eax_fail(const char* message); + [[noreturn]] static void eax_fail_unknown_property_id(); + [[noreturn]] static void eax_fail_unknown_version(); + [[noreturn]] static void eax_fail_unknown_active_fx_slot_id(); + [[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id(); + + static void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept; + static void eax1_set_defaults(Eax1Props& props) noexcept; + void eax1_set_defaults() noexcept; + static void eax2_set_defaults(Eax2Props& props) noexcept; + void eax2_set_defaults() noexcept; + static void eax3_set_defaults(Eax3Props& props) noexcept; + void eax3_set_defaults() noexcept; + static void eax4_set_sends_defaults(EaxSends& sends) noexcept; + static void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept; + void eax4_set_defaults() noexcept; + static void eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept; + static void eax5_set_sends_defaults(EaxSends& sends) noexcept; + static void eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept; + static void eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept; + static void eax5_set_defaults(Eax5Props& props) noexcept; + void eax5_set_defaults() noexcept; void eax_set_defaults() noexcept; + 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_set_fx_slots(); + void eax_update_direct_filter(); + void eax_update_room_filters(); + void eax_commit_filters(); - void eax_initialize_fx_slots(); - - void eax_update_direct_filter_internal(); - - void eax_update_room_filters_internal(); - - void eax_update_filters_internal(); - - void eax_update_primary_fx_slot_id(); - - - void eax_defer_active_fx_slots( - const EaxEaxCall& eax_call); - - - static const char* eax_get_exclusion_name() noexcept; - - static const char* eax_get_exclusion_lf_ratio_name() noexcept; - - - static const char* eax_get_occlusion_name() noexcept; - - static const char* eax_get_occlusion_lf_ratio_name() noexcept; - - static const char* eax_get_occlusion_direct_ratio_name() noexcept; - - static const char* eax_get_occlusion_room_ratio_name() noexcept; - - - static void eax1_validate_reverb_mix(float reverb_mix); - - static void eax_validate_send_receiving_fx_slot_guid( - const GUID& guidReceivingFXSlotID); - - static void eax_validate_send_send( - long lSend); - - static void eax_validate_send_send_hf( - long lSendHF); - - static void eax_validate_send_occlusion( - long lOcclusion); - - static void eax_validate_send_occlusion_lf_ratio( - float flOcclusionLFRatio); - - static void eax_validate_send_occlusion_room_ratio( - float flOcclusionRoomRatio); - - static void eax_validate_send_occlusion_direct_ratio( - float flOcclusionDirectRatio); - - static void eax_validate_send_exclusion( - long lExclusion); - - static void eax_validate_send_exclusion_lf_ratio( - float flExclusionLFRatio); - - static void eax_validate_send( - const EAXSOURCESENDPROPERTIES& all); - - static void eax_validate_send_exclusion_all( - const EAXSOURCEEXCLUSIONSENDPROPERTIES& all); - - static void eax_validate_send_occlusion_all( - const EAXSOURCEOCCLUSIONSENDPROPERTIES& all); - - static void eax_validate_send_all( - const EAXSOURCEALLSENDPROPERTIES& all); - - - static EaxFxSlotIndexValue eax_get_send_index( - const GUID& send_guid); - - - void eax_defer_send_send( - long lSend, - EaxFxSlotIndexValue index); - - void eax_defer_send_send_hf( - long lSendHF, - EaxFxSlotIndexValue index); - - void eax_defer_send_occlusion( - long lOcclusion, - EaxFxSlotIndexValue index); - - void eax_defer_send_occlusion_lf_ratio( - float flOcclusionLFRatio, - EaxFxSlotIndexValue index); - - void eax_defer_send_occlusion_room_ratio( - float flOcclusionRoomRatio, - EaxFxSlotIndexValue index); - - void eax_defer_send_occlusion_direct_ratio( - float flOcclusionDirectRatio, - EaxFxSlotIndexValue index); - - void eax_defer_send_exclusion( - long lExclusion, - EaxFxSlotIndexValue index); - - void eax_defer_send_exclusion_lf_ratio( - float flExclusionLFRatio, - EaxFxSlotIndexValue index); - - void eax_defer_send( - const EAXSOURCESENDPROPERTIES& all, - EaxFxSlotIndexValue index); - - void eax_defer_send_exclusion_all( - const EAXSOURCEEXCLUSIONSENDPROPERTIES& all, - EaxFxSlotIndexValue index); - - void eax_defer_send_occlusion_all( - const EAXSOURCEOCCLUSIONSENDPROPERTIES& all, - EaxFxSlotIndexValue index); - - void eax_defer_send_all( - const EAXSOURCEALLSENDPROPERTIES& all, - EaxFxSlotIndexValue index); - - - void eax_defer_send( - const EaxEaxCall& eax_call); - - void eax_defer_send_exclusion_all( - const EaxEaxCall& eax_call); - - void eax_defer_send_occlusion_all( - const EaxEaxCall& eax_call); - - void eax_defer_send_all( - const EaxEaxCall& eax_call); - - - static void eax_validate_source_direct( - long direct); - - static void eax_validate_source_direct_hf( - long direct_hf); - - static void eax_validate_source_room( - long room); - - static void eax_validate_source_room_hf( - long room_hf); - - static void eax_validate_source_obstruction( - long obstruction); - - static void eax_validate_source_obstruction_lf_ratio( - float obstruction_lf_ratio); - - static void eax_validate_source_occlusion( - long occlusion); - - static void eax_validate_source_occlusion_lf_ratio( - float occlusion_lf_ratio); - - static void eax_validate_source_occlusion_room_ratio( - float occlusion_room_ratio); - - static void eax_validate_source_occlusion_direct_ratio( - float occlusion_direct_ratio); - - static void eax_validate_source_exclusion( - long exclusion); - - static void eax_validate_source_exclusion_lf_ratio( - float exclusion_lf_ratio); - - static void eax_validate_source_outside_volume_hf( - long outside_volume_hf); - - static void eax_validate_source_doppler_factor( - float doppler_factor); - - static void eax_validate_source_rolloff_factor( - float rolloff_factor); - - static void eax_validate_source_room_rolloff_factor( - float room_rolloff_factor); - - static void eax_validate_source_air_absorption_factor( - float air_absorption_factor); - - static void eax_validate_source_flags( - unsigned long flags, - int eax_version); - - static void eax_validate_source_macro_fx_factor( - float macro_fx_factor); - - static void eax_validate_source_2d_all( - const EAXSOURCE2DPROPERTIES& all, - int eax_version); - - static void eax_validate_source_obstruction_all( - const EAXOBSTRUCTIONPROPERTIES& all); - - static void eax_validate_source_exclusion_all( - const EAXEXCLUSIONPROPERTIES& all); - - static void eax_validate_source_occlusion_all( - const EAXOCCLUSIONPROPERTIES& all); - - static void eax_validate_source_all( - const EAX20BUFFERPROPERTIES& all, - int eax_version); - - static void eax_validate_source_all( - const EAX30SOURCEPROPERTIES& all, - int eax_version); - - static void eax_validate_source_all( - const EAX50SOURCEPROPERTIES& all, - int eax_version); - - static void eax_validate_source_speaker_id( - long speaker_id); - - static void eax_validate_source_speaker_level( - long speaker_level); - - static void eax_validate_source_speaker_level_all( - const EAXSPEAKERLEVELPROPERTIES& all); - - - void eax_defer_source_direct( - long lDirect); - - void eax_defer_source_direct_hf( - long lDirectHF); - - void eax_defer_source_room( - long lRoom); - - void eax_defer_source_room_hf( - long lRoomHF); - - void eax_defer_source_obstruction( - long lObstruction); - - void eax_defer_source_obstruction_lf_ratio( - float flObstructionLFRatio); - - void eax_defer_source_occlusion( - long lOcclusion); - - void eax_defer_source_occlusion_lf_ratio( - float flOcclusionLFRatio); - - void eax_defer_source_occlusion_room_ratio( - float flOcclusionRoomRatio); - - void eax_defer_source_occlusion_direct_ratio( - float flOcclusionDirectRatio); - - void eax_defer_source_exclusion( - long lExclusion); - - void eax_defer_source_exclusion_lf_ratio( - float flExclusionLFRatio); - - void eax_defer_source_outside_volume_hf( - long lOutsideVolumeHF); - - void eax_defer_source_doppler_factor( - float flDopplerFactor); - - void eax_defer_source_rolloff_factor( - float flRolloffFactor); - - void eax_defer_source_room_rolloff_factor( - float flRoomRolloffFactor); - - void eax_defer_source_air_absorption_factor( - float flAirAbsorptionFactor); - - void eax_defer_source_flags( - unsigned long ulFlags); - - void eax_defer_source_macro_fx_factor( - float flMacroFXFactor); - - void eax_defer_source_2d_all( - const EAXSOURCE2DPROPERTIES& all); - - void eax_defer_source_obstruction_all( - const EAXOBSTRUCTIONPROPERTIES& all); - - void eax_defer_source_exclusion_all( - const EAXEXCLUSIONPROPERTIES& all); - - void eax_defer_source_occlusion_all( - const EAXOCCLUSIONPROPERTIES& all); - - void eax_defer_source_all( - const EAX20BUFFERPROPERTIES& all); - - void eax_defer_source_all( - const EAX30SOURCEPROPERTIES& all); - - void eax_defer_source_all( - const EAX50SOURCEPROPERTIES& all); - - void eax_defer_source_speaker_level_all( - const EAXSPEAKERLEVELPROPERTIES& all); - - - void eax_defer_source_direct( - const EaxEaxCall& eax_call); - - void eax_defer_source_direct_hf( - const EaxEaxCall& eax_call); - - void eax_defer_source_room( - const EaxEaxCall& eax_call); - - void eax_defer_source_room_hf( - const EaxEaxCall& eax_call); - - void eax_defer_source_obstruction( - const EaxEaxCall& eax_call); - - void eax_defer_source_obstruction_lf_ratio( - const EaxEaxCall& eax_call); - - void eax_defer_source_occlusion( - const EaxEaxCall& eax_call); - - void eax_defer_source_occlusion_lf_ratio( - const EaxEaxCall& eax_call); - - void eax_defer_source_occlusion_room_ratio( - const EaxEaxCall& eax_call); - - void eax_defer_source_occlusion_direct_ratio( - const EaxEaxCall& eax_call); - - void eax_defer_source_exclusion( - const EaxEaxCall& eax_call); - - void eax_defer_source_exclusion_lf_ratio( - const EaxEaxCall& eax_call); - - void eax_defer_source_outside_volume_hf( - const EaxEaxCall& eax_call); - - void eax_defer_source_doppler_factor( - const EaxEaxCall& eax_call); - - void eax_defer_source_rolloff_factor( - const EaxEaxCall& eax_call); - - void eax_defer_source_room_rolloff_factor( - const EaxEaxCall& eax_call); - - void eax_defer_source_air_absorption_factor( - const EaxEaxCall& eax_call); - - void eax_defer_source_flags( - const EaxEaxCall& eax_call); - - void eax_defer_source_macro_fx_factor( - const EaxEaxCall& eax_call); - - void eax_defer_source_2d_all( - const EaxEaxCall& eax_call); - - void eax_defer_source_obstruction_all( - const EaxEaxCall& eax_call); - - void eax_defer_source_exclusion_all( - const EaxEaxCall& eax_call); - - void eax_defer_source_occlusion_all( - const EaxEaxCall& eax_call); - - void eax_defer_source_all( - const EaxEaxCall& eax_call); - - void eax_defer_source_speaker_level_all( - const EaxEaxCall& eax_call); - - - void eax_set_outside_volume_hf(); - - void eax_set_doppler_factor(); - - void eax_set_rolloff_factor(); - - void eax_set_room_rolloff_factor(); - - void eax_set_air_absorption_factor(); - - - void eax_set_direct_hf_auto_flag(); - - void eax_set_room_auto_flag(); - - void eax_set_room_hf_auto_flag(); - - void eax_set_flags(); - - - void eax_set_macro_fx_factor(); - - void eax_set_speaker_levels(); - - - void eax1_set_efx(); - void eax1_set_reverb_mix(const EaxEaxCall& eax_call); - void eax1_set(const EaxEaxCall& eax_call); - - void eax_apply_deferred(); - - void eax_set( - const EaxEaxCall& eax_call); - - - static const GUID& eax_get_send_fx_slot_guid( - int eax_version, - EaxFxSlotIndexValue fx_slot_index); - - static void eax_copy_send( - const EAXSOURCEALLSENDPROPERTIES& src_send, - EAXSOURCESENDPROPERTIES& dst_send); - - static void eax_copy_send( - const EAXSOURCEALLSENDPROPERTIES& src_send, - EAXSOURCEALLSENDPROPERTIES& dst_send); - - static void eax_copy_send( - const EAXSOURCEALLSENDPROPERTIES& src_send, - EAXSOURCEOCCLUSIONSENDPROPERTIES& dst_send); - - static void eax_copy_send( - const EAXSOURCEALLSENDPROPERTIES& src_send, - EAXSOURCEEXCLUSIONSENDPROPERTIES& dst_send); - - template< - typename TException, - typename TSrcSend - > - void eax_api_get_send_properties( - const EaxEaxCall& eax_call) const + static void eax_copy_send_for_get( + const EAXSOURCEALLSENDPROPERTIES& src, + EAXSOURCESENDPROPERTIES& dst) noexcept { - const auto eax_version = eax_call.get_version(); - const auto dst_sends = eax_call.get_values(); - const auto send_count = dst_sends.size(); + dst = reinterpret_cast(src); + } - for (auto fx_slot_index = EaxFxSlotIndexValue{}; fx_slot_index < send_count; ++fx_slot_index) - { - auto& dst_send = dst_sends[fx_slot_index]; - const auto& src_send = eax_.sends[fx_slot_index]; + static void eax_copy_send_for_get( + const EAXSOURCEALLSENDPROPERTIES& src, + EAXSOURCEALLSENDPROPERTIES& dst) noexcept + { + dst = src; + } - eax_copy_send(src_send, dst_send); + static void eax_copy_send_for_get( + const EAXSOURCEALLSENDPROPERTIES& src, + EAXSOURCEOCCLUSIONSENDPROPERTIES& dst) noexcept + { + dst.guidReceivingFXSlotID = src.guidReceivingFXSlotID; + dst.lOcclusion = src.lOcclusion; + dst.flOcclusionLFRatio = src.flOcclusionLFRatio; + dst.flOcclusionRoomRatio = src.flOcclusionRoomRatio; + dst.flOcclusionDirectRatio = src.flOcclusionDirectRatio; + } - dst_send.guidReceivingFXSlotID = eax_get_send_fx_slot_guid(eax_version, fx_slot_index); + static void eax_copy_send_for_get( + const EAXSOURCEALLSENDPROPERTIES& src, + EAXSOURCEEXCLUSIONSENDPROPERTIES& dst) noexcept + { + dst.guidReceivingFXSlotID = src.guidReceivingFXSlotID; + dst.lExclusion = src.lExclusion; + dst.flExclusionLFRatio = src.flExclusionLFRatio; + } + + template + void eax_get_sends(const EaxCall& call, const EaxSends& src_sends) + { + const auto dst_sends = call.get_values(EAX_MAX_FXSLOTS); + const auto count = dst_sends.size(); + + for (auto i = decltype(count){}; i < count; ++i) { + const auto& src_send = src_sends[i]; + auto& dst_send = dst_sends[i]; + eax_copy_send_for_get(src_send, dst_send); } } + static void eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count); + static void eax1_get(const EaxCall& call, const Eax1Props& props); + static void eax2_get(const EaxCall& call, const Eax2Props& props); + static void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props); + static void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props); + static void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props); + static void eax3_get(const EaxCall& call, const Eax3Props& props); + void eax4_get(const EaxCall& call, const Eax4Props& props); + static void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props); + static void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props); + void eax5_get(const EaxCall& call, const Eax5Props& props); + void eax_get(const EaxCall& call); - void eax1_get(const EaxEaxCall& eax_call); + static void eax_copy_send_for_set( + const EAXSOURCESENDPROPERTIES& src, + EAXSOURCEALLSENDPROPERTIES& dst) noexcept + { + dst.lSend = src.lSend; + dst.lSendHF = src.lSendHF; + } - void eax_api_get_source_all_v2( - const EaxEaxCall& eax_call); + static void eax_copy_send_for_set( + const EAXSOURCEALLSENDPROPERTIES& src, + EAXSOURCEALLSENDPROPERTIES& dst) noexcept + { + dst.lSend = src.lSend; + dst.lSendHF = src.lSendHF; + dst.lOcclusion = src.lOcclusion; + dst.flOcclusionLFRatio = src.flOcclusionLFRatio; + dst.flOcclusionRoomRatio = src.flOcclusionRoomRatio; + dst.flOcclusionDirectRatio = src.flOcclusionDirectRatio; + dst.lExclusion = src.lExclusion; + dst.flExclusionLFRatio = src.flExclusionLFRatio; + } - void eax_api_get_source_all_v3( - const EaxEaxCall& eax_call); + static void eax_copy_send_for_set( + const EAXSOURCEOCCLUSIONSENDPROPERTIES& src, + EAXSOURCEALLSENDPROPERTIES& dst) noexcept + { + dst.lOcclusion = src.lOcclusion; + dst.flOcclusionLFRatio = src.flOcclusionLFRatio; + dst.flOcclusionRoomRatio = src.flOcclusionRoomRatio; + dst.flOcclusionDirectRatio = src.flOcclusionDirectRatio; + } - void eax_api_get_source_all_v5( - const EaxEaxCall& eax_call); + static void eax_copy_send_for_set( + const EAXSOURCEEXCLUSIONSENDPROPERTIES& src, + EAXSOURCEALLSENDPROPERTIES& dst) noexcept + { + dst.lExclusion = src.lExclusion; + dst.flExclusionLFRatio = src.flExclusionLFRatio; + } - void eax_api_get_source_all( - const EaxEaxCall& eax_call); + template + void eax_defer_sends(const EaxCall& call, EaxSends& dst_sends) + { + const auto src_sends = call.get_values(EAX_MAX_FXSLOTS); + std::for_each(src_sends.cbegin(), src_sends.cend(), TValidator{}); + const auto count = src_sends.size(); + const auto index_getter = TIndexGetter{}; - void eax_api_get_source_all_obstruction( - const EaxEaxCall& eax_call); + for (auto i = decltype(count){}; i < count; ++i) { + const auto& src_send = src_sends[i]; + const auto dst_index = index_getter(src_send.guidReceivingFXSlotID); + auto& dst_send = dst_sends[dst_index]; + eax_copy_send_for_set(src_send, dst_send); + } + } - void eax_api_get_source_all_occlusion( - const EaxEaxCall& eax_call); + template + void eax4_defer_sends(const EaxCall& call, EaxSends& dst_sends) + { + eax_defer_sends(call, dst_sends); + } - void eax_api_get_source_all_exclusion( - const EaxEaxCall& eax_call); + template + void eax5_defer_sends(const EaxCall& call, EaxSends& dst_sends) + { + eax_defer_sends(call, dst_sends); + } - void eax_api_get_source_active_fx_slot_id( - const EaxEaxCall& eax_call); + template + 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.begin()); + } - void eax_api_get_source_all_2d( - const EaxEaxCall& eax_call); + template + void eax4_defer_active_fx_slot_id(const EaxCall& call, const al::span dst_ids) + { + eax_defer_active_fx_slot_id(call, dst_ids); + } - void eax_api_get_source_speaker_level_all( - const EaxEaxCall& eax_call); + template + void eax5_defer_active_fx_slot_id(const EaxCall& call, const al::span dst_ids) + { + eax_defer_active_fx_slot_id(call, dst_ids); + } - void eax_get( - const EaxEaxCall& eax_call); + template + static void eax_defer(const EaxCall& call, TProperty& property) + { + const auto& value = call.get_value(); + TValidator{}(value); + property = value; + } + // Defers source's sub-properties (obstruction, occlusion, exclusion). + template + void eax_defer_sub(const EaxCall& call, TProperty& property) + { + const auto& src_props = call.get_value(); + TValidator{}(src_props); + auto& dst_props = reinterpret_cast(property); + dst_props = src_props; + } + + void eax_set_efx_outer_gain_hf(); + void eax_set_efx_doppler_factor(); + void eax_set_efx_rolloff_factor(); + void eax_set_efx_room_rolloff_factor(); + void eax_set_efx_air_absorption_factor(); + void eax_set_efx_dry_gain_hf_auto(); + void eax_set_efx_wet_gain_auto(); + void eax_set_efx_wet_gain_hf_auto(); + + static void eax1_set(const EaxCall& call, Eax1Props& props); + static void eax2_set(const EaxCall& call, Eax2Props& props); + void eax3_set(const EaxCall& call, Eax3Props& props); + void eax4_set(const EaxCall& call, Eax4Props& props); + static void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props); + static void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props); + void eax5_set(const EaxCall& call, Eax5Props& props); + void eax_set(const EaxCall& call); // `alSource3i(source, AL_AUXILIARY_SEND_FILTER, ...)` void eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, const EaxAlLowPassParam &filter); + + void eax_commit_active_fx_slots(); #endif // ALSOFT_EAX }; void UpdateAllSourceProps(ALCcontext *context); +struct SourceSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> 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/libs/openal-soft/al/state.cpp b/libs/openal-soft/al/state.cpp index 8142890e..a82ce8e4 100644 --- a/libs/openal-soft/al/state.cpp +++ b/libs/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" @@ -49,24 +55,26 @@ #ifdef ALSOFT_EAX #include "alc/device.h" -#include "eax_globals.h" -#include "eax_x_ram.h" +#include "eax/globals.h" +#include "eax/x_ram.h" #endif // ALSOFT_EAX 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 { }; @@ -103,21 +111,21 @@ 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) { - case AL_NONE: return al::make_optional(DistanceModel::Disable); - case AL_INVERSE_DISTANCE: return al::make_optional(DistanceModel::Inverse); - case AL_INVERSE_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::InverseClamped); - case AL_LINEAR_DISTANCE: return al::make_optional(DistanceModel::Linear); - case AL_LINEAR_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::LinearClamped); - case AL_EXPONENT_DISTANCE: return al::make_optional(DistanceModel::Exponent); - case AL_EXPONENT_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::ExponentClamped); + case AL_NONE: return DistanceModel::Disable; + case AL_INVERSE_DISTANCE: return DistanceModel::Inverse; + case AL_INVERSE_DISTANCE_CLAMPED: return DistanceModel::InverseClamped; + case AL_LINEAR_DISTANCE: return DistanceModel::Linear; + case AL_LINEAR_DISTANCE_CLAMPED: return DistanceModel::LinearClamped; + 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 +140,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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid boolean-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API 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 UNLIKELY(!context) return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid double-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API 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 UNLIKELY(!context) return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid float-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API 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 UNLIKELY(!context) return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid integer-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_DOPPLER_FACTOR: - case AL_DOPPLER_VELOCITY: - case AL_DISTANCE_MODEL: - case AL_SPEED_OF_SOUND: - case AL_DEFERRED_UPDATES_SOFT: - case AL_GAIN_LIMIT_SOFT: - case AL_NUM_RESAMPLERS_SOFT: - case AL_DEFAULT_RESAMPLER_SOFT: - values[0] = alGetInteger64SOFT(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid integer64-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, 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 UNLIKELY(!context) return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid pointer-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if UNLIKELY(!context) return nullptr; - - const ALchar *value{nullptr}; switch(pname) { - case AL_VENDOR: - value = alVendor; - break; - - case AL_VERSION: - value = alVersion; - break; - - case AL_RENDERER: - value = alRenderer; - break; - - case AL_EXTENSIONS: - value = context->mExtensionList; - break; - - case AL_NO_ERROR: - value = alNoError; - break; - - case AL_INVALID_NAME: - value = alErrInvalidName; - break; - - case AL_INVALID_ENUM: - value = alErrInvalidEnum; - break; - - case AL_INVALID_VALUE: - value = alErrInvalidValue; - break; - - case AL_INVALID_OPERATION: - value = alErrInvalidOp; - break; - - case AL_OUT_OF_MEMORY: - value = alErrOutOfMemory; - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname); + 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 UNLIKELY(!context) return; - if(!(value >= 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value); else { - std::lock_guard _{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 UNLIKELY(!context) return; + 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 UNLIKELY(!context) return; - - if(!(value > 0.0f && std::isfinite(value))) - context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value); - else - { - std::lock_guard _{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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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 UNLIKELY(!context) 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; @@ -943,6 +604,7 @@ void UpdateContextProps(ALCcontext *context) props->Gain = listener.Gain; props->MetersPerUnit = listener.mMetersPerUnit; + props->AirAbsorptionGainHF = context->mAirAbsorptionGainHF; props->DopplerFactor = context->mDopplerFactor; props->DopplerVelocity = context->mDopplerVelocity; props->SpeedOfSound = context->mSpeedOfSound; diff --git a/libs/openal-soft/alc/alc.cpp b/libs/openal-soft/alc/alc.cpp index 3bbe43d0..58f8dd0b 100644 --- a/libs/openal-soft/alc/alc.cpp +++ b/libs/openal-soft/alc/alc.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -48,11 +49,12 @@ #include #include #include -#include +#include #include #include #include #include +#include #include "AL/al.h" #include "AL/alc.h" @@ -61,16 +63,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 "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "alu.h" @@ -96,12 +98,12 @@ #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_globals.h" -#include "al/eax_x_ram.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 }, -#endif -#ifdef HAVE_SOLARIS - { "solaris", SolarisBackendFactory::getFactory }, -#endif -#ifdef HAVE_SNDIO - { "sndio", SndIOBackendFactory::getFactory }, + BackendInfo{"opensl", OSLBackendFactory::getFactory}, #endif #ifdef HAVE_ALSA - { "alsa", AlsaBackendFactory::getFactory }, + BackendInfo{"alsa", AlsaBackendFactory::getFactory}, +#endif +#ifdef HAVE_SOLARIS + BackendInfo{"solaris", SolarisBackendFactory::getFactory}, +#endif +#ifdef HAVE_SNDIO + 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,663 +256,20 @@ 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), -#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_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_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_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::string alcAllDevicesList; std::string alcCaptureDeviceList; @@ -945,31 +297,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}; @@ -983,13 +345,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")) { @@ -1009,7 +371,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()); } } @@ -1026,7 +388,7 @@ void alc_initconfig(void) ALSOFT_GIT_BRANCH); { std::string names; - if(al::size(BackendList) < 1) + if(std::size(BackendList) < 1) names = "(none)"; else { @@ -1044,7 +406,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"); @@ -1066,39 +428,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()) @@ -1119,32 +481,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 { - if(auto optval = al::getenv(envname)) + CompatFlagBitset compatflags{}; + auto checkflag = [](const char *envname, const std::string_view optname) -> bool { - if(al::strcasecmp(optval->c_str(), "true") == 0 - || strtol(optval->c_str(), nullptr, 0) == 1) - return true; - return false; - } - return GetConfigValueBool(nullptr, "game_compat", optname, false); - }; - 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")); + if(auto optval = al::getenv(envname)) + { + return al::case_compare(*optval, "true"sv) == 0 + || strtol(optval->c_str(), nullptr, 0) == 1; + } + return GetConfigValueBool({}, "game_compat", optname, false); + }; + 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); - Voice::InitMixer(ConfigValueStr(nullptr, nullptr, "resampler")); + aluInit(compatflags, ConfigValueFloat({}, "game_compat"sv, "nfc-scale"sv).value_or(1.0f)); + } + Voice::InitMixer(ConfigValueStr({}, {}, "resampler"sv)); - auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); - if(traperr && (al::strcasecmp(traperr->c_str(), "true") == 0 + if(auto uhjfiltopt = ConfigValueStr({}, "uhj"sv, "decode-filter"sv)) + { + if(al::case_compare(*uhjfiltopt, "fir256"sv) == 0) + UhjDecodeQuality = UhjQualityType::FIR256; + else if(al::case_compare(*uhjfiltopt, "fir512"sv) == 0) + UhjDecodeQuality = UhjQualityType::FIR512; + else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) + UhjDecodeQuality = UhjQualityType::IIR; + else + WARN("Unsupported uhj/decode-filter: %s\n", uhjfiltopt->c_str()); + } + if(auto uhjfiltopt = ConfigValueStr({}, "uhj"sv, "encode-filter"sv)) + { + if(al::case_compare(*uhjfiltopt, "fir256"sv) == 0) + UhjEncodeQuality = UhjQualityType::FIR256; + else if(al::case_compare(*uhjfiltopt, "fir512"sv) == 0) + UhjEncodeQuality = UhjQualityType::FIR512; + else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) + UhjEncodeQuality = UhjQualityType::IIR; + else + WARN("Unsupported uhj/encode-filter: %s\n", uhjfiltopt->c_str()); + } + + 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; @@ -1154,66 +540,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; @@ -1222,7 +611,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; @@ -1252,7 +641,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(); @@ -1261,36 +650,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) @@ -1299,15 +684,21 @@ void alc_initconfig(void) else eax_g_is_enabled = true; - if(eax_g_is_enabled && DisabledEffects[EAXREVERB_EFFECT]) + if((DisabledEffects.test(EAXREVERB_EFFECT) || DisabledEffects.test(CHORUS_EFFECT)) + && eax_g_is_enabled) { eax_g_is_enabled = false; - TRACE("%s\n", "EAX disabled because EAXReverb is disabled."); + TRACE("EAX disabled because %s disabled.\n", + (DisabledEffects.test(EAXREVERB_EFFECT) && DisabledEffects.test(CHORUS_EFFECT)) + ? "EAXReverb and Chorus are" : + DisabledEffects.test(EAXREVERB_EFFECT) ? "EAXReverb is" : + DisabledEffects.test(CHORUS_EFFECT) ? "Chorus is" : ""); } } #endif // ALSOFT_EAX } -#define DO_INITCONFIG() std::call_once(alc_config_once, [](){alc_initconfig();}) +inline void InitConfig() +{ std::call_once(alc_config_once, [](){alc_initconfig();}); } /************************************************ @@ -1315,9 +706,9 @@ void alc_initconfig(void) ************************************************/ void ProbeAllDevicesList() { - DO_INITCONFIG(); + InitConfig(); - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; if(!PlaybackFactory) decltype(alcAllDevicesList){}.swap(alcAllDevicesList); else @@ -1329,9 +720,9 @@ void ProbeAllDevicesList() } void ProbeCaptureDeviceList() { - DO_INITCONFIG(); + InitConfig(); - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; if(!CaptureFactory) decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); else @@ -1343,62 +734,86 @@ void ProbeCaptureDeviceList() } -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(DevFmtPair{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) { - case ALC_BYTE_SOFT: return al::make_optional(DevFmtByte); - case ALC_UNSIGNED_BYTE_SOFT: return al::make_optional(DevFmtUByte); - case ALC_SHORT_SOFT: return al::make_optional(DevFmtShort); - case ALC_UNSIGNED_SHORT_SOFT: return al::make_optional(DevFmtUShort); - case ALC_INT_SOFT: return al::make_optional(DevFmtInt); - case ALC_UNSIGNED_INT_SOFT: return al::make_optional(DevFmtUInt); - case ALC_FLOAT_SOFT: return al::make_optional(DevFmtFloat); + case ALC_BYTE_SOFT: return DevFmtByte; + case ALC_UNSIGNED_BYTE_SOFT: return DevFmtUByte; + case ALC_SHORT_SOFT: return DevFmtShort; + case ALC_UNSIGNED_SHORT_SOFT: return DevFmtUShort; + case ALC_INT_SOFT: return DevFmtInt; + case ALC_UNSIGNED_INT_SOFT: return DevFmtUInt; + case ALC_FLOAT_SOFT: return DevFmtFloat; } WARN("Unsupported format type: 0x%04x\n", type); - return al::nullopt; + return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtType type) { @@ -1415,20 +830,20 @@ 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) { - case ALC_MONO_SOFT: return al::make_optional(DevFmtMono); - case ALC_STEREO_SOFT: return al::make_optional(DevFmtStereo); - case ALC_QUAD_SOFT: return al::make_optional(DevFmtQuad); - case ALC_5POINT1_SOFT: return al::make_optional(DevFmtX51); - case ALC_6POINT1_SOFT: return al::make_optional(DevFmtX61); - case ALC_7POINT1_SOFT: return al::make_optional(DevFmtX71); - case ALC_BFORMAT3D_SOFT: return al::make_optional(DevFmtAmbi3D); + case ALC_MONO_SOFT: return DevFmtMono; + case ALC_STEREO_SOFT: return DevFmtStereo; + case ALC_QUAD_SOFT: return DevFmtQuad; + case ALC_5POINT1_SOFT: return DevFmtX51; + case ALC_6POINT1_SOFT: return DevFmtX61; + case ALC_7POINT1_SOFT: return DevFmtX71; + case ALC_BFORMAT3D_SOFT: return DevFmtAmbi3D; } WARN("Unsupported format channels: 0x%04x\n", channels); - return al::nullopt; + return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtChannels channels) { @@ -1441,19 +856,22 @@ ALCenum EnumFromDevFmt(DevFmtChannels channels) case DevFmtX61: return ALC_6POINT1_SOFT; case DevFmtX71: return ALC_7POINT1_SOFT; case DevFmtAmbi3D: return ALC_BFORMAT3D_SOFT; + /* FIXME: Shouldn't happen. */ + case DevFmtX714: + 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) { - case ALC_FUMA_SOFT: return al::make_optional(DevAmbiLayout::FuMa); - case ALC_ACN_SOFT: return al::make_optional(DevAmbiLayout::ACN); + case ALC_FUMA_SOFT: return DevAmbiLayout::FuMa; + 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) { @@ -1465,16 +883,16 @@ 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) { - case ALC_FUMA_SOFT: return al::make_optional(DevAmbiScaling::FuMa); - case ALC_SN3D_SOFT: return al::make_optional(DevAmbiScaling::SN3D); - case ALC_N3D_SOFT: return al::make_optional(DevAmbiScaling::N3D); + case ALC_FUMA_SOFT: return DevAmbiScaling::FuMa; + case ALC_SN3D_SOFT: return DevAmbiScaling::SN3D; + 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) { @@ -1488,58 +906,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. */ -const std::array StereoDownmix{{ - { FrontCenter, {{{FrontLeft, 0.5f}, {FrontRight, 0.5f}}} }, - { SideLeft, {{{FrontLeft, 1.0f/9.0f}, {FrontRight, 0.0f}}} }, - { SideRight, {{{FrontLeft, 0.0f}, {FrontRight, 1.0f/9.0f}}} }, - { BackLeft, {{{FrontLeft, 1.0f/9.0f}, {FrontRight, 0.0f}}} }, - { BackRight, {{{FrontLeft, 0.0f}, {FrontRight, 1.0f/9.0f}}} }, - { BackCenter, {{{FrontLeft, 0.5f/9.0f}, {FrontRight, 0.5f/9.0f}}} }, -}}; -const std::array QuadDownmix{{ - { FrontCenter, {{{FrontLeft, 0.5f}, {FrontRight, 0.5f}}} }, - { SideLeft, {{{FrontLeft, 0.5f}, {BackLeft, 0.5f}}} }, - { SideRight, {{{FrontRight, 0.5f}, {BackRight, 0.5f}}} }, - { BackCenter, {{{BackLeft, 0.5f}, {BackRight, 0.5f}}} }, -}}; -const std::array X51Downmix{{ - { BackLeft, {{{SideLeft, 1.0f}, {SideRight, 0.0f}}} }, - { BackRight, {{{SideLeft, 0.0f}, {SideRight, 1.0f}}} }, - { BackCenter, {{{SideLeft, 0.5f}, {SideRight, 0.5f}}} }, -}}; -const std::array X61Downmix{{ - { BackLeft, {{{BackCenter, 0.5f}, {SideLeft, 0.5f}}} }, - { BackRight, {{{BackCenter, 0.5f}, {SideRight, 0.5f}}} }, -}}; -const std::array X71Downmix{{ - { BackCenter, {{{BackLeft, 0.5f}, {BackRight, 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}}; - -/** 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) @@ -1569,75 +989,217 @@ std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const f * to jump forward or back. Must not be called while the device is running/ * mixing. */ -static inline void UpdateClockBase(ALCdevice *device) +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; } - al::optional stereomode{}; - al::optional optlimit{}; + uint numMono{device->NumMonoSources}; + uint numStereo{device->NumStereoSources}; + uint numSends{device->NumAuxSends}; + 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}; - // Check for attributes - if(attrList && attrList[0]) + if(device->Type != DeviceType::Loopback) { - uint numMono{device->NumMonoSources}; - uint numStereo{device->NumStereoSources}; - uint numSends{device->NumAuxSends}; + /* Get default settings from the user configuration */ - al::optional optchans; - al::optional opttype; - al::optional optlayout; - al::optional optscale; - al::optional opthrtf; + if(auto freqopt = device->configValue({}, "frequency")) + { + optsrate = std::clamp(*freqopt, MinOutputRate, MaxOutputRate); + const double scale{static_cast(*optsrate) / double{DefaultOutputRate}}; + period_size = static_cast(std::lround(period_size * scale)); + } + + 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 * uint{DefaultNumUpdates}; + + if(auto typeopt = device->configValue({}, "sample-type")) + { + struct TypeMap { + std::string_view name; + DevFmtType type; + }; + 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(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({}, "channels")) + { + struct ChannelMap { + std::string_view name; + DevFmtChannels chans; + uint8_t order; + }; + 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{"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(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 + { + optchans = iter->chans; + aorder = iter->order; + } + } + if(auto ambiopt = device->configValue({}, "ambi-format"sv)) + { + if(al::case_compare(*ambiopt, "fuma"sv) == 0) + { + optlayout = DevAmbiLayout::FuMa; + optscale = DevAmbiScaling::FuMa; + } + else if(al::case_compare(*ambiopt, "acn+fuma"sv) == 0) + { + optlayout = DevAmbiLayout::ACN; + optscale = DevAmbiScaling::FuMa; + } + 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::case_compare(*ambiopt, "acn+n3d"sv) == 0) + { + optlayout = DevAmbiLayout::ACN; + optscale = DevAmbiScaling::N3D; + } + else + ERR("Unsupported ambi-format: %s\n", ambiopt->c_str()); + } + + if(auto hrtfopt = device->configValue({}, "hrtf"sv)) + { + WARN("general/hrtf is deprecated, please use stereo-encoding instead\n"); + + if(al::case_compare(*hrtfopt, "true"sv) == 0) + stereomode = StereoEncoding::Hrtf; + else if(al::case_compare(*hrtfopt, "false"sv) == 0) + { + if(!stereomode || *stereomode == StereoEncoding::Hrtf) + stereomode = StereoEncoding::Default; + } + else if(al::case_compare(*hrtfopt, "auto"sv) != 0) + ERR("Unexpected hrtf value: %s\n", hrtfopt->c_str()); + } + } + + if(auto encopt = device->configValue({}, "stereo-encoding"sv)) + { + if(al::case_compare(*encopt, "basic"sv) == 0 || al::case_compare(*encopt, "panpot"sv) == 0) + stereomode = StereoEncoding::Basic; + else if(al::case_compare(*encopt, "uhj") == 0) + stereomode = StereoEncoding::Uhj; + else if(al::case_compare(*encopt, "hrtf") == 0) + stereomode = StereoEncoding::Hrtf; + else + ERR("Unexpected stereo-encoding: %s\n", encopt->c_str()); + } + + // Check for app-specified attributes + if(!attrList.empty()) + { ALenum outmode{ALC_ANY_SOFT}; - uint aorder{0u}; - uint freq{0u}; + 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]) { case ATTRIBUTE(ALC_FORMAT_CHANNELS_SOFT) - optchans = DevFmtChannelsFromEnum(attrList[attrIdx + 1]); + if(device->Type == DeviceType::Loopback) + optchans = DevFmtChannelsFromEnum(attrList[attrIdx + 1]); break; case ATTRIBUTE(ALC_FORMAT_TYPE_SOFT) - opttype = DevFmtTypeFromEnum(attrList[attrIdx + 1]); + if(device->Type == DeviceType::Loopback) + opttype = DevFmtTypeFromEnum(attrList[attrIdx + 1]); break; case ATTRIBUTE(ALC_FREQUENCY) - freq = static_cast(attrList[attrIdx + 1]); + freqAttr = attrList[attrIdx + 1]; break; case ATTRIBUTE(ALC_AMBISONIC_LAYOUT_SOFT) - optlayout = DevAmbiLayoutFromEnum(attrList[attrIdx + 1]); + if(device->Type == DeviceType::Loopback) + optlayout = DevAmbiLayoutFromEnum(attrList[attrIdx + 1]); break; case ATTRIBUTE(ALC_AMBISONIC_SCALING_SOFT) - optscale = DevAmbiScalingFromEnum(attrList[attrIdx + 1]); + if(device->Type == DeviceType::Loopback) + optscale = DevAmbiScalingFromEnum(attrList[attrIdx + 1]); break; case ATTRIBUTE(ALC_AMBISONIC_ORDER_SOFT) - aorder = static_cast(attrList[attrIdx + 1]); + if(device->Type == DeviceType::Loopback) + aorder = static_cast(attrList[attrIdx + 1]); break; case ATTRIBUTE(ALC_MONO_SOURCES) @@ -1652,8 +1214,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 > std::numeric_limits::max()) numSends = 0; + else numSends = std::min(numSends, uint{MaxSendCount}); break; case ATTRIBUTE(ALC_HRTF_SOFT) @@ -1662,7 +1224,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) @@ -1675,7 +1237,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) @@ -1687,17 +1249,14 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) attrList[attrIdx + 1], attrList[attrIdx + 1]); break; } - - attrIdx += 2; } #undef ATTRIBUTE - const bool loopback{device->Type == DeviceType::Loopback}; - if(loopback) + if(device->Type == DeviceType::Loopback) { if(!optchans || !opttype) return ALC_INVALID_VALUE; - if(freq < MIN_OUTPUT_RATE || freq > MAX_OUTPUT_RATE) + if(freqAttr < int{MinOutputRate} || freqAttr > int{MaxOutputRate}) return ALC_INVALID_VALUE; if(*optchans == DevFmtAmbi3D) { @@ -1709,56 +1268,18 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) && aorder > 3) return ALC_INVALID_VALUE; } - } - - /* If a context is already running on the device, stop playback so the - * device attributes can be updated. - */ - if(device->Flags.test(DeviceRunning)) - device->Backend->stop(); - device->Flags.reset(DeviceRunning); - - UpdateClockBase(device); - - /* 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(*srcsopt <= 0) numMono = 256; - else numMono = *srcsopt; - } - else - { - if(numMono > INT_MAX-numStereo) - numMono = INT_MAX-numStereo; - numMono = maxu(numMono+numStereo, 256); - } - numStereo = minu(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))); - device->NumAuxSends = numSends; - - if(loopback) - { - device->Frequency = freq; - device->FmtChans = *optchans; - device->FmtType = *opttype; - if(device->FmtChans == DevFmtAmbi3D) - { - device->mAmbiOrder = aorder; - device->mAmbiLayout = *optlayout; - device->mAmbiScale = *optscale; - } - else if(device->FmtChans == DevFmtStereo) + else if(*optchans == DevFmtStereo) { if(opthrtf) - stereomode = *opthrtf ? StereoEncoding::Hrtf : StereoEncoding::Default; + { + if(*opthrtf) + stereomode = StereoEncoding::Hrtf; + else + { + if(stereomode.value_or(StereoEncoding::Hrtf) == StereoEncoding::Hrtf) + stereomode = StereoEncoding::Default; + } + } if(outmode == ALC_STEREO_BASIC_SOFT) stereomode = StereoEncoding::Basic; @@ -1767,76 +1288,77 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) else if(outmode == ALC_STEREO_HRTF_SOFT) stereomode = StereoEncoding::Hrtf; } - device->Flags.set(FrequencyRequest).set(ChannelsRequest).set(SampleTypeRequest); + + optsrate = static_cast(freqAttr); } else { - device->Flags.reset(FrequencyRequest).reset(ChannelsRequest).reset(SampleTypeRequest); - device->FmtType = DevFmtTypeDefault; - device->FmtChans = DevFmtChannelsDefault; - device->mAmbiOrder = 0; - device->BufferSize = DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES; - device->UpdateSize = DEFAULT_UPDATE_SIZE; - device->Frequency = DEFAULT_OUTPUT_RATE; - - freq = device->configValue(nullptr, "frequency").value_or(freq); - if(freq > 0) - { - freq = clampu(freq, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE); - - const double scale{static_cast(freq) / device->Frequency}; - device->UpdateSize = static_cast(device->UpdateSize*scale + 0.5); - device->BufferSize = static_cast(device->BufferSize*scale + 0.5); - - device->Frequency = freq; - device->Flags.set(FrequencyRequest); - } - - auto set_device_mode = [device](DevFmtChannels chans) noexcept - { - device->FmtChans = chans; - device->Flags.set(ChannelsRequest); - }; if(opthrtf) { if(*opthrtf) - { - set_device_mode(DevFmtStereo); stereomode = StereoEncoding::Hrtf; - } else - stereomode = StereoEncoding::Default; + { + if(stereomode.value_or(StereoEncoding::Hrtf) == StereoEncoding::Hrtf) + stereomode = StereoEncoding::Default; + } } - using OutputMode = ALCdevice::OutputMode; - switch(OutputMode(outmode)) + if(outmode != ALC_ANY_SOFT) { - case OutputMode::Any: break; - case OutputMode::Mono: set_device_mode(DevFmtMono); break; - case OutputMode::Stereo: set_device_mode(DevFmtStereo); break; - case OutputMode::StereoBasic: - set_device_mode(DevFmtStereo); - stereomode = StereoEncoding::Basic; - break; - case OutputMode::Uhj2: - set_device_mode(DevFmtStereo); - stereomode = StereoEncoding::Uhj; - break; - case OutputMode::Hrtf: - set_device_mode(DevFmtStereo); - stereomode = StereoEncoding::Hrtf; - break; - case OutputMode::Quad: set_device_mode(DevFmtQuad); break; - case OutputMode::X51: set_device_mode(DevFmtX51); break; - case OutputMode::X61: set_device_mode(DevFmtX61); break; - case OutputMode::X71: set_device_mode(DevFmtX71); break; + using OutputMode = ALCdevice::OutputMode; + switch(OutputMode(outmode)) + { + case OutputMode::Any: break; + case OutputMode::Mono: optchans = DevFmtMono; break; + case OutputMode::Stereo: optchans = DevFmtStereo; break; + case OutputMode::StereoBasic: + optchans = DevFmtStereo; + stereomode = StereoEncoding::Basic; + break; + case OutputMode::Uhj2: + optchans = DevFmtStereo; + stereomode = StereoEncoding::Uhj; + break; + case OutputMode::Hrtf: + optchans = DevFmtStereo; + stereomode = StereoEncoding::Hrtf; + break; + case OutputMode::Quad: optchans = DevFmtQuad; break; + case OutputMode::X51: optchans = DevFmtX51; break; + case OutputMode::X61: optchans = DevFmtX61; break; + case OutputMode::X71: optchans = DevFmtX71; break; + } + } + + if(freqAttr) + { + uint oldrate = optsrate.value_or(DefaultOutputRate); + freqAttr = std::clamp(freqAttr, MinOutputRate, MaxOutputRate); + + const double scale{static_cast(freqAttr) / oldrate}; + period_size = static_cast(std::lround(period_size * scale)); + buffer_size = static_cast(std::lround(buffer_size * scale)); + optsrate = static_cast(freqAttr); } } + + /* If a context is already running on the device, stop playback so the + * device attributes can be updated. + */ + if(device->mDeviceState == DeviceState::Playing) + { + device->Backend->stop(); + 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; @@ -1853,7 +1375,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) device->Dry.Buffer = {}; std::fill(std::begin(device->NumChannelsPerOrder), std::end(device->NumChannelsPerOrder), 0u); device->RealOut.RemixMap = {}; - device->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); + device->RealOut.ChannelIndex.fill(InvalidChannelIndex); device->RealOut.Buffer = {}; device->MixBuffer.clear(); device->MixBuffer.shrink_to_fit(); @@ -1867,141 +1389,48 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) device->mHrtfStatus = ALC_HRTF_DISABLED_SOFT; /************************************************************************* - * Update device format request from the user configuration + * Update device format request */ - if(device->Type != DeviceType::Loopback) + + if(device->Type == DeviceType::Loopback) { - if(auto typeopt = device->configValue(nullptr, "sample-type")) + device->Frequency = *optsrate; + device->FmtChans = *optchans; + device->FmtType = *opttype; + if(device->FmtChans == DevFmtAmbi3D) { - static constexpr struct TypeMap { - const char name[8]; - DevFmtType type; - } typelist[] = { - { "int8", DevFmtByte }, - { "uint8", DevFmtUByte }, - { "int16", DevFmtShort }, - { "uint16", DevFmtUShort }, - { "int32", DevFmtInt }, - { "uint32", DevFmtUInt }, - { "float32", DevFmtFloat }, - }; - - const ALCchar *fmt{typeopt->c_str()}; - auto iter = std::find_if(std::begin(typelist), std::end(typelist), - [fmt](const TypeMap &entry) -> bool - { return al::strcasecmp(entry.name, fmt) == 0; }); - if(iter == std::end(typelist)) - ERR("Unsupported sample-type: %s\n", fmt); - else - { - device->FmtType = iter->type; - device->Flags.set(SampleTypeRequest); - } + device->mAmbiOrder = aorder; + device->mAmbiLayout = *optlayout; + device->mAmbiScale = *optscale; } - if(auto chanopt = device->configValue(nullptr, "channels")) + device->Flags.set(FrequencyRequest).set(ChannelsRequest).set(SampleTypeRequest); + } + else + { + device->FmtType = opttype.value_or(DevFmtTypeDefault); + device->FmtChans = optchans.value_or(DevFmtChannelsDefault); + device->mAmbiOrder = 0; + device->BufferSize = buffer_size; + device->UpdateSize = period_size; + 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) { - static constexpr struct ChannelMap { - const char name[16]; - DevFmtChannels chans; - uint8_t order; - } chanlist[] = { - { "mono", DevFmtMono, 0 }, - { "stereo", DevFmtStereo, 0 }, - { "quad", DevFmtQuad, 0 }, - { "surround51", DevFmtX51, 0 }, - { "surround61", DevFmtX61, 0 }, - { "surround71", DevFmtX71, 0 }, - { "surround51rear", DevFmtX51, 0 }, - { "ambi1", DevFmtAmbi3D, 1 }, - { "ambi2", DevFmtAmbi3D, 2 }, - { "ambi3", DevFmtAmbi3D, 3 }, - }; - - const ALCchar *fmt{chanopt->c_str()}; - auto iter = std::find_if(std::begin(chanlist), std::end(chanlist), - [fmt](const ChannelMap &entry) -> bool - { return al::strcasecmp(entry.name, fmt) == 0; }); - if(iter == std::end(chanlist)) - ERR("Unsupported channels: %s\n", fmt); - else + device->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 + && (device->mAmbiLayout == DevAmbiLayout::FuMa + || device->mAmbiScale == DevAmbiScaling::FuMa)) { - device->FmtChans = iter->chans; - device->mAmbiOrder = iter->order; - device->Flags.set(ChannelsRequest); + ERR("FuMa is incompatible with %d%s order ambisonics (up to 3rd order only)\n", + device->mAmbiOrder, GetCounterSuffix(device->mAmbiOrder)); + device->mAmbiOrder = 3; } } - if(auto ambiopt = device->configValue(nullptr, "ambi-format")) - { - const ALCchar *fmt{ambiopt->c_str()}; - if(al::strcasecmp(fmt, "fuma") == 0) - { - if(device->mAmbiOrder > 3) - ERR("FuMa is incompatible with %d%s order ambisonics (up to 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"); - else - { - device->mAmbiLayout = DevAmbiLayout::FuMa; - device->mAmbiScale = DevAmbiScaling::FuMa; - } - } - else if(al::strcasecmp(fmt, "acn+fuma") == 0) - { - if(device->mAmbiOrder > 3) - 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"); - else - { - device->mAmbiLayout = DevAmbiLayout::ACN; - device->mAmbiScale = DevAmbiScaling::FuMa; - } - } - else if(al::strcasecmp(fmt, "ambix") == 0 || al::strcasecmp(fmt, "acn+sn3d") == 0) - { - device->mAmbiLayout = DevAmbiLayout::ACN; - device->mAmbiScale = DevAmbiScaling::SN3D; - } - else if(al::strcasecmp(fmt, "acn+n3d") == 0) - { - device->mAmbiLayout = DevAmbiLayout::ACN; - device->mAmbiScale = DevAmbiScaling::N3D; - } - else - ERR("Unsupported ambi-format: %s\n", fmt); - } - - if(auto persizeopt = device->configValue(nullptr, "period_size")) - device->UpdateSize = clampu(*persizeopt, 64, 8192); - - if(auto peropt = device->configValue(nullptr, "periods")) - device->BufferSize = device->UpdateSize * clampu(*peropt, 2, 16); - else - device->BufferSize = maxu(device->BufferSize, device->UpdateSize*2); - - if(auto hrtfopt = device->configValue(nullptr, "hrtf")) - { - const char *hrtf{hrtfopt->c_str()}; - if(al::strcasecmp(hrtf, "true") == 0) - { - stereomode = StereoEncoding::Hrtf; - device->FmtChans = DevFmtStereo; - device->Flags.set(ChannelsRequest); - } - else if(al::strcasecmp(hrtf, "false") == 0) - { - if(!stereomode || *stereomode == StereoEncoding::Hrtf) - stereomode = StereoEncoding::Default; - } - else if(al::strcasecmp(hrtf, "auto") != 0) - ERR("Unexpected hrtf value: %s\n", hrtf); - } } TRACE("Pre-reset: %s%s, %s%s, %s%uhz, %u / %u buffer\n", @@ -2048,33 +1477,42 @@ 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); - } - - if(auto encopt = device->configValue(nullptr, "stereo-encoding")) - { - const char *mode{encopt->c_str()}; - if(al::strcasecmp(mode, "panpot") == 0) - stereomode = al::make_optional(StereoEncoding::Basic); - else if(al::strcasecmp(mode, "uhj") == 0) - stereomode = al::make_optional(StereoEncoding::Uhj); - else if(al::strcasecmp(mode, "hrtf") == 0) - stereomode = al::make_optional(StereoEncoding::Hrtf); - else - ERR("Unexpected stereo-encoding: %s\n", mode); + else if(al::case_compare(*modeopt, "auto"sv) != 0) + ERR("Unexpected stereo-mode: %s\n", modeopt->c_str()); } } aluInitRenderer(device, hrtf_id, stereomode); + /* 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({}, "sources"sv)) + { + if(*srcsopt <= 0) numMono = 256; + else numMono = std::max(*srcsopt, 16u); + } + else + { + numMono = std::min(numMono, std::numeric_limits::max()-numStereo); + numMono = std::max(numMono+numStereo, 256u); + } + numStereo = std::min(numStereo, numMono); + numMono -= numStereo; + device->SourcesMax = numMono + numStereo; + device->NumMonoSources = numMono; + device->NumStereoSources = numStereo; + + 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", device->SourcesMax, device->NumMonoSources, device->NumStereoSources, device->AuxiliaryEffectSlotMax, device->NumAuxSends); @@ -2090,21 +1528,18 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) case DevFmtX51: device->RealOut.RemixMap = X51Downmix; break; case DevFmtX61: device->RealOut.RemixMap = X61Downmix; break; case DevFmtX71: device->RealOut.RemixMap = X71Downmix; break; + case DevFmtX714: device->RealOut.RemixMap = X71Downmix; break; + case DevFmtX3D71: device->RealOut.RemixMap = X51Downmix; break; case DevFmtAmbi3D: break; } - nanoseconds::rep sample_delay{0}; - if(device->mUhjEncoder) - sample_delay += UhjEncoder::sFilterDelay; - if(auto *ambidec = device->AmbiDecoder.get()) - { - if(ambidec->hasStablizer()) - sample_delay += FrontStablizer::DelayLength; - } + 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) @@ -2126,7 +1561,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)); } } @@ -2136,8 +1571,8 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) TRACE("Dithering enabled (%d-bit, %g)\n", float2int(std::log2(device->DitherDepth)+0.5f)+1, device->DitherDepth); - if(auto limopt = device->configValue(nullptr, "output-limiter")) - optlimit = limopt; + if(!optlimit) + 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 @@ -2159,7 +1594,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 { @@ -2191,6 +1626,7 @@ 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()}); @@ -2199,29 +1635,50 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) { auto *context = static_cast(ctxbase); - auto GetEffectBuffer = [](ALbuffer *buffer) noexcept -> EffectState::Buffer - { - if(!buffer) return EffectState::Buffer{}; - return EffectState::Buffer{buffer, buffer->mData}; - }; std::unique_lock proplock{context->mPropLock}; std::unique_lock slotlock{context->mEffectSlotLock}; - /* Clear out unused wet buffers. */ - auto buffer_not_in_use = [](WetBufferPtr &wetbuffer) noexcept -> bool - { return !wetbuffer->mInUse; }; - auto wetbuffer_iter = std::remove_if(context->mWetBuffers.begin(), - context->mWetBuffers.end(), buffer_not_in_use); - context->mWetBuffers.erase(wetbuffer_iter, context->mWetBuffers.end()); + /* Clear out unused effect slot clusters. */ + auto slot_cluster_not_in_use = [](ContextBase::EffectSlotCluster &clusterptr) + { + const auto cluster = al::span{*clusterptr}; + for(size_t i{0};i < cluster.size();++i) + { + if(cluster[i].InUse) + return false; + } + return true; + }; + auto slotcluster_iter = std::remove_if(context->mEffectSlotClusters.begin(), + context->mEffectSlotClusters.end(), slot_cluster_not_in_use); + context->mEffectSlotClusters.erase(slotcluster_iter, context->mEffectSlotClusters.end()); + + /* Free all wet buffers. Any in use will be reallocated with an updated + * configuration in aluInitEffectPanning. + */ + for(auto& clusterptr : context->mEffectSlotClusters) + { + const auto cluster = al::span{*clusterptr}; + for(size_t i{0};i < cluster.size();++i) + { + cluster[i].mWetBuffer.clear(); + cluster[i].mWetBuffer.shrink_to_fit(); + cluster[i].Wet.Buffer = {}; + } + } 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, GetEffectBuffer(slot->Buffer)); - slot->updateProps(context); + state->deviceUpdate(device, slot->Buffer); + slot->mPropsDirty = true; } if(EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_relaxed)}) @@ -2231,18 +1688,25 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) 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, GetEffectBuffer(slot->Buffer)); - slot->updateProps(context); + state->deviceUpdate(device, slot.Buffer); + slot.mPropsDirty = true; } } + /* 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}; @@ -2252,8 +1716,8 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) 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 @@ -2263,19 +1727,18 @@ 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); + auto send_begin = source.Send.begin() + static_cast(num_sends); + std::for_each(send_begin, source.Send.end(), clear_send); - source->mPropsDirty = true; + source.mPropsDirty = true; } } - auto voicelist = context->getVoicesSpan(); - for(Voice *voice : voicelist) + for(Voice *voice : context->getVoicesSpan()) { /* Clear extraneous property set sends. */ std::fill(std::begin(voice->mProps.Send)+num_sends, std::end(voice->mProps.Send), @@ -2307,16 +1770,18 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) context->mPropsDirty = false; UpdateContextProps(context); + UpdateAllEffectSlotProps(context); UpdateAllSourceProps(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()); @@ -2335,13 +1800,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 UNLIKELY(!device->Connected.load(std::memory_order_relaxed)) + 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)) { @@ -2352,7 +1817,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; @@ -2370,7 +1835,7 @@ bool ResetDeviceParams(ALCdevice *device, const int *attrList) } ALCenum err{UpdateDeviceParams(device, attrList)}; - if LIKELY(err == ALC_NO_ERROR) return ALC_TRUE; + if(err == ALC_NO_ERROR) LIKELY return ALC_TRUE; alcSetError(device, err); return ALC_FALSE; @@ -2380,7 +1845,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) { @@ -2396,7 +1861,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) { @@ -2408,104 +1873,125 @@ 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) context->add_ref(); else { - std::lock_guard _{ListLock}; + while(ALCcontext::sGlobalContextLock.exchange(true, std::memory_order_acquire)) { + /* Wait to make sure another thread isn't trying to change the + * current context and bring its refcount to 0. + */ + } context = ALCcontext::sGlobalContext.load(std::memory_order_acquire); - if(context) context->add_ref(); + if(context) LIKELY context->add_ref(); + ALCcontext::sGlobalContextLock.store(false, std::memory_order_release); } 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: @@ -2514,10 +2000,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(); } } @@ -2535,7 +2021,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(); } } @@ -2548,7 +2034,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: @@ -2556,7 +2042,7 @@ START_API_FUNC ProbeAllDevicesList(); /* Copy first entry as default. */ - alcDefaultAllDevicesSpecifier = alcAllDevicesList.c_str(); + alcDefaultAllDevicesSpecifier = alcAllDevicesList.substr(0, alcAllDevicesList.find('\0')); value = alcDefaultAllDevicesSpecifier.c_str(); break; @@ -2565,21 +2051,22 @@ START_API_FUNC ProbeCaptureDeviceList(); /* Copy first entry as default. */ - alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceList.c_str(); + alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceList.substr(0, + alcCaptureDeviceList.find('\0')); 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 @@ -2593,13 +2080,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); @@ -2624,7 +2108,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: @@ -2650,7 +2134,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}; @@ -2660,11 +2144,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; @@ -2675,8 +2157,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; @@ -2700,7 +2184,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; @@ -2713,11 +2197,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; @@ -2783,8 +2265,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; @@ -2895,8 +2380,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: @@ -2917,8 +2402,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) @@ -2926,10 +2410,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) @@ -2937,94 +2419,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; @@ -3034,16 +2517,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: @@ -3052,51 +2536,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) { @@ -3104,6 +2585,7 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_VALUE); return nullptr; } + #ifdef ALSOFT_EAX if(eax_g_is_enabled) { @@ -3121,11 +2603,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) { @@ -3133,6 +2613,7 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_VALUE); return 0; } + #ifdef ALSOFT_EAX if(eax_g_is_enabled) { @@ -3151,11 +2632,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 @@ -3174,24 +2653,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); @@ -3206,8 +2701,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. @@ -3218,24 +2712,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()); @@ -3246,10 +2737,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); @@ -3268,33 +2757,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; @@ -3309,13 +2788,18 @@ START_API_FUNC } /* Release this reference (if any) to store it in the GlobalContext * pointer. Take ownership of the reference (if any) that was previously - * stored there. + * stored there, and let the reference go. */ + while(ALCcontext::sGlobalContextLock.exchange(true, std::memory_order_acquire)) { + /* Wait to make sure another thread isn't getting or trying to change + * the current context as its refcount is decremented. + */ + } ctx = ContextRef{ALCcontext::sGlobalContext.exchange(ctx.release())}; + ALCcontext::sGlobalContextLock.store(false, std::memory_order_release); - /* Reset (decrement) the previous global reference by replacing it with the - * thread-local context. Take ownership of the thread-local context - * reference (if any), clearing the storage to null. + /* Take ownership of the thread-local context reference (if any), clearing + * the storage to null. */ ctx = ContextRef{ALCcontext::getThreadContext()}; if(ctx) ALCcontext::setThreadContext(nullptr); @@ -3323,11 +2807,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; @@ -3346,11 +2828,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) @@ -3360,13 +2840,11 @@ 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 { - DO_INITCONFIG(); + InitConfig(); if(!PlaybackFactory) { @@ -3374,48 +2852,63 @@ START_API_FUNC return nullptr; } - if(deviceName) + std::string_view devname{deviceName ? deviceName : ""}; + if(!devname.empty()) { - 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"); - DeviceRef device{new ALCdevice{DeviceType::Playback}}; + const uint DefaultSends{ +#ifdef ALSOFT_EAX + eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : +#endif // ALSOFT_EAX + uint{DefaultSendCount} + }; + + 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; + device->NumMonoSources = device->SourcesMax - device->NumStereoSources; device->AuxiliaryEffectSlotMax = 64; - device->NumAuxSends = DEFAULT_SENDS; -#ifdef ALSOFT_EAX - if(eax_g_is_enabled) - device->NumAuxSends = EAX_MAX_FXSLOTS; -#endif // ALSOFT_EAX + device->NumAuxSends = DefaultSends; 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) { @@ -3425,36 +2918,8 @@ START_API_FUNC return nullptr; } - if(uint freq{device->configValue(nullptr, "frequency").value_or(0u)}) { - if(freq < MIN_OUTPUT_RATE || freq > MAX_OUTPUT_RATE) - { - const uint newfreq{clampu(freq, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)}; - ERR("%uhz request clamped to %uhz\n", freq, newfreq); - freq = newfreq; - } - const double scale{static_cast(freq) / device->Frequency}; - device->UpdateSize = static_cast(device->UpdateSize*scale + 0.5); - device->BufferSize = static_cast(device->BufferSize*scale + 0.5); - device->Frequency = freq; - device->Flags.set(FrequencyRequest); - } - - if(auto srcsmax = device->configValue(nullptr, "sources").value_or(0)) - device->SourcesMax = srcsmax; - - if(auto slotsmax = device->configValue(nullptr, "slots").value_or(0)) - device->AuxiliaryEffectSlotMax = minu(slotsmax, INT_MAX); - - if(auto sendsopt = device->configValue(nullptr, "sends")) - device->NumAuxSends = minu(DEFAULT_SENDS, - static_cast(clampi(*sendsopt, 0, MAX_SENDS))); - - device->NumStereoSources = 1; - device->NumMonoSources = device->SourcesMax - device->NumStereoSources; - - { - 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()); } @@ -3462,10 +2927,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); @@ -3487,13 +2950,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); } } @@ -3506,22 +2969,22 @@ 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 { - DO_INITCONFIG(); + InitConfig(); if(!CaptureFactory) { @@ -3535,14 +2998,24 @@ START_API_FUNC return nullptr; } - if(deviceName) + std::string_view devname{deviceName ? deviceName : ""}; + if(!devname.empty()) { - 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) @@ -3561,14 +3034,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) { @@ -3579,18 +3052,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); @@ -3609,17 +3081,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) @@ -3628,15 +3100,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()); @@ -3645,26 +3118,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) @@ -3681,7 +3152,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); @@ -3691,9 +3162,8 @@ START_API_FUNC return; } - backend->captureSamples(static_cast(buffer), usamples); + backend->captureSamples(static_cast(buffer), usamples); } -END_API_FUNC /************************************************ @@ -3701,42 +3171,44 @@ 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 { - DO_INITCONFIG(); + 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; } - DeviceRef device{new ALCdevice{DeviceType::Loopback}}; + const uint DefaultSends{ +#ifdef ALSOFT_EAX + eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : +#endif // ALSOFT_EAX + uint{DefaultSendCount} + }; + + 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; - device->NumAuxSends = DEFAULT_SENDS; + device->NumAuxSends = DefaultSends; //Set output format device->BufferSize = 0; device->UpdateSize = 0; - device->Frequency = DEFAULT_OUTPUT_RATE; + device->Frequency = DefaultOutputRate; device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; - if(auto srcsmax = ConfigValueUInt(nullptr, nullptr, "sources").value_or(0)) - device->SourcesMax = srcsmax; - - if(auto slotsmax = ConfigValueUInt(nullptr, nullptr, "slots").value_or(0)) - device->AuxiliaryEffectSlotMax = minu(slotsmax, INT_MAX); - - if(auto sendsopt = ConfigValueInt(nullptr, nullptr, "sends")) - device->NumAuxSends = minu(DEFAULT_SENDS, - static_cast(clampi(*sendsopt, 0, MAX_SENDS))); - device->NumStereoSources = 1; device->NumMonoSources = device->SourcesMax - device->NumStereoSources; @@ -3754,7 +3226,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()); } @@ -3762,13 +3234,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) @@ -3778,29 +3248,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 /************************************************ @@ -3808,26 +3281,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) @@ -3836,9 +3308,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; @@ -3846,7 +3330,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()); @@ -3855,10 +3339,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 /************************************************ @@ -3866,8 +3349,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) @@ -3887,11 +3369,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)}; @@ -3901,19 +3381,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 /************************************************ @@ -3922,15 +3403,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) @@ -3939,20 +3413,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() >= 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(); @@ -3962,16 +3450,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()); @@ -3982,6 +3466,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 @@ -3995,7 +3480,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/libs/openal-soft/alc/alconfig.cpp b/libs/openal-soft/alc/alconfig.cpp index 7c1eec6d..6f69e5b6 100644 --- a/libs/openal-soft/alc/alconfig.cpp +++ b/libs/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] == '[') { - char *line{&buffer[0]}; - char *section = line+1; - char *endsection; - - endsection = std::strchr(section, ']'); - if(!endsection || section == endsection) + auto endpos = buffer.find(']', 1); + if(endpos == 1 || endpos == std::string::npos) { - ERR(" config parse error: bad line \"%s\"\n", line); + ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); continue; } - if(endsection[1] != 0) + if(buffer[endpos+1] != '\0') { - char *end = endsection+1; - while(std::isspace(*end)) - ++end; - if(*end != 0 && *end != '#') + size_t last{endpos+1}; + while(last < buffer.size() && std::isspace(buffer[last])) + ++last; + + if(last < buffer.size() && buffer[last] != '#') { - ERR(" config parse error: bad line \"%s\"\n", line); + ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); continue; } } - *endsection = 0; + + auto section = std::string_view{buffer}.substr(1, endpos-1); curSection.clear(); - if(al::strcasecmp(section, "general") != 0) + if(al::case_compare(section, "general"sv) != 0) { do { - char *nextp = std::strchr(section, '%'); - if(!nextp) + auto nextp = section.find('%'); + if(nextp == std::string_view::npos) { curSection += section; break; } - curSection.append(section, nextp); - section = nextp; + curSection += section.substr(0, nextp); + section.remove_prefix(nextp); - if(((section[1] >= '0' && section[1] <= '9') || + if(section.size() > 2 && + ((section[1] >= '0' && section[1] <= '9') || (section[1] >= 'a' && section[1] <= 'f') || (section[1] >= 'A' && section[1] <= 'F')) && ((section[2] >= '0' && section[2] <= '9') || @@ -198,19 +207,19 @@ void LoadConfigFromFile(std::istream &f) else if(section[2] >= 'A' && section[2] <= 'F') b |= (section[2]-'A'+0x0a); curSection += static_cast(b); - section += 3; + section.remove_prefix(3); } - else if(section[1] == '%') + else if(section.size() > 1 && section[1] == '%') { curSection += '%'; - section += 2; + section.remove_prefix(2); } else { curSection += '%'; - section += 1; + section.remove_prefix(1); } - } while(*section != 0); + } while(!section.empty()); } continue; @@ -228,16 +237,17 @@ void LoadConfigFromFile(std::istream &f) ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); continue; } - auto keyend = sep++; - while(keyend > 0 && std::isspace(buffer[keyend-1])) - --keyend; - if(!keyend) + auto keypart = std::string_view{buffer}.substr(0, sep++); + while(!keypart.empty() && std::isspace(keypart.back())) + keypart.remove_suffix(1); + if(keypart.empty()) { ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); continue; } - while(sep < buffer.size() && std::isspace(buffer[sep])) - sep++; + auto valpart = std::string_view{buffer}.substr(sep); + while(!valpart.empty() && std::isspace(valpart.front())) + valpart.remove_prefix(1); std::string fullKey; if(!curSection.empty()) @@ -245,20 +255,24 @@ void LoadConfigFromFile(std::istream &f) fullKey += curSection; fullKey += '/'; } - fullKey += buffer.substr(0u, keyend); + fullKey += keypart; - std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}}; - if(value.size() > 1) + if(valpart.size() > std::numeric_limits::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 al::make_optional(val); - return al::nullopt; + return val; + 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 al::make_optional(static_cast(std::strtol(val, nullptr, 0))); - return al::nullopt; + return static_cast(std::strtol(val, nullptr, 0)); + 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 al::make_optional(static_cast(std::strtoul(val, nullptr, 0))); - return al::nullopt; + return static_cast(std::strtoul(val, nullptr, 0)); + 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 al::make_optional(std::strtof(val, nullptr)); - return al::nullopt; + return std::strtof(val, nullptr); + 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::make_optional(al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 - || al::strcasecmp(val, "true")==0 || atoi(val) != 0); - return al::nullopt; + return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 + || al::strcasecmp(val, "true") == 0 || atoi(val) != 0; + return std::nullopt; } -bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def) +bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName, bool def) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 - || al::strcasecmp(val, "true") == 0 || atoi(val) != 0); + return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 + || al::strcasecmp(val, "true") == 0 || atoi(val) != 0; return def; } diff --git a/libs/openal-soft/alc/alconfig.h b/libs/openal-soft/alc/alconfig.h index df2830cc..e7daac28 100644 --- a/libs/openal-soft/alc/alconfig.h +++ b/libs/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/libs/openal-soft/alc/alu.cpp b/libs/openal-soft/alc/alu.cpp index ef885152..e3b65f47 100644 --- a/libs/openal-soft/alc/alu.cpp +++ b/libs/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" @@ -55,6 +56,7 @@ #include "core/buffer_storage.h" #include "core/context.h" #include "core/cpu_caps.h" +#include "core/cubic_tables.h" #include "core/devformat.h" #include "core/device.h" #include "core/effects/base.h" @@ -75,7 +77,6 @@ #include "opthelpers.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" #include "vecmat.h" #include "vector.h" @@ -105,21 +106,15 @@ static_assert(!(MaxResamplerPadding&1), "MaxResamplerPadding is not a multiple o namespace { using uint = unsigned int; - -constexpr uint MaxPitch{10}; - -static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!"); -static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize, - "MaxPitch and/or BufferLineSize are too large for MixerFracBits!"); - -using namespace std::placeholders; +using namespace std::chrono; +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,23 +130,18 @@ float XScale{1.0f}; float YScale{1.0f}; float ZScale{1.0f}; -} // namespace +/* Source distance scale for NFC filters. */ +float NfcScale{1.0f}; -namespace { - -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, float2 *AccumSamples, + const al::span TempBuf, HrtfChannelState *ChanState, const size_t IrSize, + const size_t BufferSize); HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_}; -inline HrtfDirectMixerFunc SelectHrtfMixer(void) +inline HrtfDirectMixerFunc SelectHrtfMixer() { #ifdef HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) @@ -174,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 @@ -210,33 +200,49 @@ inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) #endif return Resample_; case Resampler::Cubic: - return Resample_; - case Resampler::BSinc12: - case Resampler::BSinc24: - if(increment <= MixerFracOne) - { - /* fall-through */ - case Resampler::FastBSinc12: - case Resampler::FastBSinc24: -#ifdef HAVE_NEON - if((CPUCapFlags&CPU_CAP_NEON)) - return Resample_; -#endif -#ifdef HAVE_SSE - if((CPUCapFlags&CPU_CAP_SSE)) - return Resample_; -#endif - return Resample_; - } #ifdef HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) - return Resample_; + 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_; + return Resample_; #endif - return Resample_; + return Resample_; + case Resampler::BSinc12: + case Resampler::BSinc24: + if(increment > MixerFracOne) + { +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return Resample_; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return Resample_; +#endif + return Resample_; + } + /* fall-through */ + case Resampler::FastBSinc12: + case Resampler::FastBSinc24: +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return Resample_; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return Resample_; +#endif + return Resample_; } return Resample_; @@ -244,12 +250,14 @@ inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) } // namespace -void aluInit(CompatFlagBitset flags) +void aluInit(CompatFlagBitset flags, const float nfcscale) { MixDirectHrtf = SelectHrtfMixer(); XScale = flags.test(CompatFlags::ReverseX) ? -1.0f : 1.0f; YScale = flags.test(CompatFlags::ReverseY) ? -1.0f : 1.0f; ZScale = flags.test(CompatFlags::ReverseZ) ? -1.0f : 1.0f; + + NfcScale = std::clamp(nfcscale, 0.0001f, 10000.0f); } @@ -259,15 +267,17 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState { case Resampler::Point: case Resampler::Linear: + break; case Resampler::Cubic: + state->emplace(al::span{gGaussianFilter.mTable}); break; case Resampler::FastBSinc12: case Resampler::BSinc12: - BsincPrepare(increment, &state->bsinc, &bsinc12); + BsincPrepare(increment, &state->emplace(), &gBSinc12); break; case Resampler::FastBSinc24: case Resampler::BSinc24: - BsincPrepare(increment, &state->bsinc, &bsinc24); + BsincPrepare(increment, &state->emplace(), &gBSinc24); break; } return SelectResampler(resampler, increment); @@ -277,52 +287,50 @@ 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); + MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData.data(), + mHrtfState->mTemp, mHrtfState->mChannels.data(), 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(), - Dry.Buffer.data(), SamplesToDo); + {{Dry.Buffer[0].data(), Dry.Buffer[1].data(), Dry.Buffer[2].data()}}, 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); } @@ -339,28 +347,58 @@ inline uint dither_rng(uint *seed) noexcept } -inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept +/* Ambisonic upsampler function. It's effectively a matrix multiply. It takes + * an 'upsampler' and 'rotator' as the input matrices, and creates a matrix + * that behaves as if the B-Format input was first decoded to a speaker array + * at its input order, encoded back into the higher order mix, then finally + * rotated. + */ +void UpsampleBFormatTransform( + const al::span,MaxAmbiChannels> output, + const al::span> upsampler, + const al::span,MaxAmbiChannels> rotator, + size_t ambi_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) + { + 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. + */ + std::transform(rotator[k].cbegin(), rotator[k].cend(), output[i].cbegin(), + output[i].begin(), [a](float rot, float dst) noexcept { return rot*a + dst; }); + } + } +} + + +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}; } @@ -420,14 +458,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 { @@ -440,8 +478,8 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa } EffectState *state{props->State.release()}; - EffectState *oldstate{slot->mEffectState}; - slot->mEffectState = state; + EffectState *oldstate{slot->mEffectState.release()}; + slot->mEffectState.reset(state); /* Only release the old state if it won't get deleted, since we can't be * deleting/freeing anything in the mixer. @@ -451,11 +489,10 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa /* Otherwise, if it would be deleted send it off with a release event. */ RingBuffer *ring{context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); - if LIKELY(evt_vec.first.len > 0) + 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 @@ -469,7 +506,7 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa } } - AtomicReplaceHead(context->mFreeEffectslotProps, props); + AtomicReplaceHead(context->mFreeEffectSlotProps, props); EffectTarget output; if(EffectSlot *target{slot->Target}) @@ -484,27 +521,76 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa } -/* 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 @@ -524,83 +610,101 @@ inline float WrapRadians(float r) * precomputed since they're constant. The second-order coefficients are * followed by the third-order coefficients, etc. */ +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 { - float u, v, w; + struct CoeffValues { + float u, v, w; + }; + std::array mCoeffs{}; - template - static std::array ConcatArrays(const std::array &lhs, - const std::array &rhs) + RotatorCoeffs() { - std::array ret; - auto iter = std::copy(lhs.cbegin(), lhs.cend(), ret.begin()); - std::copy(rhs.cbegin(), rhs.cend(), iter); - return ret; - } + auto coeffs = mCoeffs.begin(); - template - static std::array GenCoeffs() - { - std::array ret{}; - auto coeffs = ret.begin(); - - for(int m{-l};m <= l;++m) + for(int l=2;l <= MaxAmbiOrder;++l) { for(int n{-l};n <= l;++n) { - // compute u,v,w terms of Eq.8.1 (Table I) - const bool d{m == 0}; // the delta function d_m0 - const float denom{static_cast((std::abs(n) == l) ? - (2*l) * (2*l - 1) : (l*l - n*n))}; + 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 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; - ++coeffs; + 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; + } } } - - return ret; } }; -const auto RotatorCoeffArray = RotatorCoeffs::ConcatArrays(RotatorCoeffs::GenCoeffs<2>(), - RotatorCoeffs::GenCoeffs<3>()); +const RotatorCoeffs RotatorCoeffArray{}; /** * Given the matrix, pre-filled with the (zeroth- and) first-order rotation * coefficients, this fills in the coefficients for the higher orders up to and * including the given order. The matrix is in ACN layout. */ -void AmbiRotator(std::array,MaxAmbiChannels> &matrix, - const int order) +void AmbiRotator(AmbiRotateMatrix &matrix, const int order) { /* Don't do anything for < 2nd order. */ if(order < 2) return; auto P = [](const int i, const int l, const int a, const int n, const size_t last_band, - const std::array,MaxAmbiChannels> &R) + const AmbiRotateMatrix &R) { - const float ri1{ R[static_cast(i+2)][ 1+2]}; - const float rim1{R[static_cast(i+2)][-1+2]}; - const float ri0{ R[static_cast(i+2)][ 0+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)]}; - auto vec = R[static_cast(a+l-1) + last_band].cbegin() + last_band; + const size_t y{last_band + static_cast(a+l-1)}; if(n == -l) - return ri1*vec[0] + rim1*vec[static_cast(l-1)*size_t{2}]; + return ri1*R[last_band][y] + rim1*R[last_band + static_cast(l-1_z)*2][y]; if(n == l) - return ri1*vec[static_cast(l-1)*size_t{2}] - rim1*vec[0]; - return ri0*vec[static_cast(n+l-1)]; + 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, - const std::array,MaxAmbiChannels> &R) + const AmbiRotateMatrix &R) { return P(0, l, m, n, last_band, R); }; auto V = [P](const int l, const int m, const int n, const size_t last_band, - const std::array,MaxAmbiChannels> &R) + const AmbiRotateMatrix &R) { using namespace al::numbers; if(m > 0) @@ -616,7 +720,7 @@ void AmbiRotator(std::array,MaxAmbiChannels> & return d ? p1*sqrt2_v : (p0 + p1); }; auto W = [P](const int l, const int m, const int n, const size_t last_band, - const std::array,MaxAmbiChannels> &R) + const AmbiRotateMatrix &R) { assert(m != 0); if(m > 0) @@ -631,86 +735,102 @@ void AmbiRotator(std::array,MaxAmbiChannels> & }; // compute rotation matrix of each subsequent band recursively - auto coeffs = RotatorCoeffArray.cbegin(); + auto coeffs = RotatorCoeffArray.mCoeffs.cbegin(); size_t band_idx{4}, last_band{1}; for(int l{2};l <= order;++l) { size_t y{band_idx}; - for(int m{-l};m <= l;++m,++y) + for(int n{-l};n <= l;++n,++y) { size_t x{band_idx}; - for(int n{-l};n <= l;++n,++x) + for(int m{-l};m <= l;++m,++x) { float r{0.0f}; // computes Eq.8.1 - const float u{coeffs->u}; - if(u != 0.0f) r += u * U(l, m, n, last_band, matrix); - const float v{coeffs->v}; - if(v != 0.0f) r += v * V(l, m, n, last_band, matrix); - const float w{coeffs->w}; - if(w != 0.0f) r += w * W(l, m, n, last_band, matrix); + 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, const 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); @@ -727,47 +847,80 @@ 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 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)) { @@ -789,7 +942,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, 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. */ @@ -810,37 +963,25 @@ 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}, 0.0f); - - /* Clamp Y, in case rounding errors caused it to end up outside - * of -1...+1. - */ - const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - /* Negate Z for right-handed coords with -Z in front. */ - const float az{std::atan2(xpos, -zpos)}; - - /* A scalar of 1.5 for plain stereo results in +/-60 degrees - * being moved to +/-90 degrees for direct right and left - * speaker responses. - */ - return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, 0.0f); + return CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, 0.0f); + const auto pos = ScaleAzimuthFront3_2(std::array{xpos, ypos, zpos}); + return CalcDirectionCoeffs(pos, 0.0f); }; + const auto scales = GetAmbiScales(voice->mAmbiScaling); auto coeffs = calc_coeffs(Device->mRenderMode); - std::transform(coeffs.begin()+1, coeffs.end(), coeffs.begin()+1, - std::bind(std::multiplies{}, _1, 1.0f-coverage)); - /* NOTE: W needs to be scaled according to channel scaling. */ - auto&& scales = GetAmbiScales(voice->mAmbiScaling); - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base*scales[0], - voice->mChans[0].mDryParams.Gains.Target); - for(uint i{0};i < NumSends;i++) + if(!(coverage > 0.0f)) { - if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base*scales[0], - voice->mChans[0].mWetParams[i].Gains.Target); + 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, WetGain[i].Base*scales[0], + voice->mChans[0].mWetParams[i].Gains.Target); + } } - - if(coverage > 0.0f) + else { /* Local B-Format sources have their XYZ channels rotated according * to the orientation. @@ -863,44 +1004,95 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * order elements, then construct the rotation for the higher * orders. */ - std::array,MaxAmbiChannels> shrot{}; + AmbiRotateMatrix &shrot = Device->mAmbiRotateMatrix; + shrot.fill(AmbiRotateMatrix::value_type{}); + shrot[0][0] = 1.0f; - shrot[1][1] = U[0]; shrot[1][2] = -V[0]; shrot[1][3] = -N[0]; - shrot[2][1] = -U[1]; shrot[2][2] = V[1]; shrot[2][3] = N[1]; - shrot[3][1] = U[2]; shrot[3][2] = -V[2]; shrot[3][3] = -N[2]; - AmbiRotator(shrot, static_cast(minu(voice->mAmbiOrder, Device->mAmbiOrder))); + shrot[1][1] = U[0]; shrot[1][2] = -U[1]; shrot[1][3] = U[2]; + shrot[2][1] = -V[0]; shrot[2][2] = V[1]; shrot[2][3] = -V[2]; + shrot[3][1] = -N[0]; shrot[3][2] = N[1]; shrot[3][3] = -N[2]; + AmbiRotator(shrot, static_cast(Device->mAmbiOrder)); + + /* If the device is higher order than the voice, "upsample" the + * matrix. + * + * NOTE: Starting with second-order, a 2D upsample needs to be + * applied with a 2D source and 3D output, even when they're the + * same order. This is because higher orders have a height offset + * on various channels (i.e. when elevation=0, those height-related + * channels should be non-0). + */ + AmbiRotateMatrix &mixmatrix = Device->mAmbiRotateMatrix2; + if(Device->mAmbiOrder > voice->mAmbiOrder + || (Device->mAmbiOrder >= 2 && !Device->m2DMixing + && Is2DAmbisonic(voice->mFmtChannels))) + { + if(voice->mAmbiOrder == 1) + { + 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) + { + 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) + { + 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) + { + const auto upsampler = al::span{AmbiScale::FourthOrder2DUp}; + UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); + } + else + al::unreachable(); + } + else + mixmatrix = shrot; /* 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); - static const uint8_t ChansPerOrder[MaxAmbiOrder+1]{1, 3, 5, 7,}; - static const uint8_t OrderOffset[MaxAmbiOrder+1]{0, 1, 4, 9,}; - for(size_t c{1};c < num_channels;c++) + /* Scale the panned W signal inversely to coverage (full coverage + * means no panned signal), and according to the channel scaling. + */ + std::for_each(coeffs.begin(), coeffs.end(), + [scale=(1.0f-coverage)*scales[0]](float &coeff) noexcept { coeff *= scale; }); + + for(size_t c{0};c < num_channels;c++) { const size_t acn{index_map[c]}; - const size_t order{AmbiIndex::OrderFromChannel()[acn]}; - const size_t tocopy{ChansPerOrder[order]}; - const size_t offset{OrderOffset[order]}; const float scale{scales[acn] * coverage}; - auto in = shrot.cbegin() + offset; - coeffs = std::array{}; - for(size_t x{0};x < tocopy;++x) - coeffs[offset+x] = in[x][acn] * scale; + /* For channel 0, combine the B-Format signal (scaled according + * to the coverage amount) with the directional pan. For all + * other channels, use just the (scaled) B-Format signal. + */ + 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); } + + coeffs = std::array{}; } } } @@ -913,23 +1105,23 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con for(size_t c{0};c < num_channels;c++) { - uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; - if(idx != INVALID_CHANNEL_INDEX) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; + 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()) { for(const auto &target : remap->targets) { - idx = GetChannelIdxByName(Device->RealOut, target.channel); - if(idx != INVALID_CHANNEL_INDEX) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * - target.mix; + idx = Device->channelIdxByName(target.channel); + if(idx != InvalidChannelIndex) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain + * target.mix; } } } @@ -940,12 +1132,17 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con */ for(size_t c{0};c < num_channels;c++) { - const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f); + /* Skip LFE */ + if(chans[c].channel == LFE) + continue; + + 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); } } @@ -959,45 +1156,75 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con if(Distance > std::numeric_limits::epsilon()) { - const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const float az{std::atan2(xpos, -zpos)}; - - /* Get the HRIR coefficients and delays just once, for the given - * source direction. - */ - GetHrtfCoeffs(Device->mHrtf.get(), ev, az, Distance, Spread, - voice->mChans[0].mDryParams.Hrtf.Target.Coeffs, - voice->mChans[0].mDryParams.Hrtf.Target.Delay); - voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base; - - /* Remaining channels use the same results as the first. */ - for(size_t c{1};c < num_channels;c++) + if(voice->mFmtChannels == FmtMono) { - /* Skip LFE */ - if(chans[c].channel == LFE) continue; - voice->mChans[c].mDryParams.Hrtf.Target = voice->mChans[0].mDryParams.Hrtf.Target; - } + const float src_ev{std::asin(std::clamp(ypos, -1.0f, 1.0f))}; + const float src_az{std::atan2(xpos, -zpos)}; - /* Calculate the directional coefficients once, which apply to all - * input channels of the source sends. - */ - const auto coeffs = CalcDirectionCoeffs({xpos, ypos, zpos}, Spread); + 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; - for(size_t c{0};c < num_channels;c++) - { - /* Skip LFE */ - if(chans[c].channel == LFE) - continue; + 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); + } + } + else for(size_t c{0};c < num_channels;c++) + { + using namespace al::numbers; + + /* 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 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; + } + + 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 * pangain; + + const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); + for(uint i{0};i < NumSends;i++) + { + if(const EffectSlot *Slot{SendSlots[i]}) + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } } else { + /* With no distance, spread is only meaningful for mono sources + * where it can be 0 or full (non-mono sources are always full + * spread here). + */ + 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 * speaker" responses. @@ -1007,23 +1234,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. */ - GetHrtfCoeffs(Device->mHrtf.get(), 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); } } @@ -1043,7 +1273,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, Device->AvgSpeakerDist/4.0f)}; + const float mdist{std::max(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Adjust NFC filters. */ @@ -1053,40 +1283,77 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con voice->mFlags.set(VoiceHasNfc); } - /* Calculate the directional coefficients once, which apply to all - * input channels. - */ - auto calc_coeffs = [xpos,ypos,zpos,Spread](RenderMode mode) + if(voice->mFmtChannels == FmtMono) { - if(mode != RenderMode::Pairwise) - return CalcDirectionCoeffs({xpos, ypos, zpos}, Spread); - const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const float az{std::atan2(xpos, -zpos)}; - return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread); - }; - const auto coeffs = calc_coeffs(Device->mRenderMode); - - for(size_t c{0};c < num_channels;c++) - { - /* Special-case LFE */ - if(chans[c].channel == LFE) + auto calc_coeffs = [xpos,ypos,zpos,Spread](RenderMode mode) { - if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) - { - const uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; - if(idx != INVALID_CHANNEL_INDEX) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; - } - continue; - } + if(mode != RenderMode::Pairwise) + 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, - voice->mChans[c].mDryParams.Gains.Target); + 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, - voice->mChans[c].mWetParams[i].Gains.Target); + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base, + voice->mChans[0].mWetParams[i].Gains.Target); + } + } + else + { + using namespace al::numbers; + + for(size_t c{0};c < num_channels;c++) + { + const float pangain{SelectChannelGain(chans[c].channel)}; + + /* Special-case LFE */ + if(chans[c].channel == LFE) + { + if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) + { + const uint idx{Device->channelIdxByName(chans[c].channel)}; + if(idx != InvalidChannelIndex) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base + * pangain; + } + continue; + } + + /* Warp the channel position toward the source position as + * the spread decreases. With no spread, all channels are + * at the source position, at full spread (pi*2), each + * channel position is left unchanged. + */ + 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) + pos = ScaleAzimuthFront3(pos); + const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); + + 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, WetGain[i].Base * pangain, + voice->mChans[c].mWetParams[i].Gains.Target); + } } } } @@ -1104,30 +1371,36 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con voice->mFlags.set(VoiceHasNfc); } + /* With no distance, spread is only meaningful for mono sources + * where it can be 0 or full (non-mono sources are always full + * spread here). + */ + 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) { if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) { - const uint idx{GetChannelIdxByName(Device->RealOut, chans[c].channel)}; - if(idx != INVALID_CHANNEL_INDEX) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; + const uint idx{Device->channelIdxByName(chans[c].channel)}; + if(idx != InvalidChannelIndex) + 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); } } @@ -1175,8 +1448,8 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { - const DeviceBase *Device{context->mDevice}; - EffectSlot *SendSlots[MAX_SENDS]; + DeviceBase *Device{context->mDevice}; + std::array SendSlots; voice->mDirect.Buffer = Device->Dry.Buffer; for(uint i{0};i < Device->NumAuxSends;i++) @@ -1197,19 +1470,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); + 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; @@ -1221,25 +1495,35 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { - const DeviceBase *Device{context->mDevice}; + DeviceBase *Device{context->mDevice}; const uint NumSends{Device->NumAuxSends}; /* 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 = {}; @@ -1271,62 +1555,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)}; @@ -1336,129 +1635,93 @@ 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; } /* Distance-based air absorption and initial send decay. */ - if(likely(Distance > props->RefDistance)) + if(Distance > props->RefDistance) LIKELY { const float distance_base{(Distance-props->RefDistance) * props->RolloffFactor}; - const float absorption{distance_base * context->mParams.MetersPerUnit * - props->AirAbsorptionFactor}; - if(absorption > std::numeric_limits::epsilon()) - { - const float hfattn{std::pow(context->mParams.AirAbsorptionGainHF, absorption)}; - DryGain.HF *= hfattn; - for(uint i{0u};i < NumSends;++i) - WetGain[i].HF *= hfattn; - } + const float distance_meters{distance_base * context->mParams.MetersPerUnit}; + const float dryabsorb{distance_meters * props->AirAbsorptionFactor}; + if(dryabsorb > std::numeric_limits::epsilon()) + DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, dryabsorb); /* If the source's Auxiliary Send Filter Gain Auto is off, no extra * adjustment is applied to the send gains. */ for(uint i{props->WetGainAuto ? 0u : NumSends};i < NumSends;++i) { - if(!SendSlots[i]) + 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); - } } } @@ -1505,7 +1768,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}; @@ -1515,8 +1778,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa spread = std::asin(props->Radius/Distance) * 2.0f; CalcPanningAndFilters(voice, ToSource[0]*XScale, ToSource[1]*YScale, ToSource[2]*ZScale, - Distance*context->mParams.MetersPerUnit, spread, DryGain, WetGain, SendSlots, props, - context->mParams, Device); + Distance, spread, DryGain, WetGain, SendSlots, props, context->mParams, Device); } void CalcSourceParams(Voice *voice, ContextBase *context, bool force) @@ -1526,7 +1788,7 @@ void CalcSourceParams(Voice *voice, ContextBase *context, bool force) if(props) { - voice->mProps = *props; + voice->mProps = static_cast(*props); AtomicReplaceHead(context->mFreeVoiceProps, props); } @@ -1547,26 +1809,25 @@ 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: - ASSUME(0); + al::unreachable(); } ring->writeAdvance(1); @@ -1578,7 +1839,7 @@ void ProcessVoiceChanges(ContextBase *ctx) VoiceChange *next{cur->mNext.load(std::memory_order_acquire)}; if(!next) return; - const uint enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)}; + const auto enabledevt = ctx->mEnabledEvts.load(std::memory_order_acquire); do { cur = next; @@ -1659,7 +1920,7 @@ void ProcessVoiceChanges(ContextBase *ctx) } oldvoice->mPendingChange.store(false, std::memory_order_release); } - if(sendevt && (enabledevt&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); @@ -1667,16 +1928,16 @@ void ProcessVoiceChanges(ContextBase *ctx) ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); } -void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots, +void ProcessParamUpdates(ContextBase *ctx, const al::span slots, const al::span voices) { ProcessVoiceChanges(ctx); IncrementRef(ctx->mUpdateCount); - if LIKELY(!ctx->mHoldUpdates.load(std::memory_order_acquire)) + if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) LIKELY { bool force{CalcContextParams(ctx)}; - auto sorted_slots = const_cast(slots.data() + slots.size()); + auto sorted_slots = al::to_address(slots.end()); for(EffectSlot *slot : slots) force |= CalcEffectSlotParams(slot, sorted_slots, ctx); @@ -1694,12 +1955,16 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { ASSUME(SamplesToDo > 0); + 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); + auto auxslots = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)}; const al::span voices{ctx->getVoicesSpanAcquired()}; - /* Process pending propery updates for objects on the context. */ + /* Process pending property updates for objects on the context. */ ProcessParamUpdates(ctx, auxslots, voices); /* Clear auxiliary effect slot mixing buffers. */ @@ -1714,20 +1979,16 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)}; if(vstate != Voice::Stopped && vstate != Voice::Pending) - voice->mix(vstate, ctx, SamplesToDo); + voice->mix(vstate, ctx, curtime, 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). */ - const al::span sorted_slots{const_cast(slots_end), - num_slots}; + const al::span sorted_slots{al::to_address(auxslots.end()), auxslots.size()}; /* Skip sorting if it has already been done. */ if(!sorted_slots[0]) { @@ -1735,7 +1996,7 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) * 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; }); @@ -1762,7 +2023,7 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) * left that don't target any sorted slots, they can't * contribute to the output, so leave them. */ - if UNLIKELY(next_target == split_point) + if(next_target == split_point) UNLIKELY break; --next_target; @@ -1775,7 +2036,7 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) for(const EffectSlot *slot : sorted_slots) { - EffectState *state{slot->mEffectState}; + EffectState *state{slot->mEffectState.get()}; state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget); } } @@ -1789,44 +2050,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; - if LIKELY(SamplesToDo >= base) + 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, std::bind(std::multiplies{}, _1, 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}; @@ -1835,7 +2098,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) @@ -1859,12 +2122,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 @@ -1874,34 +2137,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); } } @@ -1909,28 +2170,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). @@ -1942,7 +2203,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. @@ -1961,10 +2222,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; } @@ -1980,7 +2242,7 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz { const uint samplesToDo{renderSamples(todo)}; - if LIKELY(outBuffer) + if(outBuffer) LIKELY { /* Finally, interleave and convert samples, writing to the device's * output buffer. @@ -1988,7 +2250,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) @@ -2006,24 +2268,24 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz void DeviceBase::handleDisconnect(const char *msg, ...) { - if(!Connected.exchange(false, std::memory_order_acq_rel)) - return; + const auto mixLock = getWriteMixLock(); - AsyncEvent evt{AsyncEvent::Disconnected}; - - va_list args; - va_start(args, msg); - int msglen{vsnprintf(evt.u.disconnect.msg, sizeof(evt.u.disconnect.msg), msg, args)}; - va_end(args); - - if(msglen < 0 || static_cast(msglen) >= sizeof(evt.u.disconnect.msg)) - evt.u.disconnect.msg[sizeof(evt.u.disconnect.msg)-1] = 0; - - IncrementRef(MixCount); - for(ContextBase *ctx : *mContexts.load()) + if(Connected.exchange(false, std::memory_order_acq_rel)) { - const uint enabledevt{ctx->mEnabledEvts.load(std::memory_order_acquire)}; - if((enabledevt&AsyncEvent::Disconnected)) + AsyncEvent evt{std::in_place_type}; + auto &disconnect = std::get(evt); + + /* NOLINTBEGIN(*-array-to-pointer-decay) */ + va_list args; + va_start(args, msg); + int msglen{vsnprintf(disconnect.msg.data(), disconnect.msg.size(), msg, args)}; + va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ + + if(msglen < 0 || static_cast(msglen) >= disconnect.msg.size()) + disconnect.msg[sizeof(disconnect.msg)-1] = 0; + + for(ContextBase *ctx : *mContexts.load()) { RingBuffer *ring{ctx->mAsyncEvents.get()}; auto evt_data = ring->getWriteVector().first; @@ -2033,23 +2295,22 @@ void DeviceBase::handleDisconnect(const char *msg, ...) ring->writeAdvance(1); ctx->mEventSem.post(); } - } - if(!ctx->mStopVoicesOnDisconnect) - { - ProcessVoiceChanges(ctx); - continue; - } + if(!ctx->mStopVoicesOnDisconnect.load()) + { + ProcessVoiceChanges(ctx); + continue; + } - auto voicelist = ctx->getVoicesSpanAcquired(); - auto stop_voice = [](Voice *voice) -> void - { - voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); - voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); - voice->mSourceID.store(0u, std::memory_order_relaxed); - voice->mPlayState.store(Voice::Stopped, std::memory_order_release); - }; - std::for_each(voicelist.begin(), voicelist.end(), stop_voice); + auto voicelist = ctx->getVoicesSpanAcquired(); + auto stop_voice = [](Voice *voice) -> void + { + voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); + voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); + voice->mSourceID.store(0u, std::memory_order_relaxed); + voice->mPlayState.store(Voice::Stopped, std::memory_order_release); + }; + std::for_each(voicelist.begin(), voicelist.end(), stop_voice); + } } - IncrementRef(MixCount); } diff --git a/libs/openal-soft/alc/alu.h b/libs/openal-soft/alc/alu.h index f3796a89..33453487 100644 --- a/libs/openal-soft/alc/alu.h +++ b/libs/openal-soft/alc/alu.h @@ -2,14 +2,14 @@ #define ALU_H #include - -#include "aloptional.h" +#include +#include struct ALCcontext; struct ALCdevice; struct EffectSlot; -enum class StereoEncoding : unsigned char; +enum class StereoEncoding : uint8_t; constexpr float GainMixMax{1000.0f}; /* +60dB */ @@ -24,14 +24,14 @@ enum CompatFlags : uint8_t { }; using CompatFlagBitset = std::bitset; -void aluInit(CompatFlagBitset flags); +void aluInit(CompatFlagBitset flags, const float nfcscale); /* aluInitRenderer * * Set up the appropriate panning method and mixing method given the device * properties. */ -void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional stereomode); +void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional stereomode); void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); diff --git a/libs/openal-soft/alc/backends/alsa.cpp b/libs/openal-soft/alc/backends/alsa.cpp index 9c78b6c6..7626f1de 100644 --- a/libs/openal-soft/alc/backends/alsa.cpp +++ b/libs/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 @@ -132,7 +136,7 @@ constexpr char alsaDevice[] = "ALSA Default"; MAGIC(snd_card_next); \ MAGIC(snd_config_update_free_global) -static void *alsa_handle; +void *alsa_handle; #define MAKE_FUNC(f) decltype(f) * p##f ALSA_FUNCS(MAKE_FUNC); #undef MAKE_FUNC @@ -241,61 +245,104 @@ SwParamsPtr CreateSwParams() struct DevMap { std::string name; std::string device_name; + + template + 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; -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(DevMap{alsaDevice, defname ? defname->c_str() : "default"}); + auto defname = ConfigValueStr({}, "alsa"sv, + (stream == SND_PCM_STREAM_PLAYBACK) ? "device"sv : "capture"sv); + devlist.emplace_back(GetDefaultName(), defname ? std::string_view{*defname} : "default"sv); - if(auto customdevs = ConfigValueStr(nullptr, "alsa", - (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures")) + if(auto customdevs = ConfigValueStr({}, "alsa"sv, + (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices"sv : "custom-captures"sv)) { - size_t nextpos{customdevs->find_first_not_of(';')}; - size_t curpos; - while((curpos=nextpos) < customdevs->length()) + size_t curpos{customdevs->find_first_not_of(';')}; + while(curpos < customdevs->length()) { - nextpos = customdevs->find_first_of(';', curpos+1); - - size_t seppos{customdevs->find_first_of('=', curpos)}; + size_t nextpos{customdevs->find(';', curpos+1)}; + const size_t seppos{customdevs->find('=', curpos)}; if(seppos == curpos || seppos >= nextpos) { - std::string spec{customdevs->substr(curpos, nextpos-curpos)}; + const std::string spec{customdevs->substr(curpos, nextpos-curpos)}; ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str()); } else { - devlist.emplace_back(DevMap{customdevs->substr(curpos, seppos-curpos), - customdevs->substr(seppos+1, nextpos-seppos-1)}); - const auto &entry = devlist.back(); + const std::string_view strview{*customdevs}; + const auto &entry = devlist.emplace_back(strview.substr(curpos, seppos-curpos), + strview.substr(seppos+1, nextpos-seppos-1)); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } if(nextpos < customdevs->length()) nextpos = customdevs->find_first_not_of(';', nextpos+1); + curpos = nextpos; } } - const std::string main_prefix{ - ConfigValueStr(nullptr, "alsa", prefix_name(stream)).value_or("plughw:")}; + const std::string main_prefix{ConfigValueStr({}, "alsa"sv, prefix_name(stream)) + .value_or("plughw:")}; int card{-1}; int err{snd_card_next(&card)}; @@ -303,16 +350,17 @@ al::vector probe_devices(snd_pcm_stream_t stream) { std::string name{"hw:" + std::to_string(card)}; - snd_ctl_t *handle; - if((err=snd_ctl_open(&handle, name.c_str(), 0)) < 0) + SndCtl handle; + err = handle.open(name.c_str(), 0); + if(err < 0) { ERR("control open (hw:%d): %s\n", card, snd_strerror(err)); continue; } - if((err=snd_ctl_card_info(handle, info)) < 0) + err = snd_ctl_card_info(handle, info); + if(err < 0) { ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err)); - snd_ctl_close(handle); continue; } @@ -321,11 +369,10 @@ al::vector probe_devices(snd_pcm_stream_t stream) name = prefix_name(stream); name += '-'; name += cardid; - const std::string card_prefix{ - ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)}; + const std::string card_prefix{ConfigValueStr({}, "alsa"sv, name).value_or(main_prefix)}; int dev{-1}; - while(1) + while(true) { if(snd_ctl_pcm_next_device(handle, &dev) < 0) ERR("snd_ctl_pcm_next_device failed\n"); @@ -334,7 +381,8 @@ al::vector 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)); @@ -347,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; @@ -367,18 +415,13 @@ al::vector probe_devices(snd_pcm_stream_t stream) device += ",DEV="; device += std::to_string(dev); - devlist.emplace_back(DevMap{std::move(name), std::move(device)}); - const auto &entry = devlist.back(); + const auto &entry = devlist.emplace_back(std::move(name), std::move(device)); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } - snd_ctl_close(handle); } if(err < 0) ERR("snd_card_next failed: %s\n", snd_strerror(err)); - snd_pcm_info_free(pcminfo); - snd_ctl_card_info_free(info); - return devlist; } @@ -387,7 +430,6 @@ int verify_state(snd_pcm_t *handle) { snd_pcm_state_t state{snd_pcm_state(handle)}; - int err; switch(state) { case SND_PCM_STATE_OPEN: @@ -400,13 +442,14 @@ int verify_state(snd_pcm_t *handle) break; case SND_PCM_STATE_XRUN: - if((err=snd_pcm_recover(handle, -EPIPE, 1)) < 0) + if(int err{snd_pcm_recover(handle, -EPIPE, 1)}; err < 0) return err; break; case SND_PCM_STATE_SUSPENDED: - if((err=snd_pcm_recover(handle, -ESTRPIPE, 1)) < 0) + if(int err{snd_pcm_recover(handle, -ESTRPIPE, 1)}; err < 0) return err; break; + case SND_PCM_STATE_DISCONNECTED: return -ENODEV; } @@ -422,7 +465,7 @@ struct AlsaPlayback final : public BackendBase { int mixerProc(); int mixerNoMMapProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -434,12 +477,10 @@ struct AlsaPlayback final : public BackendBase { std::mutex mMutex; uint mFrameStep{}; - al::vector mBuffer; + std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(AlsaPlayback) }; AlsaPlayback::~AlsaPlayback() @@ -453,7 +494,7 @@ AlsaPlayback::~AlsaPlayback() int AlsaPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; @@ -501,7 +542,7 @@ int AlsaPlayback::mixerProc() avail -= avail%update_size; // it is possible that contiguous areas are smaller, thus we use a loop - std::lock_guard _{mMutex}; + std::lock_guard dlock{mMutex}; while(avail > 0) { snd_pcm_uframes_t frames{avail}; @@ -515,6 +556,7 @@ int AlsaPlayback::mixerProc() break; } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ char *WritePtr{static_cast(areas->addr) + (offset * areas->step / 8)}; mDevice->renderSamples(WritePtr, static_cast(frames), mFrameStep); @@ -536,7 +578,7 @@ int AlsaPlayback::mixerProc() int AlsaPlayback::mixerNoMMapProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; @@ -580,13 +622,13 @@ int AlsaPlayback::mixerNoMMapProc() continue; } - al::byte *WritePtr{mBuffer.data()}; + auto WritePtr = mBuffer.begin(); avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast(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) { @@ -621,11 +663,10 @@ int AlsaPlayback::mixerNoMMapProc() } -void AlsaPlayback::open(const char *name) +void AlsaPlayback::open(std::string_view name) { - al::optional driveropt; - const char *driver{"default"}; - if(name) + std::string driver{"default"}; + if(!name.empty()) { if(PlaybackDevices.empty()) PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); @@ -634,22 +675,22 @@ void AlsaPlayback::open(const char *name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; - driver = iter->device_name.c_str(); + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + driver = iter->device_name; } else { - name = alsaDevice; - if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "device")}) - driver = driveropt->c_str(); + name = GetDefaultName(); + if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv)) + driver = std::move(driveropt).value(); } - TRACE("Opening device \"%s\"\n", driver); + TRACE("Opening device \"%s\"\n", driver.c_str()); snd_pcm_t *pcmHandle{}; - int err{snd_pcm_open(&pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; + int err{snd_pcm_open(&pcmHandle, driver.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; if(err < 0) throw al::backend_exception{al::backend_error::NoDevice, - "Could not open ALSA device \"%s\"", driver}; + "Could not open ALSA device \"%s\"", driver.c_str()}; if(mPcmHandle) snd_pcm_close(mPcmHandle); mPcmHandle = pcmHandle; @@ -688,15 +729,14 @@ bool AlsaPlayback::reset() break; } - bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", 1)}; + bool allowmmap{GetConfigValueBool(mDevice->DeviceName, "alsa"sv, "mmap"sv, true)}; uint periodLen{static_cast(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) @@ -711,17 +751,18 @@ bool AlsaPlayback::reset() /* test and set format (implicitly sets sample bits) */ if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) < 0) { - static const struct { + struct FormatMap { snd_pcm_format_t format; DevFmtType fmttype; - } formatlist[] = { - { SND_PCM_FORMAT_FLOAT, DevFmtFloat }, - { SND_PCM_FORMAT_S32, DevFmtInt }, - { SND_PCM_FORMAT_U32, DevFmtUInt }, - { SND_PCM_FORMAT_S16, DevFmtShort }, - { SND_PCM_FORMAT_U16, DevFmtUShort }, - { SND_PCM_FORMAT_S8, DevFmtByte }, - { SND_PCM_FORMAT_U8, DevFmtUByte }, + }; + static constexpr std::array formatlist{ + FormatMap{SND_PCM_FORMAT_FLOAT, DevFmtFloat }, + FormatMap{SND_PCM_FORMAT_S32, DevFmtInt }, + FormatMap{SND_PCM_FORMAT_U32, DevFmtUInt }, + FormatMap{SND_PCM_FORMAT_S16, DevFmtShort }, + FormatMap{SND_PCM_FORMAT_U16, DevFmtUShort}, + FormatMap{SND_PCM_FORMAT_S8, DevFmtByte }, + FormatMap{SND_PCM_FORMAT_U8, DevFmtUByte }, }; for(const auto &fmt : formatlist) @@ -746,7 +787,7 @@ bool AlsaPlayback::reset() else mDevice->FmtChans = DevFmtStereo; } /* set rate (implicitly constrains period/buffer parameters) */ - if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", 0) + if(!GetConfigValueBool(mDevice->DeviceName, "alsa", "allow-resampler", false) || !mDevice->Flags.test(FrequencyRequest)) { if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0) @@ -756,10 +797,10 @@ bool AlsaPlayback::reset() WARN("Failed to enable ALSA resampler\n"); CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr)); /* set period time (implicitly constrains period/buffer parameters) */ - if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 0) + if(int err{snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)}; err < 0) ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err)); /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */ - if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)) < 0) + if(int err{snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)}; err < 0) ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err)); /* install and prepare hardware configuration */ CHECK(snd_pcm_hw_params(mPcmHandle, hp.get())); @@ -794,11 +835,10 @@ bool AlsaPlayback::reset() void AlsaPlayback::start() { - int err{}; snd_pcm_access_t access{}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ - if((err=(x)) < 0) \ + if(int err{x}; err < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ snd_strerror(err)}; \ } while(0) @@ -847,8 +887,8 @@ ClockLatency AlsaPlayback::getClockLatency() { ClockLatency ret; - std::lock_guard _{mMutex}; - ret.ClockTime = GetDeviceClockTime(mDevice); + std::lock_guard dlock{mMutex}; + ret.ClockTime = mDevice->getClockTime(); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) @@ -867,23 +907,21 @@ struct AlsaCapture final : public BackendBase { AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaCapture() override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ClockLatency getClockLatency() override; snd_pcm_t *mPcmHandle{nullptr}; - al::vector mBuffer; + std::vector mBuffer; bool mDoCapture{false}; RingBufferPtr mRing{nullptr}; snd_pcm_sframes_t mLastAvail{0}; - - DEF_NEWDEL(AlsaCapture) }; AlsaCapture::~AlsaCapture() @@ -894,11 +932,10 @@ AlsaCapture::~AlsaCapture() } -void AlsaCapture::open(const char *name) +void AlsaCapture::open(std::string_view name) { - al::optional driveropt; - const char *driver{"default"}; - if(name) + std::string driver{"default"}; + if(!name.empty()) { if(CaptureDevices.empty()) CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); @@ -907,21 +944,20 @@ void AlsaCapture::open(const char *name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; - driver = iter->device_name.c_str(); + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + driver = iter->device_name; } else { - name = alsaDevice; - if(bool{driveropt = ConfigValueStr(nullptr, "alsa", "capture")}) - driver = driveropt->c_str(); + name = GetDefaultName(); + if(auto driveropt = ConfigValueStr({}, "alsa"sv, "capture"sv)) + driver = std::move(driveropt).value(); } - TRACE("Opening device \"%s\"\n", driver); - int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; - if(err < 0) + TRACE("Opening device \"%s\"\n", driver.c_str()); + if(int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; err < 0) throw al::backend_exception{al::backend_error::NoDevice, - "Could not open ALSA device \"%s\"", driver}; + "Could not open ALSA device \"%s\"", driver.c_str()}; /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ snd_config_update_free_global(); @@ -952,13 +988,15 @@ void AlsaCapture::open(const char *name) break; } - snd_pcm_uframes_t bufferSizeInFrames{maxu(mDevice->BufferSize, 100*mDevice->Frequency/1000)}; - snd_pcm_uframes_t periodSizeInFrames{minu(mDevice->BufferSize, 25*mDevice->Frequency/1000)}; + snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->BufferSize, + 100u*mDevice->Frequency/1000u)}; + snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->BufferSize, + 25u*mDevice->Frequency/1000u)}; bool needring{false}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ - if((err=(x)) < 0) \ + if(int err{x}; err < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ snd_strerror(err)}; \ } while(0) @@ -996,13 +1034,11 @@ void AlsaCapture::open(const char *name) void AlsaCapture::start() { - int err{snd_pcm_prepare(mPcmHandle)}; - if(err < 0) + if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0) throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s", snd_strerror(err)}; - err = snd_pcm_start(mPcmHandle); - if(err < 0) + if(int err{snd_pcm_start(mPcmHandle)}; err < 0) throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s", snd_strerror(err)}; @@ -1021,25 +1057,27 @@ void AlsaCapture::stop() /* The ring buffer implicitly captures when checking availability. * Direct access needs to explicitly capture it into temp storage. */ - auto temp = al::vector( + 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) { @@ -1052,20 +1090,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) @@ -1085,12 +1124,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() @@ -1102,7 +1141,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); @@ -1138,7 +1178,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); @@ -1167,7 +1208,7 @@ ClockLatency AlsaCapture::getClockLatency() { ClockLatency ret; - ret.ClockTime = GetDeviceClockTime(mDevice); + ret.ClockTime = mDevice->getClockTime(); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) @@ -1186,13 +1227,9 @@ ClockLatency AlsaCapture::getClockLatency() bool AlsaBackendFactory::init() { - bool error{false}; - #ifdef HAVE_DYNLOAD if(!alsa_handle) { - std::string missing_funcs; - alsa_handle = LoadLib("libasound.so.2"); if(!alsa_handle) { @@ -1200,27 +1237,25 @@ bool AlsaBackendFactory::init() return false; } - error = false; + std::string missing_funcs; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(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) diff --git a/libs/openal-soft/alc/backends/base.cpp b/libs/openal-soft/alc/backends/base.cpp index cd1b76ba..e5e28201 100644 --- a/libs/openal-soft/alc/backends/base.cpp +++ b/libs/openal-soft/alc/backends/base.cpp @@ -14,17 +14,32 @@ #include "albit.h" #include "core/logging.h" -#include "aloptional.h" #endif #include "atomic.h" #include "core/devformat.h" +namespace al { + +backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code} +{ + /* NOLINTBEGIN(*-array-to-pointer-decay) */ + std::va_list args; + va_start(args, msg); + setMessage(msg, args); + va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ +} +backend_exception::~backend_exception() = default; + +} // namespace al + + bool BackendBase::reset() { throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; } -void BackendBase::captureSamples(al::byte*, uint) +void BackendBase::captureSamples(std::byte*, uint) { } uint BackendBase::availableSamples() @@ -32,29 +47,28 @@ uint BackendBase::availableSamples() ClockLatency BackendBase::getClockLatency() { - ClockLatency ret; + ClockLatency ret{}; uint refcount; do { refcount = mDevice->waitForMix(); - ret.ClockTime = GetDeviceClockTime(mDevice); + ret.ClockTime = mDevice->getClockTime(); std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != ReadRef(mDevice->MixCount)); + } while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed)); /* NOTE: The device will generally have about all but one periods filled at * any given time during playback. Without a more accurate measurement from * the output, this is an okay approximation. */ - ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize}, - std::chrono::seconds::zero()); + ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; return ret; } -void BackendBase::setDefaultWFXChannelOrder() +void BackendBase::setDefaultWFXChannelOrder() const { - mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); + mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); switch(mDevice->FmtChans) { @@ -98,14 +112,38 @@ void BackendBase::setDefaultWFXChannelOrder() mDevice->RealOut.ChannelIndex[SideLeft] = 6; mDevice->RealOut.ChannelIndex[SideRight] = 7; break; + case DevFmtX714: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[BackLeft] = 4; + mDevice->RealOut.ChannelIndex[BackRight] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; + mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; + mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; + mDevice->RealOut.ChannelIndex[TopBackRight] = 11; + break; + case DevFmtX3D71: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[Aux0] = 4; + mDevice->RealOut.ChannelIndex[Aux1] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + break; case DevFmtAmbi3D: break; } } -void BackendBase::setDefaultChannelOrder() +void BackendBase::setDefaultChannelOrder() const { - mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX); + mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); switch(mDevice->FmtChans) { @@ -127,6 +165,30 @@ void BackendBase::setDefaultChannelOrder() mDevice->RealOut.ChannelIndex[SideLeft] = 6; mDevice->RealOut.ChannelIndex[SideRight] = 7; return; + case DevFmtX714: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[BackLeft] = 2; + mDevice->RealOut.ChannelIndex[BackRight] = 3; + mDevice->RealOut.ChannelIndex[FrontCenter] = 4; + mDevice->RealOut.ChannelIndex[LFE] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; + mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; + mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; + mDevice->RealOut.ChannelIndex[TopBackRight] = 11; + break; + case DevFmtX3D71: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[Aux0] = 2; + mDevice->RealOut.ChannelIndex[Aux1] = 3; + mDevice->RealOut.ChannelIndex[FrontCenter] = 4; + mDevice->RealOut.ChannelIndex[LFE] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + return; /* Same as WFX order */ case DevFmtMono: @@ -138,57 +200,3 @@ void BackendBase::setDefaultChannelOrder() break; } } - -#ifdef _WIN32 -void BackendBase::setChannelOrderFromWFXMask(uint chanmask) -{ - static constexpr uint x51{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER - | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT}; - static constexpr uint x51rear{SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER - | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT}; - /* Swap a 5.1 mask using the back channels for one with the sides. */ - if(chanmask == x51rear) chanmask = x51; - - auto get_channel = [](const DWORD chanbit) noexcept -> al::optional - { - switch(chanbit) - { - case SPEAKER_FRONT_LEFT: return al::make_optional(FrontLeft); - case SPEAKER_FRONT_RIGHT: return al::make_optional(FrontRight); - case SPEAKER_FRONT_CENTER: return al::make_optional(FrontCenter); - case SPEAKER_LOW_FREQUENCY: return al::make_optional(LFE); - case SPEAKER_BACK_LEFT: return al::make_optional(BackLeft); - case SPEAKER_BACK_RIGHT: return al::make_optional(BackRight); - case SPEAKER_FRONT_LEFT_OF_CENTER: break; - case SPEAKER_FRONT_RIGHT_OF_CENTER: break; - case SPEAKER_BACK_CENTER: return al::make_optional(BackCenter); - case SPEAKER_SIDE_LEFT: return al::make_optional(SideLeft); - case SPEAKER_SIDE_RIGHT: return al::make_optional(SideRight); - case SPEAKER_TOP_CENTER: return al::make_optional(TopCenter); - case SPEAKER_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft); - case SPEAKER_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter); - case SPEAKER_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight); - case SPEAKER_TOP_BACK_LEFT: return al::make_optional(TopBackLeft); - case SPEAKER_TOP_BACK_CENTER: return al::make_optional(TopBackCenter); - case SPEAKER_TOP_BACK_RIGHT: return al::make_optional(TopBackRight); - } - WARN("Unhandled WFX channel bit 0x%lx\n", chanbit); - return al::nullopt; - }; - - const uint numchans{mDevice->channelsFromFmt()}; - uint idx{0}; - while(chanmask) - { - const int bit{al::countr_zero(chanmask)}; - const uint mask{1u << bit}; - chanmask &= ~mask; - - if(auto label = get_channel(mask)) - { - mDevice->RealOut.ChannelIndex[*label] = idx; - if(++idx == numchans) break; - } - } -} -#endif diff --git a/libs/openal-soft/alc/backends/base.h b/libs/openal-soft/alc/backends/base.h index a3562f54..b575e166 100644 --- a/libs/openal-soft/alc/backends/base.h +++ b/libs/openal-soft/alc/backends/base.h @@ -3,13 +3,15 @@ #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,13 +22,13 @@ struct ClockLatency { }; struct BackendBase { - virtual void open(const char *name) = 0; + virtual void open(std::string_view name) = 0; virtual bool reset(); virtual void start() = 0; virtual void stop() = 0; - virtual void captureSamples(al::byte *buffer, uint samples); + virtual void captureSamples(std::byte *buffer, uint samples); virtual uint availableSamples(); virtual ClockLatency getClockLatency(); @@ -38,14 +40,9 @@ struct BackendBase { protected: /** Sets the default channel order used by most non-WaveFormatEx-based APIs. */ - void setDefaultChannelOrder(); + void setDefaultChannelOrder() const; /** Sets the default channel order used by WaveFormatEx. */ - void setDefaultWFXChannelOrder(); - -#ifdef _WIN32 - /** Sets the channel order given the WaveFormatEx mask. */ - void setChannelOrderFromWFXMask(uint chanmask); -#endif + void setDefaultWFXChannelOrder() const; }; using BackendPtr = std::unique_ptr; @@ -55,18 +52,6 @@ enum class BackendType { }; -/* Helper to get the current clock time from the device's ClockBase, and - * SamplesDone converted from the sample rate. - */ -inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device) -{ - using std::chrono::seconds; - using std::chrono::nanoseconds; - - auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency; - return device->ClockBase + ns; -} - /* Helper to get the device latency from the backend, including any fixed * latency from post-processing. */ @@ -79,16 +64,18 @@ inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend) struct BackendFactory { + virtual ~BackendFactory() = default; + virtual bool init() = 0; virtual bool querySupport(BackendType type) = 0; + virtual alc::EventSupport queryEventSupport(alc::EventType, BackendType) + { return alc::EventSupport::NoSupport; } + virtual std::string probe(BackendType type) = 0; virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0; - -protected: - virtual ~BackendFactory() = default; }; namespace al { @@ -103,19 +90,15 @@ class backend_exception final : public base_exception { backend_error mErrorCode; public: -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf, 3, 4)]] +#ifdef __MINGW32__ + [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] #else [[gnu::format(printf, 3, 4)]] #endif - backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code} - { - std::va_list args; - va_start(args, msg); - setMessage(msg, args); - va_end(args); - } - backend_error errorCode() const noexcept { return mErrorCode; } + backend_exception(backend_error code, const char *msg, ...); + ~backend_exception() override; + + [[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; } }; } // namespace al diff --git a/libs/openal-soft/alc/backends/coreaudio.cpp b/libs/openal-soft/alc/backends/coreaudio.cpp index ed85e2a9..2067d279 100644 --- a/libs/openal-soft/alc/backends/coreaudio.cpp +++ b/libs/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,15 +44,39 @@ #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 { @@ -145,7 +171,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) &propSize); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err); + ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return 0; } @@ -156,7 +183,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) buflist); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err); + ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return 0; } @@ -180,7 +208,7 @@ void EnumerateDevices(std::vector &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; } @@ -245,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"; @@ -258,15 +328,8 @@ struct CoreAudioPlayback final : public BackendBase { OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept; - static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData) noexcept - { - return static_cast(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); - } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -275,8 +338,6 @@ struct CoreAudioPlayback final : public BackendBase { uint mFrameSize{0u}; AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD - - DEF_NEWDEL(CoreAudioPlayback) }; CoreAudioPlayback::~CoreAudioPlayback() @@ -299,11 +360,11 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi } -void CoreAudioPlayback::open(const char *name) +void CoreAudioPlayback::open(std::string_view name) { #if CAN_ENUMERATE AudioDeviceID audioDevice{kAudioDeviceUnknown}; - if(!name) + if(name.empty()) GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice), &audioDevice); else @@ -316,16 +377,16 @@ void CoreAudioPlayback::open(const char *name) auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name); if(devmatch == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; audioDevice = devmatch->mId; } #else - if(!name) + if(name.empty()) name = ca_device; - else if(strcmp(name, ca_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != ca_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; #endif /* open the default output unit */ @@ -349,18 +410,18 @@ void CoreAudioPlayback::open(const char *name) OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: %u", err}; + "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; #if CAN_ENUMERATE if(audioDevice != kAudioDeviceUnknown) AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID)); + kAudioUnitScope_Global, OutputElement, &audioDevice, sizeof(AudioDeviceID)); #endif err = AudioUnitInitialize(audioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not initialize audio unit: %u", err}; + "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; /* WARNING: I don't know if "valid" audio unit values are guaranteed to be * non-0. If not, this logic is broken. @@ -373,19 +434,34 @@ void CoreAudioPlayback::open(const char *name) mAudioUnit = audioUnit; #if CAN_ENUMERATE - if(name) + if(!name.empty()) mDevice->DeviceName = name; else { UInt32 propSize{sizeof(audioDevice)}; audioDevice = kAudioDeviceUnknown; AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, 0, &audioDevice, &propSize); + kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize); std::string devname{GetDeviceName(audioDevice)}; if(!devname.empty()) mDevice->DeviceName = std::move(devname); else mDevice->DeviceName = "Unknown Device Name"; } + + if(audioDevice != kAudioDeviceUnknown) + { + UInt32 type{}; + err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false, + kAudioObjectPropertyElementMaster, sizeof(type), &type); + if(err != noErr) + ERR("Failed to get audio device type: %u\n", err); + else + { + TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str()); + mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones)); + } + } + #else mDevice->DeviceName = name; #endif @@ -395,16 +471,17 @@ bool CoreAudioPlayback::reset() { OSStatus err{AudioUnitUninitialize(mAudioUnit)}; if(err != noErr) - ERR("-- AudioUnitUninitialize failed.\n"); + ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); /* retrieve default output unit's properties (output side) */ AudioStreamBasicDescription streamFormat{}; UInt32 size{sizeof(streamFormat)}; err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, - 0, &streamFormat, &size); + OutputElement, &streamFormat, &size); if(err != noErr || size != sizeof(streamFormat)) { - ERR("AudioUnitGetProperty failed\n"); + ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + err); return false; } @@ -423,8 +500,8 @@ bool CoreAudioPlayback::reset() */ if(mDevice->Frequency != streamFormat.mSampleRate) { - mDevice->BufferSize = static_cast(uint64_t{mDevice->BufferSize} * - streamFormat.mSampleRate / mDevice->Frequency); + mDevice->BufferSize = static_cast(mDevice->BufferSize*streamFormat.mSampleRate/ + mDevice->Frequency + 0.5); mDevice->Frequency = static_cast(streamFormat.mSampleRate); } @@ -468,10 +545,11 @@ bool CoreAudioPlayback::reset() streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 0, &streamFormat, sizeof(streamFormat)); + OutputElement, &streamFormat, sizeof(streamFormat)); if(err != noErr) { - ERR("AudioUnitSetProperty failed\n"); + ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + err); return false; } @@ -480,14 +558,16 @@ bool CoreAudioPlayback::reset() /* setup callback */ mFrameSize = mDevice->frameSizeFromFmt(); AURenderCallbackStruct input{}; - input.inputProc = CoreAudioPlayback::MixerProcC; + input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept + { return static_cast(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); }; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct)); + kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) { - ERR("AudioUnitSetProperty failed\n"); + ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return false; } @@ -495,7 +575,7 @@ bool CoreAudioPlayback::reset() err = AudioUnitInitialize(mAudioUnit); if(err != noErr) { - ERR("AudioUnitInitialize failed\n"); + ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); return false; } @@ -507,14 +587,14 @@ void CoreAudioPlayback::start() const OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: %d", err}; + "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; } void CoreAudioPlayback::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); + ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); } @@ -525,18 +605,11 @@ struct CoreAudioCapture final : public BackendBase { OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept; - static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData) noexcept - { - return static_cast(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); - } - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; AudioUnit mAudioUnit{0}; @@ -546,9 +619,9 @@ struct CoreAudioCapture final : public BackendBase { SampleConverterPtr mConverter; - RingBufferPtr mRing{nullptr}; + std::vector mCaptureData; - DEF_NEWDEL(CoreAudioCapture) + RingBufferPtr mRing{nullptr}; }; CoreAudioCapture::~CoreAudioCapture() @@ -559,58 +632,38 @@ CoreAudioCapture::~CoreAudioCapture() } -OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*, - const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames, +OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList*) noexcept { - AudioUnitRenderActionFlags flags = 0; union { - al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2]; + std::byte buf[std::max(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))]; AudioBufferList list; } audiobuf{}; - auto rec_vec = mRing->getWriteVector(); - inNumberFrames = static_cast(minz(inNumberFrames, - rec_vec.first.len+rec_vec.second.len)); + audiobuf.list.mNumberBuffers = 1; + audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame; + audiobuf.list.mBuffers[0].mData = mCaptureData.data(); + audiobuf.list.mBuffers[0].mDataByteSize = static_cast(mCaptureData.size()); - // Fill the ringbuffer's two segments with data from the input device - if(rec_vec.first.len >= inNumberFrames) - { - audiobuf.list.mNumberBuffers = 1; - audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame; - audiobuf.list.mBuffers[0].mData = rec_vec.first.buf; - audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame; - } - else - { - const auto remaining = static_cast(inNumberFrames - rec_vec.first.len); - audiobuf.list.mNumberBuffers = 2; - audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame; - audiobuf.list.mBuffers[0].mData = rec_vec.first.buf; - audiobuf.list.mBuffers[0].mDataByteSize = static_cast(rec_vec.first.len) * - mFormat.mBytesPerFrame; - audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame; - audiobuf.list.mBuffers[1].mData = rec_vec.second.buf; - audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame; - } - OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers, + OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &audiobuf.list)}; if(err != noErr) { - ERR("AudioUnitRender error: %d\n", err); + ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); return err; } - mRing->writeAdvance(inNumberFrames); + std::ignore = mRing->write(mCaptureData.data(), inNumberFrames); return noErr; } -void CoreAudioCapture::open(const char *name) +void CoreAudioCapture::open(std::string_view name) { #if CAN_ENUMERATE AudioDeviceID audioDevice{kAudioDeviceUnknown}; - if(!name) + if(name.empty()) GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice), &audioDevice); else @@ -623,16 +676,16 @@ void CoreAudioCapture::open(const char *name) auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name); if(devmatch == CaptureList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; audioDevice = devmatch->mId; } #else - if(!name) + if(name.empty()) name = ca_device; - else if(strcmp(name, ca_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != ca_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; #endif AudioComponentDescription desc{}; @@ -656,63 +709,67 @@ void CoreAudioCapture::open(const char *name) OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: %u", err}; - -#if CAN_ENUMERATE - if(audioDevice != kAudioDeviceUnknown) - AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, 0, &audioDevice, sizeof(AudioDeviceID)); -#endif + "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Turn off AudioUnit output UInt32 enableIO{0}; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO)); + kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable audio unit output property: %u", err}; + "Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; // Turn on AudioUnit input enableIO = 1; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Input, 1, &enableIO, sizeof(enableIO)); + kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not enable audio unit input property: %u", err}; + "Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; + +#if CAN_ENUMERATE + if(audioDevice != kAudioDeviceUnknown) + AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID)); +#endif // set capture callback AURenderCallbackStruct input{}; - input.inputProc = CoreAudioCapture::RecordProcC; + input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept + { return static_cast(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); }; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback, - kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct)); + kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set capture callback: %u", err}; + "Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Disable buffer allocation for capture UInt32 flag{0}; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer, - kAudioUnitScope_Output, 1, &flag, sizeof(flag)); + kAudioUnitScope_Output, InputElement, &flag, sizeof(flag)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable buffer allocation property: %u", err}; + "Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; // Initialize the device err = AudioUnitInitialize(mAudioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not initialize audio unit: %u", err}; + "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Get the hardware format AudioStreamBasicDescription hardwareFormat{}; UInt32 propertySize{sizeof(hardwareFormat)}; err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, - 1, &hardwareFormat, &propertySize); + InputElement, &hardwareFormat, &propertySize); if(err != noErr || propertySize != sizeof(hardwareFormat)) throw al::backend_exception{al::backend_error::DeviceError, - "Could not get input format: %u", err}; + "Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Set up the requested format description AudioStreamBasicDescription requestedFormat{}; @@ -764,6 +821,8 @@ void CoreAudioCapture::open(const char *name) case DevFmtX51: case DevFmtX61: case DevFmtX71: + case DevFmtX714: + case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s not supported", DevFmtChannelsString(mDevice->FmtChans)}; @@ -788,17 +847,17 @@ void CoreAudioCapture::open(const char *name) // The output format should be the requested format, but using the hardware sample rate // This is because the AudioUnit will automatically scale other properties, except for sample rate err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, - 1, &outputFormat, sizeof(outputFormat)); + InputElement, &outputFormat, sizeof(outputFormat)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set input format: %u", err}; + "Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; /* Calculate the minimum AudioUnit output format frame count for the pre- * conversion ring buffer. Ensure at least 100ms for the total buffer. */ - double srateScale{double{outputFormat.mSampleRate} / mDevice->Frequency}; - auto FrameCount64 = maxu64(static_cast(std::ceil(mDevice->BufferSize*srateScale)), - static_cast(outputFormat.mSampleRate)/10); + double srateScale{outputFormat.mSampleRate / mDevice->Frequency}; + 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, @@ -807,29 +866,31 @@ void CoreAudioCapture::open(const char *name) UInt32 outputFrameCount{}; propertySize = sizeof(outputFrameCount); err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, - kAudioUnitScope_Global, 0, &outputFrameCount, &propertySize); + kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize); if(err != noErr || propertySize != sizeof(outputFrameCount)) throw al::backend_exception{al::backend_error::DeviceError, - "Could not get input frame count: %u", err}; + "Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; - outputFrameCount = static_cast(maxu64(outputFrameCount, FrameCount64)); + mCaptureData.resize(outputFrameCount * mFrameSize); + + outputFrameCount = static_cast(std::max(uint64_t{outputFrameCount}, FrameCount64)); mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false); /* Set up sample converter if needed */ if(outputFormat.mSampleRate != mDevice->Frequency) - mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType, + mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, mFormat.mChannelsPerFrame, static_cast(hardwareFormat.mSampleRate), mDevice->Frequency, Resampler::FastBSinc24); #if CAN_ENUMERATE - if(name) + if(!name.empty()) mDevice->DeviceName = name; else { UInt32 propSize{sizeof(audioDevice)}; audioDevice = kAudioDeviceUnknown; AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, 0, &audioDevice, &propSize); + kAudioUnitScope_Global, InputElement, &audioDevice, &propSize); std::string devname{GetDeviceName(audioDevice)}; if(!devname.empty()) mDevice->DeviceName = std::move(devname); @@ -846,21 +907,21 @@ void CoreAudioCapture::start() OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: %d", err}; + "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; } void CoreAudioCapture::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); + ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); } -void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples) +void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples) { if(!mConverter) { - mRing->read(buffer, samples); + std::ignore = mRing->read(buffer, samples); return; } @@ -894,7 +955,13 @@ BackendFactory &CoreAudioBackendFactory::getFactory() return factory; } -bool CoreAudioBackendFactory::init() { return true; } +bool CoreAudioBackendFactory::init() +{ +#if CAN_ENUMERATE + sDeviceHelper.emplace(); +#endif + return true; +} bool CoreAudioBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } @@ -942,3 +1009,18 @@ BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendTyp return BackendPtr{new CoreAudioCapture{device}}; return nullptr; } + +alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DefaultDeviceChanged: + return alc::EventSupport::FullSupport; + + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/libs/openal-soft/alc/backends/coreaudio.h b/libs/openal-soft/alc/backends/coreaudio.h index 1252edde..6ea4307c 100644 --- a/libs/openal-soft/alc/backends/coreaudio.h +++ b/libs/openal-soft/alc/backends/coreaudio.h @@ -9,6 +9,8 @@ public: bool querySupport(BackendType type) override; + alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + std::string probe(BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override; diff --git a/libs/openal-soft/alc/backends/dsound.cpp b/libs/openal-soft/alc/backends/dsound.cpp index 0edc286f..82a59dbe 100644 --- a/libs/openal-soft/alc/backends/dsound.cpp +++ b/libs/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,23 @@ #include #endif +#include #include #include -#include -#include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include "albit.h" #include "alnumeric.h" +#include "alspan.h" +#include "alstring.h" +#include "althrd_setname.h" #include "comptr.h" #include "core/device.h" #include "core/helpers.h" @@ -52,7 +56,6 @@ #include "dynload.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" /* MinGW-w64 needs this for some unknown reason now. */ using LPCWAVEFORMATEX = const WAVEFORMATEX*; @@ -115,6 +118,7 @@ HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallbac #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT) #define MAX_UPDATES 128 @@ -128,10 +132,10 @@ struct DevMap { { } }; -al::vector 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; }; @@ -143,7 +147,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}; @@ -175,7 +179,7 @@ struct DSoundPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -188,8 +192,6 @@ struct DSoundPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(DSoundPlayback) }; DSoundPlayback::~DSoundPlayback() @@ -208,7 +210,7 @@ DSoundPlayback::~DSoundPlayback() FORCE_ALIGN int DSoundPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); DSBCAPS DSBCaps{}; DSBCaps.dwSize = sizeof(DSBCaps); @@ -298,24 +300,22 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() return 0; } -void DSoundPlayback::open(const char *name) +void DSoundPlayback::open(std::string_view name) { HRESULT hr; if(PlaybackDevices.empty()) { /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; + ComWrapper com{}; hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); if(FAILED(hr)) ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); } const GUID *guid{nullptr}; - if(!name && !PlaybackDevices.empty()) + if(name.empty() && !PlaybackDevices.empty()) { - name = PlaybackDevices[0].name.c_str(); + name = PlaybackDevices[0].name; guid = &PlaybackDevices[0].guid; } else @@ -331,7 +331,7 @@ void DSoundPlayback::open(const char *name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; } guid = &iter->guid; } @@ -346,7 +346,7 @@ void DSoundPlayback::open(const char *name) //DirectSound Init code ComPtr ds; if(SUCCEEDED(hr)) - hr = DirectSoundCreate(guid, ds.getPtr(), nullptr); + hr = DirectSoundCreate(guid, al::out_ptr(ds), nullptr); if(SUCCEEDED(hr)) hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); if(FAILED(hr)) @@ -389,52 +389,56 @@ bool DSoundPlayback::reset() } WAVEFORMATEXTENSIBLE OutputType{}; - DWORD speakers; + DWORD speakers{}; HRESULT hr{mDS->GetSpeakerConfig(&speakers)}; - if(SUCCEEDED(hr)) - { - speakers = DSSPEAKER_CONFIG(speakers); - if(!mDevice->Flags.test(ChannelsRequest)) - { - if(speakers == DSSPEAKER_MONO) - mDevice->FmtChans = DevFmtMono; - else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE) - mDevice->FmtChans = DevFmtStereo; - else if(speakers == DSSPEAKER_QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK) - mDevice->FmtChans = DevFmtX51; - else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND) - mDevice->FmtChans = DevFmtX71; - else - ERR("Unknown system speaker config: 0x%lx\n", speakers); - } - mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE)); + if(FAILED(hr)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to get speaker config: 0x%08lx", hr}; - switch(mDevice->FmtChans) - { - case DevFmtMono: OutputType.dwChannelMask = MONO; break; - case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo; - /*fall-through*/ - case DevFmtStereo: OutputType.dwChannelMask = STEREO; break; - case DevFmtQuad: OutputType.dwChannelMask = QUAD; break; - case DevFmtX51: OutputType.dwChannelMask = X5DOT1; break; - case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break; - case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break; - } + speakers = DSSPEAKER_CONFIG(speakers); + if(!mDevice->Flags.test(ChannelsRequest)) + { + if(speakers == DSSPEAKER_MONO) + mDevice->FmtChans = DevFmtMono; + else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE) + mDevice->FmtChans = DevFmtStereo; + else if(speakers == DSSPEAKER_QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK) + mDevice->FmtChans = DevFmtX51; + else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND) + mDevice->FmtChans = DevFmtX71; + else + ERR("Unknown system speaker config: 0x%lx\n", speakers); + } + mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE)); + const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK}; + + switch(mDevice->FmtChans) + { + case DevFmtMono: OutputType.dwChannelMask = MONO; break; + case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo; + /* fall-through */ + case DevFmtStereo: OutputType.dwChannelMask = STEREO; break; + case DevFmtQuad: OutputType.dwChannelMask = QUAD; break; + case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break; + case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break; + case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break; + case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break; + case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break; + } retry_open: - hr = S_OK; - OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.nChannels = static_cast(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; - } + 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) { @@ -455,7 +459,7 @@ retry_open: DSBUFFERDESC DSBDescription{}; DSBDescription.dwSize = sizeof(DSBDescription); DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; - hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr); + hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr); } if(SUCCEEDED(hr)) hr = mPrimaryBuffer->SetFormat(&OutputType.Format); @@ -475,7 +479,7 @@ retry_open: DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; DSBDescription.lpwfxFormat = &OutputType.Format; - hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr); + hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr); if(FAILED(hr) && mDevice->FmtType == DevFmtFloat) { mDevice->FmtType = DevFmtShort; @@ -485,12 +489,9 @@ retry_open: if(SUCCEEDED(hr)) { - void *ptr; - hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr); + hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies)); if(SUCCEEDED(hr)) { - mNotifies = ComPtr{static_cast(ptr)}; - uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; assert(num_updates <= MAX_UPDATES); @@ -514,7 +515,7 @@ retry_open: } ResetEvent(mNotifyEvent); - setChannelOrderFromWFXMask(OutputType.dwChannelMask); + setDefaultWFXChannelOrder(); return true; } @@ -545,10 +546,10 @@ struct DSoundCapture final : public BackendBase { DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundCapture() override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ComPtr mDSC; @@ -557,8 +558,6 @@ struct DSoundCapture final : public BackendBase { DWORD mCursor{0u}; RingBufferPtr mRing; - - DEF_NEWDEL(DSoundCapture) }; DSoundCapture::~DSoundCapture() @@ -572,24 +571,22 @@ DSoundCapture::~DSoundCapture() } -void DSoundCapture::open(const char *name) +void DSoundCapture::open(std::string_view name) { HRESULT hr; if(CaptureDevices.empty()) { /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; + ComWrapper com{}; hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); if(FAILED(hr)) ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); } const GUID *guid{nullptr}; - if(!name && !CaptureDevices.empty()) + if(name.empty() && !CaptureDevices.empty()) { - name = CaptureDevices[0].name.c_str(); + name = CaptureDevices[0].name; guid = &CaptureDevices[0].guid; } else @@ -605,7 +602,7 @@ void DSoundCapture::open(const char *name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; } guid = &iter->guid; } @@ -635,6 +632,8 @@ void DSoundCapture::open(const char *name) case DevFmtX51: InputType.dwChannelMask = X5DOT1; break; case DevFmtX61: InputType.dwChannelMask = X6DOT1; break; case DevFmtX71: InputType.dwChannelMask = X7DOT1; break; + case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break; + case DevFmtX3D71: case DevFmtAmbi3D: WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans)); throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", @@ -662,8 +661,7 @@ void DSoundCapture::open(const char *name) InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); } - uint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); + const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; DSCBUFFERDESC DSCBDescription{}; DSCBDescription.dwSize = sizeof(DSCBDescription); @@ -672,9 +670,9 @@ void DSoundCapture::open(const char *name) DSCBDescription.lpwfxFormat = &InputType.Format; //DirectSoundCapture Init code - hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr); + hr = DirectSoundCaptureCreate(guid, al::out_ptr(mDSC), nullptr); if(SUCCEEDED(hr)) - mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr); + mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr); if(SUCCEEDED(hr)) mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false); @@ -689,7 +687,7 @@ void DSoundCapture::open(const char *name) } mBufferBytes = DSCBDescription.dwBufferBytes; - setChannelOrderFromWFXMask(InputType.dwChannelMask); + setDefaultWFXChannelOrder(); mDevice->DeviceName = name; } @@ -712,8 +710,8 @@ void DSoundCapture::stop() } } -void DSoundCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void DSoundCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint DSoundCapture::availableSamples() { @@ -736,9 +734,9 @@ uint DSoundCapture::availableSamples() } if(SUCCEEDED(hr)) { - mRing->write(ReadPtr1, ReadCnt1/FrameSize); + std::ignore = mRing->write(ReadPtr1, ReadCnt1/FrameSize); if(ReadPtr2 != nullptr && ReadCnt2 > 0) - mRing->write(ReadPtr2, ReadCnt2/FrameSize); + std::ignore = mRing->write(ReadPtr2, ReadCnt2/FrameSize); hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2); mCursor = ReadCursor; } @@ -807,8 +805,8 @@ std::string DSoundBackendFactory::probe(BackendType type) }; /* Initialize COM to prevent name truncation */ + ComWrapper com{}; HRESULT hr; - HRESULT hrcom{CoInitialize(nullptr)}; switch(type) { case BackendType::Playback: @@ -827,8 +825,6 @@ std::string DSoundBackendFactory::probe(BackendType type) std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } - if(SUCCEEDED(hrcom)) - CoUninitialize(); return outnames; } diff --git a/libs/openal-soft/alc/backends/jack.cpp b/libs/openal-soft/alc/backends/jack.cpp index 2b0131b3..0e972500 100644 --- a/libs/openal-soft/alc/backends/jack.cpp +++ b/libs/openal-soft/alc/backends/jack.cpp @@ -22,23 +22,27 @@ #include "jack.h" +#include #include #include #include #include - -#include +#include #include #include +#include +#include "albit.h" #include "alc/alconfig.h" #include "alnumeric.h" +#include "alsem.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" -#include "threads.h" #include #include @@ -46,6 +50,8 @@ namespace { +using namespace std::string_view_literals; + #ifdef HAVE_DYNLOAD #define JACK_FUNCS(MAGIC) \ MAGIC(jack_client_open); \ @@ -99,19 +105,13 @@ decltype(jack_error_callback) * pjack_error_callback; #endif -constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE; - jack_options_t ClientOptions = JackNullOption; bool jack_load() { - bool error{false}; - #ifdef HAVE_DYNLOAD if(!jack_handle) { - std::string missing_funcs; - #ifdef _WIN32 #define JACKLIB "libjack.dll" #else @@ -124,13 +124,10 @@ bool jack_load() return false; } - error = false; + std::string missing_funcs; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(jack_handle, #f)); \ - if(p##f == nullptr) { \ - error = true; \ - missing_funcs += "\n" #f; \ - } \ + if(p##f == nullptr) missing_funcs += "\n" #f; \ } while(0) JACK_FUNCS(LOAD_FUNC); #undef LOAD_FUNC @@ -139,56 +136,58 @@ bool jack_load() LOAD_SYM(jack_error_callback); #undef LOAD_SYM - if(error) + if(!missing_funcs.empty()) { WARN("Missing expected functions:%s\n", missing_funcs.c_str()); CloseLib(jack_handle); jack_handle = nullptr; + return false; } } #endif - return !error; + return true; } struct JackDeleter { void operator()(void *ptr) { jack_free(ptr); } }; -using JackPortsPtr = std::unique_ptr; +using JackPortsPtr = std::unique_ptr; /* NOLINT(*-avoid-c-arrays) */ struct DeviceEntry { std::string mName; std::string mPattern; + + template + DeviceEntry(T&& name, U&& pattern) + : mName{std::forward(name)}, mPattern{std::forward(pattern)} + { } }; -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 std::string_view portdev{ports[i], seppos}; auto check_name = [portdev](const DeviceEntry &entry) -> bool - { - const size_t len{portdev.size()}; - return entry.mName.length() == len - && entry.mName.compare(0, len, portdev.data(), len) == 0; - }; + { return entry.mName == portdev; }; if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend()) continue; - std::string name{portdev.data(), portdev.size()}; - list.emplace_back(DeviceEntry{name, name+":"}); - const auto &entry = list.back(); + const auto &entry = list.emplace_back(portdev, std::string{portdev}+":"); TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); } /* There are ports but couldn't get device names from them. Add a @@ -197,11 +196,11 @@ void EnumerateDevices(jack_client_t *client, al::vector &list) if(ports[0] && list.empty()) { WARN("No device names found in available ports, adding a generic name.\n"); - list.emplace_back(DeviceEntry{"JACK", ""}); + list.emplace_back("JACK"sv, ""sv); } } - if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices")) + if(auto listopt = ConfigValueStr({}, "jack", "custom-devices")) { for(size_t strpos{0};strpos < listopt->size();) { @@ -216,31 +215,25 @@ void EnumerateDevices(jack_client_t *client, al::vector &list) 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(DeviceEntry{std::string{name.data(), name.size()}, - std::string{pattern.data(), pattern.size()}}); - const auto &entry = list.back(); + const auto &entry = list.emplace_back(name, pattern); TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); } @@ -290,7 +283,7 @@ struct JackPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -299,7 +292,7 @@ struct JackPlayback final : public BackendBase { std::string mPortPattern; jack_client_t *mClient{nullptr}; - std::array mPort{}; + std::array mPort{}; std::mutex mMutex; @@ -310,8 +303,6 @@ struct JackPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(JackPlayback) }; JackPlayback::~JackPlayback() @@ -331,7 +322,7 @@ JackPlayback::~JackPlayback() int JackPlayback::processRt(jack_nframes_t numframes) noexcept { - std::array out; + std::array out; size_t numchans{0}; for(auto port : mPort) { @@ -340,7 +331,7 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept out[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); } - if LIKELY(mPlaying.load(std::memory_order_acquire)) + if(mPlaying.load(std::memory_order_acquire)) LIKELY mDevice->renderSamples({out.data(), numchans}, static_cast(numframes)); else { @@ -355,52 +346,50 @@ 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}; - if LIKELY(mPlaying.load(std::memory_order_acquire)) + if(mPlaying.load(std::memory_order_acquire)) LIKELY { auto data = mRing->getReadVector(); - jack_nframes_t todo{minu(numframes, static_cast(data.first.len))}; - auto write_first = [&data,numchans,todo](float *outbuf) -> float* + jack_nframes_t todo{std::min(numframes, static_cast(data.first.len))}; + auto firstin = al::span{reinterpret_cast(data.first.buf), todo}; + for(size_t c{0};c < numchans;++c) { - const float *RESTRICT in = reinterpret_cast(data.first.buf); - auto deinterlace_input = [&in,numchans]() noexcept -> float + auto in = firstin.cbegin(); + auto deinterlace_input = [&in,c,numchans]() noexcept -> float { - float ret{*in}; - in += numchans; + const float ret{in[c]}; + in += ptrdiff_t(numchans); return ret; }; - std::generate_n(outbuf, todo, deinterlace_input); - data.first.buf += sizeof(float); - return outbuf + todo; - }; - std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first); + std::generate_n(out[c].begin(), todo, deinterlace_input); + out[c] = out[c].subspan(todo); + } total += todo; - todo = minu(numframes-total, static_cast(data.second.len)); + todo = std::min(numframes-total, static_cast(data.second.len)); if(todo > 0) { - auto write_second = [&data,numchans,todo](float *outbuf) -> float* + auto secondin = al::span{reinterpret_cast(data.second.buf), todo}; + for(size_t c{0};c < numchans;++c) { - const float *RESTRICT in = reinterpret_cast(data.second.buf); - auto deinterlace_input = [&in,numchans]() noexcept -> float + auto in = secondin.cbegin(); + auto deinterlace_input = [&in,c,numchans]() noexcept -> float { - float ret{*in}; - in += numchans; + float ret{in[c]}; + in += ptrdiff_t(numchans); return ret; }; - std::generate_n(outbuf, todo, deinterlace_input); - data.second.buf += sizeof(float); - return outbuf + todo; - }; - std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second); + std::generate_n(out[c].begin(), todo, deinterlace_input); + out[c] = out[c].subspan(todo); + } total += todo; } @@ -410,8 +399,8 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept if(numframes > total) { - const jack_nframes_t todo{numframes - total}; - auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); }; + auto clear_buf = [](const al::span outbuf) -> void + { std::fill(outbuf.begin(), outbuf.end(), 0.0f); }; std::for_each(out.begin(), out.begin()+numchans, clear_buf); } @@ -421,7 +410,7 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept int JackPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const size_t frame_step{mDevice->channelsFromFmt()}; @@ -438,10 +427,10 @@ int JackPlayback::mixerProc() size_t todo{data.first.len + data.second.len}; todo -= todo%mDevice->UpdateSize; - const auto len1 = static_cast(minz(data.first.len, todo)); - const auto len2 = static_cast(minz(data.second.len, todo-len1)); + const auto len1 = static_cast(std::min(data.first.len, todo)); + const auto len2 = static_cast(std::min(data.second.len, todo-len1)); - std::lock_guard _{mMutex}; + std::lock_guard dlock{mMutex}; mDevice->renderSamples(data.first.buf, len1, frame_step); if(len2 > 0) mDevice->renderSamples(data.second.buf, len2, frame_step); @@ -452,14 +441,14 @@ int JackPlayback::mixerProc() } -void JackPlayback::open(const char *name) +void JackPlayback::open(std::string_view name) { if(!mClient) { const PathNamePair &binname = GetProcBinary(); const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; - jack_status_t status; + jack_status_t status{}; mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); if(mClient == nullptr) throw al::backend_exception{al::backend_error::DeviceError, @@ -476,9 +465,9 @@ void JackPlayback::open(const char *name) if(PlaybackList.empty()) EnumerateDevices(mClient, PlaybackList); - if(!name && !PlaybackList.empty()) + if(name.empty() && !PlaybackList.empty()) { - name = PlaybackList[0].mName.c_str(); + name = PlaybackList[0].mName; mPortPattern = PlaybackList[0].mPattern; } else @@ -488,14 +477,10 @@ void JackPlayback::open(const char *name) auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name); if(iter == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name?name:""}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; mPortPattern = iter->mPattern; } - mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1); - jack_set_process_callback(mClient, - mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); - mDevice->DeviceName = name; } @@ -506,6 +491,10 @@ bool JackPlayback::reset() std::for_each(mPort.begin(), mPort.end(), unregister_port); mPort.fill(nullptr); + mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true); + jack_set_process_callback(mClient, + mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); + /* Ignore the requested buffer metrics and just keep one JACK-sized buffer * ready for when requested. */ @@ -520,9 +509,9 @@ bool JackPlayback::reset() } else { - const char *devname{mDevice->DeviceName.c_str()}; + const std::string_view devname{mDevice->DeviceName}; uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); + bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); mDevice->BufferSize = bufsize + mDevice->UpdateSize; } @@ -535,7 +524,7 @@ bool JackPlayback::reset() while(bad_port != ports_end) { std::string name{"channel_" + std::to_string(++port_num)}; - *bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType, + *bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); if(!*bad_port) break; ++bad_port; @@ -570,10 +559,10 @@ void JackPlayback::start() if(jack_activate(mClient)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"}; - const char *devname{mDevice->DeviceName.c_str()}; + const std::string_view devname{mDevice->DeviceName}; if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true)) { - JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType, + JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)}; if(!pnames) { @@ -581,7 +570,7 @@ void JackPlayback::start() throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"}; } - for(size_t i{0};i < al::size(mPort) && mPort[i];++i) + for(size_t i{0};i < std::size(mPort) && mPort[i];++i) { if(!pnames[i]) { @@ -608,7 +597,7 @@ void JackPlayback::start() else { uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); + bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); mDevice->BufferSize = bufsize + mDevice->UpdateSize; mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true); @@ -648,8 +637,8 @@ ClockLatency JackPlayback::getClockLatency() { ClockLatency ret; - std::lock_guard _{mMutex}; - ret.ClockTime = GetDeviceClockTime(mDevice); + std::lock_guard dlock{mMutex}; + ret.ClockTime = mDevice->getClockTime(); ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; @@ -669,7 +658,7 @@ bool JackBackendFactory::init() if(!jack_load()) return false; - if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0)) + if(!GetConfigValueBool({}, "jack", "spawn-server", false)) ClientOptions = static_cast(ClientOptions | JackNoStartServer); const PathNamePair &binname = GetProcBinary(); @@ -677,7 +666,7 @@ bool JackBackendFactory::init() void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr}; jack_set_error_function(jack_msg_handler); - jack_status_t status; + jack_status_t status{}; jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)}; jack_set_error_function(old_error_cb); if(!client) @@ -706,7 +695,7 @@ std::string JackBackendFactory::probe(BackendType type) const PathNamePair &binname = GetProcBinary(); const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; - jack_status_t status; + jack_status_t status{}; switch(type) { case BackendType::Playback: diff --git a/libs/openal-soft/alc/backends/loopback.cpp b/libs/openal-soft/alc/backends/loopback.cpp index bf4ab246..e42e35b0 100644 --- a/libs/openal-soft/alc/backends/loopback.cpp +++ b/libs/openal-soft/alc/backends/loopback.cpp @@ -30,16 +30,14 @@ namespace { struct LoopbackBackend final : public BackendBase { LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; - - DEF_NEWDEL(LoopbackBackend) }; -void LoopbackBackend::open(const char *name) +void LoopbackBackend::open(std::string_view name) { mDevice->DeviceName = name; } diff --git a/libs/openal-soft/alc/backends/null.cpp b/libs/openal-soft/alc/backends/null.cpp index 5a8fc255..afbe8056 100644 --- a/libs/openal-soft/alc/backends/null.cpp +++ b/libs/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; } @@ -152,17 +152,15 @@ bool NullBackendFactory::querySupport(BackendType type) std::string NullBackendFactory::probe(BackendType type) { - std::string outnames; switch(type) { case BackendType::Playback: - /* Includes null char. */ - outnames.append(nullDevice, sizeof(nullDevice)); - break; + /* Include null char. */ + return std::string{GetDeviceName()} + '\0'; case BackendType::Capture: break; } - return outnames; + return std::string{}; } BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/oboe.cpp b/libs/openal-soft/alc/backends/oboe.cpp index f3b3d58d..0b9c1927 100644 --- a/libs/openal-soft/alc/backends/oboe.cpp +++ b/libs/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. */ @@ -81,7 +93,10 @@ bool OboePlayback::reset() builder.setCallback(this); if(mDevice->Flags.test(FrequencyRequest)) + { + builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High); builder.setSampleRate(static_cast(mDevice->Frequency)); + } if(mDevice->Flags.test(ChannelsRequest)) { /* Only use mono or stereo at user request. There's no telling what @@ -104,6 +119,10 @@ bool OboePlayback::reset() break; case DevFmtInt: case DevFmtUInt: +#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6) + format = oboe::AudioFormat::I32; + break; +#endif case DevFmtFloat: format = oboe::AudioFormat::Float; break; @@ -128,7 +147,7 @@ bool OboePlayback::reset() if(result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", oboe::convertToText(result)}; - mStream->setBufferSizeInFrames(mini(static_cast(mDevice->BufferSize), + mStream->setBufferSizeInFrames(std::min(static_cast(mDevice->BufferSize), mStream->getBufferCapacityInFrames())); TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); @@ -152,6 +171,15 @@ bool OboePlayback::reset() case oboe::AudioFormat::Float: mDevice->FmtType = DevFmtFloat; break; +#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6) + case oboe::AudioFormat::I32: + mDevice->FmtType = DevFmtInt; + break; + case oboe::AudioFormat::I24: +#endif +#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 8) + case oboe::AudioFormat::IEC61937: +#endif case oboe::AudioFormat::Unspecified: case oboe::AudioFormat::Invalid: throw al::backend_exception{al::backend_error::DeviceError, @@ -164,9 +192,9 @@ bool OboePlayback::reset() * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum * update size. */ - mDevice->UpdateSize = maxu(mDevice->Frequency / 100, + mDevice->UpdateSize = std::max(mDevice->Frequency/100u, static_cast(mStream->getFramesPerBurst())); - mDevice->BufferSize = maxu(mDevice->UpdateSize * 2, + mDevice->BufferSize = std::max(mDevice->UpdateSize*2u, static_cast(mStream->getBufferSizeInFrames())); return true; @@ -184,8 +212,7 @@ void OboePlayback::stop() { oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", - oboe::convertToText(result)}; + ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); } @@ -199,28 +226,28 @@ struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; }; oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData, int32_t numFrames) { - mRing->write(audioData, static_cast(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) @@ -245,6 +272,8 @@ void OboeCapture::open(const char *name) case DevFmtX51: case DevFmtX61: case DevFmtX71: + case DevFmtX714: + case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; @@ -261,10 +290,14 @@ void OboeCapture::open(const char *name) case DevFmtFloat: builder.setFormat(oboe::AudioFormat::Float); break; + case DevFmtInt: +#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6) + builder.setFormat(oboe::AudioFormat::I32); + break; +#endif case DevFmtByte: case DevFmtUByte: case DevFmtUShort: - case DevFmtInt: case DevFmtUInt: throw al::backend_exception{al::backend_error::DeviceError, "%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; @@ -278,7 +311,7 @@ void OboeCapture::open(const char *name) TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); /* Ensure a minimum ringbuffer size of 100ms. */ - mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10), + mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u), static_cast(mStream->getBytesPerFrame()), false); mDevice->DeviceName = name; @@ -296,15 +329,14 @@ void OboeCapture::stop() { const oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", - oboe::convertToText(result)}; + ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); } uint OboeCapture::availableSamples() { return static_cast(mRing->readSpace()); } -void OboeCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void OboeCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } } // namespace @@ -319,8 +351,8 @@ std::string OboeBackendFactory::probe(BackendType type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - return std::string{device_name, sizeof(device_name)}; + /* Include null char. */ + return std::string{GetDeviceName()} + '\0'; } return std::string{}; } diff --git a/libs/openal-soft/alc/backends/opensl.cpp b/libs/openal-soft/alc/backends/opensl.cpp index 696bf85a..c7056fae 100644 --- a/libs/openal-soft/alc/backends/opensl.cpp +++ b/libs/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) @@ -71,9 +76,15 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; - case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | + case DevFmtX71: + case DevFmtX3D71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; + case DevFmtX714: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | + SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT | + SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT | + SL_SPEAKER_TOP_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT | + SL_SPEAKER_TOP_BACK_RIGHT; case DevFmtAmbi3D: break; } @@ -107,7 +118,7 @@ constexpr SLuint32 GetByteOrderEndianness() noexcept return SL_BYTEORDER_BIGENDIAN; } -const char *res_str(SLresult result) noexcept +constexpr const char *res_str(SLresult result) noexcept { switch(result) { @@ -141,10 +152,11 @@ const char *res_str(SLresult result) noexcept return "Unknown error code"; } -#define PRINTERR(x, s) do { \ - if UNLIKELY((x) != SL_RESULT_SUCCESS) \ - ERR("%s: %s\n", (s), res_str((x))); \ -} while(0) +inline void PrintErr(SLresult res, const char *str) +{ + if(res != SL_RESULT_SUCCESS) UNLIKELY + ERR("%s: %s\n", str, res_str(res)); +} struct OpenSLPlayback final : public BackendBase { @@ -152,12 +164,10 @@ struct OpenSLPlayback final : public BackendBase { ~OpenSLPlayback() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept - { static_cast(context)->process(bq); } int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -182,8 +192,6 @@ struct OpenSLPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(OpenSLPlayback) }; OpenSLPlayback::~OpenSLPlayback() @@ -222,17 +230,17 @@ void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept int OpenSLPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); SLPlayItf player; SLAndroidSimpleBufferQueueItf bufferQueue; SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue)}; - PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); + PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); if(SL_RESULT_SUCCESS == result) { result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player); - PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY"); + PrintErr(result, "bufferQueue->GetInterface SL_IID_PLAY"); } const size_t frame_step{mDevice->channelsFromFmt()}; @@ -248,11 +256,11 @@ int OpenSLPlayback::mixerProc() SLuint32 state{0}; result = VCALL(player,GetPlayState)(&state); - PRINTERR(result, "player->GetPlayState"); + PrintErr(result, "player->GetPlayState"); if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING) { result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING); - PRINTERR(result, "player->SetPlayState"); + PrintErr(result, "player->SetPlayState"); } if(SL_RESULT_SUCCESS != result) { @@ -289,7 +297,7 @@ int OpenSLPlayback::mixerProc() } result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize); - PRINTERR(result, "bufferQueue->Enqueue"); + PrintErr(result, "bufferQueue->Enqueue"); if(SL_RESULT_SUCCESS != result) { mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result); @@ -305,39 +313,39 @@ int OpenSLPlayback::mixerProc() } -void OpenSLPlayback::open(const char *name) +void OpenSLPlayback::open(std::string_view name) { - if(!name) - name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; /* There's only one device, so if it's already open, there's nothing to do. */ if(mEngineObj) return; // create engine SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; - PRINTERR(result, "slCreateEngine"); + PrintErr(result, "slCreateEngine"); if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "engine->Realize"); + PrintErr(result, "engine->Realize"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine); - PRINTERR(result, "engine->GetInterface"); + PrintErr(result, "engine->GetInterface"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr); - PRINTERR(result, "engine->CreateOutputMix"); + PrintErr(result, "engine->CreateOutputMix"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "outputMix->Realize"); + PrintErr(result, "outputMix->Realize"); } if(SL_RESULT_SUCCESS != result) @@ -368,74 +376,6 @@ bool OpenSLPlayback::reset() mRing = nullptr; -#if 0 - if(!mDevice->Flags.get()) - { - /* FIXME: Disabled until I figure out how to get the Context needed for - * the getSystemService call. - */ - JNIEnv *env = Android_GetJNIEnv(); - jobject jctx = Android_GetContext(); - - /* Get necessary stuff for using java.lang.Integer, - * android.content.Context, and android.media.AudioManager. - */ - jclass int_cls = JCALL(env,FindClass)("java/lang/Integer"); - jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls, - "parseInt", "(Ljava/lang/String;)I" - ); - TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint); - - jclass ctx_cls = JCALL(env,FindClass)("android/content/Context"); - jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls, - "AUDIO_SERVICE", "Ljava/lang/String;" - ); - jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls, - "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;" - ); - TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n", - ctx_cls, ctx_audsvc, ctx_getSysSvc); - - jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager"); - jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls, - "PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;" - ); - jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls, - "getProperty", "(Ljava/lang/String;)Ljava/lang/String;" - ); - TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n", - audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty); - - const char *strchars; - jstring strobj; - - /* Now make the calls. */ - //AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE); - strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc); - jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj); - strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr); - TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr); - JCALL(env,ReleaseStringUTFChars)(strobj, strchars); - - //String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); - strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate); - jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj); - strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr); - TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr); - JCALL(env,ReleaseStringUTFChars)(strobj, strchars); - - //int sampleRate = Integer.parseInt(srateStr); - sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr); - - strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr); - TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars); - JCALL(env,ReleaseStringUTFChars)(srateStr, strchars); - - if(!sampleRate) sampleRate = device->Frequency; - else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE); - } -#endif - mDevice->FmtChans = DevFmtStereo; mDevice->FmtType = DevFmtShort; @@ -505,20 +445,20 @@ bool OpenSLPlayback::reset() result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(), ids.data(), reqs.data()); - PRINTERR(result, "engine->CreateAudioPlayer"); + PrintErr(result, "engine->CreateAudioPlayer"); } if(SL_RESULT_SUCCESS == result) { /* Set the stream type to "media" (games, music, etc), if possible. */ SLAndroidConfigurationItf config; result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config); - PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION"); + PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION"); if(SL_RESULT_SUCCESS == result) { SLint32 streamType = SL_ANDROID_STREAM_MEDIA; result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(streamType)); - PRINTERR(result, "config->SetConfiguration"); + PrintErr(result, "config->SetConfiguration"); } /* Clear any error since this was optional. */ @@ -527,7 +467,7 @@ bool OpenSLPlayback::reset() if(SL_RESULT_SUCCESS == result) { result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "bufferQueue->Realize"); + PrintErr(result, "bufferQueue->Realize"); } if(SL_RESULT_SUCCESS == result) { @@ -554,11 +494,13 @@ void OpenSLPlayback::start() SLAndroidSimpleBufferQueueItf bufferQueue; SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue)}; - PRINTERR(result, "bufferQueue->GetInterface"); + PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this); - PRINTERR(result, "bufferQueue->RegisterCallback"); + result = VCALL(bufferQueue,RegisterCallback)( + [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept + { static_cast(context)->process(bq); }, this); + PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS != result) throw al::backend_exception{al::backend_error::DeviceError, @@ -584,25 +526,25 @@ void OpenSLPlayback::stop() SLPlayItf player; SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)}; - PRINTERR(result, "bufferQueue->GetInterface"); + PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED); - PRINTERR(result, "player->SetPlayState"); + PrintErr(result, "player->SetPlayState"); } SLAndroidSimpleBufferQueueItf bufferQueue; result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); - PRINTERR(result, "bufferQueue->GetInterface"); + PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL0(bufferQueue,Clear)(); - PRINTERR(result, "bufferQueue->Clear"); + PrintErr(result, "bufferQueue->Clear"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr); - PRINTERR(result, "bufferQueue->RegisterCallback"); + PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS == result) { @@ -611,7 +553,9 @@ void OpenSLPlayback::stop() std::this_thread::yield(); result = VCALL(bufferQueue,GetState)(&state); } while(SL_RESULT_SUCCESS == result && state.count > 0); - PRINTERR(result, "bufferQueue->GetState"); + PrintErr(result, "bufferQueue->GetState"); + + mRing->reset(); } } @@ -619,8 +563,8 @@ ClockLatency OpenSLPlayback::getClockLatency() { ClockLatency ret; - std::lock_guard _{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; @@ -633,13 +577,11 @@ struct OpenSLCapture final : public BackendBase { ~OpenSLCapture() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept - { static_cast(context)->process(bq); } - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; /* engine interfaces */ @@ -653,8 +595,6 @@ struct OpenSLCapture final : public BackendBase { uint mSplOffset{0u}; uint mFrameSize{0}; - - DEF_NEWDEL(OpenSLCapture) }; OpenSLCapture::~OpenSLCapture() @@ -677,34 +617,34 @@ void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept } -void OpenSLCapture::open(const char* name) +void OpenSLCapture::open(std::string_view name) { - if(!name) - name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; - PRINTERR(result, "slCreateEngine"); + PrintErr(result, "slCreateEngine"); if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "engine->Realize"); + PrintErr(result, "engine->Realize"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine); - PRINTERR(result, "engine->GetInterface"); + PrintErr(result, "engine->GetInterface"); } if(SL_RESULT_SUCCESS == result) { mFrameSize = mDevice->frameSizeFromFmt(); /* Ensure the total length is at least 100ms */ - uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)}; + uint length{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ - uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100, - mDevice->Frequency/100*5)}; + uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u, + mDevice->Frequency/100u*5u)}; uint num_updates{(length+update_len-1) / update_len}; mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false); @@ -770,7 +710,7 @@ void OpenSLCapture::open(const char* name) result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk, ids.size(), ids.data(), reqs.data()); } - PRINTERR(result, "engine->CreateAudioRecorder"); + PrintErr(result, "engine->CreateAudioRecorder"); } } if(SL_RESULT_SUCCESS == result) @@ -778,13 +718,13 @@ void OpenSLCapture::open(const char* name) /* Set the record preset to "generic", if possible. */ SLAndroidConfigurationItf config; result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config); - PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION"); + PrintErr(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION"); if(SL_RESULT_SUCCESS == result) { SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC; result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset, sizeof(preset)); - PRINTERR(result, "config->SetConfiguration"); + PrintErr(result, "config->SetConfiguration"); } /* Clear any error since this was optional. */ @@ -793,24 +733,26 @@ void OpenSLCapture::open(const char* name) if(SL_RESULT_SUCCESS == result) { result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE); - PRINTERR(result, "recordObj->Realize"); + PrintErr(result, "recordObj->Realize"); } SLAndroidSimpleBufferQueueItf bufferQueue; if(SL_RESULT_SUCCESS == result) { result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); - PRINTERR(result, "recordObj->GetInterface"); + PrintErr(result, "recordObj->GetInterface"); } if(SL_RESULT_SUCCESS == result) { - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this); - PRINTERR(result, "bufferQueue->RegisterCallback"); + result = VCALL(bufferQueue,RegisterCallback)( + [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept + { static_cast(context)->process(bq); }, this); + PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS == result) { const uint chunk_size{mDevice->UpdateSize * mFrameSize}; - const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0}; + const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0}; auto data = mRing->getWriteVector(); std::fill_n(data.first.buf, data.first.len*chunk_size, silence); @@ -818,12 +760,12 @@ void OpenSLCapture::open(const char* name) for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); + PrintErr(result, "bufferQueue->Enqueue"); } for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); + PrintErr(result, "bufferQueue->Enqueue"); } } @@ -849,12 +791,12 @@ void OpenSLCapture::start() { SLRecordItf record; SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)}; - PRINTERR(result, "recordObj->GetInterface"); + PrintErr(result, "recordObj->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING); - PRINTERR(result, "record->SetRecordState"); + PrintErr(result, "record->SetRecordState"); } if(SL_RESULT_SUCCESS != result) throw al::backend_exception{al::backend_error::DeviceError, @@ -865,16 +807,16 @@ void OpenSLCapture::stop() { SLRecordItf record; SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)}; - PRINTERR(result, "recordObj->GetInterface"); + PrintErr(result, "recordObj->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED); - PRINTERR(result, "record->SetRecordState"); + PrintErr(result, "record->SetRecordState"); } } -void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) +void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) { const uint update_size{mDevice->UpdateSize}; const uint chunk_size{update_size * mFrameSize}; @@ -886,7 +828,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) auto rdata = mRing->getReadVector(); for(uint i{0};i < samples;) { - const uint rem{minu(samples - i, update_size - mSplOffset)}; + const uint rem{std::min(samples - i, update_size - mSplOffset)}; std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize}, buffer + i*size_t{mFrameSize}); @@ -908,22 +850,22 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) } SLAndroidSimpleBufferQueueItf bufferQueue{}; - if(likely(mDevice->Connected.load(std::memory_order_acquire))) + if(mDevice->Connected.load(std::memory_order_acquire)) LIKELY { const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue)}; - PRINTERR(result, "recordObj->GetInterface"); - if(unlikely(SL_RESULT_SUCCESS != result)) + PrintErr(result, "recordObj->GetInterface"); + if(SL_RESULT_SUCCESS != result) UNLIKELY { mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result); bufferQueue = nullptr; } } - if(unlikely(!bufferQueue) || adv_count == 0) + if(!bufferQueue || adv_count == 0) return; /* For each buffer chunk that was fully read, queue another writable buffer - * chunk to keep the OpenSL queue full. This is rather convulated, as a + * chunk to keep the OpenSL queue full. This is rather convoluted, as a * result of the ring buffer holding more elements than are writable at a * given time. The end of the write vector increments when the read pointer * advances, which will "expose" a previously unwritable element. So for @@ -934,14 +876,14 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) SLresult result{SL_RESULT_SUCCESS}; auto wdata = mRing->getWriteVector(); - if(likely(adv_count > wdata.second.len)) + if(adv_count > wdata.second.len) LIKELY { auto len1 = std::min(wdata.first.len, adv_count-wdata.second.len); auto buf1 = wdata.first.buf + chunk_size*(wdata.first.len-len1); for(size_t i{0u};i < len1 && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(buf1 + chunk_size*i, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); + PrintErr(result, "bufferQueue->Enqueue"); } } if(wdata.second.len > 0) @@ -951,7 +893,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size); - PRINTERR(result, "bufferQueue->Enqueue"); + PrintErr(result, "bufferQueue->Enqueue"); } } } @@ -968,16 +910,14 @@ bool OSLBackendFactory::querySupport(BackendType type) std::string OSLBackendFactory::probe(BackendType type) { - std::string outnames; switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - outnames.append(opensl_device, sizeof(opensl_device)); - break; + /* Include null char. */ + return std::string{GetDeviceName()} + '\0'; } - return outnames; + return std::string{}; } BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/oss.cpp b/libs/openal-soft/alc/backends/oss.cpp index 6d4fa261..b31c13f6 100644 --- a/libs/openal-soft/alc/backends/oss.cpp +++ b/libs/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; diff --git a/libs/openal-soft/alc/backends/pipewire.cpp b/libs/openal-soft/alc/backends/pipewire.cpp index a19dcb61..d8358f7c 100644 --- a/libs/openal-soft/alc/backends/pipewire.cpp +++ b/libs/openal-soft/alc/backends/pipewire.cpp @@ -24,22 +24,24 @@ #include #include +#include #include #include #include +#include #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 "alspan.h" #include "alstring.h" #include "core/devformat.h" @@ -50,8 +52,20 @@ #include "opthelpers.h" #include "ringbuffer.h" -/* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). */ +/* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). GCC + * doesn't support ignoring -Weverything, so we have the list the individual + * warnings to ignore (and ignoring -Winline doesn't seem to work). + */ _Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wpedantic\"") +_Pragma("GCC diagnostic ignored \"-Wconversion\"") +_Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"") +_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") +_Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") +_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +_Pragma("GCC diagnostic ignored \"-Wsign-compare\"") +_Pragma("GCC diagnostic ignored \"-Winline\"") +_Pragma("GCC diagnostic ignored \"-Wpragmas\"") _Pragma("GCC diagnostic ignored \"-Weverything\"") #include "pipewire/pipewire.h" #include "pipewire/extensions/metadata.h" @@ -62,6 +76,10 @@ _Pragma("GCC diagnostic ignored \"-Weverything\"") #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 @@ -94,26 +112,81 @@ 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) +{ + /* There doesn't seem to be a function to get the version as an integer, so + * instead we have to parse the string, which hopefully won't break in the + * future. + */ + int major{0}, minor{0}, revision{0}; + int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)}; + return ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR) + || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO)); +} #ifdef HAVE_DYNLOAD #define PWIRE_FUNCS(MAGIC) \ @@ -121,6 +194,7 @@ constexpr char pwireInput[] = "PipeWire Input"; MAGIC(pw_context_destroy) \ MAGIC(pw_context_new) \ MAGIC(pw_core_disconnect) \ + MAGIC(pw_get_library_version) \ MAGIC(pw_init) \ MAGIC(pw_properties_free) \ MAGIC(pw_properties_new) \ @@ -134,7 +208,6 @@ constexpr char pwireInput[] = "PipeWire Input"; MAGIC(pw_stream_dequeue_buffer) \ MAGIC(pw_stream_destroy) \ MAGIC(pw_stream_get_state) \ - MAGIC(pw_stream_get_time) \ MAGIC(pw_stream_new) \ MAGIC(pw_stream_queue_buffer) \ MAGIC(pw_stream_set_active) \ @@ -146,11 +219,19 @@ constexpr char pwireInput[] = "PipeWire Input"; MAGIC(pw_thread_loop_lock) \ MAGIC(pw_thread_loop_wait) \ MAGIC(pw_thread_loop_signal) \ - MAGIC(pw_thread_loop_unlock) \ + MAGIC(pw_thread_loop_unlock) +#if PW_CHECK_VERSION(0,3,50) +#define PWIRE_FUNCS2(MAGIC) \ + MAGIC(pw_stream_get_time_n) +#else +#define PWIRE_FUNCS2(MAGIC) \ + MAGIC(pw_stream_get_time) +#endif void *pwire_handle; #define MAKE_FUNC(f) decltype(f) * p##f; PWIRE_FUNCS(MAKE_FUNC) +PWIRE_FUNCS2(MAKE_FUNC) #undef MAKE_FUNC bool pwire_load() @@ -158,7 +239,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); @@ -173,6 +254,7 @@ bool pwire_load() if(p##f == nullptr) missing_funcs += "\n" #f; \ } while(0); PWIRE_FUNCS(LOAD_FUNC) + PWIRE_FUNCS2(LOAD_FUNC) #undef LOAD_FUNC if(!missing_funcs.empty()) @@ -191,6 +273,7 @@ bool pwire_load() #define pw_context_destroy ppw_context_destroy #define pw_context_new ppw_context_new #define pw_core_disconnect ppw_core_disconnect +#define pw_get_library_version ppw_get_library_version #define pw_init ppw_init #define pw_properties_free ppw_properties_free #define pw_properties_new ppw_properties_new @@ -204,7 +287,6 @@ bool pwire_load() #define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer #define pw_stream_destroy ppw_stream_destroy #define pw_stream_get_state ppw_stream_get_state -#define pw_stream_get_time ppw_stream_get_time #define pw_stream_new ppw_stream_new #define pw_stream_queue_buffer ppw_stream_queue_buffer #define pw_stream_set_active ppw_stream_set_active @@ -217,6 +299,12 @@ bool pwire_load() #define pw_thread_loop_stop ppw_thread_loop_stop #define pw_thread_loop_unlock ppw_thread_loop_unlock #define pw_thread_loop_wait ppw_thread_loop_wait +#if PW_CHECK_VERSION(0,3,50) +#define pw_stream_get_time_n ppw_stream_get_time_n +#else +inline auto pw_stream_get_time_n(pw_stream *stream, pw_time *ptime, size_t /*size*/) +{ return ppw_stream_get_time(stream, ptime); } +#endif #endif #else @@ -246,7 +334,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) @@ -256,12 +344,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 @@ -319,7 +407,10 @@ using PwStreamPtr = std::unique_ptr; /* Enums for bitflags... again... *sigh* */ constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept -{ return static_cast(lhs | std::underlying_type_t{rhs}); } +{ return static_cast(lhs | al::to_underlying(rhs)); } + +constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept +{ lhs = lhs | rhs; return lhs; } class ThreadMainloop { pw_thread_loop *mLoop{}; @@ -344,9 +435,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); } @@ -354,7 +447,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) @@ -382,8 +475,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. @@ -399,8 +557,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 @@ -412,24 +570,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(unlikely(!mInitDone.load(std::memory_order_acquire))) + 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); }); } } @@ -445,7 +608,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; } @@ -455,38 +618,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; } }; @@ -503,10 +662,12 @@ enum class NodeType : unsigned char { }; constexpr auto InvalidChannelConfig = DevFmtChannels(255); struct DeviceNode { + uint32_t mId{}; + + uint64_t mSerial{}; std::string mName; std::string mDevName; - uint32_t mId{}; NodeType mType{}; bool mIsHeadphones{}; bool mIs51Rear{}; @@ -517,12 +678,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; @@ -548,8 +720,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; } @@ -560,7 +731,18 @@ DeviceNode *DeviceNode::Find(uint32_t id) { return n.mId == id; }; auto match = std::find_if(sList.begin(), sList.end(), match_id); - if(match != sList.end()) return std::addressof(*match); + if(match != sList.end()) return al::to_address(match); + + 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; } @@ -572,6 +754,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; }; @@ -580,44 +767,53 @@ 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 }; +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 +}; /** * 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{}; @@ -626,7 +822,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; } @@ -634,15 +830,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 %u sample rate: %d (range: %d -> %d)\n", mId, 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; } @@ -650,7 +847,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); @@ -662,15 +859,16 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept others += ", "; others += std::to_string(srates[i]); } - TRACE("Device ID %u sample rate: %d (%s)\n", mId, 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; } } @@ -681,116 +879,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 %u sample rate: %d\n", mId, 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, 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 %u got %zu position%s for %s%s\n", mId, 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 %u got %d channel%s for %s\n", mId, *chancount, (*chancount==1)?"":"s", + 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 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. @@ -802,14 +986,16 @@ 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(unlikely(!media_class)) return; + 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) + 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 { @@ -823,80 +1009,106 @@ void NodeProxy::infoCallback(const pw_node_info *info) if(!nodeName || !*nodeName) nodeName = spa_dict_lookup(info->props, PW_KEY_NODE_NICK); if(!nodeName || !*nodeName) nodeName = devName; + uint64_t serial_id{info->id}; +#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) + { + ERR("Unexpected object serial: %s\n", serial_str); + serial_id = info->id; + } + } +#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 %u\n", nodeName ? nodeName : "(nil)", info->id); + TRACE(" \"%s\" = ID %" PRIu64 "\n", name.c_str(), serial_id); DeviceNode &node = DeviceNode::Add(info->id); - if(nodeName && *nodeName) node.mName = nodeName; - else node.mName = "PipeWire node #"+std::to_string(info->id); + node.mSerial = serial_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(unlikely(!node)) return; + if(!node) UNLIKELY return; + TRACE("Device ID %" PRIu64 " %s format:\n", node->mSerial, + (id == SPA_PARAM_EnumFormat) ? "enumerable" : "current"); + + 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; @@ -908,50 +1120,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; } } @@ -968,7 +1200,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); @@ -1009,26 +1241,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; @@ -1037,29 +1255,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, 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); @@ -1069,8 +1288,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 @@ -1084,7 +1302,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; @@ -1097,42 +1315,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) { @@ -1181,6 +1389,8 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u break; case DevFmtX61: map = X61Map; break; case DevFmtX71: map = X71Map; break; + case DevFmtX714: map = X714Map; break; + case DevFmtX3D71: map = X71Map; break; case DevFmtAmbi3D: info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED; info.channels = device->channelsFromFmt(); @@ -1189,33 +1399,24 @@ 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; ClockLatency getClockLatency() override; - uint32_t mTargetId{PwIdAny}; + uint64_t mTargetId{PwIdAny}; nanoseconds mTimeBase{0}; ThreadMainloop mLoop; PwContextPtr mContext; @@ -1223,92 +1424,102 @@ 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(unlikely(!pw_buf)) return; + if(!pw_buf) UNLIKELY return; + + const al::span datas{pw_buf->buffer->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. + */ + uint length{static_cast(pw_buf->requested)}; +#else + /* In 0.3.48 and earlier, spa_io_rate_match::size apparently has the number + * of samples per update. + */ + uint length{mRateMatch ? mRateMatch->size : 0u}; +#endif + /* If no length is specified, use the device's update size as a fallback. */ + if(!length) UNLIKELY length = mDevice->UpdateSize; /* For planar formats, each datas[] seems to contain one channel, so store * the pointers in an array. Limit the render length in case the available * buffer length in any one channel is smaller than we wanted (shouldn't * be, but just in case). */ - spa_data *datas{pw_buf->buffer->datas}; - const size_t chancount{minu(mNumChannels, pw_buf->buffer->n_datas)}; - /* TODO: How many samples should actually be written? 'maxsize' can be 16k - * samples, which is excessive (~341ms @ 48khz). SPA_IO_RateMatch contains - * a 'size' field that apparently indicates how many samples should be - * written per update, but it's not obviously right. - */ - uint length{mRateMatch ? mRateMatch->size : mDevice->UpdateSize}; - for(size_t i{0};i < chancount;++i) + auto chanptr_end = mChannelPtrs.begin(); + for(const auto &data : datas) { - length = minu(length, datas[i].maxsize/sizeof(float)); - mChannelPtrs[i] = static_cast(datas[i].data); + length = std::min(length, data.maxsize/uint{sizeof(float)}); + *chanptr_end = static_cast(data.data); + ++chanptr_end; + + data.chunk->offset = 0; + data.chunk->stride = sizeof(float); + data.chunk->size = length * sizeof(float); } - mDevice->renderSamples({mChannelPtrs.get(), chancount}, length); + mDevice->renderSamples(mChannelPtrs, length); - for(size_t i{0};i < chancount;++i) - { - datas[i].chunk->offset = 0; - datas[i].chunk->stride = sizeof(float); - datas[i].chunk->size = length * sizeof(float); - } pw_buf->size = length; pw_stream_queue_buffer(mStream.get(), pw_buf); } -void PipeWirePlayback::open(const char *name) +void PipeWirePlayback::open(std::string_view name) { static std::atomic OpenCount{0}; - uint32_t targetid{PwIdAny}; + 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(); @@ -1328,22 +1539,22 @@ void PipeWirePlayback::open(const char *name) "No PipeWire playback device found"}; } - targetid = match->mId; + targetid = match->mSerial; devname = match->mName; } 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->mId; + targetid = match->mSerial; devname = match->mName; } @@ -1383,19 +1594,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. @@ -1404,11 +1615,11 @@ 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 - { return targetid == n.mId; }; + { return targetid == n.mSerial; }; auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id); if(match != devlist.cend()) { @@ -1416,10 +1627,12 @@ bool PipeWirePlayback::reset() { /* Scale the update size if the sample rate changes. */ const double scale{static_cast(match->mSampleRate) / mDevice->Frequency}; + 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 = mDevice->UpdateSize * 2; + 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; @@ -1433,19 +1646,21 @@ 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"}; - pw_properties *props{pw_properties_new( + /* TODO: Which properties are actually needed here? Any others that could + * be useful? + */ + auto&& binary = GetProcBinary(); + const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; + pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname, + PW_KEY_NODE_DESCRIPTION, appname, PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Game", @@ -1455,16 +1670,14 @@ bool PipeWirePlayback::reset() throw al::backend_exception{al::backend_error::DeviceError, "Failed to create PipeWire stream properties (errno: %d)", errno}; - auto&& binary = GetProcBinary(); - const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; - /* TODO: Which properties are actually needed here? Any others that could - * be useful? - */ - pw_properties_set(props, PW_KEY_NODE_NAME, appname); - pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname); pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->UpdateSize, mDevice->Frequency); pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency); +#ifdef PW_KEY_TARGET_OBJECT + pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); +#else + pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId); +#endif MainloopUniqueLock plock{mLoop}; /* The stream takes overship of 'props', even in the case of failure. */ @@ -1475,32 +1688,34 @@ bool PipeWirePlayback::reset() static constexpr pw_stream_events streamEvents{CreateEvents()}; pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); - constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE - | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS}; - if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, mTargetId, Flags, ¶ms, 1)}) + pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE + | PW_STREAM_FLAG_MAP_BUFFERS}; + 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, "Error connecting PipeWire stream (res: %d)", res}; /* Wait for the stream to become paused (ready to start streaming). */ - pw_stream_state state{}; - const char *error{}; - plock.wait([stream=mStream.get(),&state,&error]() + plock.wait([stream=mStream.get()]() { - state = pw_stream_get_state(stream, &error); + const char *error{}; + pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, "Error connecting PipeWire stream: \"%s\"", error}; return state == PW_STREAM_STATE_PAUSED; }); - /* TODO: Update mDevice->BufferSize with the total known buffering delay - * from the head of this playback stream to the tail of the device output. + /* TODO: Update mDevice->UpdateSize with the stream's quantum, and + * mDevice->BufferSize with the total known buffering delay from the head + * of this playback stream to the tail of the device output. + * + * This info is apparently not available until after the stream starts. */ - mDevice->BufferSize = mDevice->UpdateSize * 2; plock.unlock(); - mNumChannels = mDevice->channelsFromFmt(); - mChannelPtrs = std::make_unique(mNumChannels); + mChannelPtrs.resize(mDevice->channelsFromFmt()); setDefaultWFXChannelOrder(); @@ -1517,30 +1732,82 @@ void PipeWirePlayback::start() /* Wait for the stream to start playing (would be nice to not, but we need * the actual update size which is only available after starting). */ - pw_stream_state state{}; - const char *error{}; - plock.wait([stream=mStream.get(),&state,&error]() + plock.wait([stream=mStream.get()]() { - state = pw_stream_get_state(stream, &error); - return state != PW_STREAM_STATE_PAUSED; + const char *error{}; + pw_stream_state state{pw_stream_get_state(stream, &error)}; + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "PipeWire stream error: %s", error ? error : "(unknown)"}; + return state == PW_STREAM_STATE_STREAMING; }); - if(state == PW_STREAM_STATE_ERROR) - throw al::backend_exception{al::backend_error::DeviceError, - "PipeWire stream error: %s", error ? error : "(unknown)"}; - if(state == PW_STREAM_STATE_STREAMING && mRateMatch && mRateMatch->size) - { - mDevice->UpdateSize = mRateMatch->size; - mDevice->BufferSize = mDevice->UpdateSize * 2; - } + /* HACK: Try to work out the update size and total buffering size. There's + * no actual query for this, so we have to work it out from the stream time + * info, and assume it stays accurate with future updates. The stream time + * info may also not be available right away, so we have to wait until it + * is (up to about 2 seconds). + */ + int wait_count{100}; + do { + pw_time ptime{}; + if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))}) + { + ERR("Failed to get PipeWire stream time (res: %d)\n", res); + break; + } + + /* The rate match size is the update size for each buffer. */ + const uint updatesize{mRateMatch ? mRateMatch->size : 0u}; +#if PW_CHECK_VERSION(0,3,50) + /* Assume ptime.avail_buffers+ptime.queued_buffers is the target buffer + * queue size. + */ + if(ptime.rate.denom > 0 && (ptime.avail_buffers || ptime.queued_buffers) && updatesize > 0) + { + const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers}; + + /* Ensure the delay is in sample frames. */ + const uint64_t delay{static_cast(ptime.delay) * mDevice->Frequency * + ptime.rate.num / ptime.rate.denom}; + + mDevice->UpdateSize = updatesize; + mDevice->BufferSize = static_cast(ptime.buffered + delay + + uint64_t{totalbuffers}*updatesize); + break; + } +#else + /* Prior to 0.3.50, we can only measure the delay with the update size, + * assuming one buffer and no resample buffering. + */ + if(ptime.rate.denom > 0 && updatesize > 0) + { + /* Ensure the delay is in sample frames. */ + const uint64_t delay{static_cast(ptime.delay) * mDevice->Frequency * + ptime.rate.num / ptime.rate.denom}; + + mDevice->UpdateSize = updatesize; + mDevice->BufferSize = static_cast(delay + updatesize); + break; + } +#endif + if(!--wait_count) + { + ERR("Timeout getting PipeWire stream buffering info\n"); + break; + } + + plock.unlock(); + std::this_thread::sleep_for(milliseconds{20}); + plock.lock(); + } while(pw_stream_get_state(mStream.get(), nullptr) == PW_STREAM_STATE_STREAMING); } 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()]() @@ -1559,8 +1826,8 @@ ClockLatency PipeWirePlayback::getClockLatency() pw_time ptime{}; if(mStream) { - MainloopLockGuard _{mLoop}; - if(int res{pw_stream_get_time(mStream.get(), &ptime)}) + 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); } @@ -1572,17 +1839,17 @@ 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. */ nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}}; nanoseconds curtic{}, delay{}; - if(unlikely(ptime.rate.denom < 1)) + if(ptime.rate.denom < 1) UNLIKELY { /* If there's no stream rate, the stream hasn't had a chance to get * going and return time info yet. Just use dummy values. @@ -1622,7 +1889,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; @@ -1633,22 +1900,16 @@ 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; - uint32_t mTargetId{PwIdAny}; + uint64_t mTargetId{PwIdAny}; ThreadMainloop mLoop; PwContextPtr mContext; PwCorePtr mCore; @@ -1661,47 +1922,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(unlikely(!pw_buf)) return; + 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}; - uint32_t targetid{PwIdAny}; + 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(); @@ -1725,31 +1987,41 @@ void PipeWireCapture::open(const char *name) "No PipeWire capture device found"}; } - targetid = match->mId; + 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->mId; - devname = name; + targetid = match->mSerial; + if(match->mType != NodeType::Sink) devname = match->mName; + else devname = std::string{GetMonitorPrefix()}+match->mName; } if(!mLoop) @@ -1788,33 +2060,37 @@ 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 - { return targetid == n.mId; }; + { return targetid == n.mSerial; }; auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id); if(match != devlist.cend()) is51rear = match->mIs51Rear; } spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)}; - constexpr uint32_t pod_buffer_size{1024}; - auto pod_buffer = std::make_unique(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"}; + auto&& binary = GetProcBinary(); + const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; pw_properties *props{pw_properties_new( + PW_KEY_NODE_NAME, appname, + PW_KEY_NODE_DESCRIPTION, appname, PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Game", @@ -1824,10 +2100,6 @@ void PipeWireCapture::open(const char *name) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create PipeWire stream properties (errno: %d)", errno}; - auto&& binary = GetProcBinary(); - const char *appname{binary.fname.length() ? binary.fname.c_str() : "OpenAL Soft"}; - pw_properties_set(props, PW_KEY_NODE_NAME, appname); - pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, appname); /* We don't actually care what the latency/update size is, as long as it's * reasonable. Unfortunately, when unspecified PipeWire seems to default to * around 40ms, which isn't great. So request 20ms instead. @@ -1835,6 +2107,11 @@ void PipeWireCapture::open(const char *name) pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->Frequency+25) / 50, mDevice->Frequency); pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->Frequency); +#ifdef PW_KEY_TARGET_OBJECT + pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); +#else + pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId); +#endif MainloopUniqueLock plock{mLoop}; mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)}; @@ -1846,16 +2123,15 @@ 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, mTargetId, 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}; /* Wait for the stream to become paused (ready to start streaming). */ - pw_stream_state state{}; - const char *error{}; - plock.wait([stream=mStream.get(),&state,&error]() + plock.wait([stream=mStream.get()]() { - state = pw_stream_get_state(stream, &error); + const char *error{}; + pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, "Error connecting PipeWire stream: \"%s\"", error}; @@ -1866,7 +2142,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); } @@ -1878,25 +2154,22 @@ void PipeWireCapture::start() throw al::backend_exception{al::backend_error::DeviceError, "Failed to start PipeWire stream (res: %d)", res}; - pw_stream_state state{}; - const char *error{}; - plock.wait([stream=mStream.get(),&state,&error]() + plock.wait([stream=mStream.get()]() { - state = pw_stream_get_state(stream, &error); - return state != PW_STREAM_STATE_PAUSED; + const char *error{}; + pw_stream_state state{pw_stream_get_state(stream, &error)}; + if(state == PW_STREAM_STATE_ERROR) + throw al::backend_exception{al::backend_error::DeviceError, + "PipeWire stream error: %s", error ? error : "(unknown)"}; + return state == PW_STREAM_STATE_STREAMING; }); - - if(state == PW_STREAM_STATE_ERROR) - throw al::backend_exception{al::backend_error::DeviceError, - "PipeWire stream error: %s", error ? error : "(unknown)"}; } void PipeWireCapture::stop() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), false)}) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to stop PipeWire stream (res: %d)", res}; + 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; }); @@ -1905,8 +2178,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 @@ -1916,11 +2189,20 @@ bool PipeWireBackendFactory::init() if(!pwire_load()) return false; - pw_init(0, nullptr); + const char *version{pw_get_library_version()}; + if(!check_version(version)) + { + WARN("PipeWire version \"%s\" too old (%s or newer required)\n", version, + pw_get_headers_version()); + return false; + } + TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version()); + + 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(); @@ -1941,7 +2223,7 @@ std::string PipeWireBackendFactory::probe(BackendType type) std::string outnames; gEventHandler.waitForInit(); - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_defsink = [](const DeviceNode &n) -> bool @@ -1974,7 +2256,7 @@ std::string PipeWireBackendFactory::probe(BackendType type) if(defmatch != devlist.cend()) { if(defmatch->mType == NodeType::Sink) - outnames.append(MonitorPrefix); + outnames.append(GetMonitorPrefix()); outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1); } for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) @@ -1982,7 +2264,7 @@ std::string PipeWireBackendFactory::probe(BackendType type) if(iter != defmatch) { if(iter->mType == NodeType::Sink) - outnames.append(MonitorPrefix); + outnames.append(GetMonitorPrefix()); outnames.append(iter->mName.c_str(), iter->mName.length()+1); } } @@ -2006,3 +2288,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/libs/openal-soft/alc/backends/pipewire.h b/libs/openal-soft/alc/backends/pipewire.h index 5f930239..5493684f 100644 --- a/libs/openal-soft/alc/backends/pipewire.h +++ b/libs/openal-soft/alc/backends/pipewire.h @@ -13,6 +13,8 @@ public: bool querySupport(BackendType type) override; + alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + std::string probe(BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override; diff --git a/libs/openal-soft/alc/backends/portaudio.cpp b/libs/openal-soft/alc/backends/portaudio.cpp index 9c94587d..0a646713 100644 --- a/libs/openal-soft/alc/backends/portaudio.cpp +++ b/libs/openal-soft/alc/backends/portaudio.cpp @@ -26,19 +26,23 @@ #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"; +using namespace std::string_view_literals; + +[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "PortAudio Default"sv; } #ifdef HAVE_DYNLOAD @@ -77,15 +81,8 @@ struct PortPlayback final : public BackendBase { int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept; - static int writeCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData) noexcept - { - return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); - } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -93,8 +90,6 @@ struct PortPlayback final : public BackendBase { PaStream *mStream{nullptr}; PaStreamParameters mParams{}; uint mUpdateSize{0u}; - - DEF_NEWDEL(PortPlayback) }; PortPlayback::~PortPlayback() @@ -115,16 +110,16 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f } -void PortPlayback::open(const char *name) +void PortPlayback::open(std::string_view name) { - if(!name) - name = pa_device; - else if(strcmp(name, pa_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDefaultName(); + else if(name != GetDefaultName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; PaStreamParameters params{}; - auto devidopt = ConfigValueInt(nullptr, "port", "device"); + auto devidopt = ConfigValueInt({}, "port", "device"); if(devidopt && *devidopt >= 0) params.device = *devidopt; else params.device = Pa_GetDefaultOutputDevice(); params.suggestedLatency = mDevice->BufferSize / static_cast(mDevice->Frequency); @@ -155,19 +150,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); @@ -217,7 +214,7 @@ bool PortPlayback::reset() void PortPlayback::start() { const PaError err{Pa_StartStream(mStream)}; - if(err == paNoError) + if(err != paNoError) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s", Pa_GetErrorText(err)}; } @@ -235,27 +232,18 @@ struct PortCapture final : public BackendBase { ~PortCapture() override; int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept; - static int readCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData) noexcept - { - return static_cast(userData)->readCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); - } + const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) const noexcept; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; PaStream *mStream{nullptr}; - PaStreamParameters mParams; + PaStreamParameters mParams{}; RingBufferPtr mRing{nullptr}; - - DEF_NEWDEL(PortCapture) }; PortCapture::~PortCapture() @@ -268,28 +256,27 @@ PortCapture::~PortCapture() int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept + const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) const noexcept { - mRing->write(inputBuffer, framesPerBuffer); + std::ignore = mRing->write(inputBuffer, framesPerBuffer); return 0; } -void PortCapture::open(const char *name) +void PortCapture::open(std::string_view name) { - if(!name) - name = pa_device; - else if(strcmp(name, pa_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDefaultName(); + else if(name != GetDefaultName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; - uint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); - uint frame_size{mDevice->frameSizeFromFmt()}; + const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; + const uint frame_size{mDevice->frameSizeFromFmt()}; mRing = RingBuffer::Create(samples, frame_size, false); - auto devidopt = ConfigValueInt(nullptr, "port", "capture"); + auto devidopt = ConfigValueInt({}, "port", "capture"); if(devidopt && *devidopt >= 0) mParams.device = *devidopt; else mParams.device = Pa_GetDefaultOutputDevice(); mParams.suggestedLatency = 0.0f; @@ -319,8 +306,15 @@ void PortCapture::open(const char *name) } mParams.channelCount = static_cast(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)}; @@ -348,16 +342,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) { @@ -396,7 +388,8 @@ bool PortBackendFactory::init() LOAD_FUNC(Pa_GetStreamInfo); #undef LOAD_FUNC - if((err=Pa_Initialize()) != paNoError) + const PaError err{Pa_Initialize()}; + if(err != paNoError) { ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); CloseLib(pa_handle); @@ -405,7 +398,8 @@ bool PortBackendFactory::init() } } #else - if((err=Pa_Initialize()) != paNoError) + const PaError err{Pa_Initialize()}; + if(err != paNoError) { ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); return false; @@ -419,16 +413,14 @@ bool PortBackendFactory::querySupport(BackendType type) std::string PortBackendFactory::probe(BackendType type) { - std::string outnames; switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - outnames.append(pa_device, sizeof(pa_device)); - break; + /* Include null char. */ + return std::string{GetDefaultName()} + '\0'; } - return outnames; + return std::string{}; } BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/pulseaudio.cpp b/libs/openal-soft/alc/backends/pulseaudio.cpp index 67e00234..b3e0a1bb 100644 --- a/libs/openal-soft/alc/backends/pulseaudio.cpp +++ b/libs/openal-soft/alc/backends/pulseaudio.cpp @@ -28,34 +28,29 @@ #include #include #include -#include +#include +#include #include -#include #include #include -#include -#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 "alspan.h" +#include "alstring.h" #include "core/devformat.h" #include "core/device.h" -#include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "opthelpers.h" #include "strutils.h" -#include "vector.h" #include @@ -66,17 +61,13 @@ using uint = unsigned int; #ifdef HAVE_DYNLOAD #define PULSE_FUNCS(MAGIC) \ - MAGIC(pa_mainloop_new); \ - MAGIC(pa_mainloop_free); \ - MAGIC(pa_mainloop_set_poll_func); \ - MAGIC(pa_mainloop_run); \ - MAGIC(pa_mainloop_quit); \ - MAGIC(pa_mainloop_get_api); \ MAGIC(pa_context_new); \ MAGIC(pa_context_unref); \ MAGIC(pa_context_get_state); \ MAGIC(pa_context_disconnect); \ MAGIC(pa_context_set_state_callback); \ + MAGIC(pa_context_set_subscribe_callback); \ + MAGIC(pa_context_subscribe); \ MAGIC(pa_context_errno); \ MAGIC(pa_context_connect); \ MAGIC(pa_context_get_server_info); \ @@ -112,6 +103,15 @@ using uint = unsigned int; MAGIC(pa_stream_disconnect); \ MAGIC(pa_stream_set_buffer_attr_callback); \ MAGIC(pa_stream_begin_write); \ + MAGIC(pa_threaded_mainloop_free); \ + MAGIC(pa_threaded_mainloop_get_api); \ + MAGIC(pa_threaded_mainloop_lock); \ + MAGIC(pa_threaded_mainloop_new); \ + MAGIC(pa_threaded_mainloop_signal); \ + MAGIC(pa_threaded_mainloop_start); \ + MAGIC(pa_threaded_mainloop_stop); \ + MAGIC(pa_threaded_mainloop_unlock); \ + MAGIC(pa_threaded_mainloop_wait); \ MAGIC(pa_channel_map_init_auto); \ MAGIC(pa_channel_map_parse); \ MAGIC(pa_channel_map_snprint); \ @@ -134,17 +134,13 @@ PULSE_FUNCS(MAKE_FUNC) #undef MAKE_FUNC #ifndef IN_IDE_PARSER -#define pa_mainloop_new ppa_mainloop_new -#define pa_mainloop_free ppa_mainloop_free -#define pa_mainloop_set_poll_func ppa_mainloop_set_poll_func -#define pa_mainloop_run ppa_mainloop_run -#define pa_mainloop_quit ppa_mainloop_quit -#define pa_mainloop_get_api ppa_mainloop_get_api #define pa_context_new ppa_context_new #define pa_context_unref ppa_context_unref #define pa_context_get_state ppa_context_get_state #define pa_context_disconnect ppa_context_disconnect #define pa_context_set_state_callback ppa_context_set_state_callback +#define pa_context_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 @@ -176,6 +172,15 @@ PULSE_FUNCS(MAKE_FUNC) #define pa_stream_get_latency ppa_stream_get_latency #define pa_stream_set_buffer_attr_callback ppa_stream_set_buffer_attr_callback #define pa_stream_begin_write ppa_stream_begin_write +#define pa_threaded_mainloop_free ppa_threaded_mainloop_free +#define pa_threaded_mainloop_get_api ppa_threaded_mainloop_get_api +#define pa_threaded_mainloop_lock ppa_threaded_mainloop_lock +#define pa_threaded_mainloop_new ppa_threaded_mainloop_new +#define pa_threaded_mainloop_signal ppa_threaded_mainloop_signal +#define pa_threaded_mainloop_start ppa_threaded_mainloop_start +#define pa_threaded_mainloop_stop ppa_threaded_mainloop_stop +#define pa_threaded_mainloop_unlock ppa_threaded_mainloop_unlock +#define pa_threaded_mainloop_wait ppa_threaded_mainloop_wait #define pa_channel_map_init_auto ppa_channel_map_init_auto #define pa_channel_map_parse ppa_channel_map_parse #define pa_channel_map_snprint ppa_channel_map_snprint @@ -234,29 +239,45 @@ constexpr pa_channel_map MonoChanMap{ PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT } +}, X714ChanMap{ + 12, { + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT, + PA_CHANNEL_POSITION_TOP_FRONT_LEFT, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, + PA_CHANNEL_POSITION_TOP_REAR_LEFT, PA_CHANNEL_POSITION_TOP_REAR_RIGHT + } }; /* *grumble* Don't use enums for bitflags. */ -constexpr inline pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) -{ return pa_stream_flags_t(int(lhs) | int(rhs)); } -inline pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) +constexpr pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) +{ return pa_stream_flags_t(lhs | al::to_underlying(rhs)); } +constexpr pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) { lhs = lhs | rhs; return lhs; } -inline pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, int rhs) +constexpr pa_stream_flags_t operator~(pa_stream_flags_t flag) +{ return pa_stream_flags_t(~al::to_underlying(flag)); } +constexpr pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) { - lhs = pa_stream_flags_t(int(lhs) & rhs); + lhs = pa_stream_flags_t(al::to_underlying(lhs) & rhs); return lhs; } -inline pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs) +constexpr pa_context_flags_t operator|(pa_context_flags_t lhs, pa_context_flags_t rhs) +{ return pa_context_flags_t(lhs | al::to_underlying(rhs)); } +constexpr pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs) { - lhs = pa_context_flags_t(int(lhs) | int(rhs)); + lhs = lhs | rhs; 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; @@ -269,110 +290,64 @@ 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 */ pa_context_flags_t pulse_ctx_flags; class PulseMainloop { - std::thread mThread; - std::mutex mMutex; - std::condition_variable mCondVar; - pa_mainloop *mMainloop{nullptr}; - - static int poll(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) noexcept - { - auto plock = static_cast*>(userdata); - plock->unlock(); - int r{::poll(ufds, nfds, timeout)}; - plock->lock(); - return r; - } - - int mainloop_proc() - { - SetRTPriority(); - - std::unique_lock plock{mMutex}; - mMainloop = pa_mainloop_new(); - - pa_mainloop_set_poll_func(mMainloop, poll, &plock); - mCondVar.notify_all(); - - int ret{}; - pa_mainloop_run(mMainloop, &ret); - - pa_mainloop_free(mMainloop); - mMainloop = nullptr; - - return ret; - } + pa_threaded_mainloop *mLoop{}; + pa_context *mContext{}; public: - ~PulseMainloop() + 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(); + + PulseMainloop& operator=(const PulseMainloop&) = delete; + PulseMainloop& operator=(PulseMainloop&& rhs) noexcept + { std::swap(mLoop, rhs.mLoop); return *this; } + PulseMainloop& operator=(std::nullptr_t) noexcept { - if(mThread.joinable()) - { - { - std::lock_guard _{mMutex}; - pa_mainloop_quit(mMainloop, 0); - } - mThread.join(); - } + if(mLoop) + pa_threaded_mainloop_free(mLoop); + mLoop = nullptr; + return *this; } - std::unique_lock getUniqueLock() { return std::unique_lock{mMutex}; } - std::condition_variable &getCondVar() noexcept { return mCondVar; } + explicit operator bool() const noexcept { return mLoop != nullptr; } - void contextStateCallback(pa_context *context) noexcept - { - pa_context_state_t state{pa_context_get_state(context)}; - if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) - mCondVar.notify_all(); - } - static void contextStateCallbackC(pa_context *context, void *pdata) noexcept - { static_cast(pdata)->contextStateCallback(context); } + [[nodiscard]] + auto start() const { return pa_threaded_mainloop_start(mLoop); } + auto stop() const { return pa_threaded_mainloop_stop(mLoop); } - void streamStateCallback(pa_stream *stream) noexcept - { - pa_stream_state_t state{pa_stream_get_state(stream)}; - if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) - mCondVar.notify_all(); - } - static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast(pdata)->streamStateCallback(stream); } + [[nodiscard]] auto getApi() const { return pa_threaded_mainloop_get_api(mLoop); } + [[nodiscard]] auto getContext() const noexcept { return mContext; } - void streamSuccessCallback(pa_stream*, int) noexcept - { mCondVar.notify_all(); } + auto lock() const { return pa_threaded_mainloop_lock(mLoop); } + auto unlock() const { return pa_threaded_mainloop_unlock(mLoop); } + + auto signal(bool wait=false) const { return pa_threaded_mainloop_signal(mLoop, wait); } + + static auto Create() { return PulseMainloop{pa_threaded_mainloop_new()}; } + + + 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 waitForOperation(pa_operation *op, std::unique_lock &plock) - { - if(op) - { - mCondVar.wait(plock, - [op]() -> bool { return pa_operation_get_state(op) != PA_OPERATION_RUNNING; }); - pa_operation_unref(op); - } - } - - pa_context *connectContext(std::unique_lock &plock); - - pa_stream *connectStream(const char *device_name, std::unique_lock &plock, - pa_context *context, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, - pa_channel_map *chanmap, BackendType type); - - void close(pa_context *context, pa_stream *stream); + void 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) { - mCondVar.notify_all(); + signal(); return; } @@ -398,14 +373,12 @@ public: TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); } - static void deviceSinkCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept - { static_cast(pdata)->deviceSinkCallback(context, info, eol); } - 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) { - mCondVar.notify_all(); + signal(); return; } @@ -431,67 +404,150 @@ public: TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); } - static void deviceSourceCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept - { static_cast(pdata)->deviceSourceCallback(context, info, eol); } void probePlaybackDevices(); void probeCaptureDevices(); + + friend struct MainloopUniqueLock; }; +struct MainloopUniqueLock : public std::unique_lock { + using std::unique_lock::unique_lock; + MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default; + auto wait() const -> void + { pa_threaded_mainloop_wait(mutex()->mLoop); } -pa_context *PulseMainloop::connectContext(std::unique_lock &plock) -{ - if(!mMainloop) + template + auto wait(Predicate done_waiting) const -> void + { while(!done_waiting()) wait(); } + + void waitForOperation(pa_operation *op) const { - mThread = std::thread{std::mem_fn(&PulseMainloop::mainloop_proc), this}; - mCondVar.wait(plock, [this]() noexcept { return mMainloop; }); - } - - pa_context *context{pa_context_new(pa_mainloop_get_api(mMainloop), nullptr)}; - if(!context) throw al::backend_exception{al::backend_error::OutOfMemory, - "pa_context_new() failed"}; - - pa_context_set_state_callback(context, &contextStateCallbackC, this); - - int err; - if((err=pa_context_connect(context, nullptr, pulse_ctx_flags, nullptr)) >= 0) - { - pa_context_state_t state; - while((state=pa_context_get_state(context)) != PA_CONTEXT_READY) + if(op) { - if(!PA_CONTEXT_IS_GOOD(state)) - { - err = pa_context_errno(context); - if(err > 0) err = -err; - break; - } - - mCondVar.wait(plock); + wait([op]{ return pa_operation_get_state(op) != PA_OPERATION_RUNNING; }); + pa_operation_unref(op); } } - pa_context_set_state_callback(context, nullptr, nullptr); + + + 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)}; + if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) + mutex()->signal(); + } + + void streamStateCallback(pa_stream *stream) noexcept + { + pa_stream_state_t state{pa_stream_get_state(stream)}; + if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) + mutex()->signal(); + } + + 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; + +PulseMainloop::~PulseMainloop() +{ + 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(mutex()->mContext, [](pa_context *ctx, void *pdata) noexcept + { return static_cast(pdata)->contextStateCallback(ctx); }, this); + + int err{pa_context_connect(mutex()->mContext, nullptr, pulse_ctx_flags, nullptr)}; + if(err >= 0) + { + wait([&err,this]() + { + pa_context_state_t state{pa_context_get_state(mutex()->mContext)}; + if(!PA_CONTEXT_IS_GOOD(state)) + { + err = pa_context_errno(mutex()->mContext); + if(err > 0) err = -err; + return true; + } + return state == PA_CONTEXT_READY; + }); + } + 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 *PulseMainloop::connectStream(const char *device_name, - std::unique_lock &plock, pa_context *context, pa_stream_flags_t flags, +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, &streamStateCallbackC, this); + pa_stream_set_state_callback(stream, [](pa_stream *strm, void *pdata) noexcept + { return static_cast(pdata)->streamStateCallback(strm); }, this); int err{(type==BackendType::Playback) ? pa_stream_connect_playback(stream, device_name, attr, flags, nullptr, nullptr) : @@ -503,93 +559,75 @@ pa_stream *PulseMainloop::connectStream(const char *device_name, 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; + }); - mCondVar.wait(plock); - } 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) { - std::lock_guard _{mMutex}; - if(stream) - { - pa_stream_set_state_callback(stream, nullptr, nullptr); - pa_stream_set_moved_callback(stream, nullptr, nullptr); - pa_stream_set_write_callback(stream, nullptr, nullptr); - pa_stream_set_buffer_attr_callback(stream, nullptr, nullptr); - pa_stream_disconnect(stream); - pa_stream_unref(stream); - } + 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{}; - pa_stream *stream{}; - PlaybackDevices.clear(); try { - std::unique_lock plock{mMutex}; + MainloopUniqueLock plock{*this}; + 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 = connectContext(plock); - pa_operation *op{pa_context_get_sink_info_by_name(context, nullptr, - &deviceSinkCallbackC, this)}; - waitForOperation(op, plock); + 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, &deviceSinkCallbackC, this); - waitForOperation(op, plock); - - pa_context_disconnect(context); - pa_context_unref(context); - context = nullptr; + op = pa_context_get_sink_info_list(mContext, sink_callback, this); + plock.waitForOperation(op); } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) close(context, stream); } } void PulseMainloop::probeCaptureDevices() { - pa_context *context{}; - pa_stream *stream{}; - CaptureDevices.clear(); try { - std::unique_lock plock{mMutex}; + MainloopUniqueLock plock{*this}; + 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 = connectContext(plock); - pa_operation *op{pa_context_get_source_info_by_name(context, nullptr, - &deviceSourceCallbackC, this)}; - waitForOperation(op, plock); + 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, &deviceSourceCallbackC, this); - waitForOperation(op, plock); - - pa_context_disconnect(context); - pa_context_unref(context); - context = nullptr; + op = pa_context_get_source_info_list(mContext, src_callback, this); + plock.waitForOperation(op); } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) close(context, stream); } } @@ -603,30 +641,13 @@ struct PulsePlayback final : public BackendBase { ~PulsePlayback() override; void bufferAttrCallback(pa_stream *stream) noexcept; - static void bufferAttrCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast(pdata)->bufferAttrCallback(stream); } - void streamStateCallback(pa_stream *stream) noexcept; - static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast(pdata)->streamStateCallback(stream); } - void streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept; - static void streamWriteCallbackC(pa_stream *stream, size_t nbytes, void *pdata) noexcept - { static_cast(pdata)->streamWriteCallback(stream, nbytes); } - void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept; - static void sinkInfoCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept - { static_cast(pdata)->sinkInfoCallback(context, info, eol); } - void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept; - static void sinkNameCallbackC(pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept - { static_cast(pdata)->sinkNameCallback(context, info, eol); } - void streamMovedCallback(pa_stream *stream) noexcept; - static void streamMovedCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast(pdata)->streamMovedCallback(stream); } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -634,29 +655,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 @@ -677,7 +688,7 @@ void PulsePlayback::streamStateCallback(pa_stream *stream) noexcept ERR("Received stream failure!\n"); mDevice->handleDisconnect("Playback stream failure"); } - mMainloop.getCondVar().notify_all(); + mMainloop.signal(); } void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept @@ -685,21 +696,21 @@ void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexce do { pa_free_cb_t free_func{nullptr}; auto buflen = static_cast(-1); - void *buf; - if UNLIKELY(pa_stream_begin_write(stream, &buf, &buflen) || !buf) + void *buf{}; + if(pa_stream_begin_write(stream, &buf, &buflen) || !buf) UNLIKELY { buflen = nbytes; buf = pa_xmalloc(buflen); 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); int ret{pa_stream_write(stream, buf, buflen, free_func, 0, PA_SEEK_RELATIVE)}; - if UNLIKELY(ret != PA_OK) + if(ret != PA_OK) UNLIKELY ERR("Failed to write to stream: %d, %s\n", ret, pa_strerror(ret)); } while(nbytes > 0); } @@ -711,7 +722,8 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int pa_channel_map map; bool is_51rear; }; - static constexpr std::array chanmaps{{ + static constexpr std::array chanmaps{{ + { DevFmtX714, X714ChanMap, false }, { DevFmtX71, X71ChanMap, false }, { DevFmtX61, X61ChanMap, false }, { DevFmtX51, X51ChanMap, false }, @@ -723,7 +735,7 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int if(eol) { - mMainloop.getCondVar().notify_all(); + mMainloop.signal(); return; } @@ -740,9 +752,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) @@ -755,7 +767,7 @@ void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int { if(eol) { - mMainloop.getCondVar().notify_all(); + mMainloop.signal(); return; } mDevice->DeviceName = info->description; @@ -768,32 +780,36 @@ void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept } -void PulsePlayback::open(const char *name) +void PulsePlayback::open(std::string_view name) { - const char *pulse_name{nullptr}; - const char *dev_name{nullptr}; + mMainloop = PulseMainloop::Create(); + if(mMainloop.start() != 0) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start device mainloop"}; - if(name) + const char *pulse_name{nullptr}; + 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; } - auto plock = mMainloop.getUniqueLock(); - if(!mContext) - mContext = mMainloop.connectContext(plock); + MainloopUniqueLock plock{mMainloop}; + 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", 1)) + if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; pa_sample_spec spec{}; @@ -807,36 +823,30 @@ void PulsePlayback::open(const char *name) if(defname) pulse_name = defname->c_str(); } TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - pa_stream *stream{mMainloop.connectStream(pulse_name, plock, mContext, flags, nullptr, &spec, - nullptr, BackendType::Playback)}; - if(mStream) - { - pa_stream_set_state_callback(mStream, nullptr, nullptr); - pa_stream_set_moved_callback(mStream, nullptr, nullptr); - pa_stream_set_write_callback(mStream, nullptr, nullptr); - pa_stream_set_buffer_attr_callback(mStream, nullptr, nullptr); - pa_stream_disconnect(mStream); - pa_stream_unref(mStream); - } - mStream = stream; + mStream = plock.connectStream(pulse_name, flags, nullptr, &spec, nullptr, + BackendType::Playback); - pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); + pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamMovedCallback(stream); }, this); mFrameSize = static_cast(pa_frame_size(pa_stream_get_sample_spec(mStream))); - mDeviceName = pulse_name ? al::make_optional(pulse_name) : al::nullopt; - if(!dev_name) + if(pulse_name) mDeviceName.emplace(pulse_name); + else mDeviceName.reset(); + if(display_name.empty()) { - pa_operation *op{pa_context_get_sink_info_by_name(mContext, - pa_stream_get_device_name(mStream), &PulsePlayback::sinkNameCallbackC, this)}; - mMainloop.waitForOperation(op, plock); + 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(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() { - auto plock = mMainloop.getUniqueLock(); + MainloopUniqueLock plock{mMainloop}; const auto deviceName = mDeviceName ? mDeviceName->c_str() : nullptr; if(mStream) @@ -850,15 +860,17 @@ bool PulsePlayback::reset() mStream = nullptr; } - pa_operation *op{pa_context_get_sink_info_by_name(mContext, deviceName, - &PulsePlayback::sinkInfoCallbackC, this)}; - mMainloop.waitForOperation(op, plock); + auto info_callback = [](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(mMainloop.getContext(), deviceName, + info_callback, 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", 1)) + if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "adjust-latency", 0)) + 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, @@ -867,7 +879,7 @@ bool PulsePlayback::reset() flags &= ~PA_STREAM_EARLY_REQUESTS; flags |= PA_STREAM_ADJUST_LATENCY; } - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", 0) + if(GetConfigValueBool(mDevice->DeviceName, "pulse", "fix-rate", false) || !mDevice->Flags.test(FrequencyRequest)) flags |= PA_STREAM_FIX_RATE; @@ -893,8 +905,12 @@ bool PulsePlayback::reset() chanmap = X61ChanMap; break; case DevFmtX71: + case DevFmtX3D71: chanmap = X71ChanMap; break; + case DevFmtX714: + chanmap = X714ChanMap; + break; } setDefaultWFXChannelOrder(); @@ -934,11 +950,13 @@ bool PulsePlayback::reset() mAttr.minreq = mDevice->UpdateSize * frame_size; mAttr.fragsize = ~0u; - mStream = mMainloop.connectStream(deviceName, plock, mContext, flags, &mAttr, &mSpec, - &chanmap, BackendType::Playback); + mStream = plock.connectStream(deviceName, flags, &mAttr, &mSpec, &chanmap, + BackendType::Playback); - pa_stream_set_state_callback(mStream, &PulsePlayback::streamStateCallbackC, this); - pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); + 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); mSpec = *(pa_stream_get_sample_spec(mStream)); mFrameSize = static_cast(pa_frame_size(&mSpec)); @@ -949,24 +967,26 @@ 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); - mMainloop.waitForOperation(op, plock); + plock.waitForOperation(op); mDevice->Frequency = mSpec.rate; } - pa_stream_set_buffer_attr_callback(mStream, &PulsePlayback::bufferAttrCallbackC, this); + 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); mDevice->BufferSize = mAttr.tlength / mFrameSize; @@ -977,46 +997,33 @@ bool PulsePlayback::reset() void PulsePlayback::start() { - auto plock = mMainloop.getUniqueLock(); + MainloopUniqueLock plock{mMainloop}; - /* Write some (silent) samples to fill the buffer before we start feeding - * it newly mixed samples. + /* Write some samples to fill the buffer before we start feeding it newly + * mixed samples. */ if(size_t todo{pa_stream_writable_size(mStream)}) { void *buf{pa_xmalloc(todo)}; - switch(mSpec.format) - { - case PA_SAMPLE_U8: - std::fill_n(static_cast(buf), todo, 0x80); - break; - case PA_SAMPLE_ALAW: - std::fill_n(static_cast(buf), todo, 0xD5); - break; - case PA_SAMPLE_ULAW: - std::fill_n(static_cast(buf), todo, 0x7f); - break; - default: - std::fill_n(static_cast(buf), todo, 0x00); - break; - } + mDevice->renderSamples(buf, static_cast(todo/mFrameSize), mSpec.channels); pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE); } - pa_stream_set_write_callback(mStream, &PulsePlayback::streamWriteCallbackC, this); + pa_stream_set_write_callback(mStream, [](pa_stream *stream, size_t nbytes, void *pdata)noexcept + { return static_cast(pdata)->streamWriteCallback(stream, nbytes); }, this); pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; - mMainloop.waitForOperation(op, plock); + plock.waitForOperation(op); } void PulsePlayback::stop() { - auto plock = mMainloop.getUniqueLock(); + MainloopUniqueLock plock{mMainloop}; pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; - mMainloop.waitForOperation(op, plock); + plock.waitForOperation(op); pa_stream_set_write_callback(mStream, nullptr, nullptr); } @@ -1024,16 +1031,16 @@ void PulsePlayback::stop() ClockLatency PulsePlayback::getClockLatency() { ClockLatency ret; - pa_usec_t latency; - int neg, err; + pa_usec_t latency{}; + int neg{}, err{}; { - auto plock = mMainloop.getUniqueLock(); - ret.ClockTime = GetDeviceClockTime(mDevice); + MainloopUniqueLock plock{mMainloop}; + ret.ClockTime = mDevice->getClockTime(); err = pa_stream_get_latency(mStream, &latency, &neg); } - if UNLIKELY(err != 0) + if(err != 0) UNLIKELY { /* If err = -PA_ERR_NODATA, it means we were called too soon after * starting the stream and no timing info has been received from the @@ -1044,7 +1051,7 @@ ClockLatency PulsePlayback::getClockLatency() latency = mDevice->BufferSize - mDevice->UpdateSize; neg = 0; } - else if UNLIKELY(neg) + else if(neg) UNLIKELY latency = 0; ret.Latency = std::chrono::microseconds{latency}; @@ -1057,52 +1064,35 @@ struct PulseCapture final : public BackendBase { ~PulseCapture() override; void streamStateCallback(pa_stream *stream) noexcept; - static void streamStateCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast(pdata)->streamStateCallback(stream); } - void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol) noexcept; - static void sourceNameCallbackC(pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept - { static_cast(pdata)->sourceNameCallback(context, info, eol); } - void streamMovedCallback(pa_stream *stream) noexcept; - static void streamMovedCallbackC(pa_stream *stream, void *pdata) noexcept - { static_cast(pdata)->streamMovedCallback(stream); } - 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; + size_t mHoleLength{0}; + size_t mPacketLength{0}; uint mLastReadable{0u}; - al::byte mSilentVal{}; - - al::span mCapBuffer; - ssize_t mCapLen{0}; + 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 @@ -1112,14 +1102,14 @@ void PulseCapture::streamStateCallback(pa_stream *stream) noexcept ERR("Received stream failure!\n"); mDevice->handleDisconnect("Capture stream failure"); } - mMainloop.getCondVar().notify_all(); + mMainloop.signal(); } void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, int eol) noexcept { if(eol) { - mMainloop.getCondVar().notify_all(); + mMainloop.signal(); return; } mDevice->DeviceName = info->description; @@ -1132,25 +1122,34 @@ 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(); + 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; } - auto plock = mMainloop.getUniqueLock(); - mContext = mMainloop.connectContext(plock); + MainloopUniqueLock plock{mMainloop}; + plock.connectContext(); pa_channel_map chanmap{}; switch(mDevice->FmtChans) @@ -1173,6 +1172,10 @@ void PulseCapture::open(const char *name) case DevFmtX71: chanmap = X71ChanMap; break; + case DevFmtX714: + chanmap = X714ChanMap; + break; + case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; @@ -1182,7 +1185,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: @@ -1206,103 +1209,116 @@ 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", 1)) + if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = mMainloop.connectStream(pulse_name, plock, mContext, flags, &mAttr, &mSpec, &chanmap, + mStream = plock.connectStream(pulse_name, flags, &mAttr, &mSpec, &chanmap, BackendType::Capture); - pa_stream_set_moved_callback(mStream, &PulseCapture::streamMovedCallbackC, this); - pa_stream_set_state_callback(mStream, &PulseCapture::streamStateCallbackC, this); + 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); - mDeviceName = pulse_name ? al::make_optional(pulse_name) : al::nullopt; + if(pulse_name) mDeviceName.emplace(pulse_name); + else mDeviceName.reset(); if(mDevice->DeviceName.empty()) { - pa_operation *op{pa_context_get_source_info_by_name(mContext, - pa_stream_get_device_name(mStream), &PulseCapture::sourceNameCallbackC, this)}; - mMainloop.waitForOperation(op, plock); + 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(mMainloop.getContext(), + pa_stream_get_device_name(mStream), name_callback, this)}; + plock.waitForOperation(op); } } void PulseCapture::start() { - auto plock = mMainloop.getUniqueLock(); + MainloopUniqueLock plock{mMainloop}; pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; - mMainloop.waitForOperation(op, plock); + plock.waitForOperation(op); } void PulseCapture::stop() { - auto plock = mMainloop.getUniqueLock(); + MainloopUniqueLock plock{mMainloop}; pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; - mMainloop.waitForOperation(op, plock); + 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 */ + * that's available. + */ mLastReadable -= static_cast(dstbuf.size()); while(!dstbuf.empty()) { + if(mHoleLength > 0) UNLIKELY + { + const size_t rem{std::min(dstbuf.size(), mHoleLength)}; + std::fill_n(dstbuf.begin(), rem, mSilentVal); + dstbuf = dstbuf.subspan(rem); + mHoleLength -= rem; + + continue; + } if(!mCapBuffer.empty()) { - const size_t rem{minz(dstbuf.size(), mCapBuffer.size())}; - if UNLIKELY(mCapLen < 0) - std::fill_n(dstbuf.begin(), rem, mSilentVal); - else - std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin()); + 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); continue; } - if UNLIKELY(!mDevice->Connected.load(std::memory_order_acquire)) + if(!mDevice->Connected.load(std::memory_order_acquire)) UNLIKELY break; - auto plock = mMainloop.getUniqueLock(); - if(mCapLen != 0) + MainloopUniqueLock plock{mMainloop}; + if(mPacketLength > 0) { pa_stream_drop(mStream); - mCapBuffer = {}; - mCapLen = 0; + mPacketLength = 0; } + const pa_stream_state_t state{pa_stream_get_state(mStream)}; - if UNLIKELY(!PA_STREAM_IS_GOOD(state)) + if(!PA_STREAM_IS_GOOD(state)) UNLIKELY { mDevice->handleDisconnect("Bad capture state: %u", state); break; } - const void *capbuf; - size_t caplen; - if UNLIKELY(pa_stream_peek(mStream, &capbuf, &caplen) < 0) + + 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(); if(caplen == 0) break; - if UNLIKELY(!capbuf) - mCapLen = -static_cast(caplen); + if(!capbuf) UNLIKELY + mHoleLength = caplen; else - mCapLen = static_cast(caplen); - mCapBuffer = {static_cast(capbuf), caplen}; + mCapBuffer = {static_cast(capbuf), caplen}; + mPacketLength = caplen; } if(!dstbuf.empty()) std::fill(dstbuf.begin(), dstbuf.end(), mSilentVal); @@ -1310,13 +1326,13 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) uint PulseCapture::availableSamples() { - size_t readable{mCapBuffer.size()}; + size_t readable{std::max(mCapBuffer.size(), mHoleLength)}; if(mDevice->Connected.load(std::memory_order_acquire)) { - auto plock = mMainloop.getUniqueLock(); + MainloopUniqueLock plock{mMainloop}; size_t got{pa_stream_readable_size(mStream)}; - if UNLIKELY(static_cast(got) < 0) + if(static_cast(got) < 0) UNLIKELY { const char *err{pa_strerror(static_cast(got))}; ERR("pa_stream_readable_size() failed: %s\n", err); @@ -1324,11 +1340,17 @@ uint PulseCapture::availableSamples() } else { - const auto caplen = static_cast(std::abs(mCapLen)); - if(got > caplen) readable += got - caplen; + /* "readable" is the number of bytes from the last packet that have + * not yet been read by the caller. So add the stream's readable + * size excluding the last packet (the stream size includes the + * last packet until it's dropped). + */ + if(got > mPacketLength) + readable += got - mPacketLength; } } + /* Avoid uint overflow, and avoid decreasing the readable count. */ readable = std::min(readable, std::numeric_limits::max()); mLastReadable = std::max(mLastReadable, static_cast(readable)); return mLastReadable / static_cast(pa_frame_size(&mSpec)); @@ -1338,22 +1360,22 @@ uint PulseCapture::availableSamples() ClockLatency PulseCapture::getClockLatency() { ClockLatency ret; - pa_usec_t latency; - int neg, err; + pa_usec_t latency{}; + int neg{}, err{}; { - auto plock = mMainloop.getUniqueLock(); - ret.ClockTime = GetDeviceClockTime(mDevice); + MainloopUniqueLock plock{mMainloop}; + ret.ClockTime = mDevice->getClockTime(); err = pa_stream_get_latency(mStream, &latency, &neg); } - if UNLIKELY(err != 0) + if(err != 0) UNLIKELY { ERR("Failed to get stream latency: 0x%x\n", err); latency = 0; neg = 0; } - else if UNLIKELY(neg) + else if(neg) UNLIKELY latency = 0; ret.Latency = std::chrono::microseconds{latency}; @@ -1368,9 +1390,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__) @@ -1385,17 +1404,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); @@ -1406,14 +1423,23 @@ bool PulseBackendFactory::init() #endif /* HAVE_DYNLOAD */ pulse_ctx_flags = PA_CONTEXT_NOFLAGS; - if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", 1)) + if(!GetConfigValueBool({}, "pulse", "spawn-server", false)) pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN; try { - auto plock = gGlobalMainloop.getUniqueLock(); - pa_context *context{gGlobalMainloop.connectContext(plock)}; - pa_context_disconnect(context); - pa_context_unref(context); + if(!gGlobalMainloop) + { + gGlobalMainloop = PulseMainloop::Create(); + if(gGlobalMainloop.start() != 0) + { + gGlobalMainloop = nullptr; + return false; + } + } + + MainloopUniqueLock plock{gGlobalMainloop}; + plock.connectContext(); + plock.setEventHandler(); return true; } catch(...) { @@ -1466,3 +1492,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/libs/openal-soft/alc/backends/pulseaudio.h b/libs/openal-soft/alc/backends/pulseaudio.h index 6690fe8a..4752a891 100644 --- a/libs/openal-soft/alc/backends/pulseaudio.h +++ b/libs/openal-soft/alc/backends/pulseaudio.h @@ -9,6 +9,8 @@ public: bool querySupport(BackendType type) override; + alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + std::string probe(BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override; diff --git a/libs/openal-soft/alc/backends/sdl2.cpp b/libs/openal-soft/alc/backends/sdl2.cpp index c0726033..17ef502a 100644 --- a/libs/openal-soft/alc/backends/sdl2.cpp +++ b/libs/openal-soft/alc/backends/sdl2.cpp @@ -26,13 +26,17 @@ #include #include #include +#include #include "almalloc.h" #include "alnumeric.h" #include "core/device.h" #include "core/logging.h" -#include +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") +#include "SDL.h" +_Pragma("GCC diagnostic pop") namespace { @@ -43,17 +47,17 @@ namespace { #define DEVNAME_PREFIX "" #endif -constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device"; +constexpr auto getDevicePrefix() noexcept -> std::string_view { return DEVNAME_PREFIX; } +constexpr auto getDefaultDeviceName() noexcept -> std::string_view +{ return DEVNAME_PREFIX "Default Device"; } struct Sdl2Backend final : public BackendBase { Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { } ~Sdl2Backend() override; void audioCallback(Uint8 *stream, int len) noexcept; - static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept - { static_cast(ptr)->audioCallback(stream, len); } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -65,8 +69,6 @@ struct Sdl2Backend final : public BackendBase { DevFmtChannels mFmtChans{}; DevFmtType mFmtType{}; uint mUpdateSize{0u}; - - DEF_NEWDEL(Sdl2Backend) }; Sdl2Backend::~Sdl2Backend() @@ -83,7 +85,7 @@ void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt()); } -void Sdl2Backend::open(const char *name) +void Sdl2Backend::open(std::string_view name) { SDL_AudioSpec want{}, have{}; @@ -99,24 +101,39 @@ void Sdl2Backend::open(const char *name) case DevFmtFloat: want.format = AUDIO_F32; break; } want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; - want.samples = static_cast(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()}; @@ -158,7 +175,7 @@ void Sdl2Backend::open(const char *name) mFmtType = devtype; mUpdateSize = have.samples; - mDevice->DeviceName = name ? name : defaultDeviceName; + mDevice->DeviceName = name; } bool Sdl2Backend::reset() @@ -202,13 +219,16 @@ std::string SDL2BackendFactory::probe(BackendType type) int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)}; /* Includes null char. */ - outnames.append(defaultDeviceName, sizeof(defaultDeviceName)); + outnames += getDefaultDeviceName(); + outnames += '\0'; for(int i{0};i < num_devices;++i) { - std::string name{DEVNAME_PREFIX}; - name += SDL_GetAudioDeviceName(i, SDL_FALSE); - if(!name.empty()) - outnames.append(name.c_str(), name.length()+1); + outnames += getDevicePrefix(); + if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE)) + outnames += name; + else + outnames += "Unknown Device Name #"+std::to_string(i); + outnames += '\0'; } return outnames; } diff --git a/libs/openal-soft/alc/backends/sndio.cpp b/libs/openal-soft/alc/backends/sndio.cpp index 48387c65..2985a2ae 100644 --- a/libs/openal-soft/alc/backends/sndio.cpp +++ b/libs/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 #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,21 +88,21 @@ int SndioPlayback::mixerProc() const size_t frameSize{frameStep * mDevice->bytesFromFmt()}; SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { - al::span buffer{mBuffer}; + al::span buffer{mBuffer}; mDevice->renderSamples(buffer.data(), static_cast(buffer.size() / frameSize), frameStep); while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire)) { size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())}; - if(wrote == 0) + if(wrote > buffer.size() || wrote == 0) { - ERR("sio_write failed\n"); + ERR("sio_write failed: 0x%" PRIx64 "\n", wrote); mDevice->handleDisconnect("Failed to write playback samples"); break; } @@ -112,13 +114,13 @@ int SndioPlayback::mixerProc() } -void SndioPlayback::open(const char *name) +void SndioPlayback::open(std::string_view name) { - if(!name) - name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDefaultName(); + else if(name != GetDefaultName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)}; if(!sndHandle) @@ -136,72 +138,75 @@ bool SndioPlayback::reset() SioPar par; auto tryfmt = mDevice->FmtType; -retry_params: - switch(tryfmt) + while(true) { - case DevFmtByte: - par.bits = 8; - par.sig = 1; - break; - case DevFmtUByte: - par.bits = 8; - par.sig = 0; - break; - case DevFmtShort: - par.bits = 16; - par.sig = 1; - break; - case DevFmtUShort: - par.bits = 16; - par.sig = 0; - break; - case DevFmtFloat: - case DevFmtInt: - par.bits = 32; - par.sig = 1; - break; - case DevFmtUInt: - par.bits = 32; - par.sig = 0; - break; - } - par.bps = SIO_BPS(par.bits); - par.le = SIO_LE_NATIVE; - par.msb = 1; + 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,11 +353,18 @@ 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())}; - if(got == 0) break; + if(got == 0) + break; + if(got > buffer.size()) + { + ERR("sio_read failed: 0x%" PRIx64 "\n", got); + mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got); + break; + } mRing->writeAdvance(got / frameSize); buffer = buffer.subspan(got); @@ -366,8 +377,8 @@ int SndioCapture::recordProc() if(buffer.empty()) { /* Got samples to read, but no place to store it. Drop it. */ - static char junk[4096]; - sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize)); + static std::array junk; + sio_read(mSndHandle, junk.data(), junk.size() - (junk.size()%frameSize)); } } @@ -375,13 +386,13 @@ int SndioCapture::recordProc() } -void SndioCapture::open(const char *name) +void SndioCapture::open(std::string_view name) { - if(!name) - name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDefaultName(); + else if(name != GetDefaultName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; mSndHandle = sio_open(nullptr, SIO_REC, true); if(mSndHandle == nullptr) @@ -424,12 +435,12 @@ void SndioCapture::open(const char *name) par.rchan = mDevice->channelsFromFmt(); par.rate = mDevice->Frequency; - par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10); - par.round = minu(par.appbufsz/2, mDevice->Frequency/40); + par.appbufsz = std::max(mDevice->BufferSize, mDevice->Frequency/10u); + par.round = std::min(par.appbufsz/2u, mDevice->Frequency/40u); if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to set device praameters"}; + "Failed to set device parameters"}; if(par.bps > 1 && par.le != SIO_LE_NATIVE) throw al::backend_exception{al::backend_error::DeviceError, @@ -454,7 +465,7 @@ void SndioCapture::open(const char *name) DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate}; - mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false); + mRing = RingBuffer::Create(mDevice->BufferSize, size_t{par.bps}*par.rchan, false); mDevice->BufferSize = static_cast(mRing->writeSpace()); mDevice->UpdateSize = par.round; @@ -489,8 +500,8 @@ void SndioCapture::stop() ERR("Error stopping device\n"); } -void SndioCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void SndioCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint SndioCapture::availableSamples() { return static_cast(mRing->readSpace()); } @@ -511,16 +522,14 @@ bool SndIOBackendFactory::querySupport(BackendType type) std::string SndIOBackendFactory::probe(BackendType type) { - std::string outnames; switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - outnames.append(sndio_device, sizeof(sndio_device)); - break; + /* Include null char. */ + return std::string{GetDefaultName()} + '\0'; } - return outnames; + return std::string{}; } BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/solaris.cpp b/libs/openal-soft/alc/backends/solaris.cpp index 791609ce..da2b4032 100644 --- a/libs/openal-soft/alc/backends/solaris.cpp +++ b/libs/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; } @@ -278,21 +277,17 @@ bool SolarisBackendFactory::querySupport(BackendType type) std::string SolarisBackendFactory::probe(BackendType type) { - std::string outnames; switch(type) { case BackendType::Playback: - { - struct stat buf; - if(stat(solaris_driver.c_str(), &buf) == 0) - outnames.append(solaris_device, sizeof(solaris_device)); - } - break; + if(struct stat buf{}; stat(solaris_driver.c_str(), &buf) == 0) + return std::string{GetDefaultName()} + '\0'; + break; case BackendType::Capture: break; } - return outnames; + return std::string{}; } BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/wasapi.cpp b/libs/openal-soft/alc/backends/wasapi.cpp index 063fca98..d32fc775 100644 --- a/libs/openal-soft/alc/backends/wasapi.cpp +++ b/libs/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,11 +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" @@ -65,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 @@ -78,20 +99,24 @@ 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 +using ReferenceTime = std::chrono::duration>; + +constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept { return ReferenceTime{static_cast(n)}; } @@ -102,6 +127,7 @@ inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) no #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT) constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept { @@ -119,30 +145,73 @@ constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)}; constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)}; 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}; + + +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 { @@ -174,71 +243,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) +static 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); + name = wstr_to_utf8(pvprop->pwszVal); else - { - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - name += UnknownName; - } + WARN("Unexpected Device_FriendlyName PROPVARIANT type: 0x%04x\n", pvprop->vt); 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 + WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: 0x%04x\n", pvprop->vt); +#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); @@ -256,90 +377,424 @@ EndpointFormFactor get_device_formfactor(IMMDevice *device) WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); 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) { @@ -422,59 +877,60 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format) enum class MsgType { OpenDevice, - ReopenDevice, ResetDevice, StartDevice, StopDevice, CloseDevice, - EnumeratePlayback, - EnumerateCapture, - Count, - QuitThread = Count + QuitThread }; -constexpr char MessageStr[static_cast(MsgType::Count)][20]{ - "Open Device", - "Reopen 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 { virtual ~WasapiProxy() = default; - virtual HRESULT openProxy(const char *name) = 0; + 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::deque mMsgQueue; - static std::mutex mMsgQueueLock; - static std::condition_variable mMsgQueueCond; + 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(); @@ -486,8 +942,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; @@ -504,65 +960,43 @@ struct WasapiProxy { static int messageHandler(std::promise *promise); }; -std::deque WasapiProxy::mMsgQueue; -std::mutex WasapiProxy::mMsgQueueLock; -std::condition_variable WasapiProxy::mMsgQueueCond; int WasapiProxy::messageHandler(std::promise *promise) { TRACE("Starting message thread\n"); - HRESULT cohr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(cohr)) + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) { - WARN("Failed to initialize COM: 0x%08lx\n", cohr); - promise->set_value(cohr); + WARN("Failed to initialize COM: 0x%08lx\n", com.status()); + promise->set_value(com.status()); return 0; } - void *ptr{}; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr)}; - if(FAILED(hr)) - { - WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); - promise->set_value(hr); - CoUninitialize(); - return 0; - } - static_cast(ptr)->Release(); - CoUninitialize(); - - TRACE("Message thread initialization complete\n"); - promise->set_value(S_OK); + HRESULT hr{sDeviceHelper.emplace().init()}; + promise->set_value(hr); promise = nullptr; + if(FAILED(hr)) + goto skip_loop; + + { + 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"); - uint deviceCount{0}; 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) { case MsgType::OpenDevice: - hr = cohr = S_OK; - if(++deviceCount == 1) - hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if(SUCCEEDED(hr)) - hr = msg.mProxy->openProxy(msg.mParam); - msg.mPromise.set_value(hr); - - if(FAILED(hr)) - { - if(--deviceCount == 0 && SUCCEEDED(cohr)) - CoUninitialize(); - } - continue; - - case MsgType::ReopenDevice: hr = msg.mProxy->openProxy(msg.mParam); msg.mPromise.set_value(hr); continue; @@ -585,34 +1019,6 @@ int WasapiProxy::messageHandler(std::promise *promise) case MsgType::CloseDevice: msg.mProxy->closeProxy(); msg.mPromise.set_value(S_OK); - - if(--deviceCount == 0) - CoUninitialize(); - continue; - - case MsgType::EnumeratePlayback: - case MsgType::EnumerateCapture: - hr = cohr = S_OK; - if(++deviceCount == 1) - hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if(SUCCEEDED(hr)) - hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr); - if(FAILED(hr)) - msg.mPromise.set_value(hr); - else - { - ComPtr enumerator{static_cast(ptr)}; - - if(msg.mType == MsgType::EnumeratePlayback) - probe_devices(enumerator.get(), eRender, PlaybackDevices); - else if(msg.mType == MsgType::EnumerateCapture) - probe_devices(enumerator.get(), eCapture, CaptureDevices); - msg.mPromise.set_value(S_OK); - } - - if(--deviceCount == 0 && SUCCEEDED(cohr)) - CoUninitialize(); continue; case MsgType::QuitThread: @@ -623,18 +1029,21 @@ int WasapiProxy::messageHandler(std::promise *promise) } TRACE("Message loop finished\n"); +skip_loop: + sDeviceHelper.reset(); + 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; @@ -646,21 +1055,36 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ClockLatency getClockLatency() override; + void prepareFormat(WAVEFORMATEXTENSIBLE &OutputType); + void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType); + 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 mFrameStep{0u}; + UINT32 mOrigBufferSize{}, mOrigUpdateSize{}; + std::vector mResampleBuffer{}; + uint mBufferFilled{0}; + SampleConverterPtr mResampler; + + WAVEFORMATEXTENSIBLE mFormat{}; std::atomic mPadding{0u}; std::mutex mMutex; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WasapiPlayback) }; WasapiPlayback::~WasapiPlayback() @@ -677,23 +1101,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; } - SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + auto &audio = std::get(mAudio); - const uint update_size{mDevice->UpdateSize}; - const UINT32 buffer_len{mDevice->BufferSize}; + SetRTPriority(); + 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); @@ -712,15 +1142,36 @@ 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}; - mDevice->renderSamples(buffer, len, mFrameStep); + std::lock_guard dlock{mMutex}; + for(UINT32 done{0};done < len;) + { + if(mBufferFilled == 0) + { + mDevice->renderSamples(mResampleBuffer.data(), mDevice->UpdateSize, + mFormat.Format.nChannels); + resbufferptr = mResampleBuffer.data(); + mBufferFilled = mDevice->UpdateSize; + } + + uint got{mResampler->convert(&resbufferptr, &mBufferFilled, buffer, len-done)}; + buffer += got*frame_size; + done += got; + + mPadding.store(written + done, std::memory_order_relaxed); + } + } + else + { + 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)) { @@ -731,173 +1182,238 @@ 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()); + for(size_t i{0};i < tmpbuffers.size();++i) + resbuffers[i] = reinterpret_cast(mResampleBuffer.data()) + + mDevice->UpdateSize*i; + } + } + + /* 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; + 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) { - HRESULT hr{S_OK}; + if(SUCCEEDED(mOpenStatus)) + throw al::backend_exception{al::backend_error::DeviceError, + "Unexpected duplicate open call"}; - if(!mNotifyEvent) + mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if(mNotifyEvent == nullptr) { - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(mNotifyEvent == nullptr) - { - ERR("Failed to create notify events: %lu\n", GetLastError()); - hr = E_FAIL; - } + ERR("Failed to create notify events: %lu\n", GetLastError()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create notify events"}; } - if(SUCCEEDED(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()); - if(SUCCEEDED(mOpenStatus)) - hr = pushMessage(MsgType::ReopenDevice, name).get(); - else - { - hr = pushMessage(MsgType::OpenDevice, name).get(); - mOpenStatus = hr; - } - } - - if(FAILED(hr)) + mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); + if(FAILED(mOpenStatus)) throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - hr}; + 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; - } - - 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)) mDevice->Frequency = OutputType.Format.nSamplesPerSec; if(!mDevice->Flags.test(ChannelsRequest)) { + /* If not requesting a channel configuration, auto-select given what + * fits the mask's lsb (to ensure no gaps in the output channels). If + * there's no mask, we can only assume mono or stereo. + */ const uint32_t chancount{OutputType.Format.nChannels}; const DWORD chanmask{OutputType.dwChannelMask}; - if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) + 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)) + 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) + else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) mDevice->FmtChans = DevFmtStereo; - else if(chancount >= 1 && (chanmask&MonoMask) == MONO) + else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) mDevice->FmtChans = DevFmtMono; else 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); + } OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; switch(mDevice->FmtChans) @@ -919,16 +1435,21 @@ HRESULT WasapiPlayback::resetProxy() break; case DevFmtX51: OutputType.Format.nChannels = 6; - OutputType.dwChannelMask = X5DOT1; + OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break; case DevFmtX61: OutputType.Format.nChannels = 7; OutputType.dwChannelMask = X6DOT1; break; case DevFmtX71: + case DevFmtX3D71: OutputType.Format.nChannels = 8; OutputType.dwChannelMask = X7DOT1; break; + case DevFmtX714: + OutputType.Format.nChannels = 12; + OutputType.dwChannelMask = X7DOT1DOT4; + break; } switch(mDevice->FmtType) { @@ -968,13 +1489,397 @@ HRESULT WasapiPlayback::resetProxy() OutputType.Format.wBitsPerSample / 8); OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * OutputType.Format.nBlockAlign; +} - TraceFormat("Requesting playback format", &OutputType.Format); - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); +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 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; +} + + +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)) + { + 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); + goto no_spatial; + } + + ComPtr fmtenum; + hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum)); + if(FAILED(hr)) + { + ERR("Failed to get format enumerator: 0x%08lx\n", hr); + goto no_spatial; + } + + UINT32 fmtcount{}; + hr = fmtenum->GetCount(&fmtcount); + if(FAILED(hr) || fmtcount == 0) + { + ERR("Failed to get format count: 0x%08lx\n", hr); + goto no_spatial; + } + + WAVEFORMATEX *preferredFormat{}; + hr = fmtenum->GetFormat(0, &preferredFormat); + if(FAILED(hr)) + { + ERR("Failed to get preferred format: 0x%08lx\n", hr); + goto no_spatial; + } + 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)) + goto no_spatial; + + /* 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; + } + 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 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->vt = VT_BLOB; + paramProp->blob.cbSize = sizeof(streamParams); + paramProp->blob.pBlobData = reinterpret_cast(&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); + goto no_spatial; + } + + 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 S_OK; + } + +no_spatial: + 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 check format support: 0x%08lx\n", hr); - hr = mClient->GetMixFormat(&wfx); + 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 = audio.mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); + if(FAILED(hr)) + { + WARN("Failed to check format support: 0x%08lx\n", hr); + hr = audio.mClient->GetMixFormat(&wfx); } if(FAILED(hr)) { @@ -993,103 +1898,19 @@ HRESULT WasapiPlayback::resetProxy() CoTaskMemFree(wfx); wfx = nullptr; - 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)) - { - switch(mDevice->FmtChans) - { - case DevFmtMono: - chansok = (chancount >= 1 && (chanmask&MonoMask) == MONO); - break; - case DevFmtStereo: - chansok = (chancount >= 2 && (chanmask&StereoMask) == STEREO); - break; - case DevFmtQuad: - chansok = (chancount >= 4 && (chanmask&QuadMask) == QUAD); - break; - case DevFmtX51: - chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 - || (chanmask&X51RearMask) == X5DOT1REAR)); - break; - case DevFmtX61: - chansok = (chancount >= 7 && (chanmask&X61Mask) == X6DOT1); - break; - case DevFmtX71: - chansok = (chancount >= 8 && (chanmask&X71Mask) == X7DOT1); - break; - case DevFmtAmbi3D: - break; - } - } - if(!chansok) - { - if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) - mDevice->FmtChans = DevFmtX71; - else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 - || (chanmask&X51RearMask) == X5DOT1REAR)) - mDevice->FmtChans = DevFmtX51; - else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(chancount >= 2 && (chanmask&StereoMask) == STEREO) - mDevice->FmtChans = DevFmtStereo; - else if(chancount >= 1 && (chanmask&MonoMask) == MONO) - mDevice->FmtChans = DevFmtMono; - else - { - ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, - OutputType.dwChannelMask); - mDevice->FmtChans = DevFmtStereo; - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; - } - } - - if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) - { - 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); } - mFrameStep = OutputType.Format.nChannels; + 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(); - setChannelOrderFromWFXMask(OutputType.dwChannelMask); - - 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)) { @@ -1099,28 +1920,53 @@ 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; } - /* Find the nearest multiple of the period size to the update size */ - if(min_per < per_time) - min_per *= maxi64((per_time + min_per/2) / min_per, 1); - mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency), buffer_len/2); - mDevice->BufferSize = buffer_len; - - hr = mClient->SetEventHandle(mNotifyEvent); + 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 *= std::max((per_time + min_per/2) / min_per, 1_i64); + + mOrigBufferSize = buffer_len; + 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 = std::min(RefTime2Samples(min_per, mDevice->Frequency), + mDevice->BufferSize/2u); + + mResampler = 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.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), + mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency, + mDevice->UpdateSize); + } + return hr; } @@ -1137,33 +1983,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); } @@ -1172,14 +2046,25 @@ 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); } @@ -1187,10 +2072,16 @@ ClockLatency WasapiPlayback::getClockLatency() { ClockLatency ret; - std::lock_guard _{mMutex}; - ret.ClockTime = GetDeviceClockTime(mDevice); - ret.Latency = std::chrono::seconds{mPadding.load(std::memory_order_relaxed)}; - ret.Latency /= mDevice->Frequency; + std::lock_guard dlock{mMutex}; + ret.ClockTime = mDevice->getClockTime(); + ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)}; + ret.Latency /= mFormat.Format.nSamplesPerSec; + if(mResampler) + { + auto extra = mResampler->currentInputDelay(); + ret.Latency += std::chrono::duration_cast(extra) / mDevice->Frequency; + ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency; + } return ret; } @@ -1202,8 +2093,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; @@ -1212,11 +2103,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}; @@ -1227,8 +2118,6 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WasapiCapture) }; WasapiCapture::~WasapiCapture() @@ -1245,21 +2134,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) @@ -1285,11 +2174,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 @@ -1297,14 +2187,14 @@ 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)}; + 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); if(len2 > 0) @@ -1330,44 +2220,33 @@ 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) { - HRESULT hr{S_OK}; + if(SUCCEEDED(mOpenStatus)) + throw al::backend_exception{al::backend_error::DeviceError, + "Unexpected duplicate open call"}; mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if(mNotifyEvent == nullptr) { - ERR("Failed to create notify event: %lu\n", GetLastError()); - hr = E_FAIL; + ERR("Failed to create notify events: %lu\n", GetLastError()); + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to create notify events"}; } - if(SUCCEEDED(hr)) - { - if(name) - { - if(CaptureDevices.empty()) - pushMessage(MsgType::EnumerateCapture); - if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) - { - name += DevNameHeadLen; - if(*name == '\0') - name = nullptr; - } - } - hr = pushMessage(MsgType::OpenDevice, name).get(); - } - mOpenStatus = hr; + if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix)) + name = name.substr(prefix.size()); - if(FAILED(hr)) + mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); + if(FAILED(mOpenStatus)) throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", - hr}; + mOpenStatus}; - hr = pushMessage(MsgType::ResetDevice).get(); + HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; if(FAILED(hr)) { if(hr == E_OUTOFMEMORY) @@ -1376,52 +2255,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() @@ -1434,20 +2307,40 @@ 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); + if(FAILED(hr)) + { + ERR("Failed to get capture format: 0x%08lx\n", hr); + return hr; + } + TraceFormat("Device capture format", wfx); + + WAVEFORMATEXTENSIBLE InputType{}; + if(!MakeExtensible(&InputType, wfx)) + { + CoTaskMemFree(wfx); + return E_FAIL; + } + CoTaskMemFree(wfx); + wfx = nullptr; + + const bool isRear51{InputType.Format.nChannels == 6 + && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR}; // Make sure buffer is at least 100ms in size ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}}); - WAVEFORMATEXTENSIBLE InputType{}; + InputType = {}; InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; switch(mDevice->FmtChans) { @@ -1465,7 +2358,7 @@ HRESULT WasapiCapture::resetProxy() break; case DevFmtX51: InputType.Format.nChannels = 6; - InputType.dwChannelMask = X5DOT1; + InputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break; case DevFmtX61: InputType.Format.nChannels = 7; @@ -1475,7 +2368,12 @@ HRESULT WasapiCapture::resetProxy() InputType.Format.nChannels = 8; InputType.dwChannelMask = X7DOT1; break; + case DevFmtX714: + InputType.Format.nChannels = 12; + InputType.dwChannelMask = X7DOT1DOT4; + break; + case DevFmtX3D71: case DevFmtAmbi3D: return E_FAIL; } @@ -1512,11 +2410,15 @@ HRESULT WasapiCapture::resetProxy() InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format); TraceFormat("Requesting capture format", &InputType.Format); - WAVEFORMATEX *wfx; hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx); if(FAILED(hr)) { - ERR("Failed to check format support: 0x%08lx\n", hr); + WARN("Failed to check capture format support: 0x%08lx\n", hr); + hr = mClient->GetMixFormat(&wfx); + } + if(FAILED(hr)) + { + ERR("Failed to find a supported capture format: 0x%08lx\n", hr); return hr; } @@ -1556,7 +2458,10 @@ HRESULT WasapiCapture::resetProxy() case DevFmtX61: return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1)); case DevFmtX71: + case DevFmtX3D71: return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1)); + case DevFmtX714: + return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4)); case DevFmtAmbi3D: return (chanmask == 0 && chancount == device->channelsFromFmt()); } @@ -1632,8 +2537,9 @@ HRESULT WasapiCapture::resetProxy() if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) { - mSampleConv = CreateSampleConverter(srcType, mDevice->FmtType, mDevice->channelsFromFmt(), - InputType.Format.nSamplesPerSec, mDevice->Frequency, Resampler::FastBSinc24); + mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType, + mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency, + Resampler::FastBSinc24); if(!mSampleConv) { ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n", @@ -1699,11 +2605,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}; @@ -1742,8 +2646,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()); } @@ -1754,7 +2658,6 @@ uint WasapiCapture::availableSamples() bool WasapiBackendFactory::init() { static HRESULT InitResult{E_FAIL}; - if(FAILED(InitResult)) try { std::promise promise; @@ -1775,23 +2678,46 @@ bool WasapiBackendFactory::querySupport(BackendType type) std::string WasapiBackendFactory::probe(BackendType type) { std::string 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) + { + /* +1 to also append the null char (to ensure a null- + * separated list and double-null terminated list). + */ + outnames.append(GetDevicePrefix()).append(entry.name.c_str(), + entry.name.length()+1); + continue; + } + /* Default device goes first. */ + std::string name{std::string{GetDevicePrefix()} + entry.name}; + outnames.insert(0, name.c_str(), name.length()+1); + } } 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.append(GetDevicePrefix()).append(entry.name.c_str(), + entry.name.length()+1); + continue; + } + std::string name{std::string{GetDevicePrefix()} + entry.name}; + outnames.insert(0, name.c_str(), name.length()+1); + } + } break; } @@ -1812,3 +2738,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/libs/openal-soft/alc/backends/wasapi.h b/libs/openal-soft/alc/backends/wasapi.h index bb2671ee..12fd95ef 100644 --- a/libs/openal-soft/alc/backends/wasapi.h +++ b/libs/openal-soft/alc/backends/wasapi.h @@ -9,6 +9,8 @@ public: bool querySupport(BackendType type) override; + alc::EventSupport queryEventSupport(alc::EventType eventType, BackendType type) override; + std::string probe(BackendType type) override; BackendPtr createBackend(DeviceBase *device, BackendType type) override; diff --git a/libs/openal-soft/alc/backends/wave.cpp b/libs/openal-soft/alc/backends/wave.cpp index 6360166c..f18c80ac 100644 --- a/libs/openal-soft/alc/backends/wave.cpp +++ b/libs/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", 0)) + if(GetConfigValueBool({}, "wave", "bformat", false)) { mDevice->FmtChans = DevFmtAmbi3D; mDevice->mAmbiOrder = 1; @@ -265,9 +264,15 @@ bool WaveBackend::reset() case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; + case DevFmtX714: + chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000 + | 0x8000 | 0x20000; + break; + /* NOTE: Same as 7.1. */ + case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; case DevFmtAmbi3D: /* .amb output requires FuMa */ - mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3); + mDevice->mAmbiOrder = std::min(mDevice->mAmbiOrder, 3u); mDevice->mAmbiLayout = DevAmbiLayout::FuMa; mDevice->mAmbiScale = DevAmbiScaling::FuMa; isbformat = true; @@ -277,49 +282,48 @@ bool WaveBackend::reset() bytes = mDevice->bytesFromFmt(); channels = mDevice->channelsFromFmt(); - rewind(mFile); + rewind(mFile.get()); - fputs("RIFF", mFile); - fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close + fputs("RIFF", mFile.get()); + fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at close - fputs("WAVE", mFile); + fputs("WAVE", mFile.get()); - fputs("fmt ", mFile); - fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE + fputs("fmt ", mFile.get()); + fwrite32le(40, mFile.get()); // 'fmt ' header len; 40 bytes for EXTENSIBLE // 16-bit val, format type id (extensible: 0xFFFE) - fwrite16le(0xFFFE, mFile); + fwrite16le(0xFFFE, mFile.get()); // 16-bit val, channel count - fwrite16le(static_cast(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(); @@ -331,7 +335,7 @@ bool WaveBackend::reset() void WaveBackend::start() { - if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0) + if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0) WARN("Failed to seek on output file\n"); try { mKillNow.store(false, std::memory_order_release); @@ -351,14 +355,14 @@ void WaveBackend::stop() if(mDataStart > 0) { - long size{ftell(mFile)}; + long size{ftell(mFile.get())}; if(size > 0) { long dataLen{size - mDataStart}; - if(fseek(mFile, 4, SEEK_SET) == 0) - fwrite32le(static_cast(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 } } } @@ -374,17 +378,15 @@ bool WaveBackendFactory::querySupport(BackendType type) std::string WaveBackendFactory::probe(BackendType type) { - std::string outnames; switch(type) { case BackendType::Playback: - /* Includes null char. */ - outnames.append(waveDevice, sizeof(waveDevice)); - break; + /* Include null char. */ + return std::string{GetDeviceName()} + '\0'; case BackendType::Capture: break; } - return outnames; + return std::string{}; } BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/libs/openal-soft/alc/backends/winmm.cpp b/libs/openal-soft/alc/backends/winmm.cpp index 0fdd8a02..16126027 100644 --- a/libs/openal-soft/alc/backends/winmm.cpp +++ b/libs/openal-soft/alc/backends/winmm.cpp @@ -22,8 +22,8 @@ #include "winmm.h" -#include -#include +#include +#include #include #include @@ -39,12 +39,15 @@ #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 +58,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(); @@ -92,7 +95,7 @@ void ProbePlaybackDevices(void) } } -void ProbeCaptureDevices(void) +void ProbeCaptureDevices() { CaptureDevices.clear(); @@ -134,7 +137,7 @@ struct WinMMPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -143,6 +146,7 @@ struct WinMMPlayback final : public BackendBase { al::semaphore mSem; uint mIdx{0u}; std::array mWaveBuffer{}; + al::vector mBuffer; HWAVEOUT mOutHdl{nullptr}; @@ -150,8 +154,6 @@ struct WinMMPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WinMMPlayback) }; WinMMPlayback::~WinMMPlayback() @@ -159,14 +161,11 @@ WinMMPlayback::~WinMMPlayback() if(mOutHdl) waveOutClose(mOutHdl); mOutHdl = nullptr; - - al_free(mWaveBuffer[0].lpData); - std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); } /* WinMMPlayback::waveOutProc * - * Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is + * Posts a message to 'WinMMPlayback::mixerProc' every time a WaveOut Buffer is * completed and returns to the application (for more data) */ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept @@ -179,7 +178,7 @@ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PT FORCE_ALIGN int WinMMPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) @@ -207,18 +206,18 @@ FORCE_ALIGN int WinMMPlayback::mixerProc() } -void WinMMPlayback::open(const char *name) +void WinMMPlayback::open(std::string_view name) { if(PlaybackDevices.empty()) ProbePlaybackDevices(); // Find the Device ID matching the deviceName if valid - auto iter = name ? + auto iter = !name.empty() ? std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : PlaybackDevices.cbegin(); if(iter == PlaybackDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; auto DeviceID = static_cast(std::distance(PlaybackDevices.cbegin(), iter)); DevFmtType fmttype{mDevice->FmtType}; @@ -301,29 +300,22 @@ bool WinMMPlayback::reset() return false; } - uint chanmask{}; if(mFormat.nChannels >= 2) - { mDevice->FmtChans = DevFmtStereo; - chanmask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; - } else if(mFormat.nChannels == 1) - { mDevice->FmtChans = DevFmtMono; - chanmask = SPEAKER_FRONT_CENTER; - } else { ERR("Unhandled channel count: %d\n", mFormat.nChannels); return false; } - setChannelOrderFromWFXMask(chanmask); + setDefaultWFXChannelOrder(); - uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; + const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; - al_free(mWaveBuffer[0].lpData); + decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer); mWaveBuffer[0] = WAVEHDR{}; - mWaveBuffer[0].lpData = static_cast(al_calloc(16, BufferSize * mWaveBuffer.size())); + mWaveBuffer[0].lpData = mBuffer.data(); mWaveBuffer[0].dwBufferLength = BufferSize; for(size_t i{1};i < mWaveBuffer.size();i++) { @@ -376,16 +368,17 @@ struct WinMMCapture final : public BackendBase { int captureProc(); - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; std::atomic mReadable{0u}; al::semaphore mSem; uint mIdx{0}; std::array mWaveBuffer{}; + al::vector mBuffer; HWAVEIN mInHdl{nullptr}; @@ -395,8 +388,6 @@ struct WinMMCapture final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WinMMCapture) }; WinMMCapture::~WinMMCapture() @@ -405,14 +396,11 @@ WinMMCapture::~WinMMCapture() if(mInHdl) waveInClose(mInHdl); mInHdl = nullptr; - - al_free(mWaveBuffer[0].lpData); - std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); } /* WinMMCapture::waveInProc * - * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is + * Posts a message to 'WinMMCapture::captureProc' every time a WaveIn Buffer is * completed and returns to the application (with more data). */ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept @@ -424,7 +412,7 @@ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) int WinMMCapture::captureProc() { - althrd_setname(RECORD_THREAD_NAME); + althrd_setname(GetRecordThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) @@ -441,7 +429,8 @@ int WinMMCapture::captureProc() WAVEHDR &waveHdr = mWaveBuffer[widx]; widx = (widx+1) % mWaveBuffer.size(); - mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign); + std::ignore = mRing->write(waveHdr.lpData, + waveHdr.dwBytesRecorded / mFormat.nBlockAlign); mReadable.fetch_sub(1, std::memory_order_acq_rel); waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); @@ -452,18 +441,18 @@ int WinMMCapture::captureProc() } -void WinMMCapture::open(const char *name) +void WinMMCapture::open(std::string_view name) { if(CaptureDevices.empty()) ProbeCaptureDevices(); // Find the Device ID matching the deviceName if valid - auto iter = name ? + auto iter = !name.empty() ? std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : CaptureDevices.cbegin(); if(iter == CaptureDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; auto DeviceID = static_cast(std::distance(CaptureDevices.cbegin(), iter)); switch(mDevice->FmtChans) @@ -476,6 +465,8 @@ void WinMMCapture::open(const char *name) case DevFmtX51: case DevFmtX61: case DevFmtX71: + case DevFmtX714: + case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; @@ -518,14 +509,14 @@ void WinMMCapture::open(const char *name) // Allocate circular memory buffer for the captured audio // Make sure circular buffer is at least 100ms in size - uint CapturedDataSize{mDevice->BufferSize}; - CapturedDataSize = static_cast(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) { @@ -576,8 +567,8 @@ void WinMMCapture::stop() mIdx = 0; } -void WinMMCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void WinMMCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint WinMMCapture::availableSamples() { return static_cast(mRing->readSpace()); } diff --git a/libs/openal-soft/alc/context.cpp b/libs/openal-soft/alc/context.cpp index 456c054e..1b58cffd 100644 --- a/libs/openal-soft/alc/context.cpp +++ b/libs/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,72 +33,79 @@ #include "core/voice.h" #include "core/voice_change.h" #include "device.h" +#include "flexarray.h" #include "ringbuffer.h" #include "vecmat.h" #ifdef ALSOFT_EAX -#include -#include - #include "alstring.h" -#include "al/eax_exception.h" -#include "al/eax_globals.h" +#include "al/eax/globals.h" #endif // ALSOFT_EAX namespace { -using namespace std::placeholders; - +using namespace std::string_view_literals; using voidp = void*; /* Default context extensions */ -constexpr ALchar alExtList[] = - "AL_EXT_ALAW " - "AL_EXT_BFORMAT " - "AL_EXT_DOUBLE " - "AL_EXT_EXPONENT_DISTANCE " - "AL_EXT_FLOAT32 " - "AL_EXT_IMA4 " - "AL_EXT_LINEAR_DISTANCE " - "AL_EXT_MCFORMATS " - "AL_EXT_MULAW " - "AL_EXT_MULAW_BFORMAT " - "AL_EXT_MULAW_MCFORMATS " - "AL_EXT_OFFSET " - "AL_EXT_source_distance_model " - "AL_EXT_SOURCE_RADIUS " - "AL_EXT_STEREO_ANGLES " - "AL_LOKI_quadriphonic " - "AL_SOFT_bformat_ex " - "AL_SOFTX_bformat_hoa " - "AL_SOFT_block_alignment " - "AL_SOFT_callback_buffer " - "AL_SOFTX_convolution_reverb " - "AL_SOFT_deferred_updates " - "AL_SOFT_direct_channels " - "AL_SOFT_direct_channels_remix " - "AL_SOFT_effect_target " - "AL_SOFT_events " - "AL_SOFT_gain_clamp_ex " - "AL_SOFTX_hold_on_disconnect " - "AL_SOFT_loop_points " - "AL_SOFTX_map_buffer " - "AL_SOFT_MSADPCM " - "AL_SOFT_source_latency " - "AL_SOFT_source_length " - "AL_SOFT_source_resampler " - "AL_SOFT_source_spatialize " - "AL_SOFT_UHJ"; +std::vector 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 +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}, @@ -103,23 +117,18 @@ thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext; ALeffect ALCcontext::sDefaultEffect; -#ifdef __MINGW32__ -ALCcontext *ALCcontext::getThreadContext() noexcept -{ return sLocalContext; } -void ALCcontext::setThreadContext(ALCcontext *context) noexcept -{ sThreadContext.set(context); } -#endif - -ALCcontext::ALCcontext(al::intrusive_ptr 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) @@ -128,11 +137,11 @@ ALCcontext::~ALCcontext() mNumSources = 0; #ifdef ALSOFT_EAX - eax_uninitialize(); + eaxUninitialize(); #endif // ALSOFT_EAX mDefaultSlot = nullptr; - count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u}, + count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), 0_uz, [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) @@ -145,20 +154,21 @@ void ALCcontext::init() { if(sDefaultEffect.type != AL_EFFECT_NULL && mDevice->Type == DeviceType::Playback) { - mDefaultSlot = std::make_unique(); - aluInitEffectPanning(&mDefaultSlot->mSlot, this); + mDefaultSlot = std::make_unique(this); + aluInitEffectPanning(mDefaultSlot->mSlot, this); } - EffectSlotArray *auxslots; + std::unique_ptr auxslots; if(!mDefaultSlot) auxslots = EffectSlot::CreatePtrArray(0); else { auxslots = EffectSlot::CreatePtrArray(1); - (*auxslots)[0] = &mDefaultSlot->mSlot; + (*auxslots)[0] = mDefaultSlot->mSlot; + std::uninitialized_fill_n(al::to_address(auxslots->end()), 1_uz, nullptr); mDefaultSlot->mState = SlotState::Playing; } - mActiveAuxSlots.store(auxslots, std::memory_order_relaxed); + mActiveAuxSlots.store(std::move(auxslots), std::memory_order_relaxed); allocVoiceChanges(); { @@ -168,12 +178,41 @@ void ALCcontext::init() mCurrentVoiceChange.store(cur, std::memory_order_relaxed); } - mExtensionList = alExtList; + mExtensions = getContextExtensions(); + + if(sBufferSubDataCompat) + { + auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS"); + if(iter != mExtensions.end()) mExtensions.erase(iter); + /* TODO: Would be nice to sort this alphabetically. Needs case- + * insensitive searching. + */ + mExtensions.emplace_back("AL_SOFT_buffer_sub_data"); + } #ifdef ALSOFT_EAX eax_initialize_extensions(); #endif // ALSOFT_EAX + if(!mExtensions.empty()) + { + const size_t len{std::accumulate(mExtensions.cbegin()+1, mExtensions.cend(), + mExtensions.front().length(), + [](size_t current, std::string_view ext) noexcept + { return current + ext.length() + 1; })}; + + std::string extensions; + extensions.reserve(len); + extensions += mExtensions.front(); + for(std::string_view ext : al::span{mExtensions}.subspan<1>()) + { + extensions += ' '; + extensions += ext; + } + + mExtensionsString = std::move(extensions); + } + mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f}; mParams.Matrix = alu::Matrix::Identity(); mParams.Velocity = alu::Vector{}; @@ -194,56 +233,59 @@ void ALCcontext::init() mActiveVoiceCount.store(64, std::memory_order_relaxed); } -bool ALCcontext::deinit() +void ALCcontext::deinit() { if(sLocalContext == this) { WARN("%p released while current on thread\n", voidp{this}); sThreadContext.set(nullptr); - release(); + dec_ref(); } ALCcontext *origctx{this}; if(sGlobalContext.compare_exchange_strong(origctx, nullptr)) - release(); + { + while(sGlobalContextLock.load()) { + /* Wait to make sure another thread didn't get the context and is + * trying to increment its refcount. + */ + } + dec_ref(); + } - bool ret{}; + bool stopPlayback{}; /* First make sure this context exists in the device's list. */ auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire); if(auto toremove = static_cast(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. */ std::copy_if(oldarray->begin(), oldarray->end(), newarray->begin(), - std::bind(std::not_equal_to<>{}, _1, this)); + [this](ContextBase *ctx) { return ctx != this; }); /* Store the new context array in the device. Wait for any current mix * to finish before deleting the old array. */ - mDevice->mContexts.store(newarray); - if(oldarray != &DeviceBase::sEmptyContextArray) - { - mDevice->waitForMix(); - delete oldarray; - } + auto prevarray = mDevice->mContexts.exchange(std::move(newarray)); + std::ignore = mDevice->waitForMix(); - ret = !newarray->empty(); + stopPlayback = (newsize == 0); } else - ret = !oldarray->empty(); + stopPlayback = oldarray->empty(); StopEventThrd(this); - return ret; + if(stopPlayback && mALDevice->mDeviceState == DeviceState::Playing) + { + mALDevice->Backend->stop(); + mALDevice->mDeviceState = DeviceState::Configured; + } } void ALCcontext::applyAllUpdates() @@ -257,8 +299,10 @@ void ALCcontext::applyAllUpdates() } #ifdef ALSOFT_EAX - eax_apply_deferred(); + if(mEaxNeedsCommit) + eaxCommit(); #endif + if(std::exchange(mPropsDirty, false)) UpdateContextProps(this); UpdateAllEffectSlotProps(this); @@ -270,22 +314,10 @@ void ALCcontext::applyAllUpdates() mHoldUpdates.store(false, std::memory_order_release); } + #ifdef ALSOFT_EAX namespace { -class ContextException : - public EaxException -{ -public: - explicit ContextException( - const char* message) - : - EaxException{"EAX_CONTEXT", message} - { - } -}; // ContextException - - template void ForEachSource(ALCcontext *context, F func) { @@ -294,10 +326,10 @@ void ForEachSource(ALCcontext *context, F func) uint64_t usemask{~sublist.FreeMask}; while(usemask) { - const int idx{al::countr_zero(usemask)}; + const auto idx = static_cast(al::countr_zero(usemask)); usemask &= ~(1_u64 << idx); - func(sublist.Sources[idx]); + func((*sublist.Sources)[idx]); } } } @@ -305,22 +337,19 @@ void ForEachSource(ALCcontext *context, F func) } // namespace -bool ALCcontext::eax_is_capable() const noexcept +bool ALCcontext::eaxIsCapable() const noexcept { return eax_has_enough_aux_sends(); } -void ALCcontext::eax_uninitialize() noexcept +void ALCcontext::eaxUninitialize() noexcept { - if (!eax_is_initialized_) - { + if(!mEaxIsInitialized) return; - } - eax_is_initialized_ = true; - eax_is_tried_ = false; - - eax_fx_slots_.uninitialize(); + mEaxIsInitialized = false; + mEaxIsTried = false; + mEaxFxSlots.uninitialize(); } ALenum ALCcontext::eax_eax_set( @@ -330,41 +359,39 @@ ALenum ALCcontext::eax_eax_set( ALvoid* property_value, ALuint property_value_size) { - eax_initialize(); - - const auto eax_call = create_eax_call( - false, + const auto call = create_eax_call( + EaxCallType::set, property_set_id, property_id, property_source_id, property_value, - property_value_size - ); + property_value_size); - eax_unlock_legacy_fx_slots(eax_call); + eax_initialize(); - switch (eax_call.get_property_set_id()) + switch(call.get_property_set_id()) { - case EaxEaxCallPropertySetId::context: - eax_set(eax_call); - break; - - case EaxEaxCallPropertySetId::fx_slot: - case EaxEaxCallPropertySetId::fx_slot_effect: - eax_dispatch_fx_slot(eax_call); - break; - - case EaxEaxCallPropertySetId::source: - eax_dispatch_source(eax_call); - break; - - default: - eax_fail("Unsupported property set id."); + case EaxCallPropertySetId::context: + eax_set(call); + break; + case EaxCallPropertySetId::fx_slot: + case EaxCallPropertySetId::fx_slot_effect: + eax_dispatch_fx_slot(call); + break; + case EaxCallPropertySetId::source: + eax_dispatch_source(call); + break; + default: + eax_fail_unknown_property_set_id(); } + mEaxNeedsCommit = true; - static constexpr auto deferred_flag = 0x80000000u; - if(!(property_id&deferred_flag) && !mDeferUpdates) - applyAllUpdates(); + if(!call.is_deferred()) + { + eaxCommit(); + if(!mDeferUpdates) + applyAllUpdates(); + } return AL_NO_ERROR; } @@ -376,135 +403,101 @@ ALenum ALCcontext::eax_eax_get( ALvoid* property_value, ALuint property_value_size) { - eax_initialize(); - - const auto eax_call = create_eax_call( - true, + const auto call = create_eax_call( + EaxCallType::get, property_set_id, property_id, property_source_id, property_value, - property_value_size - ); + property_value_size); - eax_unlock_legacy_fx_slots(eax_call); + eax_initialize(); - switch (eax_call.get_property_set_id()) + switch(call.get_property_set_id()) { - case EaxEaxCallPropertySetId::context: - eax_get(eax_call); - break; - - case EaxEaxCallPropertySetId::fx_slot: - case EaxEaxCallPropertySetId::fx_slot_effect: - eax_dispatch_fx_slot(eax_call); - break; - - case EaxEaxCallPropertySetId::source: - eax_dispatch_source(eax_call); - break; - - default: - eax_fail("Unsupported property set id."); + case EaxCallPropertySetId::context: + eax_get(call); + break; + case EaxCallPropertySetId::fx_slot: + case EaxCallPropertySetId::fx_slot_effect: + eax_dispatch_fx_slot(call); + break; + case EaxCallPropertySetId::source: + eax_dispatch_source(call); + break; + default: + eax_fail_unknown_property_set_id(); } return AL_NO_ERROR; } -void ALCcontext::eax_update_filters() +void ALCcontext::eaxSetLastError() noexcept { - ForEachSource(this, std::mem_fn(&ALsource::eax_update_filters)); + mEaxLastError = EAXERR_INVALID_OPERATION; } -void ALCcontext::eax_commit_and_update_sources() -{ - std::unique_lock source_lock{mSourceLock}; - ForEachSource(this, std::mem_fn(&ALsource::eax_commit_and_update)); -} - -void ALCcontext::eax_set_last_error() noexcept -{ - eax_last_error_ = EAXERR_INVALID_OPERATION; -} - -[[noreturn]] -void ALCcontext::eax_fail( - const char* message) +[[noreturn]] void ALCcontext::eax_fail(const char* message) { throw ContextException{message}; } +[[noreturn]] void ALCcontext::eax_fail_unknown_property_set_id() +{ + eax_fail("Unknown property ID."); +} + +[[noreturn]] void ALCcontext::eax_fail_unknown_primary_fx_slot_id() +{ + eax_fail("Unknown primary FX Slot ID."); +} + +[[noreturn]] void ALCcontext::eax_fail_unknown_property_id() +{ + eax_fail("Unknown property ID."); +} + +[[noreturn]] void ALCcontext::eax_fail_unknown_version() +{ + eax_fail("Unknown version."); +} + void ALCcontext::eax_initialize_extensions() { - if (!eax_g_is_enabled) - { + if(!eax_g_is_enabled) return; - } - const auto string_max_capacity = - std::strlen(mExtensionList) + 1 + - std::strlen(eax1_ext_name) + 1 + - std::strlen(eax2_ext_name) + 1 + - std::strlen(eax3_ext_name) + 1 + - std::strlen(eax4_ext_name) + 1 + - std::strlen(eax5_ext_name) + 1 + - std::strlen(eax_x_ram_ext_name) + 1 + - 0; - - eax_extension_list_.reserve(string_max_capacity); - - if (eax_is_capable()) + mExtensions.emplace(mExtensions.begin(), "EAX-RAM"sv); + if(eaxIsCapable()) { - eax_extension_list_ += eax1_ext_name; - eax_extension_list_ += ' '; - - eax_extension_list_ += eax2_ext_name; - eax_extension_list_ += ' '; - - eax_extension_list_ += eax3_ext_name; - eax_extension_list_ += ' '; - - eax_extension_list_ += eax4_ext_name; - eax_extension_list_ += ' '; - - eax_extension_list_ += eax5_ext_name; - eax_extension_list_ += ' '; + mExtensions.emplace(mExtensions.begin(), "EAX5.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX4.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX3.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX2.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX"sv); } - - eax_extension_list_ += eax_x_ram_ext_name; - eax_extension_list_ += ' '; - - eax_extension_list_ += mExtensionList; - mExtensionList = eax_extension_list_.c_str(); } void ALCcontext::eax_initialize() { - if (eax_is_initialized_) - { + if(mEaxIsInitialized) return; - } - if (eax_is_tried_) - { + if(mEaxIsTried) eax_fail("No EAX."); - } - eax_is_tried_ = true; + mEaxIsTried = true; - if (!eax_g_is_enabled) - { + if(!eax_g_is_enabled) eax_fail("EAX disabled by a configuration."); - } eax_ensure_compatibility(); eax_set_defaults(); - eax_set_air_absorbtion_hf(); + eax_context_commit_air_absorbtion_hf(); eax_update_speaker_configuration(); eax_initialize_fx_slots(); - eax_initialize_sources(); - eax_is_initialized_ = true; + mEaxIsInitialized = true; } bool ALCcontext::eax_has_no_default_effect_slot() const noexcept @@ -514,10 +507,8 @@ bool ALCcontext::eax_has_no_default_effect_slot() const noexcept void ALCcontext::eax_ensure_no_default_effect_slot() const { - if (!eax_has_no_default_effect_slot()) - { + if(!eax_has_no_default_effect_slot()) eax_fail("There is a default effect slot in the context."); - } } bool ALCcontext::eax_has_enough_aux_sends() const noexcept @@ -527,10 +518,8 @@ bool ALCcontext::eax_has_enough_aux_sends() const noexcept void ALCcontext::eax_ensure_enough_aux_sends() const { - if (!eax_has_enough_aux_sends()) - { + if(!eax_has_enough_aux_sends()) eax_fail("Not enough aux sends."); - } } void ALCcontext::eax_ensure_compatibility() @@ -558,6 +547,14 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const case DevFmtX51: return SPEAKERS_5; case DevFmtX61: return SPEAKERS_6; case DevFmtX71: return SPEAKERS_7; + /* 7.1.4 is compatible with 7.1. This could instead be HEADPHONES to + * suggest with-height surround sound (like HRTF). + */ + case DevFmtX714: return SPEAKERS_7; + /* 3D7.1 is only compatible with 5.1. This could instead be HEADPHONES to + * suggest full-sphere surround sound (like HRTF). + */ + case DevFmtX3D71: return SPEAKERS_5; /* This could also be HEADPHONES, since headphones-based HRTF and Ambi3D * provide full-sphere surround sound. Depends if apps are more likely to * consider headphones or 7.1 for surround sound support. @@ -572,728 +569,491 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const void ALCcontext::eax_update_speaker_configuration() { - eax_speaker_config_ = eax_detect_speaker_configuration(); + mEaxSpeakerConfig = eax_detect_speaker_configuration(); } void ALCcontext::eax_set_last_error_defaults() noexcept { - eax_last_error_ = EAX_OK; + mEaxLastError = EAXCONTEXT_DEFAULTLASTERROR; } -void ALCcontext::eax_set_session_defaults() noexcept +void ALCcontext::eax_session_set_defaults() noexcept { - eax_session_.ulEAXVersion = EAXCONTEXT_MINEAXSESSION; - eax_session_.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS; + mEaxSession.ulEAXVersion = EAXCONTEXT_DEFAULTEAXSESSION; + mEaxSession.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS; } -void ALCcontext::eax_set_context_defaults() noexcept +void ALCcontext::eax4_context_set_defaults(Eax4Props& props) noexcept { - eax_.context.guidPrimaryFXSlotID = EAXCONTEXT_DEFAULTPRIMARYFXSLOTID; - eax_.context.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR; - eax_.context.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF; - eax_.context.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE; + props.guidPrimaryFXSlotID = EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID; + props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR; + props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF; + props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE; } -void ALCcontext::eax_set_defaults() noexcept +void ALCcontext::eax4_context_set_defaults(Eax4State& state) noexcept +{ + eax4_context_set_defaults(state.i); + state.d = state.i; +} + +void ALCcontext::eax5_context_set_defaults(Eax5Props& props) noexcept +{ + props.guidPrimaryFXSlotID = EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID; + props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR; + props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF; + props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE; + props.flMacroFXFactor = EAXCONTEXT_DEFAULTMACROFXFACTOR; +} + +void ALCcontext::eax5_context_set_defaults(Eax5State& state) noexcept +{ + eax5_context_set_defaults(state.i); + state.d = state.i; +} + +void ALCcontext::eax_context_set_defaults() +{ + eax5_context_set_defaults(mEax123); + eax4_context_set_defaults(mEax4); + eax5_context_set_defaults(mEax5); + mEax = mEax5.i; + mEaxVersion = 5; + mEaxDf = EaxDirtyFlags{}; +} + +void ALCcontext::eax_set_defaults() { eax_set_last_error_defaults(); - eax_set_session_defaults(); - eax_set_context_defaults(); - - eax_d_ = eax_; + eax_session_set_defaults(); + eax_context_set_defaults(); } -void ALCcontext::eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) noexcept +void ALCcontext::eax_dispatch_fx_slot(const EaxCall& call) { - if (eax_call.get_version() != 5 || eax_are_legacy_fx_slots_unlocked_) - return; - - eax_are_legacy_fx_slots_unlocked_ = true; - eax_fx_slots_.unlock_legacy(); -} - -void ALCcontext::eax_dispatch_fx_slot( - const EaxEaxCall& eax_call) -{ - const auto fx_slot_index = eax_call.get_fx_slot_index(); + const auto fx_slot_index = call.get_fx_slot_index(); if(!fx_slot_index.has_value()) eax_fail("Invalid fx slot index."); - auto& fx_slot = eax_get_fx_slot(*fx_slot_index); - if(fx_slot.eax_dispatch(eax_call)) + auto& fx_slot = eaxGetFxSlot(*fx_slot_index); + if(fx_slot.eax_dispatch(call)) { std::lock_guard source_lock{mSourceLock}; - eax_update_filters(); + ForEachSource(this, std::mem_fn(&ALsource::eaxMarkAsChanged)); } } -void ALCcontext::eax_dispatch_source( - const EaxEaxCall& eax_call) +void ALCcontext::eax_dispatch_source(const EaxCall& call) { - const auto source_id = eax_call.get_property_al_name(); - + const auto source_id = call.get_property_al_name(); std::lock_guard source_lock{mSourceLock}; + const auto source = ALsource::EaxLookupSource(*this, source_id); - const auto source = ALsource::eax_lookup_source(*this, source_id); - - if (!source) - { + if (source == nullptr) eax_fail("Source not found."); - } - source->eax_dispatch(eax_call); + source->eaxDispatch(call); } -void ALCcontext::eax_get_primary_fx_slot_id( - const EaxEaxCall& eax_call) +void ALCcontext::eax_get_misc(const EaxCall& call) { - eax_call.set_value(eax_.context.guidPrimaryFXSlotID); -} - -void ALCcontext::eax_get_distance_factor( - const EaxEaxCall& eax_call) -{ - eax_call.set_value(eax_.context.flDistanceFactor); -} - -void ALCcontext::eax_get_air_absorption_hf( - const EaxEaxCall& eax_call) -{ - eax_call.set_value(eax_.context.flAirAbsorptionHF); -} - -void ALCcontext::eax_get_hf_reference( - const EaxEaxCall& eax_call) -{ - eax_call.set_value(eax_.context.flHFReference); -} - -void ALCcontext::eax_get_last_error( - const EaxEaxCall& eax_call) -{ - const auto eax_last_error = eax_last_error_; - eax_last_error_ = EAX_OK; - eax_call.set_value(eax_last_error); -} - -void ALCcontext::eax_get_speaker_config( - const EaxEaxCall& eax_call) -{ - eax_call.set_value(eax_speaker_config_); -} - -void ALCcontext::eax_get_session( - const EaxEaxCall& eax_call) -{ - eax_call.set_value(eax_session_); -} - -void ALCcontext::eax_get_macro_fx_factor( - const EaxEaxCall& eax_call) -{ - eax_call.set_value(eax_.context.flMacroFXFactor); -} - -void ALCcontext::eax_get_context_all( - const EaxEaxCall& eax_call) -{ - switch (eax_call.get_version()) + switch(call.get_property_id()) { - case 4: - eax_call.set_value(static_cast(eax_.context)); - break; - - case 5: - eax_call.set_value(static_cast(eax_.context)); - break; - - default: - eax_fail("Unsupported EAX version."); + case EAXCONTEXT_NONE: + break; + case EAXCONTEXT_LASTERROR: + call.set_value(mEaxLastError); + mEaxLastError = EAX_OK; + break; + case EAXCONTEXT_SPEAKERCONFIG: + call.set_value(mEaxSpeakerConfig); + break; + case EAXCONTEXT_EAXSESSION: + call.set_value(mEaxSession); + break; + default: + eax_fail_unknown_property_id(); } } -void ALCcontext::eax_get( - const EaxEaxCall& eax_call) +void ALCcontext::eax4_get(const EaxCall& call, const Eax4Props& props) { - switch (eax_call.get_property_id()) + switch(call.get_property_id()) { - case EAXCONTEXT_NONE: - break; - - case EAXCONTEXT_ALLPARAMETERS: - eax_get_context_all(eax_call); - break; - - case EAXCONTEXT_PRIMARYFXSLOTID: - eax_get_primary_fx_slot_id(eax_call); - break; - - case EAXCONTEXT_DISTANCEFACTOR: - eax_get_distance_factor(eax_call); - break; - - case EAXCONTEXT_AIRABSORPTIONHF: - eax_get_air_absorption_hf(eax_call); - break; - - case EAXCONTEXT_HFREFERENCE: - eax_get_hf_reference(eax_call); - break; - - case EAXCONTEXT_LASTERROR: - eax_get_last_error(eax_call); - break; - - case EAXCONTEXT_SPEAKERCONFIG: - eax_get_speaker_config(eax_call); - break; - - case EAXCONTEXT_EAXSESSION: - eax_get_session(eax_call); - break; - - case EAXCONTEXT_MACROFXFACTOR: - eax_get_macro_fx_factor(eax_call); - break; - - default: - eax_fail("Unsupported property id."); + case EAXCONTEXT_ALLPARAMETERS: + call.set_value(props); + break; + case EAXCONTEXT_PRIMARYFXSLOTID: + call.set_value(props.guidPrimaryFXSlotID); + break; + case EAXCONTEXT_DISTANCEFACTOR: + call.set_value(props.flDistanceFactor); + break; + case EAXCONTEXT_AIRABSORPTIONHF: + call.set_value(props.flAirAbsorptionHF); + break; + case EAXCONTEXT_HFREFERENCE: + call.set_value(props.flHFReference); + break; + default: + eax_get_misc(call); + break; } } -void ALCcontext::eax_set_primary_fx_slot_id() +void ALCcontext::eax5_get(const EaxCall& call, const Eax5Props& props) { - eax_previous_primary_fx_slot_index_ = eax_primary_fx_slot_index_; - eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID; + switch(call.get_property_id()) + { + case EAXCONTEXT_ALLPARAMETERS: + call.set_value(props); + break; + case EAXCONTEXT_PRIMARYFXSLOTID: + call.set_value(props.guidPrimaryFXSlotID); + break; + case EAXCONTEXT_DISTANCEFACTOR: + call.set_value(props.flDistanceFactor); + break; + case EAXCONTEXT_AIRABSORPTIONHF: + call.set_value(props.flAirAbsorptionHF); + break; + case EAXCONTEXT_HFREFERENCE: + call.set_value(props.flHFReference); + break; + case EAXCONTEXT_MACROFXFACTOR: + call.set_value(props.flMacroFXFactor); + break; + default: + eax_get_misc(call); + break; + } } -void ALCcontext::eax_set_distance_factor() +void ALCcontext::eax_get(const EaxCall& call) { - mListener.mMetersPerUnit = eax_.context.flDistanceFactor; + switch(call.get_version()) + { + case 4: eax4_get(call, mEax4.i); break; + case 5: eax5_get(call, mEax5.i); break; + default: eax_fail_unknown_version(); + } +} + +void ALCcontext::eax_context_commit_primary_fx_slot_id() +{ + mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID; +} + +void ALCcontext::eax_context_commit_distance_factor() +{ + if(mListener.mMetersPerUnit == mEax.flDistanceFactor) + return; + + mListener.mMetersPerUnit = mEax.flDistanceFactor; mPropsDirty = true; } -void ALCcontext::eax_set_air_absorbtion_hf() +void ALCcontext::eax_context_commit_air_absorbtion_hf() { - mAirAbsorptionGainHF = level_mb_to_gain(eax_.context.flAirAbsorptionHF); + const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF); + + if(mAirAbsorptionGainHF == new_value) + return; + + mAirAbsorptionGainHF = new_value; mPropsDirty = true; } -void ALCcontext::eax_set_hf_reference() +void ALCcontext::eax_context_commit_hf_reference() { // TODO } -void ALCcontext::eax_set_macro_fx_factor() +void ALCcontext::eax_context_commit_macro_fx_factor() { // TODO } -void ALCcontext::eax_set_context() -{ - eax_set_primary_fx_slot_id(); - eax_set_distance_factor(); - eax_set_air_absorbtion_hf(); - eax_set_hf_reference(); -} - void ALCcontext::eax_initialize_fx_slots() { - eax_fx_slots_.initialize(*this); - eax_previous_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID; - eax_primary_fx_slot_index_ = eax_.context.guidPrimaryFXSlotID; -} - -void ALCcontext::eax_initialize_sources() -{ - std::unique_lock source_lock{mSourceLock}; - auto init_source = [this](ALsource &source) noexcept - { source.eax_initialize(this); }; - ForEachSource(this, init_source); + mEaxFxSlots.initialize(*this); + mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID; } void ALCcontext::eax_update_sources() { std::unique_lock source_lock{mSourceLock}; - auto update_source = [this](ALsource &source) - { source.eax_update(eax_context_shared_dirty_flags_); }; + auto update_source = [](ALsource &source) + { source.eaxCommit(); }; ForEachSource(this, update_source); } -void ALCcontext::eax_validate_primary_fx_slot_id( - const GUID& primary_fx_slot_id) +void ALCcontext::eax_set_misc(const EaxCall& call) { - if (primary_fx_slot_id != EAX_NULL_GUID && - primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot0 && - primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot0 && - primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot1 && - primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot1 && - primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot2 && - primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot2 && - primary_fx_slot_id != EAXPROPERTYID_EAX40_FXSlot3 && - primary_fx_slot_id != EAXPROPERTYID_EAX50_FXSlot3) + switch(call.get_property_id()) { - eax_fail("Unsupported primary FX slot id."); - } -} - -void ALCcontext::eax_validate_distance_factor( - float distance_factor) -{ - eax_validate_range( - "Distance Factor", - distance_factor, - EAXCONTEXT_MINDISTANCEFACTOR, - EAXCONTEXT_MAXDISTANCEFACTOR); -} - -void ALCcontext::eax_validate_air_absorption_hf( - float air_absorption_hf) -{ - eax_validate_range( - "Air Absorption HF", - air_absorption_hf, - EAXCONTEXT_MINAIRABSORPTIONHF, - EAXCONTEXT_MAXAIRABSORPTIONHF); -} - -void ALCcontext::eax_validate_hf_reference( - float hf_reference) -{ - eax_validate_range( - "HF Reference", - hf_reference, - EAXCONTEXT_MINHFREFERENCE, - EAXCONTEXT_MAXHFREFERENCE); -} - -void ALCcontext::eax_validate_speaker_config( - unsigned long speaker_config) -{ - switch (speaker_config) - { - case HEADPHONES: - case SPEAKERS_2: - case SPEAKERS_4: - case SPEAKERS_5: - case SPEAKERS_6: - case SPEAKERS_7: - break; - - default: - eax_fail("Unsupported speaker configuration."); - } -} - -void ALCcontext::eax_validate_session_eax_version( - unsigned long eax_version) -{ - switch (eax_version) - { - case EAX_40: - case EAX_50: - break; - - default: - eax_fail("Unsupported session EAX version."); - } -} - -void ALCcontext::eax_validate_session_max_active_sends( - unsigned long max_active_sends) -{ - eax_validate_range( - "Max Active Sends", - max_active_sends, - EAXCONTEXT_MINMAXACTIVESENDS, - EAXCONTEXT_MAXMAXACTIVESENDS); -} - -void ALCcontext::eax_validate_session( - const EAXSESSIONPROPERTIES& eax_session) -{ - eax_validate_session_eax_version(eax_session.ulEAXVersion); - eax_validate_session_max_active_sends(eax_session.ulMaxActiveSends); -} - -void ALCcontext::eax_validate_macro_fx_factor( - float macro_fx_factor) -{ - eax_validate_range( - "Macro FX Factor", - macro_fx_factor, - EAXCONTEXT_MINMACROFXFACTOR, - EAXCONTEXT_MAXMACROFXFACTOR); -} - -void ALCcontext::eax_validate_context_all( - const EAX40CONTEXTPROPERTIES& context_all) -{ - eax_validate_primary_fx_slot_id(context_all.guidPrimaryFXSlotID); - eax_validate_distance_factor(context_all.flDistanceFactor); - eax_validate_air_absorption_hf(context_all.flAirAbsorptionHF); - eax_validate_hf_reference(context_all.flHFReference); -} - -void ALCcontext::eax_validate_context_all( - const EAX50CONTEXTPROPERTIES& context_all) -{ - eax_validate_context_all(static_cast(context_all)); - eax_validate_macro_fx_factor(context_all.flMacroFXFactor); -} - -void ALCcontext::eax_defer_primary_fx_slot_id( - const GUID& primary_fx_slot_id) -{ - eax_d_.context.guidPrimaryFXSlotID = primary_fx_slot_id; - - eax_context_dirty_flags_.guidPrimaryFXSlotID = - (eax_.context.guidPrimaryFXSlotID != eax_d_.context.guidPrimaryFXSlotID); -} - -void ALCcontext::eax_defer_distance_factor( - float distance_factor) -{ - eax_d_.context.flDistanceFactor = distance_factor; - - eax_context_dirty_flags_.flDistanceFactor = - (eax_.context.flDistanceFactor != eax_d_.context.flDistanceFactor); -} - -void ALCcontext::eax_defer_air_absorption_hf( - float air_absorption_hf) -{ - eax_d_.context.flAirAbsorptionHF = air_absorption_hf; - - eax_context_dirty_flags_.flAirAbsorptionHF = - (eax_.context.flAirAbsorptionHF != eax_d_.context.flAirAbsorptionHF); -} - -void ALCcontext::eax_defer_hf_reference( - float hf_reference) -{ - eax_d_.context.flHFReference = hf_reference; - - eax_context_dirty_flags_.flHFReference = - (eax_.context.flHFReference != eax_d_.context.flHFReference); -} - -void ALCcontext::eax_defer_macro_fx_factor( - float macro_fx_factor) -{ - eax_d_.context.flMacroFXFactor = macro_fx_factor; - - eax_context_dirty_flags_.flMacroFXFactor = - (eax_.context.flMacroFXFactor != eax_d_.context.flMacroFXFactor); -} - -void ALCcontext::eax_defer_context_all( - const EAX40CONTEXTPROPERTIES& context_all) -{ - eax_defer_primary_fx_slot_id(context_all.guidPrimaryFXSlotID); - eax_defer_distance_factor(context_all.flDistanceFactor); - eax_defer_air_absorption_hf(context_all.flAirAbsorptionHF); - eax_defer_hf_reference(context_all.flHFReference); -} - -void ALCcontext::eax_defer_context_all( - const EAX50CONTEXTPROPERTIES& context_all) -{ - eax_defer_context_all(static_cast(context_all)); - eax_defer_macro_fx_factor(context_all.flMacroFXFactor); -} - -void ALCcontext::eax_defer_context_all( - const EaxEaxCall& eax_call) -{ - switch(eax_call.get_version()) - { - case 4: - { - const auto& context_all = - eax_call.get_value(); - - eax_validate_context_all(context_all); - eax_defer_context_all(context_all); - } + case EAXCONTEXT_NONE: break; - - case 5: - { - const auto& context_all = - eax_call.get_value(); - - eax_validate_context_all(context_all); - eax_defer_context_all(context_all); - } + case EAXCONTEXT_SPEAKERCONFIG: + eax_set(call, mEaxSpeakerConfig); + break; + case EAXCONTEXT_EAXSESSION: + eax_set(call, mEaxSession); break; - default: - eax_fail("Unsupported EAX version."); + eax_fail_unknown_property_id(); } } -void ALCcontext::eax_defer_primary_fx_slot_id( - const EaxEaxCall& eax_call) +void ALCcontext::eax4_defer_all(const EaxCall& call, Eax4State& state) { - const auto& primary_fx_slot_id = - eax_call.get_value(); + const auto& src = call.get_value(); + Eax4AllValidator{}(src); + const auto& dst_i = state.i; + auto& dst_d = state.d; + dst_d = src; - eax_validate_primary_fx_slot_id(primary_fx_slot_id); - eax_defer_primary_fx_slot_id(primary_fx_slot_id); + if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) + mEaxDf |= eax_primary_fx_slot_id_dirty_bit; + + if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) + mEaxDf |= eax_distance_factor_dirty_bit; + + if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) + mEaxDf |= eax_air_absorption_hf_dirty_bit; + + if(dst_i.flHFReference != dst_d.flHFReference) + mEaxDf |= eax_hf_reference_dirty_bit; } -void ALCcontext::eax_defer_distance_factor( - const EaxEaxCall& eax_call) +void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state) { - const auto& distance_factor = - eax_call.get_value(); - - eax_validate_distance_factor(distance_factor); - eax_defer_distance_factor(distance_factor); -} - -void ALCcontext::eax_defer_air_absorption_hf( - const EaxEaxCall& eax_call) -{ - const auto& air_absorption_hf = - eax_call.get_value(); - - eax_validate_air_absorption_hf(air_absorption_hf); - eax_defer_air_absorption_hf(air_absorption_hf); -} - -void ALCcontext::eax_defer_hf_reference( - const EaxEaxCall& eax_call) -{ - const auto& hf_reference = - eax_call.get_value(); - - eax_validate_hf_reference(hf_reference); - eax_defer_hf_reference(hf_reference); -} - -void ALCcontext::eax_set_session( - const EaxEaxCall& eax_call) -{ - const auto& eax_session = - eax_call.get_value(); - - eax_validate_session(eax_session); - - eax_session_ = eax_session; -} - -void ALCcontext::eax_defer_macro_fx_factor( - const EaxEaxCall& eax_call) -{ - const auto& macro_fx_factor = - eax_call.get_value(); - - eax_validate_macro_fx_factor(macro_fx_factor); - eax_defer_macro_fx_factor(macro_fx_factor); -} - -void ALCcontext::eax_set( - const EaxEaxCall& eax_call) -{ - switch (eax_call.get_property_id()) + switch(call.get_property_id()) { - case EAXCONTEXT_NONE: - break; - - case EAXCONTEXT_ALLPARAMETERS: - eax_defer_context_all(eax_call); - break; - - case EAXCONTEXT_PRIMARYFXSLOTID: - eax_defer_primary_fx_slot_id(eax_call); - break; - - case EAXCONTEXT_DISTANCEFACTOR: - eax_defer_distance_factor(eax_call); - break; - - case EAXCONTEXT_AIRABSORPTIONHF: - eax_defer_air_absorption_hf(eax_call); - break; - - case EAXCONTEXT_HFREFERENCE: - eax_defer_hf_reference(eax_call); - break; - - case EAXCONTEXT_LASTERROR: - eax_fail("Last error is read-only."); - - case EAXCONTEXT_SPEAKERCONFIG: - eax_fail("Speaker configuration is read-only."); - - case EAXCONTEXT_EAXSESSION: - eax_set_session(eax_call); - break; - - case EAXCONTEXT_MACROFXFACTOR: - eax_defer_macro_fx_factor(eax_call); - break; - - default: - eax_fail("Unsupported property id."); + case EAXCONTEXT_ALLPARAMETERS: + eax4_defer_all(call, state); + break; + case EAXCONTEXT_PRIMARYFXSLOTID: + eax_defer( + call, state, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); + break; + case EAXCONTEXT_DISTANCEFACTOR: + eax_defer( + call, state, &EAX40CONTEXTPROPERTIES::flDistanceFactor); + break; + case EAXCONTEXT_AIRABSORPTIONHF: + eax_defer( + call, state, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); + break; + case EAXCONTEXT_HFREFERENCE: + eax_defer( + call, state, &EAX40CONTEXTPROPERTIES::flHFReference); + break; + default: + eax_set_misc(call); + break; } } -void ALCcontext::eax_apply_deferred() +void ALCcontext::eax5_defer_all(const EaxCall& call, Eax5State& state) { - if (eax_context_dirty_flags_ == ContextDirtyFlags{}) + const auto& src = call.get_value(); + Eax4AllValidator{}(src); + const auto& dst_i = state.i; + auto& dst_d = state.d; + dst_d = src; + + if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) + mEaxDf |= eax_primary_fx_slot_id_dirty_bit; + + if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) + mEaxDf |= eax_distance_factor_dirty_bit; + + if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) + mEaxDf |= eax_air_absorption_hf_dirty_bit; + + if(dst_i.flHFReference != dst_d.flHFReference) + mEaxDf |= eax_hf_reference_dirty_bit; + + if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor) + mEaxDf |= eax_macro_fx_factor_dirty_bit; +} + +void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state) +{ + switch(call.get_property_id()) { + case EAXCONTEXT_ALLPARAMETERS: + eax5_defer_all(call, state); + break; + case EAXCONTEXT_PRIMARYFXSLOTID: + eax_defer( + call, state, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); + break; + case EAXCONTEXT_DISTANCEFACTOR: + eax_defer( + call, state, &EAX50CONTEXTPROPERTIES::flDistanceFactor); + break; + case EAXCONTEXT_AIRABSORPTIONHF: + eax_defer( + call, state, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); + break; + case EAXCONTEXT_HFREFERENCE: + eax_defer( + call, state, &EAX50CONTEXTPROPERTIES::flHFReference); + break; + case EAXCONTEXT_MACROFXFACTOR: + eax_defer( + call, state, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); + break; + default: + eax_set_misc(call); + break; + } +} + +void ALCcontext::eax_set(const EaxCall& call) +{ + const auto version = call.get_version(); + switch(version) + { + case 4: eax4_defer(call, mEax4); break; + case 5: eax5_defer(call, mEax5); break; + default: eax_fail_unknown_version(); + } + if(version != mEaxVersion) + mEaxDf = ~EaxDirtyFlags(); + mEaxVersion = version; +} + +void ALCcontext::eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df) +{ + if(mEaxDf == EaxDirtyFlags{}) return; + + eax_context_commit_property( + state, dst_df, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_context_commit_property( + state, dst_df, &EAX40CONTEXTPROPERTIES::flDistanceFactor); + eax_context_commit_property( + state, dst_df, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_context_commit_property( + state, dst_df, &EAX40CONTEXTPROPERTIES::flHFReference); + + mEaxDf = EaxDirtyFlags{}; +} + +void ALCcontext::eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df) +{ + if(mEaxDf == EaxDirtyFlags{}) + return; + + eax_context_commit_property( + state, dst_df, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); + eax_context_commit_property( + state, dst_df, &EAX50CONTEXTPROPERTIES::flDistanceFactor); + eax_context_commit_property( + state, dst_df, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); + eax_context_commit_property( + state, dst_df, &EAX50CONTEXTPROPERTIES::flHFReference); + eax_context_commit_property( + state, dst_df, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); + + mEaxDf = EaxDirtyFlags{}; +} + +void ALCcontext::eax_context_commit() +{ + auto dst_df = EaxDirtyFlags{}; + + switch(mEaxVersion) + { + case 1: + case 2: + case 3: + eax5_context_commit(mEax123, dst_df); + break; + case 4: + eax4_context_commit(mEax4, dst_df); + break; + case 5: + eax5_context_commit(mEax5, dst_df); + break; } - eax_ = eax_d_; + if(dst_df == EaxDirtyFlags{}) + return; - if (eax_context_dirty_flags_.guidPrimaryFXSlotID) - { - eax_context_shared_dirty_flags_.primary_fx_slot_id = true; - eax_set_primary_fx_slot_id(); - } + if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_primary_fx_slot_id(); - if (eax_context_dirty_flags_.flDistanceFactor) - { - eax_set_distance_factor(); - } + if((dst_df & eax_distance_factor_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_distance_factor(); - if (eax_context_dirty_flags_.flAirAbsorptionHF) - { - eax_set_air_absorbtion_hf(); - } + if((dst_df & eax_air_absorption_hf_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_air_absorbtion_hf(); - if (eax_context_dirty_flags_.flHFReference) - { - eax_set_hf_reference(); - } + if((dst_df & eax_hf_reference_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_hf_reference(); - if (eax_context_dirty_flags_.flMacroFXFactor) - { - eax_set_macro_fx_factor(); - } + if((dst_df & eax_macro_fx_factor_dirty_bit) != EaxDirtyFlags{}) + eax_context_commit_macro_fx_factor(); - if (eax_context_shared_dirty_flags_ != EaxContextSharedDirtyFlags{}) - { + if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) eax_update_sources(); - } +} - eax_context_shared_dirty_flags_ = EaxContextSharedDirtyFlags{}; - eax_context_dirty_flags_ = ContextDirtyFlags{}; +void ALCcontext::eaxCommit() +{ + mEaxNeedsCommit = false; + eax_context_commit(); + eaxCommitFxSlots(); + eax_update_sources(); } -namespace -{ - - -class EaxSetException : - public EaxException -{ -public: - explicit EaxSetException( - const char* message) - : - EaxException{"EAX_SET", message} - { - } -}; // EaxSetException - - -[[noreturn]] -void eax_fail_set( - const char* message) -{ - throw EaxSetException{message}; -} - - -class EaxGetException : - public EaxException -{ -public: - explicit EaxGetException( - const char* message) - : - EaxException{"EAX_GET", message} - { - } -}; // EaxGetException - - -[[noreturn]] -void eax_fail_get( - const char* message) -{ - throw EaxGetException{message}; -} - - -} // namespace - - -FORCE_ALIGN ALenum AL_APIENTRY EAXSet( - const GUID* property_set_id, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_value, - ALuint property_value_size) noexcept -try +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/libs/openal-soft/alc/context.h b/libs/openal-soft/alc/context.h index 72b259e9..10afd4ab 100644 --- a/libs/openal-soft/alc/context.h +++ b/libs/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 "AL/al.h" #include "AL/alc.h" @@ -14,100 +20,71 @@ #include "al/listener.h" #include "almalloc.h" #include "alnumeric.h" +#include "althreads.h" #include "atomic.h" #include "core/context.h" +#include "inprogext.h" #include "intrusive_ptr.h" -#include "vector.h" #ifdef ALSOFT_EAX -#include "al/eax_eax_call.h" -#include "al/eax_fx_slot_index.h" -#include "al/eax_fx_slots.h" -#include "al/eax_utils.h" - - -using EaxContextSharedDirtyFlagsValue = std::uint_least8_t; - -struct EaxContextSharedDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - EaxContextSharedDirtyFlagsValue primary_fx_slot_id : 1; -}; // EaxContextSharedDirtyFlags - - -using ContextDirtyFlagsValue = std::uint_least8_t; - -struct ContextDirtyFlags -{ - using EaxIsBitFieldStruct = bool; - - ContextDirtyFlagsValue guidPrimaryFXSlotID : 1; - ContextDirtyFlagsValue flDistanceFactor : 1; - ContextDirtyFlagsValue flAirAbsorptionHF : 1; - ContextDirtyFlagsValue flHFReference : 1; - ContextDirtyFlagsValue flMacroFXFactor : 1; -}; // ContextDirtyFlags - - -struct EaxAlIsExtensionPresentResult -{ - ALboolean is_present; - bool is_return; -}; // EaxAlIsExtensionPresentResult +#include "al/eax/call.h" +#include "al/eax/exception.h" +#include "al/eax/fx_slot_index.h" +#include "al/eax/fx_slots.h" +#include "al/eax/utils.h" #endif // ALSOFT_EAX struct ALeffect; struct ALeffectslot; struct ALsource; +struct DebugGroup; +struct EffectSlotSubList; +struct SourceSubList; + +enum class DebugSource : uint8_t; +enum class DebugType : uint8_t; +enum class DebugSeverity : 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; - /* Wet buffers used by effect slots. */ - al::vector mWetBuffers; - bool mPropsDirty{true}; bool mDeferUpdates{false}; 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}; @@ -121,23 +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::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(); @@ -145,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. @@ -173,19 +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. @@ -193,37 +192,29 @@ private: class ThreadCtx { public: ~ThreadCtx(); + /* NOLINTBEGIN(readability-convert-member-functions-to-static) + * This should be non-static to invoke construction of the thread-local + * sThreadContext, so that it's destructor gets run at thread exit to + * clear sLocalContext (which isn't a member variable to make read + * access efficient). + */ void set(ALCcontext *ctx) const noexcept { sLocalContext = ctx; } + /* NOLINTEND(readability-convert-member-functions-to-static) */ }; static thread_local ThreadCtx sThreadContext; public: - /* HACK: MinGW generates bad code when accessing an extern thread_local - * object. Add a wrapper function for it that only accesses it where it's - * defined. - */ -#ifdef __MINGW32__ - static ALCcontext *getThreadContext() noexcept; - static void setThreadContext(ALCcontext *context) noexcept; -#else static ALCcontext *getThreadContext() noexcept { return sLocalContext; } static void setThreadContext(ALCcontext *context) noexcept { sThreadContext.set(context); } -#endif /* Default effect that applies to sources that don't have an effect on send 0. */ static ALeffect sDefaultEffect; - DEF_NEWDEL(ALCcontext) - #ifdef ALSOFT_EAX -public: - bool has_eax() const noexcept { return eax_is_initialized_; } - - bool eax_is_capable() const noexcept; - - - void eax_uninitialize() noexcept; + bool hasEax() const noexcept { return mEaxIsInitialized; } + bool eaxIsCapable() const noexcept; + void eaxUninitialize() noexcept; ALenum eax_eax_set( const GUID* property_set_id, @@ -239,266 +230,327 @@ public: ALvoid* property_value, ALuint property_value_size); + void eaxSetLastError() noexcept; - void eax_update_filters(); + EaxFxSlotIndex eaxGetPrimaryFxSlotIndex() const noexcept + { return mEaxPrimaryFxSlotIndex; } - void eax_commit_and_update_sources(); + const ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) const + { return mEaxFxSlots.get(fx_slot_index); } + ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) + { return mEaxFxSlots.get(fx_slot_index); } + bool eaxNeedsCommit() const noexcept { return mEaxNeedsCommit; } + void eaxCommit(); - void eax_set_last_error() noexcept; - - - EaxFxSlotIndex eax_get_previous_primary_fx_slot_index() const noexcept - { return eax_previous_primary_fx_slot_index_; } - EaxFxSlotIndex eax_get_primary_fx_slot_index() const noexcept - { return eax_primary_fx_slot_index_; } - - const ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index) const - { return eax_fx_slots_.get(fx_slot_index); } - ALeffectslot& eax_get_fx_slot(EaxFxSlotIndexValue fx_slot_index) - { return eax_fx_slots_.get(fx_slot_index); } - - void eax_commit_fx_slots() - { eax_fx_slots_.commit(); } + void eaxCommitFxSlots() + { mEaxFxSlots.commit(); } private: - struct Eax + static constexpr auto eax_primary_fx_slot_id_dirty_bit = EaxDirtyFlags{1} << 0; + static constexpr auto eax_distance_factor_dirty_bit = EaxDirtyFlags{1} << 1; + static constexpr auto eax_air_absorption_hf_dirty_bit = EaxDirtyFlags{1} << 2; + static constexpr auto eax_hf_reference_dirty_bit = EaxDirtyFlags{1} << 3; + static constexpr auto eax_macro_fx_factor_dirty_bit = EaxDirtyFlags{1} << 4; + + using Eax4Props = EAX40CONTEXTPROPERTIES; + + struct Eax4State { + Eax4Props i; // Immediate. + Eax4Props d; // Deferred. + }; + + using Eax5Props = EAX50CONTEXTPROPERTIES; + + struct Eax5State { + Eax5Props i; // Immediate. + Eax5Props d; // Deferred. + }; + + class ContextException : public EaxException { - EAX50CONTEXTPROPERTIES context{}; - }; // Eax + public: + explicit ContextException(const char* message) + : EaxException{"EAX_CONTEXT", message} + {} + }; + struct Eax4PrimaryFxSlotIdValidator { + void operator()(const GUID& guidPrimaryFXSlotID) const + { + if(guidPrimaryFXSlotID != EAX_NULL_GUID && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot3) + { + eax_fail_unknown_primary_fx_slot_id(); + } + } + }; - bool eax_is_initialized_{}; - bool eax_is_tried_{}; - bool eax_are_legacy_fx_slots_unlocked_{}; + struct Eax4DistanceFactorValidator { + void operator()(float flDistanceFactor) const + { + eax_validate_range( + "Distance Factor", + flDistanceFactor, + EAXCONTEXT_MINDISTANCEFACTOR, + EAXCONTEXT_MAXDISTANCEFACTOR); + } + }; - long eax_last_error_{}; - unsigned long eax_speaker_config_{}; + struct Eax4AirAbsorptionHfValidator { + void operator()(float flAirAbsorptionHF) const + { + eax_validate_range( + "Air Absorption HF", + flAirAbsorptionHF, + EAXCONTEXT_MINAIRABSORPTIONHF, + EAXCONTEXT_MAXAIRABSORPTIONHF); + } + }; - EaxFxSlotIndex eax_previous_primary_fx_slot_index_{}; - EaxFxSlotIndex eax_primary_fx_slot_index_{}; - EaxFxSlots eax_fx_slots_{}; + struct Eax4HfReferenceValidator { + void operator()(float flHFReference) const + { + eax_validate_range( + "HF Reference", + flHFReference, + EAXCONTEXT_MINHFREFERENCE, + EAXCONTEXT_MAXHFREFERENCE); + } + }; - EaxContextSharedDirtyFlags eax_context_shared_dirty_flags_{}; + struct Eax4AllValidator { + void operator()(const EAX40CONTEXTPROPERTIES& all) const + { + Eax4PrimaryFxSlotIdValidator{}(all.guidPrimaryFXSlotID); + Eax4DistanceFactorValidator{}(all.flDistanceFactor); + Eax4AirAbsorptionHfValidator{}(all.flAirAbsorptionHF); + Eax4HfReferenceValidator{}(all.flHFReference); + } + }; - Eax eax_{}; - Eax eax_d_{}; - EAXSESSIONPROPERTIES eax_session_{}; + struct Eax5PrimaryFxSlotIdValidator { + void operator()(const GUID& guidPrimaryFXSlotID) const + { + if(guidPrimaryFXSlotID != EAX_NULL_GUID && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 && + guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot3) + { + eax_fail_unknown_primary_fx_slot_id(); + } + } + }; - ContextDirtyFlags eax_context_dirty_flags_{}; + struct Eax5MacroFxFactorValidator { + void operator()(float flMacroFXFactor) const + { + eax_validate_range( + "Macro FX Factor", + flMacroFXFactor, + EAXCONTEXT_MINMACROFXFACTOR, + EAXCONTEXT_MAXMACROFXFACTOR); + } + }; - std::string eax_extension_list_{}; + struct Eax5AllValidator { + void operator()(const EAX50CONTEXTPROPERTIES& all) const + { + Eax5PrimaryFxSlotIdValidator{}(all.guidPrimaryFXSlotID); + Eax4DistanceFactorValidator{}(all.flDistanceFactor); + Eax4AirAbsorptionHfValidator{}(all.flAirAbsorptionHF); + Eax4HfReferenceValidator{}(all.flHFReference); + Eax5MacroFxFactorValidator{}(all.flMacroFXFactor); + } + }; + struct Eax5EaxVersionValidator { + void operator()(unsigned long ulEAXVersion) const + { + eax_validate_range( + "EAX version", + ulEAXVersion, + EAXCONTEXT_MINEAXSESSION, + EAXCONTEXT_MAXEAXSESSION); + } + }; - [[noreturn]] - static void eax_fail( - const char* message); + struct Eax5MaxActiveSendsValidator { + void operator()(unsigned long ulMaxActiveSends) const + { + eax_validate_range( + "Max Active Sends", + ulMaxActiveSends, + EAXCONTEXT_MINMAXACTIVESENDS, + EAXCONTEXT_MAXMAXACTIVESENDS); + } + }; + struct Eax5SessionAllValidator { + void operator()(const EAXSESSIONPROPERTIES& all) const + { + Eax5EaxVersionValidator{}(all.ulEAXVersion); + Eax5MaxActiveSendsValidator{}(all.ulMaxActiveSends); + } + }; + + struct Eax5SpeakerConfigValidator { + void operator()(unsigned long ulSpeakerConfig) const + { + eax_validate_range( + "Speaker Config", + ulSpeakerConfig, + EAXCONTEXT_MINSPEAKERCONFIG, + EAXCONTEXT_MAXSPEAKERCONFIG); + } + }; + + bool mEaxIsInitialized{}; + bool mEaxIsTried{}; + + long mEaxLastError{}; + unsigned long mEaxSpeakerConfig{}; + + EaxFxSlotIndex mEaxPrimaryFxSlotIndex{}; + EaxFxSlots mEaxFxSlots{}; + + int mEaxVersion{}; // Current EAX version. + bool mEaxNeedsCommit{}; + EaxDirtyFlags mEaxDf{}; // Dirty flags for the current EAX version. + Eax5State mEax123{}; // EAX1/EAX2/EAX3 state. + Eax4State mEax4{}; // EAX4 state. + Eax5State mEax5{}; // EAX5 state. + Eax5Props mEax{}; // Current EAX state. + EAXSESSIONPROPERTIES mEaxSession{}; + + [[noreturn]] static void eax_fail(const char* message); + [[noreturn]] static void eax_fail_unknown_property_set_id(); + [[noreturn]] static void eax_fail_unknown_primary_fx_slot_id(); + [[noreturn]] static void eax_fail_unknown_property_id(); + [[noreturn]] static void eax_fail_unknown_version(); + + // Gets a value from EAX call, + // validates it, + // and updates the current value. + template + static void eax_set(const EaxCall& call, TProperty& property) + { + const auto& value = call.get_value(); + TValidator{}(value); + property = value; + } + + // Gets a new value from EAX call, + // validates it, + // updates the deferred value, + // updates a dirty flag. + template< + typename TValidator, + EaxDirtyFlags TDirtyBit, + typename TMemberResult, + typename TProps, + typename TState> + void eax_defer(const EaxCall& call, TState& state, TMemberResult TProps::*member) + { + const auto& src = call.get_value(); + TValidator{}(src); + const auto& dst_i = state.i.*member; + auto& dst_d = state.d.*member; + dst_d = src; + + if(dst_i != dst_d) + mEaxDf |= TDirtyBit; + } + + template< + EaxDirtyFlags TDirtyBit, + typename TMemberResult, + typename TProps, + typename TState> + void eax_context_commit_property(TState& state, EaxDirtyFlags& dst_df, + TMemberResult TProps::*member) noexcept + { + if((mEaxDf & TDirtyBit) != EaxDirtyFlags{}) + { + dst_df |= TDirtyBit; + const auto& src_d = state.d.*member; + state.i.*member = src_d; + mEax.*member = src_d; + } + } void eax_initialize_extensions(); - void eax_initialize(); - bool eax_has_no_default_effect_slot() const noexcept; - void eax_ensure_no_default_effect_slot() const; - bool eax_has_enough_aux_sends() const noexcept; - void eax_ensure_enough_aux_sends() const; - void eax_ensure_compatibility(); - unsigned long eax_detect_speaker_configuration() const; void eax_update_speaker_configuration(); - void eax_set_last_error_defaults() noexcept; + void eax_session_set_defaults() noexcept; + static void eax4_context_set_defaults(Eax4Props& props) noexcept; + static void eax4_context_set_defaults(Eax4State& state) noexcept; + static void eax5_context_set_defaults(Eax5Props& props) noexcept; + static void eax5_context_set_defaults(Eax5State& state) noexcept; + void eax_context_set_defaults(); + void eax_set_defaults(); - void eax_set_session_defaults() noexcept; + void eax_dispatch_fx_slot(const EaxCall& call); + void eax_dispatch_source(const EaxCall& call); - void eax_set_context_defaults() noexcept; + void eax_get_misc(const EaxCall& call); + void eax4_get(const EaxCall& call, const Eax4Props& props); + void eax5_get(const EaxCall& call, const Eax5Props& props); + void eax_get(const EaxCall& call); - void eax_set_defaults() noexcept; - - void eax_initialize_sources(); - - - void eax_unlock_legacy_fx_slots(const EaxEaxCall& eax_call) noexcept; - - - void eax_dispatch_fx_slot( - const EaxEaxCall& eax_call); - - void eax_dispatch_source( - const EaxEaxCall& eax_call); - - - void eax_get_primary_fx_slot_id( - const EaxEaxCall& eax_call); - - void eax_get_distance_factor( - const EaxEaxCall& eax_call); - - void eax_get_air_absorption_hf( - const EaxEaxCall& eax_call); - - void eax_get_hf_reference( - const EaxEaxCall& eax_call); - - void eax_get_last_error( - const EaxEaxCall& eax_call); - - void eax_get_speaker_config( - const EaxEaxCall& eax_call); - - void eax_get_session( - const EaxEaxCall& eax_call); - - void eax_get_macro_fx_factor( - const EaxEaxCall& eax_call); - - void eax_get_context_all( - const EaxEaxCall& eax_call); - - void eax_get( - const EaxEaxCall& eax_call); - - - void eax_set_primary_fx_slot_id(); - - void eax_set_distance_factor(); - - void eax_set_air_absorbtion_hf(); - - void eax_set_hf_reference(); - - void eax_set_macro_fx_factor(); - - void eax_set_context(); + void eax_context_commit_primary_fx_slot_id(); + void eax_context_commit_distance_factor(); + void eax_context_commit_air_absorbtion_hf(); + void eax_context_commit_hf_reference(); + void eax_context_commit_macro_fx_factor(); void eax_initialize_fx_slots(); - void eax_update_sources(); + void eax_set_misc(const EaxCall& call); + void eax4_defer_all(const EaxCall& call, Eax4State& state); + void eax4_defer(const EaxCall& call, Eax4State& state); + void eax5_defer_all(const EaxCall& call, Eax5State& state); + void eax5_defer(const EaxCall& call, Eax5State& state); + void eax_set(const EaxCall& call); - void eax_validate_primary_fx_slot_id( - const GUID& primary_fx_slot_id); - - void eax_validate_distance_factor( - float distance_factor); - - void eax_validate_air_absorption_hf( - float air_absorption_hf); - - void eax_validate_hf_reference( - float hf_reference); - - void eax_validate_speaker_config( - unsigned long speaker_config); - - void eax_validate_session_eax_version( - unsigned long eax_version); - - void eax_validate_session_max_active_sends( - unsigned long max_active_sends); - - void eax_validate_session( - const EAXSESSIONPROPERTIES& eax_session); - - void eax_validate_macro_fx_factor( - float macro_fx_factor); - - void eax_validate_context_all( - const EAX40CONTEXTPROPERTIES& context_all); - - void eax_validate_context_all( - const EAX50CONTEXTPROPERTIES& context_all); - - - void eax_defer_primary_fx_slot_id( - const GUID& primary_fx_slot_id); - - void eax_defer_distance_factor( - float distance_factor); - - void eax_defer_air_absorption_hf( - float air_absorption_hf); - - void eax_defer_hf_reference( - float hf_reference); - - void eax_defer_macro_fx_factor( - float macro_fx_factor); - - void eax_defer_context_all( - const EAX40CONTEXTPROPERTIES& context_all); - - void eax_defer_context_all( - const EAX50CONTEXTPROPERTIES& context_all); - - - void eax_defer_context_all( - const EaxEaxCall& eax_call); - - void eax_defer_primary_fx_slot_id( - const EaxEaxCall& eax_call); - - void eax_defer_distance_factor( - const EaxEaxCall& eax_call); - - void eax_defer_air_absorption_hf( - const EaxEaxCall& eax_call); - - void eax_defer_hf_reference( - const EaxEaxCall& eax_call); - - void eax_set_session( - const EaxEaxCall& eax_call); - - void eax_defer_macro_fx_factor( - const EaxEaxCall& eax_call); - - void eax_set( - const EaxEaxCall& eax_call); - - void eax_apply_deferred(); + void eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df); + void eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df); + void eax_context_commit(); #endif // ALSOFT_EAX }; -#define SETERR_RETURN(ctx, err, retval, ...) do { \ - (ctx)->setError((err), __VA_ARGS__); \ - return retval; \ -} while(0) - - using ContextRef = al::intrusive_ptr; -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/libs/openal-soft/alc/device.cpp b/libs/openal-soft/alc/device.cpp index e06c0d74..fc145579 100644 --- a/libs/openal-soft/alc/device.cpp +++ b/libs/openal-soft/alc/device.cpp @@ -3,9 +3,12 @@ #include "device.h" +#include #include -#include +#include "al/buffer.h" +#include "al/effect.h" +#include "al/filter.h" #include "albit.h" #include "alconfig.h" #include "backends/base.h" @@ -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()) @@ -84,7 +87,10 @@ auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 case DevFmtX51: return OutputMode1::X51; case DevFmtX61: return OutputMode1::X61; case DevFmtX71: return OutputMode1::X71; - case DevFmtAmbi3D: break; + case DevFmtX714: + case DevFmtX3D71: + case DevFmtAmbi3D: + break; } return OutputMode1::Any; } diff --git a/libs/openal-soft/alc/device.h b/libs/openal-soft/alc/device.h index 04931a5a..dd9335dc 100644 --- a/libs/openal-soft/alc/device.h +++ b/libs/openal-soft/alc/device.h @@ -1,12 +1,16 @@ #ifndef ALC_DEVICE_H #define ALC_DEVICE_H +#include #include #include #include +#include #include #include +#include #include +#include #include "AL/alc.h" #include "AL/alext.h" @@ -17,66 +21,22 @@ #include "core/device.h" #include "inprogext.h" #include "intrusive_ptr.h" -#include "vector.h" #ifdef ALSOFT_EAX -#include "al/eax_x_ram.h" +#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 +54,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 +77,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 - 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/libs/openal-soft/alc/effects/autowah.cpp b/libs/openal-soft/alc/effects/autowah.cpp index 50c4a5ad..0b5aa1d2 100644 --- a/libs/openal-soft/alc/effects/autowah.cpp +++ b/libs/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,45 +50,47 @@ 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 { - /* Effect filters' history. */ - struct { - float z1, z2; - } Filter; + struct ChannelData { + uint mTargetChannel{InvalidChannelIndex}; + + struct FilterHistory { + float z1{}, z2{}; + }; + FilterHistory mFilter; /* Effect gains for each output channel */ - float CurrentGains[MAX_OUTPUT_CHANNELS]; - float TargetGains[MAX_OUTPUT_CHANNELS]; - } 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 Buffer &buffer) override; + 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(AutowahState) }; -void AutowahState::deviceUpdate(const DeviceBase*, const Buffer&) +void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*) { /* (Re-)initializing parameters and clear the buffers. */ @@ -108,32 +110,37 @@ void AutowahState::deviceUpdate(const DeviceBase*, const Buffer&) for(auto &chan : mChans) { - std::fill(std::begin(chan.CurrentGains), std::end(chan.CurrentGains), 0.0f); - chan.Filter.z1 = 0.0f; - chan.Filter.z2 = 0.0f; + chan.mTargetChannel = InvalidChannelIndex; + chan.mFilter.z1 = 0.0f; + chan.mFilter.z2 = 0.0f; + chan.mCurrentGain = 0.0f; } } 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; mOutTarget = target.Main->Buffer; - auto set_gains = [slot,target](auto &chan, al::span coeffs) - { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; - SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); + auto set_channel = [this](size_t idx, uint outchan, float outgain) + { + mChans[idx].mTargetChannel = outchan; + mChans[idx].mTargetGain = outgain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void AutowahState::process(const size_t samplesToDo, @@ -149,60 +156,66 @@ 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::addressof(mChans[0]); + auto chandata = mChans.begin(); for(const auto &insamples : samplesIn) { + const size_t outidx{chandata->mTargetChannel}; + if(outidx == InvalidChannelIndex) + { + ++chandata; + continue; + } + /* This effectively inlines BiquadFilter_setParams for a peaking * filter and BiquadFilter_processC. The alpha and cosine components * for the filter coefficients were previously calculated with the * envelope. Because the filter changes for each sample, the * coefficients are transient and don't need to be held. */ - float z1{chandata->Filter.z1}; - float z2{chandata->Filter.z2}; + float z1{chandata->mFilter.z1}; + float z2{chandata->mFilter.z2}; for(size_t i{0u};i < samplesToDo;i++) { const float alpha{mEnv[i].alpha}; const float cos_w0{mEnv[i].cos_w0}; - float input, output; - float a[3], b[3]; - b[0] = 1.0f + alpha*res_gain; - b[1] = -2.0f * cos_w0; - b[2] = 1.0f - alpha*res_gain; - a[0] = 1.0f + alpha/res_gain; - a[1] = -2.0f * cos_w0; - a[2] = 1.0f - alpha/res_gain; + 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; } - chandata->Filter.z1 = z1; - chandata->Filter.z2 = z2; + chandata->mFilter.z1 = z1; + chandata->mFilter.z2 = z2; /* Now, mix the processed sound data to the output. */ - MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains, - chandata->TargetGains, samplesToDo, 0); + MixSamples({mBufferOut.data(), samplesToDo}, samplesOut[outidx].data(), + chandata->mCurrentGain, chandata->mTargetGain, samplesToDo); ++chandata; } } diff --git a/libs/openal-soft/alc/effects/base.h b/libs/openal-soft/alc/effects/base.h index 95695857..9bbbfc71 100644 --- a/libs/openal-soft/alc/effects/base.h +++ b/libs/openal-soft/alc/effects/base.h @@ -4,23 +4,30 @@ #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 *StdReverbStateFactory_getFactory(); +EffectStateFactory *AutowahStateFactory_getFactory(); +EffectStateFactory *ChorusStateFactory_getFactory(); +EffectStateFactory *CompressorStateFactory_getFactory(); +EffectStateFactory *DistortionStateFactory_getFactory(); +EffectStateFactory *EchoStateFactory_getFactory(); +EffectStateFactory *EqualizerStateFactory_getFactory(); +EffectStateFactory *FlangerStateFactory_getFactory(); +EffectStateFactory *FshifterStateFactory_getFactory(); +EffectStateFactory *ModulatorStateFactory_getFactory(); +EffectStateFactory *PshifterStateFactory_getFactory(); +EffectStateFactory* VmorpherStateFactory_getFactory(); + +EffectStateFactory *DedicatedDialogStateFactory_getFactory(); +EffectStateFactory *DedicatedLfeStateFactory_getFactory(); + +EffectStateFactory *ConvolutionStateFactory_getFactory(); #endif /* EFFECTS_BASE_H */ diff --git a/libs/openal-soft/alc/effects/chorus.cpp b/libs/openal-soft/alc/effects/chorus.cpp index 99a2a68a..98a4a408 100644 --- a/libs/openal-soft/alc/effects/chorus.cpp +++ b/libs/openal-soft/alc/effects/chorus.cpp @@ -22,36 +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; -#define MAX_UPDATE_SAMPLES 256 +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 mSampleBuffer; + +struct ChorusState : public EffectState { + std::vector mDelayBuffer; uint mOffset{0}; uint mLfoOffset{0}; @@ -59,11 +67,18 @@ struct ChorusState final : public EffectState { float mLfoScale{0.0f}; uint mLfoDisp{0}; - /* Gains for left and right sides */ - struct { - float Current[MAX_OUTPUT_CHANNELS]{}; - float Target[MAX_OUTPUT_CHANNELS]{}; - } mGains[2]; + /* Calculated delays to apply to the left and right outputs. */ + std::array,2> mModDelays{}; + + /* Temp storage for the modulated left and right outputs. */ + alignas(16) std::array mBuffer{}; + + /* Gains for left and right outputs. */ + struct OutGains { + std::array Current{}; + std::array Target{}; + }; + std::array mGains; /* effect parameters */ ChorusWaveform mWaveform{}; @@ -71,63 +86,83 @@ struct ChorusState final : public EffectState { float mDepth{0.0f}; float mFeedback{0.0f}; - void getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo); - void getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo); + void calcTriangleDelays(const size_t todo); + void calcSinusoidDelays(const size_t todo); - void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; - void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, - const EffectTarget target) override; + void 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); + + void deviceUpdate(const DeviceBase *device, const BufferStorage*) override + { deviceUpdate(device, ChorusMaxDelay); } + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, + const EffectTarget target) override + { + auto &props = std::get(*props_); + update(context, slot, props.Waveform, props.Delay, props.Depth, props.Feedback, props.Rate, + props.Phase, target); + } void process(const size_t samplesToDo, const al::span samplesIn, - const al::span samplesOut) override; - - DEF_NEWDEL(ChorusState) + const al::span samplesOut) final; }; -void ChorusState::deviceUpdate(const DeviceBase *Device, const Buffer&) +struct FlangerState final : public ChorusState { + void deviceUpdate(const DeviceBase *device, const BufferStorage*) final + { ChorusState::deviceUpdate(device, FlangerMaxDelay); } + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, + const EffectTarget target) final + { + auto &props = std::get(*props_); + ChorusState::update(context, slot, props.Waveform, props.Delay, props.Depth, + props.Feedback, props.Rate, props.Phase, target); + } +}; + + +void ChorusState::deviceUpdate(const DeviceBase *Device, const float MaxDelay) { - constexpr float max_delay{maxf(ChorusMaxDelay, FlangerMaxDelay)}; - const auto frequency = static_cast(Device->Frequency); - const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)}; - if(maxlen != mSampleBuffer.size()) - al::vector(maxlen).swap(mSampleBuffer); + const size_t maxlen{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)}; + if(maxlen != mDelayBuffer.size()) + decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer); - std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); + 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 ChorusWaveform waveform, const float delay, const float depth, const float feedback, + const float rate, int phase, const EffectTarget target) { - constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits}; + static constexpr int mindelay{MaxResamplerEdge << gCubicTable.sTableBits}; /* 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 = waveform; - mDelay = maxi(float2int(props->Chorus.Delay*frequency*MixerFracOne + 0.5f), mindelay); - mDepth = minf(props->Chorus.Depth * static_cast(mDelay), - static_cast(mDelay - mindelay)); + mDelay = std::max(float2int(std::round(delay*frequency*gCubicTable.sTableSteps)), mindelay); + mDepth = std::min(static_cast(mDelay)*depth, static_cast(mDelay-mindelay)); - mFeedback = props->Chorus.Feedback; + mFeedback = feedback; /* Gains for left and right sides */ - const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f); - const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f); + 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}; 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); - float rate{props->Chorus.Rate}; if(!(rate > 0.0f)) { mLfoOffset = 0; @@ -140,7 +175,8 @@ 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 uint lfo_range{float2uint(std::min(std::round(frequency/rate), float{range_limit}))}; mLfoOffset = mLfoOffset * lfo_range / mLfoRange; mLfoRange = lfo_range; @@ -155,14 +191,13 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot, } /* Calculate lfo phase displacement */ - int phase{props->Chorus.Phase}; if(phase < 0) phase = 360 + phase; mLfoDisp = (mLfoRange*static_cast(phase) + 180) / 360; } } -void ChorusState::getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo) +void ChorusState::calcTriangleDelays(const size_t todo) { const uint lfo_range{mLfoRange}; const float lfo_scale{mLfoScale}; @@ -172,22 +207,38 @@ void ChorusState::getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const si ASSUME(lfo_range > 0); ASSUME(todo > 0); - uint offset{mLfoOffset}; - auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint + auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { - offset = (offset+1)%lfo_range; const float offset_norm{static_cast(offset) * lfo_scale}; return static_cast(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay); }; - std::generate_n(delays[0], todo, gen_lfo); + + uint offset{mLfoOffset}; + for(size_t i{0};i < todo;) + { + size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + do { + mModDelays[0][i++] = gen_lfo(offset++); + } while(--rem); + if(offset == lfo_range) + offset = 0; + } offset = (mLfoOffset+mLfoDisp) % lfo_range; - std::generate_n(delays[1], todo, gen_lfo); + for(size_t i{0};i < todo;) + { + size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + do { + mModDelays[1][i++] = gen_lfo(offset++); + } while(--rem); + if(offset == lfo_range) + offset = 0; + } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; } -void ChorusState::getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo) +void ChorusState::calcSinusoidDelays(const size_t todo) { const uint lfo_range{mLfoRange}; const float lfo_scale{mLfoScale}; @@ -197,69 +248,85 @@ void ChorusState::getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const si ASSUME(lfo_range > 0); ASSUME(todo > 0); - uint offset{mLfoOffset}; - auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint + auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { - offset = (offset+1)%lfo_range; const float offset_norm{static_cast(offset) * lfo_scale}; return static_cast(fastf2i(std::sin(offset_norm)*depth) + delay); }; - std::generate_n(delays[0], todo, gen_lfo); + + uint offset{mLfoOffset}; + for(size_t i{0};i < todo;) + { + size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + do { + mModDelays[0][i++] = gen_lfo(offset++); + } while(--rem); + if(offset == lfo_range) + offset = 0; + } offset = (mLfoOffset+mLfoDisp) % lfo_range; - std::generate_n(delays[1], todo, gen_lfo); + for(size_t i{0};i < todo;) + { + size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + do { + mModDelays[1][i++] = gen_lfo(offset++); + } while(--rem); + if(offset == lfo_range) + offset = 0; + } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; } void ChorusState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - const size_t bufmask{mSampleBuffer.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) + (MixerFracOne>>1)) >> MixerFracBits}; - float *RESTRICT delaybuf{mSampleBuffer.data()}; + const uint avgdelay{(static_cast(mDelay) + MixerFracHalf) >> MixerFracBits}; uint offset{mOffset}; - for(size_t base{0u};base < samplesToDo;) + if(mWaveform == ChorusWaveform::Sinusoid) + calcSinusoidDelays(samplesToDo); + else /*if(mWaveform == ChorusWaveform::Triangle)*/ + calcTriangleDelays(samplesToDo); + + 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) { - const size_t todo{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; + // Feed the buffer's input first (necessary for delays < 1). + delaybuf[offset&bufmask] = samplesIn[0][i]; - uint moddelays[2][MAX_UPDATE_SAMPLES]; - if(mWaveform == ChorusWaveform::Sinusoid) - getSinusoidDelays(moddelays, todo); - else /*if(mWaveform == ChorusWaveform::Triangle)*/ - getTriangleDelays(moddelays, todo); + // Tap for the left output. + 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); - alignas(16) float temps[2][MAX_UPDATE_SAMPLES]; - for(size_t i{0u};i < todo;++i) - { - // Feed the buffer's input first (necessary for delays < 1). - delaybuf[offset&bufmask] = samplesIn[0][base+i]; + // Tap for the right output. + 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); - // Tap for the left output. - uint delay{offset - (moddelays[0][i]>>MixerFracBits)}; - float mu{static_cast(moddelays[0][i]&MixerFracMask) * (1.0f/MixerFracOne)}; - temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); - - // Tap for the right output. - delay = offset - (moddelays[1][i]>>MixerFracBits); - mu = static_cast(moddelays[1][i]&MixerFracMask) * (1.0f/MixerFracOne); - temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); - - // Accumulate feedback from the average delay of the taps. - delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback; - ++offset; - } - - for(size_t c{0};c < 2;++c) - MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target, - samplesToDo-base, base); - - base += todo; + // Accumulate feedback from the average delay of the taps. + delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback; + ++offset; } + MixSamples(lbuffer.first(samplesToDo), samplesOut, mGains[0].Current.data(), + mGains[0].Target.data(), samplesToDo, 0); + MixSamples(rbuffer.first(samplesToDo), samplesOut, mGains[1].Current.data(), + mGains[1].Target.data(), samplesToDo, 0); + mOffset = offset; } @@ -275,7 +342,7 @@ struct ChorusStateFactory final : public EffectStateFactory { */ struct FlangerStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override - { return al::intrusive_ptr{new ChorusState{}}; } + { return al::intrusive_ptr{new FlangerState{}}; } }; } // namespace diff --git a/libs/openal-soft/alc/effects/compressor.cpp b/libs/openal-soft/alc/effects/compressor.cpp index 366f2275..ed5c9be6 100644 --- a/libs/openal-soft/alc/effects/compressor.cpp +++ b/libs/openal-soft/alc/effects/compressor.cpp @@ -32,39 +32,42 @@ #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 */ - float mGain[MaxAmbiChannels][MAX_OUTPUT_CHANNELS]{}; + struct TargetGain { + uint mTarget{InvalidChannelIndex}; + float mGain{1.0f}; + }; + std::array mChans; /* Effect parameters */ bool mEnabled{true}; @@ -73,39 +76,40 @@ struct CompressorState final : public EffectState { float mEnvFollower{1.0f}; - void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + 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(CompressorState) }; -void CompressorState::deviceUpdate(const DeviceBase *device, const Buffer&) +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_gains = [slot,target](auto &gains, al::span coeffs) - { ComputePanGains(target.Main, coeffs.data(), slot->Gain, gains); }; - SetAmbiPanIdentity(std::begin(mGain), slot->Wet.Buffer.size(), set_gains); + auto set_channel = [this](size_t idx, uint outchan, float outgain) + { + mChans[idx].mTarget = outchan; + mChans[idx].mGain = outgain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void CompressorState::process(const size_t samplesToDo, @@ -113,8 +117,8 @@ void CompressorState::process(const size_t samplesToDo, { for(size_t base{0u};base < samplesToDo;) { - float gains[256]; - const size_t td{minz(256, samplesToDo-base)}; + std::array gains; + const size_t td{std::min(gains.size(), samplesToDo-base)}; /* Generate the per-sample gains from the signal envelope. */ float env{mEnvFollower}; @@ -125,12 +129,12 @@ void CompressorState::process(const size_t samplesToDo, /* 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)}; + const float amplitude{std::clamp(std::fabs(samplesIn[0][base+i]), AmpEnvelopeMin, + AmpEnvelopeMax)}; if(amplitude > env) - env = minf(env*mAttackMult, amplitude); + env = std::min(env*mAttackMult, amplitude); else if(amplitude < env) - env = maxf(env*mReleaseMult, amplitude); + env = std::max(env*mReleaseMult, amplitude); /* Apply the reciprocal of the envelope to normalize the volume * (compress the dynamic range). @@ -148,9 +152,9 @@ void CompressorState::process(const size_t samplesToDo, { const float amplitude{1.0f}; if(amplitude > env) - env = minf(env*mAttackMult, amplitude); + env = std::min(env*mAttackMult, amplitude); else if(amplitude < env) - env = maxf(env*mReleaseMult, amplitude); + env = std::max(env*mReleaseMult, amplitude); gains[i] = 1.0f / env; } @@ -158,19 +162,22 @@ void CompressorState::process(const size_t samplesToDo, mEnvFollower = env; /* Now compress the signal amplitude to output. */ - auto changains = std::addressof(mGain[0]); + auto chan = mChans.cbegin(); for(const auto &input : samplesIn) { - const float *outgains{*(changains++)}; - for(FloatBufferLine &output : samplesOut) + const size_t outidx{chan->mTarget}; + if(outidx != InvalidChannelIndex) { - const float gain{*(outgains++)}; + const auto src = al::span{input}.subspan(base); + float *RESTRICT dst{samplesOut[outidx].data() + base}; + const float gain{chan->mGain}; if(!(std::fabs(gain) > GainSilenceThreshold)) - continue; - - for(size_t i{0u};i < td;i++) - output[base+i] += input[base+i] * gains[i] * gain; + { + for(size_t i{0u};i < td;i++) + dst[i] += src[i] * gains[i] * gain; + } } + ++chan; } base += td; diff --git a/libs/openal-soft/alc/effects/convolution.cpp b/libs/openal-soft/alc/effects/convolution.cpp index 196238fc..8280dcb8 100644 --- a/libs/openal-soft/alc/effects/convolution.cpp +++ b/libs/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,110 +31,148 @@ #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(double *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); HANDLE_FMT(FmtAlaw); + /* FIXME: Handle ADPCM decoding here. */ + case FmtIMA4: + case FmtMSADPCM: + 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_d = std::complex; +using complex_f = std::complex; 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) @@ -143,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{}; @@ -183,25 +223,26 @@ 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}; struct ChannelData { alignas(16) FloatBufferLine mBuffer{}; - float mHfScale{}; + 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; @@ -212,76 +253,83 @@ struct ConvolutionState final : public EffectState { void (ConvolutionState::*mMix)(const al::span,const size_t) {&ConvolutionState::NormalMix}; - void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + 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(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({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current.data(), + chan.Target.data(), 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}; - chan.mFilter.processHfScale(src, chan.mHfScale); - MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0); + chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale); + MixSamples(src, samplesOut, chan.Current.data(), chan.Target.data(), samplesToDo, 0); } } -void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buffer) +void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) { - constexpr uint MaxConvolveAmbiOrder{1u}; + using UhjDecoderType = UhjDecoder<512>; + static constexpr auto DecoderPadding = UhjDecoderType::sInputPadding; + + 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_d{}); + 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.storage || buffer.storage->mSampleLen < 1) return; + if(!buffer || buffer->mSampleLen < 1) return; - constexpr size_t m{ConvolveUpdateSize/2 + 1}; - auto bytesPerSample = BytesFromFmt(buffer.storage->mType); - auto realChannels = ChannelsFromFmt(buffer.storage->mChannels, buffer.storage->mAmbiOrder); - auto numChannels = ChannelsFromFmt(buffer.storage->mChannels, - minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder)); + mChannels = buffer->mChannels; + mAmbiLayout = IsUHJ(mChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout; + mAmbiScaling = IsUHJ(mChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling; + mAmbiOrder = std::min(buffer->mAmbiOrder, MaxConvolveAmbiOrder); - mChans = ChannelDataArray::Create(numChannels); + const auto realChannels = buffer->channelsFromFmt(); + const auto numChannels = (mChannels == FmtUHJ2) ? 3u : ChannelsFromFmt(mChannels, mAmbiOrder); + + 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 - * attenation that some people may be able to pick up on. Since this is + * attenuation that some people may be able to pick up on. Since this is * called very infrequently, go ahead and use the polyphase resampler. */ PPhaseResampler resampler; - if(device->Frequency != buffer.storage->mSampleRate) - resampler.init(buffer.storage->mSampleRate, device->Frequency); + if(device->Frequency != buffer->mSampleRate) + resampler.init(buffer->mSampleRate, device->Frequency); const auto resampledCount = static_cast( - (uint64_t{buffer.storage->mSampleLen}*device->Frequency+(buffer.storage->mSampleRate-1)) / - buffer.storage->mSampleRate); + (uint64_t{buffer->mSampleLen}*device->Frequency+(buffer->mSampleRate-1)) / + 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, {}); @@ -293,161 +341,219 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buff * 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_d{}); + const size_t complex_length{mNumConvolveSegs * ConvolveUpdateSize * (numChannels+1)}; + mComplexData.resize(complex_length, 0.0f); - mChannels = buffer.storage->mChannels; - mAmbiLayout = buffer.storage->mAmbiLayout; - mAmbiScaling = buffer.storage->mAmbiScaling; - mAmbiOrder = minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder); + /* Load the samples from the buffer. */ + const size_t srclinelength{RoundUp(buffer->mSampleLen+DecoderPadding, 16)}; + 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(al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen), + buffer->mData.data(), c, realChannels, buffer->mType); - auto srcsamples = std::make_unique(maxz(buffer.storage->mSampleLen, resampledCount)); - complex_d *filteriter = mComplexData.get() + mNumConvolveSegs*m; + if(IsUHJ(mChannels)) + { + auto decoder = std::make_unique(); + std::array samples{}; + for(size_t c{0};c < numChannels;++c) + samples[c] = al::to_address(srcsamples.begin() + ptrdiff_t(srclinelength*c)); + decoder->decode({samples.data(), numChannels}, buffer->mSampleLen, buffer->mSampleLen); + } + + 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) { - /* Load the samples from the buffer, and resample to match the device. */ - LoadSamples(srcsamples.get(), buffer.samples.data() + bytesPerSample*c, realChannels, - buffer.storage->mType, buffer.storage->mSampleLen); - if(device->Frequency != buffer.storage->mSampleRate) - resampler.process(buffer.storage->mSampleLen, srcsamples.get(), resampledCount, - srcsamples.get()); + auto bufsamples = al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen); + /* Resample to match the device. */ + if(resampler) + { + 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(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(srcsamples.get(), srcsamples.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); }); 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(&srcsamples[done], todo, mFftBuffer.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, mFftBuffer.end(), complex_d{}); + std::fill(iter, fftbuffer.end(), std::complex{}); + forward_fft(al::span{fftbuffer}); - forward_fft(mFftBuffer); - filteriter = std::copy_n(mFftBuffer.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) + 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}; - /* TODO: UHJ should be decoded to B-Format and processed that way, since - * there's no telling if it can ever do a direct-out mix (even if the - * device is outputing UHJ, the effect slot can feed another effect that's - * not UHJ). - * - * Not that UHJ should really ever be used for convolution, but it's a - * valid format regardless. - */ - if((mChannels == FmtUHJ2 || mChannels == FmtUHJ3 || mChannels == FmtUHJ4) && target.RealOut - && target.RealOut->ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX - && target.RealOut->ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX) - { - mOutTarget = target.RealOut->Buffer; - const uint lidx = target.RealOut->ChannelIndex[FrontLeft]; - const uint ridx = target.RealOut->ChannelIndex[FrontRight]; - (*mChans)[0].Target[lidx] = gain; - (*mChans)[1].Target[ridx] = gain; - } - else if(IsBFormat(mChannels)) + if(IsAmbisonic(mChannels)) { DeviceBase *device{context->mDevice}; - if(device->mAmbiOrder > mAmbiOrder) + if(mChannels == FmtUHJ2 && !device->mUhjEncoder) { mMix = &ConvolutionState::UpsampleMix; - const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder); - (*mChans)[0].mHfScale = scales[0]; - for(size_t i{1};i < mChans->size();++i) - (*mChans)[i].mHfScale = scales[1]; + 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[i].mHfScale = scales[1]; + mChans[i].mLfScale = 1.0f; + } } mOutTarget = target.Main->Buffer; - auto&& scales = GetAmbiScales(mAmbiScaling); - const uint8_t *index_map{(mChannels == FmtBFormat2D) ? - 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; @@ -466,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); } } } @@ -495,31 +628,29 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot void ConvolutionState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - if(mNumConvolveSegs < 1) + 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({std::addressof(*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; @@ -531,61 +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_d{}); - forward_fft(mFftBuffer); + mFft.transform(mInput.data(), &mComplexData[curseg*ConvolveUpdateSize], + mFftWorkBuffer.data(), PFFFT_FORWARD); - std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]); - - const complex_d *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m}; - for(size_t c{0};c < chans.size();++c) + auto filter = mComplexData.cbegin() + ptrdiff_t(mNumConvolveSegs*ConvolveUpdateSize); + for(size_t c{0};c < mChans.size();++c) { - std::fill_n(mFftBuffer.begin(), m, complex_d{}); - /* Convolve each input segment with its IR filter counterpart * (aligned in time). */ - const complex_d *RESTRICT input{&mComplexData[curseg*m]}; + 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(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] = - static_cast(mFftBuffer[i].real() * (1.0/double{ConvolveUpdateSize})) + - mOutput[c][ConvolveUpdateSamples+i]; - for(size_t i{0};i < ConvolveUpdateSamples;++i) - mOutput[c][ConvolveUpdateSamples+i] = - static_cast(mFftBuffer[ConvolveUpdateSamples+i].real() * - (1.0/double{ConvolveUpdateSize})); + /* 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/libs/openal-soft/alc/effects/dedicated.cpp b/libs/openal-soft/alc/effects/dedicated.cpp index 671eb5ec..371f02b9 100644 --- a/libs/openal-soft/alc/effects/dedicated.cpp +++ b/libs/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; @@ -42,79 +43,100 @@ namespace { using uint = unsigned int; -struct DedicatedState final : public EffectState { - float mCurrentGains[MAX_OUTPUT_CHANNELS]; - float mTargetGains[MAX_OUTPUT_CHANNELS]; +struct DedicatedState : public EffectState { + /* The "dedicated" effect can output to the real output, so should have + * gains for all possible output channels and not just the main ambisonic + * buffer. + */ + std::array mCurrentGains{}; + std::array mTargetGains{}; - void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) final; 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(DedicatedState) + const al::span samplesOut) final; }; -void DedicatedState::deviceUpdate(const DeviceBase*, const Buffer&) +struct DedicatedLfeState final : public DedicatedState { + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, + const EffectTarget target) 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) { - 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}; + const float Gain{slot->Gain * std::get(*props).Gain}; - if(slot->EffectType == EffectSlotType::DedicatedLFE) + /* Dialog goes to the front-center speaker if it exists, otherwise it plays + * from the front-center location. + */ + const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter] + : InvalidChannelIndex}; + if(idx != InvalidChannelIndex) { - const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX : - GetChannelIdxByName(*target.RealOut, LFE)}; - if(idx != INVALID_CHANNEL_INDEX) - { - mOutTarget = target.RealOut->Buffer; - mTargetGains[idx] = Gain; - } + mOutTarget = target.RealOut->Buffer; + mTargetGains[idx] = Gain; } - else if(slot->EffectType == EffectSlotType::DedicatedDialog) + else { - /* Dialog goes to the front-center speaker if it exists, otherwise it - * plays from the front-center location. */ - const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX : - GetChannelIdxByName(*target.RealOut, FrontCenter)}; - if(idx != INVALID_CHANNEL_INDEX) - { - mOutTarget = target.RealOut->Buffer; - mTargetGains[idx] = Gain; - } - else - { - const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f); + static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); - mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains); - } + mOutTarget = target.Main->Buffer; + ComputePanGains(target.Main, coeffs, Gain, mTargetGains); + } +} + +void DedicatedLfeState::update(const ContextBase*, const EffectSlot *slot, + const EffectProps *props, const EffectTarget target) +{ + std::fill(mTargetGains.begin(), mTargetGains.end(), 0.0f); + + const float Gain{slot->Gain * std::get(*props).Gain}; + + 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, - samplesToDo, 0); + MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains.data(), + mTargetGains.data(), samplesToDo, 0); } -struct DedicatedStateFactory final : public EffectStateFactory { +struct DedicatedDialogStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new DedicatedState{}}; } }; +struct DedicatedLfeStateFactory final : public EffectStateFactory { + al::intrusive_ptr create() override + { return al::intrusive_ptr{new DedicatedLfeState{}}; } +}; + } // namespace -EffectStateFactory *DedicatedStateFactory_getFactory() +EffectStateFactory *DedicatedDialogStateFactory_getFactory() { - static DedicatedStateFactory DedicatedFactory{}; + static DedicatedDialogStateFactory DedicatedFactory{}; + return &DedicatedFactory; +} + +EffectStateFactory *DedicatedLfeStateFactory_getFactory() +{ + static DedicatedLfeStateFactory DedicatedFactory{}; return &DedicatedFactory; } diff --git a/libs/openal-soft/alc/effects/distortion.cpp b/libs/openal-soft/alc/effects/distortion.cpp index 74cffd4a..6d5ba670 100644 --- a/libs/openal-soft/alc/effects/distortion.cpp +++ b/libs/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[MAX_OUTPUT_CHANNELS]{}; + std::array mGain{}; /* Effect parameters */ BiquadFilter mLowpass; @@ -53,35 +55,33 @@ struct DistortionState final : public EffectState { float mAttenuation{}; float mEdgeCoeff{}; - float mBuffer[2][BufferLineSize]{}; + alignas(16) std::array mBuffer{}; - void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + 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(DistortionState) }; -void DistortionState::deviceUpdate(const DeviceBase*, const Buffer&) +void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*) { mLowpass.clear(); mBandpass.clear(); } 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); - const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.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({mBuffer[0].data(), todo}, mBuffer[1]); todo >>= 2; - const float *outgains{mGain}; - for(FloatBufferLine &output : samplesOut) + auto outgains = mGain.cbegin(); + for(FloatBufferLine &RESTRICT output : samplesOut) { /* Fourth step, final, do attenuation and perform decimation, * storing only one sample out of four. diff --git a/libs/openal-soft/alc/effects/echo.cpp b/libs/openal-soft/alc/effects/echo.cpp index 5d003718..c029eb5b 100644 --- a/libs/openal-soft/alc/effects/echo.cpp +++ b/libs/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,36 +50,33 @@ 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[MAX_OUTPUT_CHANNELS]{}; - float Target[MAX_OUTPUT_CHANNELS]{}; - } 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 Buffer &buffer) override; + 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 Buffer&) +void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { const auto frequency = static_cast(Device->Frequency); @@ -87,61 +85,62 @@ void EchoState::deviceUpdate(const DeviceBase *Device, const Buffer&) 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,42 +86,44 @@ namespace { struct EqualizerState final : public EffectState { - struct { + struct OutParams { + uint mTargetChannel{InvalidChannelIndex}; + /* Effect parameters */ - BiquadFilter filter[4]; + std::array mFilter; /* Effect gains for each channel */ - float CurrentGains[MAX_OUTPUT_CHANNELS]{}; - float TargetGains[MAX_OUTPUT_CHANNELS]{}; - } mChans[MaxAmbiChannels]; + float mCurrentGain{}; + float mTargetGain{}; + }; + std::array mChans; - FloatBufferLine mSampleBuffer{}; + alignas(16) FloatBufferLine mSampleBuffer{}; - void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + 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(EqualizerState) }; -void EqualizerState::deviceUpdate(const DeviceBase*, const Buffer&) +void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*) { for(auto &e : mChans) { - std::for_each(std::begin(e.filter), std::end(e.filter), std::mem_fn(&BiquadFilter::clear)); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + e.mTargetChannel = InvalidChannelIndex; + 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, @@ -129,50 +131,58 @@ 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; - mChans[0].filter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f); + 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; - mChans[0].filter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, - props->Equalizer.Mid1Width); + gain = std::sqrt(props.Mid1Gain); + f0norm = props.Mid1Center / frequency; + mChans[0].mFilter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, + props.Mid1Width); - gain = std::sqrt(props->Equalizer.Mid2Gain); - f0norm = props->Equalizer.Mid2Center / frequency; - mChans[0].filter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, - props->Equalizer.Mid2Width); + gain = std::sqrt(props.Mid2Gain); + f0norm = props.Mid2Center / frequency; + mChans[0].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, + props.Mid2Width); - gain = std::sqrt(props->Equalizer.HighGain); - f0norm = props->Equalizer.HighCutoff / frequency; - mChans[0].filter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f); + 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. */ for(size_t i{1u};i < slot->Wet.Buffer.size();++i) { - mChans[i].filter[0].copyParamsFrom(mChans[0].filter[0]); - mChans[i].filter[1].copyParamsFrom(mChans[0].filter[1]); - mChans[i].filter[2].copyParamsFrom(mChans[0].filter[2]); - mChans[i].filter[3].copyParamsFrom(mChans[0].filter[3]); + mChans[i].mFilter[0].copyParamsFrom(mChans[0].mFilter[0]); + mChans[i].mFilter[1].copyParamsFrom(mChans[0].mFilter[1]); + mChans[i].mFilter[2].copyParamsFrom(mChans[0].mFilter[2]); + mChans[i].mFilter[3].copyParamsFrom(mChans[0].mFilter[3]); } mOutTarget = target.Main->Buffer; - auto set_gains = [slot,target](auto &chan, al::span coeffs) - { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; - SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); + auto set_channel = [this](size_t idx, uint outchan, float outgain) + { + mChans[idx].mTargetChannel = outchan; + mChans[idx].mTargetGain = outgain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } 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::addressof(mChans[0]); + auto chan = mChans.begin(); for(const auto &input : samplesIn) { - const al::span inbuf{input.data(), samplesToDo}; - DualBiquad{chan->filter[0], chan->filter[1]}.process(inbuf, buffer.begin()); - DualBiquad{chan->filter[2], chan->filter[3]}.process(buffer, buffer.begin()); + const size_t outidx{chan->mTargetChannel}; + if(outidx != InvalidChannelIndex) + { + const al::span inbuf{input.data(), samplesToDo}; + DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer); + DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer); - MixSamples(buffer, samplesOut, chan->CurrentGains, chan->TargetGains, samplesToDo, 0u); + MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain, + samplesToDo); + } ++chan; } } diff --git a/libs/openal-soft/alc/effects/fshifter.cpp b/libs/openal-soft/alc/effects/fshifter.cpp index def745c4..e4d473ba 100644 --- a/libs/openal-soft/alc/effects/fshifter.cpp +++ b/libs/openal-soft/alc/effects/fshifter.cpp @@ -25,114 +25,119 @@ #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 { using uint = unsigned int; using complex_d = std::complex; -#define HIL_SIZE 1024 -#define OVERSAMP (1<<2) +constexpr size_t HilSize{1024}; +constexpr size_t HilHalfSize{HilSize >> 1}; +constexpr size_t OversampleFactor{4}; -#define HIL_STEP (HIL_SIZE / OVERSAMP) -#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1)) +static_assert(HilSize%OversampleFactor == 0, "Factor must be a clean divisor of the size"); +constexpr size_t HilStep{HilSize / OversampleFactor}; /* Define a Hann window, used to filter the HIL input and output. */ -std::array InitHannWindow() -{ - std::array ret; - /* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */ - for(size_t i{0};i < HIL_SIZE>>1;i++) +struct Windower { + alignas(16) std::array mData{}; + + Windower() { - constexpr double scale{al::numbers::pi / double{HIL_SIZE}}; - const double val{std::sin(static_cast(i+1) * scale)}; - ret[i] = ret[HIL_SIZE-1-i] = val * val; + /* Create lookup table of the Hann window for the desired size. */ + for(size_t i{0};i < HilHalfSize;i++) + { + constexpr double scale{al::numbers::pi / double{HilSize}}; + const double val{std::sin((static_cast(i)+0.5) * scale)}; + mData[i] = mData[HilSize-1-i] = val * val; + } } - return ret; -} -alignas(16) const std::array HannWindow = InitHannWindow(); +}; +const Windower gWindow{}; struct FshifterState final : public EffectState { /* Effect parameters */ size_t mCount{}; size_t mPos{}; - uint mPhaseStep[2]{}; - uint mPhase[2]{}; - double mSign[2]{}; + std::array mPhaseStep{}; + std::array mPhase{}; + std::array mSign{}; /* Effects buffers */ - double mInFIFO[HIL_SIZE]{}; - complex_d mOutFIFO[HIL_STEP]{}; - complex_d mOutputAccum[HIL_SIZE]{}; - complex_d mAnalytic[HIL_SIZE]{}; - complex_d mOutdata[BufferLineSize]{}; + std::array mInFIFO{}; + std::array mOutFIFO{}; + std::array mOutputAccum{}; + std::array mAnalytic{}; + std::array mOutdata{}; - alignas(16) float mBufferOut[BufferLineSize]{}; + alignas(16) FloatBufferLine mBufferOut{}; /* Effect gains for each output channel */ - struct { - float Current[MAX_OUTPUT_CHANNELS]{}; - float Target[MAX_OUTPUT_CHANNELS]{}; - } mGains[2]; + struct OutGains { + std::array Current{}; + std::array Target{}; + }; + std::array mGains; - void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + 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(FshifterState) }; -void FshifterState::deviceUpdate(const DeviceBase*, const Buffer&) +void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) { /* (Re-)initializing parameters and clear the buffers. */ mCount = 0; - mPos = FIFO_LATENCY; + mPos = HilSize - HilStep; - std::fill(std::begin(mPhaseStep), std::end(mPhaseStep), 0u); - std::fill(std::begin(mPhase), std::end(mPhase), 0u); - std::fill(std::begin(mSign), std::end(mSign), 1.0); - std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0); - std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), complex_d{}); - std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), complex_d{}); - std::fill(std::begin(mAnalytic), std::end(mAnalytic), complex_d{}); + mPhaseStep.fill(0u); + mPhase.fill(0u); + mSign.fill(1.0); + mInFIFO.fill(0.0); + mOutFIFO.fill(complex_d{}); + mOutputAccum.fill(complex_d{}); + mAnalytic.fill(complex_d{}); for(auto &gain : mGains) { - std::fill(std::begin(gain.Current), std::end(gain.Current), 0.0f); - std::fill(std::begin(gain.Target), std::end(gain.Target), 0.0f); + 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; @@ -146,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; @@ -160,19 +165,24 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot, break; } - const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f); - const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f); + static constexpr auto inv_sqrt2 = static_cast(1.0 / al::numbers::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(HIL_STEP-mCount, samplesToDo-base)}; + size_t todo{std::min(HilStep-mCount, samplesToDo-base)}; /* Fill FIFO buffer with samples data */ const size_t pos{mPos}; @@ -185,50 +195,52 @@ void FshifterState::process(const size_t samplesToDo, const al::span(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({mBufferOut.data(), samplesToDo}, samplesOut, mGains[c].Current.data(), + mGains[c].Target.data(), std::max(samplesToDo, 512_uz), 0); } } diff --git a/libs/openal-soft/alc/effects/modulator.cpp b/libs/openal-soft/alc/effects/modulator.cpp index 84561f5c..3941b720 100644 --- a/libs/openal-soft/alc/effects/modulator.cpp +++ b/libs/openal-soft/alc/effects/modulator.cpp @@ -22,149 +22,193 @@ #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 { - BiquadFilter Filter; + alignas(16) FloatBufferLine mModSamples{}; + alignas(16) FloatBufferLine mBuffer{}; - float CurrentGains[MAX_OUTPUT_CHANNELS]{}; - float TargetGains[MAX_OUTPUT_CHANNELS]{}; - } mChans[MaxAmbiChannels]; + struct OutParams { + uint mTargetChannel{InvalidChannelIndex}; + + BiquadFilter mFilter; + + float mCurrentGain{}; + float mTargetGain{}; + }; + std::array mChans; - void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + 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(ModulatorState) }; -void ModulatorState::deviceUpdate(const DeviceBase*, const Buffer&) +void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*) { for(auto &e : mChans) { - e.Filter.clear(); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + e.mTargetChannel = InvalidChannelIndex; + e.mFilter.clear(); + e.mCurrentGain = 0.0f; } } 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].Filter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); + mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); for(size_t i{1u};i < slot->Wet.Buffer.size();++i) - mChans[i].Filter.copyParamsFrom(mChans[0].Filter); + mChans[i].mFilter.copyParamsFrom(mChans[0].mFilter); mOutTarget = target.Main->Buffer; - auto set_gains = [slot,target](auto &chan, al::span coeffs) - { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; - SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); + auto set_channel = [this](size_t idx, uint outchan, float outgain) + { + mChans[idx].mTargetChannel = outchan; + mChans[idx].mTargetGain = outgain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } 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;) { - alignas(16) float temps[MAX_UPDATE_SAMPLES]; - - chandata->Filter.process({&input[base], td}, temps); - for(size_t i{0u};i < td;i++) - temps[i] *= modsamples[i]; - - MixSamples({temps, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains, - samplesToDo-base, base); - ++chandata; + 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) + { + const size_t outidx{chandata->mTargetChannel}; + if(outidx != InvalidChannelIndex) + { + chandata->mFilter.process({input.data(), samplesToDo}, mBuffer); + std::transform(mBuffer.cbegin(), mBuffer.cbegin()+samplesToDo, mModSamples.cbegin(), + mBuffer.begin(), std::multiplies<>{}); + + MixSamples({mBuffer.data(), samplesToDo}, samplesOut[outidx].data(), + chandata->mCurrentGain, chandata->mTargetGain, std::min(samplesToDo, 64_uz)); + } + ++chandata; } } diff --git a/libs/openal-soft/alc/effects/null.cpp b/libs/openal-soft/alc/effects/null.cpp index cda1420e..217181a8 100644 --- a/libs/openal-soft/alc/effects/null.cpp +++ b/libs/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; @@ -20,13 +21,11 @@ struct NullState final : public EffectState { NullState(); ~NullState() override; - void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + 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(NullState) }; /* This constructs the effect state. It's called when the object is first @@ -44,7 +43,7 @@ NullState::~NullState() = default; * format) have been changed. Will always be followed by a call to the update * method, if successful. */ -void NullState::deviceUpdate(const DeviceBase* /*device*/, const Buffer& /*buffer*/) +void NullState::deviceUpdate(const DeviceBase* /*device*/, const BufferStorage* /*buffer*/) { } diff --git a/libs/openal-soft/alc/effects/pshifter.cpp b/libs/openal-soft/alc/effects/pshifter.cpp index aa20c660..08c8564c 100644 --- a/libs/openal-soft/alc/effects/pshifter.cpp +++ b/libs/openal-soft/alc/effects/pshifter.cpp @@ -25,128 +25,137 @@ #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; namespace { using uint = unsigned int; -using complex_d = std::complex; +using complex_f = std::complex; -#define STFT_SIZE 1024 -#define STFT_HALF_SIZE (STFT_SIZE>>1) -#define OVERSAMP (1<<2) +constexpr size_t StftSize{1024}; +constexpr size_t StftHalfSize{StftSize >> 1}; +constexpr size_t OversampleFactor{8}; -#define STFT_STEP (STFT_SIZE / OVERSAMP) -#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1)) +static_assert(StftSize%OversampleFactor == 0, "Factor must be a clean divisor of the size"); +constexpr size_t StftStep{StftSize / OversampleFactor}; /* Define a Hann window, used to filter the STFT input and output. */ -std::array InitHannWindow() -{ - std::array ret; - /* Create lookup table of the Hann window for the desired size, i.e. STFT_SIZE */ - for(size_t i{0};i < STFT_SIZE>>1;i++) +struct Windower { + alignas(16) std::array mData{}; + + Windower() { - constexpr double scale{al::numbers::pi / double{STFT_SIZE}}; - const double val{std::sin(static_cast(i+1) * scale)}; - ret[i] = ret[STFT_SIZE-1-i] = val * val; + /* Create lookup table of the Hann window for the desired size. */ + for(size_t i{0};i < StftHalfSize;i++) + { + constexpr double scale{al::numbers::pi / double{StftSize}}; + const double val{std::sin((static_cast(i)+0.5) * scale)}; + mData[i] = mData[StftSize-1-i] = static_cast(val * val); + } } - return ret; -} -alignas(16) const std::array HannWindow = InitHannWindow(); +}; +const Windower gWindow{}; struct FrequencyBin { - double Amplitude; - double FreqBin; + float Magnitude; + float FreqBin; }; struct PshifterState final : public EffectState { /* Effect parameters */ - size_t mCount; - size_t mPos; - uint mPitchShiftI; - double 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[MAX_OUTPUT_CHANNELS]; - float mTargetGains[MAX_OUTPUT_CHANNELS]; + std::array mCurrentGains{}; + std::array mTargetGains{}; - void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + 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(PshifterState) }; -void PshifterState::deviceUpdate(const DeviceBase*, const Buffer&) +void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) { /* (Re-)initializing parameters and clear the buffers. */ mCount = 0; - mPos = FIFO_LATENCY; + mPos = StftSize - StftStep; mPitchShiftI = MixerFracOne; - mPitchShift = 1.0; + mPitchShift = 1.0f; - std::fill(mFIFO.begin(), mFIFO.end(), 0.0); - std::fill(mLastPhase.begin(), mLastPhase.end(), 0.0); - std::fill(mSumPhase.begin(), mSumPhase.end(), 0.0); - std::fill(mOutputAccum.begin(), mOutputAccum.end(), 0.0); - std::fill(mFftBuffer.begin(), mFftBuffer.end(), complex_d{}); - std::fill(mAnalysisBuffer.begin(), mAnalysisBuffer.end(), FrequencyBin{}); - std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{}); + mFIFO.fill(0.0f); + mLastPhase.fill(0.0f); + mSumPhase.fill(0.0f); + mOutputAccum.fill(0.0f); + 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 = fastf2u(pitch*MixerFracOne); - mPitchShift = mPitchShiftI * double{1.0/MixerFracOne}; + mPitchShiftI = std::clamp(fastf2u(pitch*MixerFracOne), uint{MixerFracHalf}, + uint{MixerFracOne}*2u); + mPitchShift = static_cast(mPitchShiftI) * float{1.0f/MixerFracOne}; - const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.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, const al::span samplesIn, const al::span samplesOut) +void PshifterState::process(const size_t samplesToDo, + const al::span samplesIn, const al::span samplesOut) { /* Pitch shifter engine based on the work of Stephan Bernsee. * http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/ @@ -155,108 +164,150 @@ void PshifterState::process(const size_t samplesToDo, const al::span*2.0f / OversampleFactor}; for(size_t base{0u};base < samplesToDo;) { - const size_t todo{minz(STFT_STEP-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. */ auto fifo_iter = mFIFO.begin()+mPos + mCount; - std::transform(fifo_iter, fifo_iter+todo, mBufferOut.begin()+base, - [](double d) noexcept -> float { return static_cast(d); }); + std::copy_n(fifo_iter, todo, mBufferOut.begin()+base); std::copy_n(samplesIn[0].begin()+base, todo, fifo_iter); mCount += todo; base += todo; /* Check whether FIFO buffer is filled with new samples. */ - if(mCount < STFT_STEP) break; + if(mCount < StftStep) break; mCount = 0; - mPos = (mPos+STFT_STEP) & (mFIFO.size()-1); + mPos = (mPos+StftStep) & (mFIFO.size()-1); /* Time-domain signal windowing, store in FftBuffer, and apply a * forward FFT to get the frequency-domain signal. */ - for(size_t src{mPos}, k{0u};src < STFT_SIZE;++src,++k) - mFftBuffer[k] = mFIFO[src] * HannWindow[k]; - for(size_t src{0u}, k{STFT_SIZE-mPos};src < mPos;++src,++k) - mFftBuffer[k] = mFIFO[src] * HannWindow[k]; - forward_fft(mFftBuffer); + for(size_t src{mPos}, k{0u};src < StftSize;++src,++k) + 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]; + mFft.transform_ordered(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(), + PFFFT_FORWARD); /* Analyze the obtained data. Since the real FFT is symmetric, only - * STFT_HALF_SIZE+1 samples are needed. + * StftHalfSize+1 samples are needed. */ - for(size_t k{0u};k < STFT_HALF_SIZE+1;k++) + for(size_t k{0u};k < StftHalfSize+1;++k) { - const double amplitude{std::abs(mFftBuffer[k])}; - const double 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 phase difference and subtract expected phase difference */ - double tmp{(phase - mLastPhase[k]) - static_cast(k)*expected_cycles}; - - /* Map delta phase into +/- Pi interval */ - int qpd{double2int(tmp / al::numbers::pi)}; - tmp -= al::numbers::pi * (qpd + (qpd%2)); - - /* Get deviation from bin frequency from the +/- Pi interval */ - tmp /= expected_cycles; - - /* Compute the k-th partials' true frequency and store the - * amplitude and frequency bin in the analysis buffer. + /* Compute the phase difference from the last update and subtract + * the expected phase difference for this bin. + * + * When oversampling, the expected per-update offset increments by + * 1/OversampleFactor for every frequency bin. So, the offset wraps + * every 'OversampleFactor' bin. */ - mAnalysisBuffer[k].Amplitude = amplitude; - mAnalysisBuffer[k].FreqBin = static_cast(k) + tmp; - - /* Store the actual phase[k] for the next frame. */ + const auto bin_offset = static_cast(k % OversampleFactor); + float tmp{(phase - mLastPhase[k]) - bin_offset*expected_cycles}; + /* Store the actual phase for the next update. */ mLastPhase[k] = phase; + + /* Normalize from pi, and wrap the delta between -1 and +1. */ + tmp *= al::numbers::inv_pi_v; + int qpd{float2int(tmp)}; + tmp -= static_cast(qpd + (qpd%2)); + + /* Get deviation from bin frequency (-0.5 to +0.5), and account for + * oversampling. + */ + tmp *= 0.5f * OversampleFactor; + + /* Compute the k-th partials' frequency bin target and store the + * magnitude and frequency bin in the analysis buffer. We don't + * need the "true frequency" since it's a linear relationship with + * the bin. + */ + mAnalysisBuffer[k].Magnitude = magnitude; + mAnalysisBuffer[k].FreqBin = static_cast(k) + tmp; } /* Shift the frequency bins according to the pitch adjustment, - * accumulating the amplitudes of overlapping frequency bins. + * accumulating the magnitudes of overlapping frequency bins. */ std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{}); - const size_t bin_count{minz(STFT_HALF_SIZE+1, - (((STFT_HALF_SIZE+1)<>1) - 1)/mPitchShiftI + 1)}; + + static constexpr size_t bin_limit{((StftHalfSize+1)<>1)) >> MixerFracBits}; - mSynthesisBuffer[j].Amplitude += mAnalysisBuffer[k].Amplitude; - mSynthesisBuffer[j].FreqBin = mAnalysisBuffer[k].FreqBin * mPitchShift; + const size_t j{(k*mPitchShiftI + MixerFracHalf) >> MixerFracBits}; + + /* If more than two bins end up together, use the target frequency + * bin for the one with the dominant magnitude. There might be a + * better way to handle this, but it's better than last-index-wins. + */ + if(mAnalysisBuffer[k].Magnitude > mSynthesisBuffer[j].Magnitude) + mSynthesisBuffer[j].FreqBin = mAnalysisBuffer[k].FreqBin * mPitchShift; + mSynthesisBuffer[j].Magnitude += mAnalysisBuffer[k].Magnitude; } /* Reconstruct the frequency-domain signal from the adjusted frequency * bins. */ - for(size_t k{0u};k < STFT_HALF_SIZE+1;k++) + for(size_t k{0u};k < StftHalfSize+1;k++) { - /* Calculate actual delta phase and accumulate it to get bin phase */ - mSumPhase[k] += mSynthesisBuffer[k].FreqBin * expected_cycles; + /* Calculate the actual delta phase for this bin's target frequency + * bin, and accumulate it to get the actual bin phase. + */ + float tmp{mSumPhase[k] + mSynthesisBuffer[k].FreqBin*expected_cycles}; - mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Amplitude, mSumPhase[k]); + /* Wrap between -pi and +pi for the sum. If mSumPhase is left to + * grow indefinitely, it will lose precision and produce less exact + * phase over time. + */ + tmp *= al::numbers::inv_pi_v; + int qpd{float2int(tmp)}; + tmp -= static_cast(qpd + (qpd%2)); + mSumPhase[k] = tmp * al::numbers::pi_v; + + 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{STFT_HALF_SIZE+1};k < STFT_SIZE;++k) - mFftBuffer[k] = std::conj(mFftBuffer[STFT_SIZE-k]); - /* Apply an inverse FFT to get the time-domain siganl, and accumulate + /* Apply an inverse FFT to get the time-domain signal, and accumulate * for the output with windowing. */ - inverse_fft(mFftBuffer); - for(size_t dst{mPos}, k{0u};dst < STFT_SIZE;++dst,++k) - mOutputAccum[dst] += HannWindow[k]*mFftBuffer[k].real() * (4.0/OVERSAMP/STFT_SIZE); - for(size_t dst{0u}, k{STFT_SIZE-mPos};dst < mPos;++dst,++k) - mOutputAccum[dst] += HannWindow[k]*mFftBuffer[k].real() * (4.0/OVERSAMP/STFT_SIZE); + 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] * scale; + for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k) + 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, STFT_STEP, mFIFO.begin() + mPos); - std::fill_n(mOutputAccum.begin() + mPos, STFT_STEP, 0.0); + std::copy_n(mOutputAccum.begin() + mPos, StftStep, mFIFO.begin() + mPos); + std::fill_n(mOutputAccum.begin() + mPos, StftStep, 0.0f); } /* Now, mix the processed sound data to the output. */ - MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains, - maxz(samplesToDo, 512), 0); + MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains.data(), + mTargetGains.data(), std::max(samplesToDo, 512_uz), 0); } diff --git a/libs/openal-soft/alc/effects/reverb.cpp b/libs/openal-soft/alc/effects/reverb.cpp index 81c6f865..392971b2 100644 --- a/libs/openal-soft/alc/effects/reverb.cpp +++ b/libs/openal-soft/alc/effects/reverb.cpp @@ -22,11 +22,13 @@ #include #include +#include +#include +#include #include -#include -#include #include -#include +#include +#include #include "alc/effects/base.h" #include "almalloc.h" @@ -36,8 +38,9 @@ #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,8 +64,6 @@ constexpr float DefaultModulationTime{0.25f}; #define MOD_FRACMASK (MOD_FRACONE-1) -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. */ @@ -89,36 +86,42 @@ 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 } -}; +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 early reflections. */ -alignas(16) constexpr float EarlyA2B[NUM_LINES][NUM_LINES]{ - { 0.5f, 0.5f, 0.5f, 0.5f }, - { 0.5f, -0.5f, 0.5f, -0.5f }, - { 0.5f, -0.5f, -0.5f, 0.5f }, - { 0.5f, 0.5f, -0.5f, -0.5f } -}; +/* Converts (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 A-Format to B-Format for late reverb. */ +/* 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 float LateA2B[NUM_LINES][NUM_LINES]{ - { 0.5f, 0.5f, 0.5f, 0.5f }, - { InvSqrt2, -InvSqrt2, 0.0f, 0.0f }, - { 0.0f, 0.0f, InvSqrt2, -InvSqrt2 }, - { 0.5f, 0.5f, -0.5f, -0.5f } -}; +alignas(16) constexpr std::array,NUM_LINES> LateA2B{{ + /* 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 * effect's density parameter, which helps alter the perceived environment @@ -218,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: @@ -256,20 +259,16 @@ struct DelayLineI { * 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; - }; + float *Line{}; /* Given the allocated sample buffer, this function updates each delay line * offset. */ - void realizeLineOffset(std::array *sampleBuffer) noexcept - { Line = sampleBuffer + LineOffset; } + void realizeLineOffset(float *sampleBuffer) noexcept + { Line = 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) + size_t calcLineLength(const float length, const float frequency, const uint extra) { /* All line lengths are powers of 2, calculated from their lengths in * seconds, rounded up. @@ -279,21 +278,87 @@ struct DelayLineI { /* All lines share a single sample buffer. */ Mask = samples - 1; - LineOffset = offset; /* Return the sample count for accumulation. */ - return samples; + return samples*NUM_LINES; } - void write(size_t offset, const size_t c, const float *RESTRICT in, const size_t count) const noexcept + [[nodiscard]] + auto &get(size_t i, size_t chan) const noexcept { return Line[i*NUM_LINES + chan]; } + void set(size_t i, const std::array in) const noexcept + { std::copy_n(in.begin(), NUM_LINES, Line + i*NUM_LINES); } +}; + +struct DelayLineU { + size_t Mask{0u}; + float *Line{}; + + void realizeLineOffset(float *sampleBuffer) noexcept + { Line = sampleBuffer; } + + size_t calcLineLength(const float length, const float frequency, const uint extra) { + uint samples{float2uint(std::ceil(length*frequency))}; + samples = NextPowerOf2(samples + extra); + + Mask = samples - 1; + + return samples*NUM_LINES; + } + + [[nodiscard]] + auto get(size_t chan) const noexcept { return al::span{Line + chan*(Mask+1), Mask+1}; } + + void write(size_t offset, const size_t c, const float *in, const size_t count) const noexcept + { + ASSUME(count > 0); + float *RESTRICT out{al::assume_aligned<16>(Line + c*(Mask+1))}; + for(size_t i{0u};i < count;) + { + offset &= Mask; + const size_t td{std::min(Mask+1 - offset, count - i)}; + std::copy_n(in+i, td, out+offset); + offset += td; + i += 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{Mask+1}; ASSUME(count > 0); for(size_t i{0u};i < count;) { offset &= Mask; - size_t td{minz(Mask+1 - offset, count - i)}; + size_t td{std::min(Mask+1 - 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 + }; + Line[0*stride + offset] = f[0]; + Line[1*stride + offset] = f[1]; + Line[2*stride + offset] = f[2]; + Line[3*stride + offset] = f[3]; + ++offset; } while(--td); } } @@ -302,20 +367,26 @@ struct DelayLineI { struct VecAllpass { DelayLineI Delay; float Coeff{0.0f}; - size_t Offset[NUM_LINES][2]{}; + std::array Offset{}; - void processFaded(const al::span samples, size_t offset, - const float xCoeff, const float yCoeff, float fadeCount, const float fadeStep, - const size_t todo); - void processUnfaded(const al::span samples, size_t offset, - const float xCoeff, const float yCoeff, const size_t todo); + void process(const al::span samples, size_t offset, + const float xCoeff, const float yCoeff, const size_t todo) 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) noexcept; }; struct T60Filter { /* Two filters are used to adjust the signal. One to control the low * frequencies, and one to control the high frequencies. */ - float MidGain[2]{0.0f, 0.0f}; + float MidGain{0.0f}; BiquadFilter HFFilter, LFFilter; void calcCoeffs(const float length, const float lfDecayTime, const float mfDecayTime, @@ -323,25 +394,27 @@ 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][2]{}; - float Coeff[NUM_LINES][2]{}; + DelayLineU Delay; + std::array Offset{}; + std::array Coeff{}; /* The gain for each output channel based on 3D panning. */ - float CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; - float PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; + struct OutGains { + std::array Current{}; + std::array Target{}; + }; + std::array Gains{}; void updateLines(const float density_mult, const float diffusion, const float decayTime, const float frequency); @@ -352,31 +425,30 @@ struct Modulation { /* The vibrato time is tracked with an index over a (MOD_FRACONE) * normalized range. */ - uint Index, Step; + uint Index{}, Step{}; /* The depth of frequency change, in samples. */ - float Depth[2]; + float Depth{}; - float ModDelays[MAX_UPDATE_SAMPLES]; + std::array ModDelays{}; void updateModulator(float modTime, float modDepth, float frequency); void calcDelays(size_t todo); - void calcFadedDelays(size_t todo, float fadeCount, float fadeStep); }; struct LateReverb { /* A recursive delay line is used fill in the reverb tail. */ - DelayLineI Delay; - size_t Offset[NUM_LINES][2]{}; + DelayLineU Delay; + std::array Offset{}; /* Attenuation to compensate for the modal density and decay rate of the * late lines. */ - float DensityGain[2]{0.0f, 0.0f}; + float DensityGain{0.0f}; /* T60 decay filters are used to simulate absorption. */ - T60Filter T60[NUM_LINES]; + std::array T60; Modulation Mod; @@ -384,21 +456,91 @@ struct LateReverb { VecAllpass VecAp; /* The gain for each output channel based on 3D panning. */ - float CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; - float PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]{}; + struct OutGains { + std::array Current{}; + std::array Target{}; + }; + 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 + { + for(auto &filter : T60) + filter.clear(); + } +}; + +struct ReverbPipeline { + /* Master effect filters */ + struct FilterPair { + BiquadFilter Lp; + BiquadFilter Hp; + }; + std::array mFilter; + + /* Late reverb input delay line (early reflections feed this, and late + * reverb taps from it). + */ + DelayLineU mLateDelayIn; + + /* 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. */ + std::array,NUM_LINES> mLateDelayTap{}; + + /* Coefficients for the all-pass and line scattering matrices. */ + float mMixX{0.0f}; + float mMixY{0.0f}; + + EarlyReflections mEarly; + + LateReverb mLate; + + std::array,2> mAmbiSplitter; + + size_t mFadeSampleCount{1}; + + 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(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, + const al::span tempSamples, + const al::span outSamples); + + void clear() noexcept + { + for(auto &filter : mFilter) + { + filter.Lp.clear(); + filter.Hp.clear(); + } + mLate.clear(); + for(auto &filters : mAmbiSplitter) + { + for(auto &filter : filters) + filter.clear(); + } + } }; 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. */ @@ -411,152 +553,137 @@ struct ReverbState final : public EffectState { float ModulationDepth{0.0f}; float HFReference{5000.0f}; float LFReference{250.0f}; - } mParams; + }; + Params mParams; - /* Master effect filters */ - struct { - BiquadFilter Lp; - BiquadFilter Hp; - } mFilter[NUM_LINES]; + enum PipelineState : uint8_t { + DeviceClear, + StartFade, + Fading, + Cleanup, + Normal, + }; + PipelineState mPipelineState{DeviceClear}; + bool mCurrentPipeline{false}; - /* Core delay line (early reflections and late reverb tap from this). */ - DelayLineI mDelay; + /* Core delay line (early reflections tap from this). */ + DelayLineU mMainDelay; - /* Tap points for early reflection delay. */ - size_t mEarlyDelayTap[NUM_LINES][2]{}; - float mEarlyDelayCoeff[NUM_LINES][2]{}; - - /* Tap points for late reverb feed and delay. */ - size_t mLateFeedTap{}; - size_t mLateDelayTap[NUM_LINES][2]{}; - - /* Coefficients for the all-pass and line scattering matrices. */ - float mMixX{0.0f}; - float mMixY{0.0f}; - - EarlyReflections mEarly; - - LateReverb mLate; - - bool mDoFading{}; - - /* Maximum number of samples to process at once. */ - size_t mMaxUpdate[2]{MAX_UPDATE_SAMPLES, MAX_UPDATE_SAMPLES}; + 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) std::array mEarlySamples{}; - alignas(16) std::array mLateSamples{}; + alignas(16) FloatBufferLine mTempLine{}; + alignas(16) std::array mTempSamples{}; - using MixOutT = void (ReverbState::*)(const al::span samplesOut, - const size_t counter, const size_t offset, const size_t todo); + alignas(16) std::array mEarlySamples{}; + alignas(16) std::array mLateSamples{}; - MixOutT mMixOut{&ReverbState::MixOutPlain}; std::array mOrderScales{}; - std::array,2> mAmbiSplitter; + + bool mUpmixOutput{false}; - static void DoMixRow(const al::span OutBuffer, const al::span Gains, - const float *InSamples, const size_t InStride) + void MixOutPlain(ReverbPipeline &pipeline, const al::span samplesOut, + const size_t todo) const { - std::fill(OutBuffer.begin(), OutBuffer.end(), 0.0f); - for(const float gain : Gains) + ASSUME(todo > 0); + + /* When not upsampling, the panning gains convert to B-Format and pan + * at the same time. + */ + auto inBuffer = mEarlySamples.cbegin(); + for(auto &gains : pipeline.mEarly.Gains) { - const float *RESTRICT input{al::assume_aligned<16>(InSamples)}; - InSamples += InStride; + MixSamples(al::span{inBuffer->cbegin(), todo}, samplesOut, gains.Current.data(), + gains.Target.data(), todo, 0); + ++inBuffer; + } + inBuffer = mLateSamples.cbegin(); + for(auto &gains : pipeline.mLate.Gains) + { + MixSamples(al::span{inBuffer->cbegin(), todo}, samplesOut, gains.Current.data(), + gains.Target.data(), todo, 0); + ++inBuffer; + } + } - if(!(std::fabs(gain) > GainSilenceThreshold)) - continue; + void MixOutAmbiUp(ReverbPipeline &pipeline, const al::span samplesOut, + const size_t todo) + { + ASSUME(todo > 0); - for(float &sample : OutBuffer) + auto DoMixRow = [](const al::span OutBuffer, const al::span Gains, + const al::span InSamples) + { + auto inBuffer = InSamples.cbegin(); + std::fill(OutBuffer.begin(), OutBuffer.end(), 0.0f); + for(const float gain : Gains) { - sample += *input * gain; - ++input; + const float *RESTRICT input{al::assume_aligned<16>(inBuffer->data())}; + ++inBuffer; + + 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); } - } - } + }; - - void MixOutPlain(const al::span samplesOut, const size_t counter, - const size_t offset, const size_t todo) - { - ASSUME(todo > 0); - - /* Convert back to B-Format, and mix the results to output. */ + /* When upsampling, the B-Format conversion needs to be done separately + * 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++) + float hfscale{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()); - MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], counter, - offset); - } - for(size_t c{0u};c < NUM_LINES;c++) - { - DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size()); - MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], counter, - offset); - } - } - - void MixOutAmbiUp(const al::span samplesOut, const size_t counter, - const size_t offset, const size_t todo) - { - ASSUME(todo > 0); - - const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), todo}; - for(size_t c{0u};c < NUM_LINES;c++) - { - 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]}; - mAmbiSplitter[0][c].processHfScale(tmpspan, hfscale); + splitter->processHfScale(tmpspan, hfscale); + ++splitter; hfscale = mOrderScales[1]; - MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], counter, - offset); + MixSamples(tmpspan, samplesOut, gains.Current.data(), gains.Target.data(), 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]}; - mAmbiSplitter[1][c].processHfScale(tmpspan, hfscale); + splitter->processHfScale(tmpspan, hfscale); + ++splitter; hfscale = mOrderScales[1]; - MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], counter, - offset); + MixSamples(tmpspan, samplesOut, gains.Current.data(), gains.Target.data(), todo, 0); } } + void mixOut(ReverbPipeline &pipeline, const al::span samplesOut, const size_t todo) + { + if(mUpmixOutput) + MixOutAmbiUp(pipeline, samplesOut, todo); + else + MixOutPlain(pipeline, samplesOut, todo); + } + void allocLines(const float frequency); - 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 EffectTarget &target); - - void earlyUnfaded(const size_t offset, const size_t todo); - void earlyFaded(const size_t offset, const size_t todo, const float fade, - const float fadeStep); - - void lateUnfaded(const size_t offset, const size_t todo); - void lateFaded(const size_t offset, const size_t todo, const float fade, - const float fadeStep); - - void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override; + 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(ReverbState) }; /************************************** @@ -564,58 +691,73 @@ 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... */ const float multiplier{CalcDelayLengthMult(1.0f)}; - /* The main delay length includes the maximum early reflection delay, the - * largest early tap width, the maximum late reverb delay, and the - * largest late tap width. Finally, it must also be extended by the - * update size (BufferLineSize) for block processing. - */ - constexpr float LateLineDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) / - float{NUM_LINES}}; - float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier + - ReverbMaxLateReverbDelay + LateLineDiffAvg*multiplier}; - totalSamples += mDelay.calcLineLength(length, totalSamples, frequency, BufferLineSize); - - /* The early vector all-pass line. */ - length = EARLY_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += mEarly.VecAp.Delay.calcLineLength(length, totalSamples, frequency, 0); - - /* The early reflection line. */ - length = EARLY_LINE_LENGTHS.back() * multiplier; - totalSamples += mEarly.Delay.calcLineLength(length, totalSamples, frequency, 0); - - /* The late vector all-pass line. */ - length = LATE_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += mLate.VecAp.Delay.calcLineLength(length, totalSamples, frequency, 0); - /* The modulator's line length is calculated from the maximum modulation * 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}; - /* The late delay lines are calculated from the largest maximum density - * line length, and the maximum modulation delay. An additional sample is - * added to keep it stable when there is no modulation. + std::array lineoffsets{}; + 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. */ - length = LATE_LINE_LENGTHS.back()*multiplier + max_mod_delay; - totalSamples += mLate.Delay.calcLineLength(length, totalSamples, frequency, 1); + float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier}; + size_t count{mMainDelay.calcLineLength(length, frequency, BufferLineSize)}; + lineoffsets[oidx++] = totalSamples; + totalSamples += count; + for(auto &pipeline : mPipelines) + { + static constexpr float LateDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) / + float{NUM_LINES}}; + length = ReverbMaxLateReverbDelay + LateDiffAvg*multiplier; + count = pipeline.mLateDelayIn.calcLineLength(length, frequency, BufferLineSize); + lineoffsets[oidx++] = totalSamples; + totalSamples += count; + + /* The early vector all-pass line. */ + length = EARLY_ALLPASS_LENGTHS.back() * multiplier; + count = pipeline.mEarly.VecAp.Delay.calcLineLength(length, frequency, 0); + lineoffsets[oidx++] = totalSamples; + totalSamples += count; + + /* The early reflection line. */ + length = EARLY_LINE_LENGTHS.back() * multiplier; + count = pipeline.mEarly.Delay.calcLineLength(length, frequency, MAX_UPDATE_SAMPLES); + lineoffsets[oidx++] = totalSamples; + totalSamples += count; + + /* The late vector all-pass line. */ + length = LATE_ALLPASS_LENGTHS.back() * multiplier; + count = pipeline.mLate.VecAp.Delay.calcLineLength(length, frequency, 0); + lineoffsets[oidx++] = totalSamples; + 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; + count = pipeline.mLate.Delay.calcLineLength(length, frequency, 4); + lineoffsets[oidx++] = totalSamples; + totalSamples += count; + } + assert(oidx == lineoffsets.size()); if(totalSamples != mSampleBuffer.size()) decltype(mSampleBuffer)(totalSamples).swap(mSampleBuffer); @@ -624,81 +766,85 @@ void ReverbState::allocLines(const float frequency) std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), decltype(mSampleBuffer)::value_type{}); /* Update all delays to reflect the new sample buffer. */ - mDelay.realizeLineOffset(mSampleBuffer.data()); - mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); - mEarly.Delay.realizeLineOffset(mSampleBuffer.data()); - mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); - mLate.Delay.realizeLineOffset(mSampleBuffer.data()); + oidx = 0; + mMainDelay.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); + for(auto &pipeline : mPipelines) + { + pipeline.mLateDelayIn.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); + pipeline.mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); + pipeline.mEarly.Delay.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); + pipeline.mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); + pipeline.mLate.Delay.realizeLineOffset(mSampleBuffer.data() + lineoffsets[oidx++]); + } } -void ReverbState::deviceUpdate(const DeviceBase *device, const Buffer&) +void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) { const auto frequency = static_cast(device->Frequency); /* Allocate the delay lines. */ allocLines(frequency); - const float multiplier{CalcDelayLengthMult(1.0f)}; - - /* The late feed taps are set a fixed position past the latest delay tap. */ - mLateFeedTap = float2uint((ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier) * - frequency); - - /* Clear filters and gain coefficients since the delay lines were all just - * cleared (if not reallocated). - */ - for(auto &filter : mFilter) + for(auto &pipeline : mPipelines) { - filter.Lp.clear(); - filter.Hp.clear(); + /* 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(); + } + + for(auto &coeffs : pipeline.mEarlyDelayCoeff) + coeffs.fill(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.Gains) + { + gains.Current.fill(0.0f); + gains.Target.fill(0.0f); + } + for(auto &gains : pipeline.mLate.Gains) + { + gains.Current.fill(0.0f); + gains.Target.fill(0.0f); + } } + mPipelineState = DeviceClear; - for(auto &coeff : mEarlyDelayCoeff) - std::fill(std::begin(coeff), std::end(coeff), 0.0f); - for(auto &coeff : mEarly.Coeff) - std::fill(std::begin(coeff), std::end(coeff), 0.0f); - - mLate.DensityGain[0] = 0.0f; - mLate.DensityGain[1] = 0.0f; - for(auto &t60 : mLate.T60) - { - t60.MidGain[0] = 0.0f; - t60.MidGain[1] = 0.0f; - t60.HFFilter.clear(); - t60.LFFilter.clear(); - } - - mLate.Mod.Index = 0; - mLate.Mod.Step = 1; - std::fill(std::begin(mLate.Mod.Depth), std::end(mLate.Mod.Depth), 0.0f); - - for(auto &gains : mEarly.CurrentGain) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : mEarly.PanGain) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : mLate.CurrentGain) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : mLate.PanGain) - std::fill(std::begin(gains), std::end(gains), 0.0f); - - /* Reset fading and offset base. */ - mDoFading = true; - std::fill(std::begin(mMaxUpdate), std::end(mMaxUpdate), MAX_UPDATE_SAMPLES); + /* Reset offset base. */ mOffset = 0; if(device->mAmbiOrder > 1) { - mMixOut = &ReverbState::MixOutAmbiUp; - mOrderScales = AmbiScale::GetHFOrderScales(1, device->mAmbiOrder); + mUpmixOutput = true; + mOrderScales = AmbiScale::GetHFOrderScales(1, device->mAmbiOrder, device->m2DMixing); } else { - mMixOut = &ReverbState::MixOutPlain; + mUpmixOutput = false; mOrderScales.fill(1.0f); } - mAmbiSplitter[0][0].init(device->mXOverFreq / frequency); - std::fill(mAmbiSplitter[0].begin()+1, mAmbiSplitter[0].end(), mAmbiSplitter[0][0]); - std::fill(mAmbiSplitter[1].begin(), mAmbiSplitter[1].end(), mAmbiSplitter[0][0]); + mPipelines[0].mAmbiSplitter[0][0].init(device->mXOverFreq / frequency); + for(auto &pipeline : mPipelines) + { + 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]); + } } /************************************** @@ -769,7 +915,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); } @@ -785,7 +931,7 @@ void T60Filter::calcCoeffs(const float length, const float lfDecayTime, const float lfGain{CalcDecayCoeff(length, lfDecayTime) / mfGain}; const float hfGain{CalcDecayCoeff(length, hfDecayTime) / mfGain}; - MidGain[1] = mfGain; + MidGain = mfGain; LFFilter.setParamsFromSlope(BiquadType::LowShelf, lf0norm, lfGain, 1.0f); HFFilter.setParamsFromSlope(BiquadType::HighShelf, hf0norm, hfGain, 1.0f); } @@ -801,14 +947,14 @@ void EarlyReflections::updateLines(const float density_mult, const float diffusi { /* Calculate the delay length of each all-pass line. */ float length{EARLY_ALLPASS_LENGTHS[i] * density_mult}; - VecAp.Offset[i][1] = float2uint(length * frequency); + VecAp.Offset[i] = float2uint(length * frequency); /* Calculate the delay length of each delay line. */ length = EARLY_LINE_LENGTHS[i] * density_mult; - Offset[i][1] = float2uint(length * frequency); + Offset[i] = float2uint(length * frequency); /* Calculate the gain (coefficient) for each line. */ - Coeff[i][1] = CalcDecayCoeff(length, decayTime); + Coeff[i] = CalcDecayCoeff(length, decayTime); } } @@ -825,7 +971,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 @@ -842,10 +988,10 @@ void Modulation::updateModulator(float modTime, float modDepth, float frequency) * according to the modulation time. The natural form is varying * inversely, in fact resulting in an invariant. */ - Depth[1] = MODULATION_DEPTH_COEFF / 4.0f * DefaultModulationTime * modDepth * frequency; + Depth = MODULATION_DEPTH_COEFF / 4.0f * DefaultModulationTime * modDepth * frequency; } else - Depth[1] = MODULATION_DEPTH_COEFF / 4.0f * modTime * modDepth * frequency; + Depth = MODULATION_DEPTH_COEFF / 4.0f * modTime * modDepth * frequency; } /* Update the late reverb line lengths and T60 coefficients. */ @@ -882,7 +1028,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, lf0norm*norm_weight_factor*lfDecayTime + (hf0norm - lf0norm)*norm_weight_factor*mfDecayTime + (1.0f - hf0norm*norm_weight_factor)*hfDecayTime}; - DensityGain[1] = CalcDensityGain(CalcDecayCoeff(length, decayTimeWeighted)); + DensityGain = CalcDensityGain(CalcDecayCoeff(length, decayTimeWeighted)); /* Calculate the all-pass feed-back/forward coefficient. */ VecAp.Coeff = diffusion*diffusion * InvSqrt2; @@ -891,11 +1037,14 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, { /* Calculate the delay length of each all-pass line. */ length = LATE_ALLPASS_LENGTHS[i] * density_mult; - VecAp.Offset[i][1] = float2uint(length * frequency); + VecAp.Offset[i] = float2uint(length * frequency); - /* Calculate the delay length of each feedback delay line. */ + /* Calculate the delay length of each feedback delay line. A cubic + * resampler is used for modulation on the feedback delay, which + * includes one sample of delay. Reduce by one to compensate. + */ length = LATE_LINE_LENGTHS[i] * density_mult; - Offset[i][1] = float2uint(length*frequency + 0.5f); + 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 @@ -903,7 +1052,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, * modulation delay (depth is half the max delay in samples). */ length += lerpf(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion)*density_mult + - Mod.Depth[1]/frequency; + Mod.Depth/frequency; /* Calculate the T60 damping coefficients for each line. */ T60[i].calcCoeffs(length, lfDecayTime, mfDecayTime, hfDecayTime, lf0norm, hf0norm); @@ -912,8 +1061,8 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, /* Update the offsets for the main effect delay line. */ -void ReverbState::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. @@ -929,11 +1078,15 @@ void ReverbState::updateDelayLine(const float earlyDelay, const float lateDelay, { float length{EARLY_TAP_LENGTHS[i]*density_mult}; mEarlyDelayTap[i][1] = float2uint((earlyDelay+length) * frequency); - mEarlyDelayCoeff[i][1] = 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] = mLateFeedTap + float2uint(length * frequency); + mLateDelayTap[i][1] = float2uint(length * frequency); } } @@ -942,7 +1095,7 @@ void ReverbState::updateDelayLine(const float earlyDelay, const float lateDelay, * focal strength. This function results in a B-Format transformation matrix * that spatially focuses the signal in the desired direction. */ -alu::Matrix 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 @@ -951,13 +1104,14 @@ alu::Matrix 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; 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] = vec[0] * -scale; + norm[1] = vec[1] * scale; + norm[2] = vec[2] * scale; mag = 1.0f; } else @@ -971,131 +1125,215 @@ alu::Matrix GetTransformFromVector(const float *vec) norm[2] = vec[2] * al::numbers::sqrt3_v; } - return alu::Matrix{ - 1.0f, 0.0f, 0.0f, 0.0f, - norm[0], 1.0f-mag, 0.0f, 0.0f, - norm[1], 0.0f, 1.0f-mag, 0.0f, - norm[2], 0.0f, 0.0f, 1.0f-mag - }; + return std::array,4>{{ + {{1.0f, 0.0f, 0.0f, 0.0f}}, + {{norm[0], 1.0f-mag, 0.0f, 0.0f}}, + {{norm[1], 0.0f, 1.0f-mag, 0.0f}}, + {{norm[2], 0.0f, 0.0f, 1.0f-mag}} + }}; } /* Update the early and late 3D panning gains. */ -void ReverbState::update3DPanning(const float *ReflectionsPan, const float *LateReverbPan, - const float earlyGain, const float lateGain, const EffectTarget &target) +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 alu::Matrix earlymat{GetTransformFromVector(ReflectionsPan)}; - const alu::Matrix latemat{GetTransformFromVector(LateReverbPan)}; + const auto earlymat = GetTransformFromVector(ReflectionsPan); + const auto latemat = GetTransformFromVector(LateReverbPan); - mOutTarget = target.Main->Buffer; - for(size_t i{0u};i < NUM_LINES;i++) - { - const float coeffs[MaxAmbiChannels]{earlymat[0][i], earlymat[1][i], earlymat[2][i], - earlymat[3][i]}; - ComputePanGains(target.Main, coeffs, earlyGain, mEarly.PanGain[i]); - } - for(size_t i{0u};i < NUM_LINES;i++) - { - const float coeffs[MaxAmbiChannels]{latemat[0][i], latemat[1][i], latemat[2][i], - latemat[3][i]}; - ComputePanGains(target.Main, coeffs, lateGain, mLate.PanGain[i]); - } + const auto [earlycoeffs, latecoeffs] = [&]{ + 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) + { + std::array,NUM_LINES> res{}; + const auto mtx2 = al::span{AmbiScale::FirstOrderUp}; + + for(size_t i{0};i < mtx1[0].size();++i) + { + 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; + }; + return std::make_pair(mult_matrix(earlymat), mult_matrix(latemat)); + } + + /* 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. + */ + auto mult_matrix = [](const al::span,4> mtx1, + const al::span,4> mtx2) + { + std::array,NUM_LINES> res{}; + + for(size_t i{0};i < mtx1[0].size();++i) + { + 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; + }; + return std::make_pair(mult_matrix(EarlyA2B, earlymat), mult_matrix(LateA2B, latemat)); + }(); + + 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); - /* Calculate the master filters */ - float hf0norm{minf(props->Reverb.HFReference/frequency, 0.49f)}; - mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, props->Reverb.GainHF, 1.0f); - float lf0norm{minf(props->Reverb.LFReference/frequency, 0.49f)}; - mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, props->Reverb.GainLF, 1.0f); - for(size_t i{1u};i < NUM_LINES;i++) - { - mFilter[i].Lp.copyParamsFrom(mFilter[0].Lp); - mFilter[i].Hp.copyParamsFrom(mFilter[0].Hp); - } - - /* The density-based room size (delay length) multiplier. */ - const float density_mult{CalcDelayLengthMult(props->Reverb.Density)}; - - /* Update the main effect delay and associated taps. */ - updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, - density_mult, props->Reverb.DecayTime, frequency); - - /* Update the early lines. */ - mEarly.updateLines(density_mult, props->Reverb.Diffusion, props->Reverb.DecayTime, frequency); - - /* Get the mixing matrix coefficients. */ - CalcMatrixCoeffs(props->Reverb.Diffusion, &mMixX, &mMixY); - /* If the HF limit parameter is flagged, calculate an appropriate limit * based on the air absorption parameter. */ - 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)}; - /* Update the modulator rate and depth. */ - mLate.Mod.updateModulator(props->Reverb.ModulationTime, props->Reverb.ModulationDepth, - frequency); - - /* Update the late lines. */ - mLate.updateLines(density_mult, props->Reverb.Diffusion, lfDecayTime, - props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm, frequency); - - /* Update early and late 3D panning. */ - const float gain{props->Reverb.Gain * Slot->Gain * ReverbBoost}; - update3DPanning(props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan, - props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, target); - - /* Calculate the max update size from the smallest relevant delay. */ - mMaxUpdate[1] = minz(MAX_UPDATE_SAMPLES, minz(mEarly.Offset[0][1], mLate.Offset[0][1])); - - /* Determine if delay-line cross-fading is required. Density is essentially - * a master control for the feedback delays, so changes the offsets of many - * delay lines. - */ - mDoFading |= (mParams.Density != props->Reverb.Density || + /* 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.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); - if(mDoFading) + 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 = !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{Slot->Gain * ReverbBoost}; + pipeline.update3DPanning(props.ReflectionsPan, props.LateReverbPan, props.ReflectionsGain*gain, + props.LateReverbGain*gain, mUpmixOutput, target.Main); + + /* Calculate the master filters */ + 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); + } + + if(fullUpdate) + { + /* Update the early lines. */ + pipeline.mEarly.updateLines(density_mult, props.Diffusion, props.DecayTime, frequency); + + /* Get the mixing matrix coefficients. */ + CalcMatrixCoeffs(props.Diffusion, &pipeline.mMixX, &pipeline.mMixY); + + /* Update the modulator rate and depth. */ + pipeline.mLate.Mod.updateModulator(props.ModulationTime, props.ModulationDepth, frequency); + + /* Update the late lines. */ + pipeline.mLate.updateLines(density_mult, props.Diffusion, lfDecayTime, props.DecayTime, + hfDecayTime, lf0norm, hf0norm, frequency); + } + + /* 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)); } @@ -1142,34 +1380,33 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, * whose combination of signs are being iterated. */ inline auto VectorPartialScatter(const std::array &RESTRICT in, - const float xCoeff, const float yCoeff) -> std::array + const float xCoeff, const float yCoeff) noexcept { - 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]; } } @@ -1179,21 +1416,18 @@ 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::processUnfaded(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 offset, + const float xCoeff, const float yCoeff, const size_t todo) noexcept { const DelayLineI delay{Delay}; const float feedCoeff{Coeff}; ASSUME(todo > 0); - size_t vap_offset[NUM_LINES]; + std::array vap_offset; for(size_t j{0u};j < NUM_LINES;j++) - vap_offset[j] = offset - Offset[j][0]; + vap_offset[j] = offset - Offset[j]; for(size_t i{0u};i < todo;) { for(size_t j{0u};j < NUM_LINES;j++) @@ -1202,257 +1436,182 @@ void VecAllpass::processUnfaded(const al::span sampl 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)}; + maxoff = std::max(maxoff, vap_offset[j]); + size_t td{std::min(delay.Mask+1 - maxoff, todo - i)}; do { 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{delay.get(vap_offset[j]++, j) - feedCoeff*input}; f[j] = input + feedCoeff*out; samples[j][i] = out; } ++i; - delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff); + delay.set(offset++, VectorPartialScatter(f, xCoeff, yCoeff)); } while(--td); } } -void VecAllpass::processFaded(const al::span samples, size_t offset, - const float xCoeff, const float yCoeff, float fadeCount, const float fadeStep, - const size_t todo) + +/* 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) noexcept { - const DelayLineI delay{Delay}; + const DelayLineU delay{Delay}; const float feedCoeff{Coeff}; ASSUME(todo > 0); - size_t vap_offset[NUM_LINES][2]; for(size_t j{0u};j < NUM_LINES;j++) { - vap_offset[j][0] = offset - Offset[j][0]; - vap_offset[j][1] = offset - Offset[j][1]; - } - for(size_t i{0u};i < todo;) - { - for(size_t j{0u};j < NUM_LINES;j++) + float *buffer{delay.get(j).data()}; + size_t dstoffset{offset}; + size_t vap_offset{offset - Offset[j]}; + for(size_t i{0u};i < todo;) { - vap_offset[j][0] &= delay.Mask; - vap_offset[j][1] &= delay.Mask; + vap_offset &= delay.Mask; + dstoffset &= delay.Mask; + + const size_t maxoff{std::max(dstoffset, vap_offset)}; + size_t td{std::min(delay.Mask+1 - maxoff, todo - i)}; + + do { + const float x{samples[j][i]}; + const float y{buffer[vap_offset++] - feedCoeff*x}; + buffer[dstoffset++] = x + feedCoeff*y; + + samples[j][i++] = y; + } while(--td); } - offset &= delay.Mask; - - size_t maxoff{offset}; - for(size_t j{0u};j < NUM_LINES;j++) - maxoff = maxz(maxoff, maxz(vap_offset[j][0], vap_offset[j][1])); - size_t td{minz(delay.Mask+1 - maxoff, todo - i)}; - - do { - fadeCount += 1.0f; - const float fade{fadeCount * fadeStep}; - - std::array f; - for(size_t j{0u};j < NUM_LINES;j++) - f[j] = delay.Line[vap_offset[j][0]++][j]*(1.0f-fade) + - delay.Line[vap_offset[j][1]++][j]*fade; - - for(size_t j{0u};j < NUM_LINES;j++) - { - const float input{samples[j][i]}; - const float out{f[j] - feedCoeff*input}; - f[j] = input + feedCoeff*out; - - samples[j][i] = out; - } - ++i; - - delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff); - } while(--td); } } + /* This generates early reflections. * * This is done by obtaining the primary reflections (those arriving from the * same direction as the source) from the main delay line. These are * attenuated and all-pass filtered (based on the diffusion parameter). * - * The early lines are then fed in reverse (according to the approximately - * opposite spatial location of the A-Format lines) to create the secondary + * 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. - * - * Two static specializations are used for transitional (cross-faded) delay - * line processing and non-transitional processing. */ -void ReverbState::earlyUnfaded(const size_t offset, const size_t todo) +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 main_delay{mDelay}; + const DelayLineU early_delay{mEarly.Delay}; + const DelayLineU in_delay{main_delay}; const float mixX{mMixX}; const float mixY{mMixY}; - ASSUME(todo > 0); + ASSUME(samplesToDo > 0); - /* First, load decorrelated samples from the main delay line as the primary - * reflections. - */ - for(size_t j{0u};j < NUM_LINES;j++) + for(size_t base{0};base < samplesToDo;) { - size_t early_delay_tap{offset - mEarlyDelayTap[j][0]}; - const float coeff{mEarlyDelayCoeff[j][0]}; - for(size_t i{0u};i < todo;) + 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++) { - early_delay_tap &= main_delay.Mask; - size_t td{minz(main_delay.Mask+1 - early_delay_tap, todo - i)}; - do { - mTempSamples[j][i++] = main_delay.Line[early_delay_tap++][j] * coeff; - } while(--td); + const float *input{in_delay.get(j).data()}; + size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]}; + size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]}; + mEarlyDelayTap[j][0] = mEarlyDelayTap[j][1]; + const float coeff0{mEarlyDelayCoeff[j][0]}; + const float coeff1{mEarlyDelayCoeff[j][1]}; + mEarlyDelayCoeff[j][0] = mEarlyDelayCoeff[j][1]; + const float coeffStep{(coeff1-coeff0)*fadeStep}; + 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{std::max(early_delay_tap0, early_delay_tap1)}; + size_t td{std::min(in_delay.Mask+1 - max_tap, todo-i)}; + do { + tempSamples[j][i++] = lerpf(input[early_delay_tap0++]*coeff0, + input[early_delay_tap1++]*coeff1, coeffStep*fadeCount); + fadeCount += 1.0f; + } while(--td); + } + + /* Band-pass the incoming samples. */ + auto&& filter = DualBiquad{mFilter[j].Lp, mFilter[j].Hp}; + filter.process({tempSamples[j].cbegin(), todo}, tempSamples[j]); } - } - /* Apply a vector all-pass, to help color the initial reflections based on - * the diffusion strength. - */ - mEarly.VecAp.processUnfaded(mTempSamples, offset, mixX, mixY, todo); + /* Apply 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++) - { - size_t feedb_tap{offset - mEarly.Offset[j][0]}; - const float feedb_coeff{mEarly.Coeff[j][0]}; - float *out{mEarlySamples[j].data()}; - - for(size_t i{0u};i < todo;) + /* Apply a delay and bounce to generate secondary reflections, combine + * with the primary reflections and write out the result for mixing. + */ + early_delay.writeReflected(offset, tempSamples, todo); + for(size_t j{0u};j < NUM_LINES;j++) { - feedb_tap &= early_delay.Mask; - size_t td{minz(early_delay.Mask+1 - feedb_tap, todo - i)}; - do { - out[i] = mTempSamples[j][i] + early_delay.Line[feedb_tap++][j]*feedb_coeff; - ++i; - } while(--td); - } - } - for(size_t j{0u};j < NUM_LINES;j++) - early_delay.write(offset, NUM_LINES-1-j, mTempSamples[j].data(), todo); + const float *input{early_delay.get(j).data()}; + 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)}; - /* Also write the result back to the main delay line for the late reverb - * stage to pick up at the appropriate time, appplying a scatter and - * bounce to improve the initial diffusion in the late reverb. - */ - const size_t late_feed_tap{offset - mLateFeedTap}; - VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, mEarlySamples, todo); + for(size_t i{0u};i < todo;) + { + feedb_tap &= early_delay.Mask; + size_t td{std::min(early_delay.Mask+1 - feedb_tap, todo - i)}; + do { + float sample{input[feedb_tap++]}; + out[i] = tempSamples[j][i] + sample*feedb_coeff; + tempSamples[j][i] = sample; + ++i; + } while(--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. + */ + VectorScatterRev(mixX, mixY, tempSamples, todo); + for(size_t j{0u};j < NUM_LINES;j++) + mLateDelayIn.write(offset, j, tempSamples[j].data(), todo); + + base += todo; + offset += todo; + } } -void ReverbState::earlyFaded(const size_t offset, const size_t todo, const float fade, - const float fadeStep) -{ - const DelayLineI early_delay{mEarly.Delay}; - const DelayLineI main_delay{mDelay}; - const float mixX{mMixX}; - const float mixY{mMixY}; - - ASSUME(todo > 0); - - for(size_t j{0u};j < NUM_LINES;j++) - { - size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]}; - size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]}; - const float oldCoeff{mEarlyDelayCoeff[j][0]}; - const float oldCoeffStep{-oldCoeff * fadeStep}; - const float newCoeffStep{mEarlyDelayCoeff[j][1] * fadeStep}; - float fadeCount{fade}; - - for(size_t i{0u};i < todo;) - { - early_delay_tap0 &= main_delay.Mask; - early_delay_tap1 &= main_delay.Mask; - size_t td{minz(main_delay.Mask+1 - maxz(early_delay_tap0, early_delay_tap1), todo-i)}; - do { - fadeCount += 1.0f; - const float fade0{oldCoeff + oldCoeffStep*fadeCount}; - const float fade1{newCoeffStep*fadeCount}; - mTempSamples[j][i++] = - main_delay.Line[early_delay_tap0++][j]*fade0 + - main_delay.Line[early_delay_tap1++][j]*fade1; - } while(--td); - } - } - - mEarly.VecAp.processFaded(mTempSamples, offset, mixX, mixY, fade, fadeStep, todo); - - for(size_t j{0u};j < NUM_LINES;j++) - { - size_t feedb_tap0{offset - mEarly.Offset[j][0]}; - size_t feedb_tap1{offset - mEarly.Offset[j][1]}; - const float feedb_oldCoeff{mEarly.Coeff[j][0]}; - const float feedb_oldCoeffStep{-feedb_oldCoeff * fadeStep}; - const float feedb_newCoeffStep{mEarly.Coeff[j][1] * fadeStep}; - float *out{mEarlySamples[j].data()}; - float fadeCount{fade}; - - for(size_t i{0u};i < todo;) - { - feedb_tap0 &= early_delay.Mask; - feedb_tap1 &= early_delay.Mask; - size_t td{minz(early_delay.Mask+1 - maxz(feedb_tap0, feedb_tap1), todo - i)}; - - do { - fadeCount += 1.0f; - const float fade0{feedb_oldCoeff + feedb_oldCoeffStep*fadeCount}; - const float fade1{feedb_newCoeffStep*fadeCount}; - out[i] = mTempSamples[j][i] + - early_delay.Line[feedb_tap0++][j]*fade0 + - early_delay.Line[feedb_tap1++][j]*fade1; - ++i; - } while(--td); - } - } - for(size_t j{0u};j < NUM_LINES;j++) - early_delay.write(offset, NUM_LINES-1-j, mTempSamples[j].data(), todo); - - const size_t late_feed_tap{offset - mLateFeedTap}; - VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, mEarlySamples, todo); -} - void Modulation::calcDelays(size_t todo) { - constexpr float inv_scale{MOD_FRACONE / al::numbers::pi_v / 2.0f}; uint idx{Index}; const uint step{Step}; - const float depth{Depth[0]}; + const float depth{Depth * float{gCubicTable.sTableSteps}}; for(size_t i{0};i < todo;++i) { idx += step; - const float lfo{std::sin(static_cast(idx&MOD_FRACMASK) / inv_scale)}; - ModDelays[i] = (lfo+1.0f) * depth; - } - Index = idx; -} - -void Modulation::calcFadedDelays(size_t todo, float fadeCount, float fadeStep) -{ - constexpr float inv_scale{MOD_FRACONE / al::numbers::pi_v / 2.0f}; - uint idx{Index}; - const uint step{Step}; - const float depth{Depth[0]}; - const float depthStep{(Depth[1]-depth) * fadeStep}; - for(size_t i{0};i < todo;++i) - { - fadeCount += 1.0f; - idx += step; - const float lfo{std::sin(static_cast(idx&MOD_FRACMASK) / inv_scale)}; - ModDelays[i] = (lfo+1.0f) * (depth + depthStep*fadeCount); + const float x{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 float lfo{!(idx&(MOD_FRACONE>>1)) + ? ((-16.0f * x * x) + (8.0f * x)) + : ((16.0f * x * x) + (-8.0f * x) + (-16.0f * x) + 8.0f)}; + ModDelays[i] = float2uint((lfo+1.0f) * depth); } Index = idx; } @@ -1468,151 +1627,122 @@ void Modulation::calcFadedDelays(size_t todo, float fadeCount, float fadeStep) * * Finally, the lines are reversed (so they feed their opposite directions) * and scattered with the FDN matrix before re-feeding the delay lines. - * - * Two variations are made, one for for transitional (cross-faded) delay line - * processing and one for non-transitional processing. */ -void ReverbState::lateUnfaded(const size_t offset, const size_t todo) +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 main_delay{mDelay}; + const DelayLineU late_delay{mLate.Delay}; + const DelayLineU in_delay{mLateDelayIn}; const float mixX{mMixX}; const float mixY{mMixY}; - ASSUME(todo > 0); + ASSUME(samplesToDo > 0); - /* First, calculate the modulated delays for the late feedback. */ - mLate.Mod.calcDelays(todo); - - /* Next, load decorrelated samples from the main and feedback delay lines. - * Filter the signal to apply its frequency-dependent decay. - */ - for(size_t j{0u};j < NUM_LINES;j++) + for(size_t base{0};base < samplesToDo;) { - size_t late_delay_tap{offset - mLateDelayTap[j][0]}; - size_t late_feedb_tap{offset - mLate.Offset[j][0]}; - const float midGain{mLate.T60[j].MidGain[0]}; - const float densityGain{mLate.DensityGain[0] * midGain}; + const size_t todo{std::min(std::min(mLate.Offset[0], MAX_UPDATE_SAMPLES), + samplesToDo-base)}; + ASSUME(todo > 0); - for(size_t i{0u};i < todo;) + /* First, calculate the modulated delays for the late feedback. */ + mLate.Mod.calcDelays(todo); + + /* Now load samples from the feedback delay lines. Filter the signal to + * apply its frequency-dependent decay. + */ + for(size_t j{0u};j < NUM_LINES;++j) { - late_delay_tap &= main_delay.Mask; - size_t td{minz(todo - i, main_delay.Mask+1 - late_delay_tap)}; - do { - /* Calculate the read offset and fraction between it and the - * next sample. - */ - const float fdelay{mLate.Mod.ModDelays[i]}; - const size_t delay{float2uint(fdelay)}; - const float frac{fdelay - static_cast(delay)}; + const float *input{late_delay.get(j).data()}; + size_t late_feedb_tap{offset - mLate.Offset[j]}; + const float midGain{mLate.T60[j].MidGain}; - /* Feed the delay line with the late feedback sample, and get - * the two samples crossed by the delayed offset. + for(size_t i{0u};i < todo;++i) + { + /* Calculate the read offset and offset between it and the next + * sample. */ - const float out0{late_delay.Line[(late_feedb_tap-delay) & late_delay.Mask][j]}; - const float out1{late_delay.Line[(late_feedb_tap-delay-1) & late_delay.Mask][j]}; + const size_t idelay{mLate.Mod.ModDelays[i]}; + const size_t delay{late_feedb_tap - (idelay>>gCubicTable.sTableBits)}; + const size_t delayoffset{idelay & gCubicTable.sTableMask}; ++late_feedb_tap; - /* The output is obtained by linearly interpolating the two - * samples that were acquired above, and combined with the main - * delay tap. - */ - mTempSamples[j][i] = lerpf(out0, out1, frac)*midGain + - main_delay.Line[late_delay_tap++][j]*densityGain; - ++i; - } while(--td); + /* Get the samples around by the delayed offset. */ + const float out0{input[(delay ) & late_delay.Mask]}; + const float out1{input[(delay-1) & late_delay.Mask]}; + const float out2{input[(delay-2) & late_delay.Mask]}; + const float out3{input[(delay-3) & late_delay.Mask]}; + + const float out{out0*gCubicTable.getCoeff0(delayoffset) + + out1*gCubicTable.getCoeff1(delayoffset) + + out2*gCubicTable.getCoeff2(delayoffset) + + out3*gCubicTable.getCoeff3(delayoffset)}; + tempSamples[j][i] = out * midGain; + } + + mLate.T60[j].process({tempSamples[j].data(), todo}); } - mLate.T60[j].process({mTempSamples[j].data(), todo}); - } - /* Apply a vector all-pass to improve micro-surface diffusion, and write - * out the results for mixing. - */ - mLate.VecAp.processUnfaded(mTempSamples, offset, mixX, mixY, todo); - for(size_t j{0u};j < NUM_LINES;j++) - std::copy_n(mTempSamples[j].begin(), todo, mLateSamples[j].begin()); - - /* Finally, scatter and bounce the results to refeed the feedback buffer. */ - VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, mTempSamples, todo); -} -void ReverbState::lateFaded(const size_t offset, const size_t todo, const float fade, - const float fadeStep) -{ - const DelayLineI late_delay{mLate.Delay}; - const DelayLineI main_delay{mDelay}; - const float mixX{mMixX}; - const float mixY{mMixY}; - - ASSUME(todo > 0); - - mLate.Mod.calcFadedDelays(todo, fade, fadeStep); - - for(size_t j{0u};j < NUM_LINES;j++) - { - const float oldMidGain{mLate.T60[j].MidGain[0]}; - const float midGain{mLate.T60[j].MidGain[1]}; - const float oldMidStep{-oldMidGain * fadeStep}; - const float midStep{midGain * fadeStep}; - const float oldDensityGain{mLate.DensityGain[0] * oldMidGain}; - const float densityGain{mLate.DensityGain[1] * midGain}; - const float oldDensityStep{-oldDensityGain * fadeStep}; - const float densityStep{densityGain * fadeStep}; - size_t late_delay_tap0{offset - mLateDelayTap[j][0]}; - size_t late_delay_tap1{offset - mLateDelayTap[j][1]}; - size_t late_feedb_tap0{offset - mLate.Offset[j][0]}; - size_t late_feedb_tap1{offset - mLate.Offset[j][1]}; - float fadeCount{fade}; - - for(size_t i{0u};i < todo;) + /* Next load decorrelated samples from the main delay lines. */ + const float fadeStep{1.0f / static_cast(todo)}; + for(size_t j{0u};j < NUM_LINES;++j) { - late_delay_tap0 &= main_delay.Mask; - late_delay_tap1 &= main_delay.Mask; - size_t td{minz(todo - i, main_delay.Mask+1 - maxz(late_delay_tap0, late_delay_tap1))}; - do { - fadeCount += 1.0f; + const float *input{in_delay.get(j).data()}; + size_t late_delay_tap0{offset - mLateDelayTap[j][0]}; + size_t late_delay_tap1{offset - mLateDelayTap[j][1]}; + const float densityGain{mLate.DensityGain}; + const float densityStep{late_delay_tap0 != late_delay_tap1 ? + densityGain*fadeStep : 0.0f}; + float fadeCount{0.0f}; - const float fdelay{mLate.Mod.ModDelays[i]}; - const size_t delay{float2uint(fdelay)}; - const float frac{fdelay - static_cast(delay)}; - - const float out00{late_delay.Line[(late_feedb_tap0-delay) & late_delay.Mask][j]}; - const float out01{late_delay.Line[(late_feedb_tap0-delay-1) & late_delay.Mask][j]}; - ++late_feedb_tap0; - const float out10{late_delay.Line[(late_feedb_tap1-delay) & late_delay.Mask][j]}; - const float out11{late_delay.Line[(late_feedb_tap1-delay-1) & late_delay.Mask][j]}; - ++late_feedb_tap1; - - const float fade0{oldDensityGain + oldDensityStep*fadeCount}; - const float fade1{densityStep*fadeCount}; - const float gfade0{oldMidGain + oldMidStep*fadeCount}; - const float gfade1{midStep*fadeCount}; - mTempSamples[j][i] = lerpf(out00, out01, frac)*gfade0 + - lerpf(out10, out11, frac)*gfade1 + - main_delay.Line[late_delay_tap0++][j]*fade0 + - main_delay.Line[late_delay_tap1++][j]*fade1; - ++i; - } while(--td); + for(size_t i{0u};i < todo;) + { + late_delay_tap0 &= in_delay.Mask; + late_delay_tap1 &= in_delay.Mask; + size_t td{std::min(in_delay.Mask+1 - std::max(late_delay_tap0, late_delay_tap1), + todo-i)}; + do { + const float fade0{densityGain - densityStep*fadeCount}; + const float fade1{densityStep*fadeCount}; + fadeCount += 1.0f; + tempSamples[j][i] += input[late_delay_tap0++]*fade0 + + input[late_delay_tap1++]*fade1; + ++i; + } while(--td); + } + mLateDelayTap[j][0] = mLateDelayTap[j][1]; } - mLate.T60[j].process({mTempSamples[j].data(), todo}); + + /* Apply a vector all-pass to improve micro-surface diffusion, and + * write out the results for mixing. + */ + mLate.VecAp.process(tempSamples, offset, mixX, mixY, todo); + for(size_t j{0u};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. */ + VectorScatterRev(mixX, mixY, tempSamples, todo); + for(size_t j{0u};j < NUM_LINES;++j) + late_delay.write(offset, j, tempSamples[j].data(), todo); + + base += todo; + offset += todo; } - - mLate.VecAp.processFaded(mTempSamples, offset, mixX, mixY, fade, fadeStep, todo); - for(size_t j{0u};j < NUM_LINES;j++) - std::copy_n(mTempSamples[j].begin(), todo, mLateSamples[j].begin()); - - VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, mTempSamples, todo); } void ReverbState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - size_t offset{mOffset}; + const size_t offset{mOffset}; ASSUME(samplesToDo > 0); + auto &oldpipeline = mPipelines[!mCurrentPipeline]; + auto &pipeline = mPipelines[mCurrentPipeline]; + /* Convert B-Format to A-Format for processing. */ - const size_t numInput{minz(samplesIn.size(), NUM_LINES)}; + 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++) + 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) @@ -1620,81 +1750,61 @@ void ReverbState::process(const size_t samplesToDo, const al::span(samplesIn[i].data())}; - for(float &sample : tmpspan) - { - sample += *input * gain; - ++input; - } + 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. */ - DualBiquad{mFilter[c].Lp, mFilter[c].Hp}.process(tmpspan, tmpspan.data()); - mDelay.write(offset, c, tmpspan.cbegin(), samplesToDo); + mMainDelay.write(offset, c, tmpspan.data(), samplesToDo); } - /* Process reverb for these samples. */ - if LIKELY(!mDoFading) + if(mPipelineState < Fading) + mPipelineState = Fading; + + /* Process reverb for these samples. and mix them to the output. */ + pipeline.processEarly(mMainDelay, offset, samplesToDo, mTempSamples, mEarlySamples); + pipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples); + mixOut(pipeline, samplesOut, samplesToDo); + + if(mPipelineState != Normal) { - for(size_t base{0};base < samplesToDo;) + if(mPipelineState == Cleanup) { - /* Calculate the number of samples we can do this iteration. */ - size_t todo{minz(samplesToDo - base, mMaxUpdate[0])}; - /* Some mixers require maintaining a 4-sample alignment, so ensure - * that if it's not the last iteration. + size_t numSamples{mSampleBuffer.size()/2}; + size_t pipelineOffset{numSamples * (!mCurrentPipeline)}; + std::fill_n(mSampleBuffer.data()+pipelineOffset, numSamples, + decltype(mSampleBuffer)::value_type{}); + + oldpipeline.clear(); + mPipelineState = Normal; + } + else + { + /* If this is the final mix for this old pipeline, set the target + * gains to 0 to ensure a complete fade out, and set the state to + * Cleanup so the next invocation cleans up the delay buffers and + * filters. */ - if(base+todo < samplesToDo) todo &= ~size_t{3}; - ASSUME(todo > 0); + if(samplesToDo >= oldpipeline.mFadeSampleCount) + { + 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; + } + else + oldpipeline.mFadeSampleCount -= samplesToDo; - /* Generate non-faded early reflections and late reverb. */ - earlyUnfaded(offset, todo); - lateUnfaded(offset, todo); - - /* Finally, mix early reflections and late reverb. */ - (this->*mMixOut)(samplesOut, samplesToDo-base, base, todo); - - offset += todo; - base += todo; + /* Process the old reverb for these samples. */ + oldpipeline.processEarly(mMainDelay, offset, samplesToDo, mTempSamples, mEarlySamples); + oldpipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples); + mixOut(oldpipeline, samplesOut, samplesToDo); } } - else - { - const float fadeStep{1.0f / static_cast(samplesToDo)}; - for(size_t base{0};base < samplesToDo;) - { - size_t todo{minz(samplesToDo - base, minz(mMaxUpdate[0], mMaxUpdate[1]))}; - if(base+todo < samplesToDo) todo &= ~size_t{3}; - ASSUME(todo > 0); - /* Generate cross-faded early reflections and late reverb. */ - auto fadeCount = static_cast(base); - earlyFaded(offset, todo, fadeCount, fadeStep); - lateFaded(offset, todo, fadeCount, fadeStep); - - (this->*mMixOut)(samplesOut, samplesToDo-base, base, todo); - - offset += todo; - base += todo; - } - - /* Update the cross-fading delay line taps. */ - for(size_t c{0u};c < NUM_LINES;c++) - { - mEarlyDelayTap[c][0] = mEarlyDelayTap[c][1]; - mEarlyDelayCoeff[c][0] = mEarlyDelayCoeff[c][1]; - mLateDelayTap[c][0] = mLateDelayTap[c][1]; - mEarly.VecAp.Offset[c][0] = mEarly.VecAp.Offset[c][1]; - mEarly.Offset[c][0] = mEarly.Offset[c][1]; - mEarly.Coeff[c][0] = mEarly.Coeff[c][1]; - mLate.Offset[c][0] = mLate.Offset[c][1]; - mLate.T60[c].MidGain[0] = mLate.T60[c].MidGain[1]; - mLate.VecAp.Offset[c][0] = mLate.VecAp.Offset[c][1]; - } - mLate.DensityGain[0] = mLate.DensityGain[1]; - mLate.Mod.Depth[0] = mLate.Mod.Depth[1]; - mMaxUpdate[0] = mMaxUpdate[1]; - mDoFading = false; - } - mOffset = offset; + mOffset = offset + samplesToDo; } diff --git a/libs/openal-soft/alc/effects/vmorpher.cpp b/libs/openal-soft/alc/effects/vmorpher.cpp index edc50eb1..b07e6244 100644 --- a/libs/openal-soft/alc/effects/vmorpher.cpp +++ b/libs/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,39 +147,40 @@ struct FormantFilter struct VmorpherState final : public EffectState { - struct { + struct OutParams { + uint mTargetChannel{InvalidChannelIndex}; + /* Effect parameters */ - FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS]; + std::array,NumFilters> mFormants; /* Effect gains for each channel */ - float CurrentGains[MAX_OUTPUT_CHANNELS]{}; - float TargetGains[MAX_OUTPUT_CHANNELS]{}; - } mChans[MaxAmbiChannels]; + float mCurrentGain{}; + float mTargetGain{}; + }; + 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 Buffer &buffer) override; + 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; - 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. @@ -225,54 +231,57 @@ std::array VmorpherState::getFiltersByPhoneme(VMorpherPhenome p } -void VmorpherState::deviceUpdate(const DeviceBase*, const Buffer&) +void VmorpherState::deviceUpdate(const DeviceBase*, const BufferStorage*) { for(auto &e : mChans) { - std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[VOWEL_A_INDEX]), + e.mTargetChannel = InvalidChannelIndex; + std::for_each(e.mFormants[VowelAIndex].begin(), e.mFormants[VowelAIndex].end(), std::mem_fn(&FormantFilter::clear)); - std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]), + std::for_each(e.mFormants[VowelBIndex].begin(), e.mFormants[VowelBIndex].end(), std::mem_fn(&FormantFilter::clear)); - std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f); + 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].Formants[VOWEL_A_INDEX])); - std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].Formants[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; - auto set_gains = [slot,target](auto &chan, al::span coeffs) - { ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); }; - SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains); + auto set_channel = [this](size_t idx, uint outchan, float outgain) + { + mChans[idx].mTargetChannel = outchan; + mChans[idx].mTargetGain = outgain; + }; + target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void VmorpherState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) @@ -282,39 +291,46 @@ void VmorpherState::process(const size_t samplesToDo, const al::span(mStep * td); - mIndex &= WAVEFORM_FRACMASK; + mIndex &= WaveformFracMask; - auto chandata = std::begin(mChans); + auto chandata = mChans.begin(); for(const auto &input : samplesIn) { - auto& vowelA = chandata->Formants[VOWEL_A_INDEX]; - auto& vowelB = chandata->Formants[VOWEL_B_INDEX]; + const size_t outidx{chandata->mTargetChannel}; + if(outidx == InvalidChannelIndex) + { + ++chandata; + continue; + } + + 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]; + alignas(16) std::array blended; 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, chandata->CurrentGains, chandata->TargetGains, - samplesToDo-base, base); + MixSamples({blended.data(), td}, samplesOut[outidx].data()+base, + chandata->mCurrentGain, chandata->mTargetGain, samplesToDo-base); ++chandata; } diff --git a/libs/openal-soft/alc/events.cpp b/libs/openal-soft/alc/events.cpp new file mode 100644 index 00000000..1010a338 --- /dev/null +++ b/libs/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/libs/openal-soft/alc/events.h b/libs/openal-soft/alc/events.h new file mode 100644 index 00000000..3f53ec76 --- /dev/null +++ b/libs/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/libs/openal-soft/alc/export_list.h b/libs/openal-soft/alc/export_list.h new file mode 100644 index 00000000..23a74e62 --- /dev/null +++ b/libs/openal-soft/alc/export_list.h @@ -0,0 +1,922 @@ +#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(alAuxiliaryEffectSlotPlaySOFT), + DECL(alAuxiliaryEffectSlotPlayvSOFT), + DECL(alAuxiliaryEffectSlotStopSOFT), + DECL(alAuxiliaryEffectSlotStopvSOFT), + + 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/libs/openal-soft/alc/inprogext.h b/libs/openal-soft/alc/inprogext.h index 9af80f12..925ff8bd 100644 --- a/libs/openal-soft/alc/inprogext.h +++ b/libs/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,19 +42,20 @@ 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); +#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 +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYSOFT)(ALuint slotid) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYVSOFT)(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPSOFT)(ALuint slotid) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPVSOFT)(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT17; #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); +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) AL_API_NOEXCEPT; #endif #endif @@ -55,19 +65,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 -/* Functions from abandoned extenions. Only here for binary compatibility. */ +#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/libs/openal-soft/alc/panning.cpp b/libs/openal-soft/alc/panning.cpp index 00bf5662..bf719384 100644 --- a/libs/openal-soft/alc/panning.cpp +++ b/libs/openal-soft/alc/panning.cpp @@ -22,48 +22,58 @@ #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; @@ -89,6 +99,23 @@ inline const char *GetLabelFromChannel(Channel channel) case TopBackCenter: return "top-back-center"; case TopBackRight: return "top-back-right"; + case Aux0: return "Aux0"; + case Aux1: return "Aux1"; + case Aux2: return "Aux2"; + case Aux3: return "Aux3"; + case Aux4: return "Aux4"; + case Aux5: return "Aux5"; + case Aux6: return "Aux6"; + case Aux7: return "Aux7"; + case Aux8: return "Aux8"; + case Aux9: return "Aux9"; + case Aux10: return "Aux10"; + case Aux11: return "Aux11"; + case Aux12: return "Aux12"; + case Aux13: return "Aux13"; + case Aux14: return "Aux14"; + case Aux15: return "Aux15"; + case MaxChannels: break; } return "(unknown)"; @@ -98,13 +125,13 @@ inline const char *GetLabelFromChannel(Channel channel) std::unique_ptr CreateStablizer(const size_t outchans, const uint srate) { auto stablizer = FrontStablizer::Create(outchans); - for(auto &buf : stablizer->DelayBuf) - std::fill(buf.begin(), buf.end(), 0.0f); /* Initialize band-splitting filter for the mid signal, with a crossover at * 5khz (could be higher). */ stablizer->MidFilter.init(5000.0f / static_cast(srate)); + for(auto &filter : stablizer->ChannelFilters) + filter = stablizer->MidFilter; return stablizer; } @@ -202,48 +229,54 @@ struct DecoderConfig { mCoeffsLF = rhs.mCoeffsLF; return *this; } + + explicit operator bool() const noexcept { return !mChannels.empty(); } }; 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", 0) || !(ctrl_dist > 0.0f)) + 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", 1) || !(maxdist > 0.0f)) + 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]}; - if(idx == INVALID_CHANNEL_INDEX) + const size_t idx{device->RealOut.ChannelIndex[ch]}; + if(idx == InvalidChannelIndex) continue; const float distance{dists[chidx]}; @@ -255,14 +288,14 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, * will be in steps of about 7 millimeters. */ float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)}; - if(delay > float{MAX_DELAY_LENGTH-1}) + if(delay > float{DistanceComp::MaxDelay-1}) { - ERR("Delay for channel %u (%s) exceeds buffer length (%f > %d)\n", idx, - GetLabelFromChannel(ch), delay, MAX_DELAY_LENGTH-1); - delay = float{MAX_DELAY_LENGTH-1}; + 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), @@ -277,103 +310,69 @@ 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{}; - decoder.mOrder = (conf->ChanMask > Ambi2OrderMask) ? uint8_t{3} : + decoder.mOrder = (conf->ChanMask > Ambi3OrderMask) ? uint8_t{4} : + (conf->ChanMask > Ambi2OrderMask) ? uint8_t{3} : (conf->ChanMask > Ambi1OrderMask) ? uint8_t{2} : uint8_t{1}; decoder.mIs3D = (conf->ChanMask&AmbiPeriphonicMask) != 0; switch(conf->CoeffScale) { + case AmbDecScale::Unset: ASSUME(false); break; case AmbDecScale::N3D: decoder.mScaling = DevAmbiScaling::N3D; break; case AmbDecScale::SN3D: decoder.mScaling = DevAmbiScaling::SN3D; break; 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()); - std::array idx_map{}; - if(decoder.mIs3D) - { - uint flags{conf->ChanMask}; - auto elem = idx_map.begin(); - while(flags) - { - int acn{al::countr_zero(flags)}; - flags &= ~(1u<(acn); - ++elem; - } - } - else - { - uint flags{conf->ChanMask}; - auto elem = idx_map.begin(); - while(flags) - { - int acn{al::countr_zero(flags)}; - flags &= ~(1u<(al::popcount(conf->ChanMask)); + const auto num_coeffs = decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) + : Ambi2DChannelsFromOrder(decoder.mOrder); + 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{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 @@ -388,6 +387,10 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, * RB = Back right * CE = Front center * CB = Back center + * LFT = Top front left + * RFT = Top front right + * LBT = Top back left + * RBT = Top back right * * Additionally, surround51 will acknowledge back speakers for side * channels, to avoid issues with an ambdec expecting 5.1 to use the @@ -410,23 +413,38 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, ch = (device->FmtChans == DevFmtX51) ? SideRight : BackRight; else if(speaker.Name == "CB") ch = BackCenter; + else if(speaker.Name == "LFT") + ch = TopFrontLeft; + else if(speaker.Name == "RFT") + ch = TopFrontRight; + else if(speaker.Name == "LBT") + ch = TopBackLeft; + else if(speaker.Name == "RBT") + ch = TopBackRight; else { - ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str()); - continue; + int idx{}; + char c{}; + if(sscanf(speaker.Name.c_str(), "AUX%d%c", &idx, &c) != 1 || idx < 0 + || idx >= MaxChannels-Aux0) + { + ERR("AmbDec speaker label \"%s\" not recognized\n", speaker.Name.c_str()); + continue; + } + ch = static_cast(Aux0+idx); } decoder.mChannels[chan_count] = ch; - for(size_t src{0};src < num_coeffs;++src) + for(size_t dst{0};dst < num_coeffs;++dst) { - const size_t dst{idx_map[src]}; + const size_t src{idx_map[dst]}; decoder.mCoeffs[chan_count][dst] = hfmatrix[chan_count][src]; } if(conf->FreqBands > 1) { - for(size_t src{0};src < num_coeffs;++src) + for(size_t dst{0};dst < num_coeffs;++dst) { - const size_t dst{idx_map[src]}; + const size_t src{idx_map[dst]}; decoder.mCoeffsLF[chan_count][dst] = lfmatrix[chan_count][src]; } } @@ -438,13 +456,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; @@ -466,21 +484,21 @@ constexpr DecoderConfig StereoConfig{ }} }; constexpr DecoderConfig QuadConfig{ - 2, false, {{BackLeft, FrontLeft, FrontRight, BackRight}}, + 1, false, {{BackLeft, FrontLeft, FrontRight, BackRight}}, DevAmbiScaling::N3D, - /*HF*/{{1.15470054e+0f, 1.00000000e+0f, 5.77350269e-1f}}, + /*HF*/{{1.41421356e+0f, 1.00000000e+0f}}, {{ - {{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}}, - {{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}}, - {{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}}, - {{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}}, + {{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f}}, + {{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f}}, + {{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f}}, + {{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f}}, }}, - /*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f}}, {{ - {{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}}, - {{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}}, - {{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f, -1.29099445e-1f, 0.00000000e+0f}}, - {{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f, 1.29099445e-1f, 0.00000000e+0f}}, + {{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f}}, + {{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f}}, + {{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f}}, + {{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f}}, }} }; constexpr DecoderConfig X51Config{ @@ -516,32 +534,71 @@ constexpr DecoderConfig X61Config{ }} }; constexpr DecoderConfig X71Config{ - 3, false, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight}}, + 2, false, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight}}, DevAmbiScaling::N3D, - /*HF*/{{1.22474487e+0f, 1.13151672e+0f, 8.66025404e-1f, 4.68689571e-1f}}, + /*HF*/{{1.41421356e+0f, 1.22474487e+0f, 7.07106781e-1f}}, {{ - {{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}}, - {{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, -7.96819073e-2f, 0.00000000e+0f}}, - {{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}}, - {{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}}, - {{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, 7.96819073e-2f, 0.00000000e+0f}}, - {{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, + {{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, + {{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, }}, - /*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, {{ - {{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}}, - {{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, -7.96819073e-2f, 0.00000000e+0f}}, - {{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, 7.96819073e-2f, 0.00000000e+0f}}, - {{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}}, - {{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f, 7.96819073e-2f, 0.00000000e+0f}}, - {{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f, -7.96819073e-2f, 0.00000000e+0f}}, + {{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, + {{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, + {{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, + {{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, + }} +}; +constexpr DecoderConfig X3D71Config{ + 1, true, {{Aux0, SideLeft, FrontLeft, FrontRight, SideRight, Aux1}}, + DevAmbiScaling::N3D, + /*HF*/{{1.73205081e+0f, 1.00000000e+0f}}, + {{ + {{1.666666667e-01f, 0.000000000e+00f, 2.356640879e-01f, -1.667265410e-01f}}, + {{1.666666667e-01f, 2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, + {{1.666666667e-01f, 2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, + {{1.666666667e-01f, -2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, + {{1.666666667e-01f, -2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, + {{1.666666667e-01f, 0.000000000e+00f, -2.356640879e-01f, 1.667265410e-01f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{1.666666667e-01f, 0.000000000e+00f, 2.356640879e-01f, -1.667265410e-01f}}, + {{1.666666667e-01f, 2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, + {{1.666666667e-01f, 2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, + {{1.666666667e-01f, -2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, + {{1.666666667e-01f, -2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, + {{1.666666667e-01f, 0.000000000e+00f, -2.356640879e-01f, 1.667265410e-01f}}, + }} +}; +constexpr DecoderConfig X714Config{ + 1, true, {{FrontLeft, FrontRight, SideLeft, SideRight, BackLeft, BackRight, TopFrontLeft, TopFrontRight, TopBackLeft, TopBackRight }}, + DevAmbiScaling::N3D, + {{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{1.27149251e-01f, 7.63047539e-02f, -3.64373750e-02f, 1.59700680e-01f}}, + {{1.07005418e-01f, -7.67638760e-02f, -4.92129762e-02f, 1.29012797e-01f}}, + {{1.26400196e-01f, 1.77494694e-01f, -3.71203389e-02f, 0.00000000e+00f}}, + {{1.26396516e-01f, -1.77488059e-01f, -3.71297878e-02f, 0.00000000e+00f}}, + {{1.06996956e-01f, 7.67615256e-02f, -4.92166307e-02f, -1.29001640e-01f}}, + {{1.27145671e-01f, -7.63003471e-02f, -3.64353304e-02f, -1.59697510e-01f}}, + {{8.80919747e-02f, 7.48940670e-02f, 9.08786244e-02f, 6.22527183e-02f}}, + {{1.57880745e-01f, -7.28755272e-02f, 1.82364187e-01f, 8.74240284e-02f}}, + {{1.57892225e-01f, 7.28944768e-02f, 1.82363474e-01f, -8.74301086e-02f}}, + {{8.80892603e-02f, -7.48948724e-02f, 9.08779842e-02f, -6.22480443e-02f}}, }} }; void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false, DecoderView decoder={}) { - if(!decoder.mOrder) + if(!decoder) { switch(device->FmtChans) { @@ -551,74 +608,76 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= case DevFmtX51: decoder = X51Config; break; case DevFmtX61: decoder = X61Config; break; case DevFmtX71: decoder = X71Config; break; + case DevFmtX714: decoder = X714Config; 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; - float nfc_delay{device->configValue("decoder", "nfc-ref-delay").value_or(0.0f)}; - if(nfc_delay > 0.0f) - InitNearFieldCtrl(device, nfc_delay * SpeedOfSoundMetersPerSec, device->mAmbiOrder, - true); + float avg_dist{}; + if(auto distopt = device->configValue("decoder", "speaker-dist")) + avg_dist = *distopt; + else if(auto delayopt = device->configValue("decoder", "nfc-ref-delay")) + { + WARN("nfc-ref-delay is deprecated, use speaker-dist instead\n"); + avg_dist = *delayopt * SpeedOfSoundMetersPerSec; + } + + InitNearFieldCtrl(device, avg_dist, device->mAmbiOrder, true); return; } } + 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{GetChannelIdxByName(device->RealOut, decoder.mChannels[i])}; - if(idx == INVALID_CHANNEL_INDEX) + const size_t idx{device->channelIdxByName(decoder.mChannels[i])}; + if(idx == InvalidChannelIndex) { ERR("Failed to find %s channel in device\n", GetLabelFromChannel(decoder.mChannels[i])); continue; } - chancoeffs.resize(maxz(chancoeffs.size(), idx+1u), ChannelDec{}); - al::span coeffs{chancoeffs[idx]}; - size_t ambichan{0}; - for(uint o{0};o < decoder.mOrder+1u;++o) - { - const float order_gain{decoder.mOrderGain[o]}; - const size_t order_max{decoder.mIs3D ? AmbiChannelsFromOrder(o) : - Ambi2DChannelsFromOrder(o)}; - for(;ambichan < order_max;++ambichan) - coeffs[ambichan] = decoder.mCoeffs[i][ambichan] * order_gain; - } + auto ordermap = decoder.mIs3D ? al::span{AmbiIndex::OrderFromChannel} + : al::span{AmbiIndex::OrderFrom2DChannel}; + + 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) + dst[ambichan] = src[ambichan] * decoder.mOrderGain[ordermap[ambichan]]; + if(!dual_band) continue; - chancoeffslf.resize(maxz(chancoeffslf.size(), idx+1u), ChannelDec{}); - coeffs = chancoeffslf[idx]; - ambichan = 0; - for(uint o{0};o < decoder.mOrder+1u;++o) - { - const float order_gain{decoder.mOrderGainLF[o]}; - const size_t order_max{decoder.mIs3D ? AmbiChannelsFromOrder(o) : - Ambi2DChannelsFromOrder(o)}; - for(;ambichan < order_max;++ambichan) - coeffs[ambichan] = decoder.mCoeffsLF[i][ambichan] * order_gain; - } + 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) + dst[ambichan] = src[ambichan] * decoder.mOrderGainLF[ordermap[ambichan]]; } /* For non-DevFmtAmbi3D, set the ambisonic order. */ device->mAmbiOrder = decoder.mOrder; + device->m2DMixing = !decoder.mIs3D; - const size_t ambicount{decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) : - Ambi2DChannelsFromOrder(decoder.mOrder)}; - 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 al::span acnmap{decoder.mIs3D ? AmbiIndex::FromACN.data() : + AmbiIndex::FromACN2D.data(), 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()); @@ -628,7 +687,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()) { @@ -649,6 +708,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= TRACE("Enabling %s-band %s-order%s ambisonic decoder\n", !dual_band ? "single" : "dual", + (decoder.mOrder > 3) ? "fourth" : (decoder.mOrder > 2) ? "third" : (decoder.mOrder > 1) ? "second" : "first", decoder.mIs3D ? " periphonic" : ""); @@ -658,121 +718,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_35{6.154797087e-01f /* 35~ 36 degrees*/}; - constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/}; - constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/}; - constexpr float Deg_21{3.648638281e-01f /* 20~ 21 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{ 0.0f}, AzRadians{ 0.0f} }, - { EvRadians{ 0.0f}, AzRadians{ Deg180} }, - { EvRadians{ 0.0f}, AzRadians{-Deg_90} }, - { EvRadians{ 0.0f}, AzRadians{ Deg_90} }, - { EvRadians{ Deg_90}, AzRadians{ 0.0f} }, - { EvRadians{-Deg_90}, AzRadians{ 0.0f} }, - { 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} }, - }, 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]{ - { 7.142857143e-02f, 0.000000000e+00f, 0.000000000e+00f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, 1.290994449e-01f, }, - { 7.142857143e-02f, 0.000000000e+00f, 0.000000000e+00f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, 1.290994449e-01f, }, - { 7.142857143e-02f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, -1.290994449e-01f, }, - { 7.142857143e-02f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, -1.290994449e-01f, }, - { 7.142857143e-02f, 0.000000000e+00f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 1.490711985e-01f, 0.000000000e+00f, 0.000000000e+00f, }, - { 7.142857143e-02f, 0.000000000e+00f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 1.490711985e-01f, 0.000000000e+00f, 0.000000000e+00f, }, - { 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, 9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, - { 7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, -9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, - { 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, 7.142857143e-02f, -9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, - { 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, 9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, - { 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, 9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, - { 7.142857143e-02f, 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, -9.682458366e-02f, -9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, - { 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, -9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }, - { 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, 9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }, - }, 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]{ - /*ENRGY 2.357022604e+00f, 1.825741858e+00f, 9.428090416e-01f*/ + }; + 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]{ + /*RMS 9.128709292e-01f, 7.071067812e-01f, 3.651483717e-01f*/ + }; + 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 + /*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 @@ -792,32 +857,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; @@ -825,18 +890,16 @@ 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()); - al::span AmbiPoints{AmbiPoints1O}; - const float (*AmbiMatrix)[MaxAmbiChannels]{AmbiMatrix1O}; - al::span AmbiOrderHFGain{AmbiOrderHFGain1O}; + bool perHrirMin{false}; + 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; AmbiPoints = AmbiPoints3O; AmbiMatrix = AmbiMatrix3O; AmbiOrderHFGain = AmbiOrderHFGain3O; @@ -848,40 +911,41 @@ void InitHrtfPanning(ALCdevice *device) AmbiOrderHFGain = AmbiOrderHFGain2O; } device->mAmbiOrder = ambi_order; + 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()}; auto hrtfstate = DirectHrtfState::Create(count); - hrtfstate->build(Hrtf, device->mIrSize, AmbiPoints, AmbiMatrix, device->mXOverFreq, + hrtfstate->build(Hrtf, device->mIrSize, perHrirMin, AmbiPoints, AmbiMatrix, device->mXOverFreq, AmbiOrderHFGain); device->mHrtfState = std::move(hrtfstate); - InitNearFieldCtrl(device, Hrtf->field[0].distance, ambi_order, true); + InitNearFieldCtrl(device, Hrtf->mFields[0].distance, ambi_order, true); } 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::FromFuMa().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)}; @@ -891,6 +955,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalmIrSize = 0; device->mHrtfName.clear(); device->mXOverFreq = 400.0f; + device->m2DMixing = false; device->mRenderMode = RenderMode::Normal; if(device->FmtChans != DevFmtStereo) @@ -906,6 +971,8 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional> decoder_store; + std::unique_ptr> decoder_store; DecoderView decoder{}; - float speakerdists[MaxChannels]{}; + std::array speakerdists{}; auto load_config = [device,&decoder_store,&decoder,&speakerdists](const char *config) { AmbDecConf conf{}; @@ -923,39 +990,52 @@ 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{conf.Speakers.cbegin(), 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. */ - const bool stablize{device->RealOut.ChannelIndex[FrontCenter] != INVALID_CHANNEL_INDEX - && device->RealOut.ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX - && device->RealOut.ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX - && device->getConfigValueBool(nullptr, "front-stablizer", 0) != 0}; - const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", 1) != 0}; + const bool stablize{device->RealOut.ChannelIndex[FrontCenter] != InvalidChannelIndex + && device->RealOut.ChannelIndex[FrontLeft] != InvalidChannelIndex + && device->RealOut.ChannelIndex[FrontRight] != InvalidChannelIndex + && device->getConfigValueBool({}, "front-stablizer", false)}; + const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", true)}; InitPanning(device, hqdec, stablize, decoder); - if(decoder.mOrder > 0) + if(decoder) { float accum_dist{0.0f}, spkr_count{0.0f}; for(auto dist : speakerdists) @@ -966,11 +1046,13 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional 0.0f && spkr_count > 0) ? accum_dist/spkr_count : + device->configValue("decoder", "speaker-dist").value_or(1.0f)}; + InitNearFieldCtrl(device, avg_dist, decoder.mOrder, decoder.mIs3D); + if(spkr_count > 0) - { - InitNearFieldCtrl(device, accum_dist / spkr_count, decoder.mOrder, decoder.mIs3D); InitDistanceComp(device, decoder.mChannels, speakerdists); - } } if(auto *ambidec{device->AmbiDecoder.get()}) { @@ -992,7 +1074,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); @@ -1002,7 +1084,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)}) { @@ -1018,11 +1100,11 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalmHrtf.get()}; - device->mIrSize = hrtf->irSize; - if(auto hrtfsizeopt = device->configValue(nullptr, "hrtf-size")) + device->mIrSize = hrtf->mIrSize; + 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); @@ -1035,7 +1117,20 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalmUhjEncoder = std::make_unique(); + switch(UhjEncodeQuality) + { + case UhjQualityType::IIR: + device->mUhjEncoder = std::make_unique(); + break; + case UhjQualityType::FIR256: + device->mUhjEncoder = std::make_unique>(); + break; + case UhjQualityType::FIR512: + device->mUhjEncoder = std::make_unique>(); + break; + } + assert(device->mUhjEncoder != nullptr); + TRACE("UHJ enabled\n"); InitUhjPanning(device); device->PostProcess = &ALCdevice::ProcessUhj; @@ -1045,13 +1140,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; @@ -1071,49 +1166,11 @@ void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context) DeviceBase *device{context->mDevice}; const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; - auto wetbuffer_iter = context->mWetBuffers.end(); - if(slot->mWetBuffer) - { - /* If the effect slot already has a wet buffer attached, allocate a new - * one in its place. - */ - wetbuffer_iter = context->mWetBuffers.begin(); - for(;wetbuffer_iter != context->mWetBuffers.end();++wetbuffer_iter) - { - if(wetbuffer_iter->get() == slot->mWetBuffer) - { - slot->mWetBuffer = nullptr; - slot->Wet.Buffer = {}; + slot->mWetBuffer.resize(count); - *wetbuffer_iter = WetBufferPtr{new(FamCount(count)) WetBuffer{count}}; - - break; - } - } - } - if(wetbuffer_iter == context->mWetBuffers.end()) - { - /* Otherwise, search for an unused wet buffer. */ - wetbuffer_iter = context->mWetBuffers.begin(); - for(;wetbuffer_iter != context->mWetBuffers.end();++wetbuffer_iter) - { - if(!(*wetbuffer_iter)->mInUse) - break; - } - if(wetbuffer_iter == context->mWetBuffers.end()) - { - /* Otherwise, allocate a new one to use. */ - context->mWetBuffers.emplace_back(WetBufferPtr{new(FamCount(count)) WetBuffer{count}}); - wetbuffer_iter = context->mWetBuffers.end()-1; - } - } - WetBuffer *wetbuffer{slot->mWetBuffer = wetbuffer_iter->get()}; - wetbuffer->mInUse = true; - - 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 = wetbuffer->mBuffer; + slot->Wet.Buffer = slot->mWetBuffer; } diff --git a/libs/openal-soft/alsoftrc.sample b/libs/openal-soft/alsoftrc.sample index 73338995..3ca93796 100644 --- a/libs/openal-soft/alsoftrc.sample +++ b/libs/openal-soft/alsoftrc.sample @@ -53,17 +53,19 @@ #drivers = ## channels: -# Sets the output channel configuration. If left unspecified, one will try to -# be detected from the system, and defaulting to stereo. The available values -# are: mono, stereo, quad, surround51, surround61, surround71, ambi1, ambi2, -# ambi3. Note that the ambi* configurations provide ambisonic channels of the -# given order (using ACN ordering and SN3D normalization by default), which -# need to be decoded to play correctly on speakers. +# 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, +# 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: -# Sets the output sample type. Currently, all mixing is done with 32-bit float -# and converted to the output sample type as needed. Available values are: +# Sets the default output sample type. Currently, all mixing is done with +# 32-bit float and converted to the output sample type as needed. Available +# values are: # int8 - signed 8-bit int # uint8 - unsigned 8-bit int # int16 - signed 16-bit int @@ -74,8 +76,8 @@ #sample-type = float32 ## frequency: -# Sets the output frequency. If left unspecified it will try to detect a -# default from the system, otherwise it will default to 44100. +# Sets the default output frequency. If left unspecified it will try to detect +# a default from the system, otherwise it will fallback to 48000. #frequency = ## period_size: @@ -99,13 +101,16 @@ #stereo-mode = auto ## stereo-encoding: -# Specifies the encoding method for non-HRTF stereo output. 'panpot' (default) -# uses standard amplitude panning (aka pair-wise, stereo pair, etc) between -# -30 and +30 degrees, while 'uhj' creates stereo-compatible two-channel UHJ -# output, which encodes some surround sound information into stereo output -# that can be decoded with a surround sound receiver. If crossfeed filters are -# used, UHJ is disabled. -#stereo-encoding = panpot +# Specifies the default encoding method for stereo output. Valid values are: +# basic - Standard amplitude panning (aka pair-wise, stereo pair, etc) between +# -30 and +30 degrees. +# uhj - Creates a stereo-compatible two-channel UHJ mix, which encodes some +# surround sound information into stereo output that can be decoded with +# a surround sound receiver. +# hrtf - Uses filters to provide better spatialization of sounds while using +# stereo headphones. +# If crossfeed filters are used, basic stereo mixing is used. +#stereo-encoding = basic ## ambi-format: # Specifies the channel order and normalization for the "ambi*" set of channel @@ -114,12 +119,8 @@ #ambi-format = ambix ## hrtf: -# Controls HRTF processing. These filters provide better spatialization of -# sounds while using headphones, but do require a bit more CPU power. While -# HRTF is used, the cf_level option is ignored. Setting this to auto (default) -# will allow HRTF to be used when headphones are detected or the app requests -# it, while setting true or false will forcefully enable or disable HRTF -# respectively. +# Deprecated. Consider using stereo-encoding instead. Valid values are auto, +# off, and on. #hrtf = auto ## hrtf-mode: @@ -139,7 +140,7 @@ # Specifies the impulse response size, in samples, for the HRTF filter. Larger # values increase the filter quality, while smaller values reduce processing # cost. A value of 0 (default) uses the full filter size in the dataset, and -# the default dataset has a filter size of 32 samples at 44.1khz. +# the default dataset has a filter size of 64 samples at 48khz. #hrtf-size = 0 ## default-hrtf: @@ -178,7 +179,7 @@ # 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 +# cubic - 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- @@ -187,7 +188,7 @@ # between 24 and 48 points, with anti-aliasing) # fast_bsinc24 - same as bsinc24, except without interpolation between down- # sampling scales -#resampler = linear +#resampler = cubic ## rt-prio: (global) # Sets the real-time priority value for the mixing thread. Not all drivers may @@ -265,9 +266,9 @@ ## default-reverb: (global) # A reverb preset that applies by default to all sources on send 0 # (applications that set their own slots on send 0 will override this). -# Available presets are: None, Generic, PaddedCell, Room, Bathroom, +# Available presets include: None, Generic, PaddedCell, Room, Bathroom, # Livingroom, Stoneroom, Auditorium, ConcertHall, Cave, Arena, Hangar, -# CarpetedHallway, Hallway, StoneCorridor, Alley, Forest, City, Moutains, +# CarpetedHallway, Hallway, StoneCorridor, Alley, Forest, City, Mountains, # Quarry, Plain, ParkingLot, SewerPipe, Underwater, Drugged, Dizzy, Psychotic. #default-reverb = @@ -305,22 +306,22 @@ ## nfc: # Enables near-field control filters. This simulates and compensates for low- # frequency effects caused by the curvature of nearby sound-waves, which -# creates a more realistic perception of sound distance. Note that the effect -# may be stronger or weaker than intended if the application doesn't use or -# specify an appropriate unit scale, or if incorrect speaker distances are set -# in the decoder configuration file. +# creates a more realistic perception of sound distance with surround sound +# output. Note that the effect may be stronger or weaker than intended if the +# application doesn't use or specify an appropriate unit scale, or if +# incorrect speaker distances are set. For HRTF output, hrtf-mode must be set +# to one of the ambi* values for this to function. #nfc = false -## nfc-ref-delay -# Specifies the reference delay value for ambisonic output when NFC filters -# are enabled. If channels is set to one of the ambi* formats, this option -# enables NFC-HOA output with the specified Reference Delay parameter. The -# specified value can then be shared with an appropriate NFC-HOA decoder to -# reproduce correct near-field effects. Keep in mind that despite being -# designed for higher-order ambisonics, this also applies to first-order -# output. When left unset, normal output is created with no near-field -# simulation. Requires the nfc option to also be enabled. -#nfc-ref-delay = +## speaker-dist: +# Specifies the speaker distance in meters, used by the near-field control +# filters with surround sound output. For ambisonic output modes, this value +# is the basis for the NFC-HOA Reference Delay parameter (calculated as +# delay_seconds = speaker_dist/343.3). This value is not used when a decoder +# configuration is set for the output mode (since they specify the per-speaker +# distances, overriding this setting), or when the NFC filters are off. Valid +# values range from 0.1 to 10. +#speaker-dist = 1 ## quad: # Decoder configuration file for Quadraphonic channel output. See @@ -339,11 +340,41 @@ ## surround71: # Decoder configuration file for 7.1 Surround channel output. See -# docs/ambdec.txt for a description of the file format. Note: This can be used -# to enable 3D7.1 with the appropriate configuration and speaker placement, -# see docs/3D7.1.txt. +# 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 +# docs/3D7.1.txt for information about 3D7.1. +#surround3d71 = + +## +## UHJ and Super Stereo stuff +## +[uhj] + +## decode-filter: (global) +# Specifies the all-pass filter type for UHJ decoding and Super Stereo +# processing. Valid values are: +# iir - utilizes dual IIR filters, providing a wide pass-band with low CPU +# use, but causes additional phase shifts on the signal. +# fir256 - utilizes a 256-point FIR filter, providing more stable results but +# exhibiting attenuation in the lower and higher frequency bands. +# fir512 - utilizes a 512-point FIR filter, providing a wider pass-band than +# fir256, at the cost of more CPU use. +#decode-filter = iir + +## encode-filter: (global) +# Specifies the all-pass filter type for UHJ output encoding. Valid values are +# the same as for decode-filter. +#encode-filter = iir + ## ## Reverb effect stuff (includes EAX reverb) ## @@ -371,6 +402,12 @@ # support even when no nodes may be reported at initialization time. #assume-audio = false +## rt-mix: +# 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 = false + ## ## PulseAudio backend stuff ## @@ -380,7 +417,7 @@ # Attempts to autospawn a PulseAudio server whenever needed (initializing the # backend, enumerating devices, etc). Setting autospawn to false in Pulse's # client.conf will still prevent autospawning even if this is set to true. -#spawn-server = true +#spawn-server = false ## allow-moves: (global) # Allows PulseAudio to move active streams to different devices. Note that the @@ -542,6 +579,20 @@ ## [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 +# than the actual output can accept, causing the backend to apply its own +# resampling pass after OpenAL Soft mixes the sources and processes effects +# for output. +#allow-resampler = true + ## ## DirectSound backend stuff ## @@ -598,6 +649,30 @@ ## [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 +# or too-distant effect. For instance, if the game uses 1 foot per unit, a +# value of 0.3048 will correctly adjust the filters. Or if the game uses 1 +# kilometer per unit, a value of 1000 will correctly adjust the filters. +#nfc-scale = 1 + +## enable-sub-data-ext: (global) +# Enables the AL_SOFT_buffer_sub_data extension, disabling the +# AL_EXT_SOURCE_RADIUS extension. These extensions are incompatible, so only +# one can be available. The latter extension is more commonly used, but this +# option can be enabled for older apps that want the former extension. +#enable-sub-data-ext = false + ## reverse-x: (global) # Reverses the local X (left-right) position of 3D sound sources. #reverse-x = false diff --git a/libs/openal-soft/appveyor.yml b/libs/openal-soft/appveyor.yml index c468d4e9..e86f7a30 100644 --- a/libs/openal-soft/appveyor.yml +++ b/libs/openal-soft/appveyor.yml @@ -1,8 +1,8 @@ -version: 1.22.0.{build} +version: 1.23.1.{build} environment: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - GEN: "Visual Studio 15 2017" + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + GEN: "Visual Studio 16 2019" matrix: - ARCH: Win32 CFG: Release diff --git a/libs/openal-soft/cmake/FindFFmpeg.cmake b/libs/openal-soft/cmake/FindFFmpeg.cmake index 60ca68fd..26ed4d2f 100644 --- a/libs/openal-soft/cmake/FindFFmpeg.cmake +++ b/libs/openal-soft/cmake/FindFFmpeg.cmake @@ -80,22 +80,8 @@ macro(find_component _component _pkgconfig _library _header) ${PC_LIB${_component}_LIBRARY_DIRS} ) - STRING(REGEX REPLACE "/.*" "/version.h" _ver_header ${_header}) - if(EXISTS "${${_component}_INCLUDE_DIRS}/${_ver_header}") - file(STRINGS "${${_component}_INCLUDE_DIRS}/${_ver_header}" version_str REGEX "^#define[\t ]+LIB${_component}_VERSION_M.*") - - string(REGEX REPLACE "^.*LIB${_component}_VERSION_MAJOR[\t ]+([0-9]*).*$" "\\1" version_maj "${version_str}") - string(REGEX REPLACE "^.*LIB${_component}_VERSION_MINOR[\t ]+([0-9]*).*$" "\\1" version_min "${version_str}") - string(REGEX REPLACE "^.*LIB${_component}_VERSION_MICRO[\t ]+([0-9]*).*$" "\\1" version_mic "${version_str}") - unset(version_str) - - set(${_component}_VERSION "${version_maj}.${version_min}.${version_mic}" CACHE STRING "The ${_component} version number.") - unset(version_maj) - unset(version_min) - unset(version_mic) - endif(EXISTS "${${_component}_INCLUDE_DIRS}/${_ver_header}") - set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") - set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") + set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number." FORCE) + set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS." FORCE) set_component_found(${_component}) diff --git a/libs/openal-soft/cmake/FindOpenSL.cmake b/libs/openal-soft/cmake/FindOpenSL.cmake index 00428749..3df54d44 100644 --- a/libs/openal-soft/cmake/FindOpenSL.cmake +++ b/libs/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/libs/openal-soft/cmake/FindPulseAudio.cmake b/libs/openal-soft/cmake/FindPulseAudio.cmake index 1f6f843a..fdcbc20f 100644 --- a/libs/openal-soft/cmake/FindPulseAudio.cmake +++ b/libs/openal-soft/cmake/FindPulseAudio.cmake @@ -2,8 +2,6 @@ # # PULSEAUDIO_FOUND - True if PULSEAUDIO_INCLUDE_DIR & # PULSEAUDIO_LIBRARY are found -# PULSEAUDIO_LIBRARIES - Set when PULSEAUDIO_LIBRARY is found -# PULSEAUDIO_INCLUDE_DIRS - Set when PULSEAUDIO_INCLUDE_DIR is found # # PULSEAUDIO_INCLUDE_DIR - where to find pulse/pulseaudio.h, etc. # PULSEAUDIO_LIBRARY - the pulse library @@ -34,10 +32,3 @@ find_package_handle_standard_args(PulseAudio REQUIRED_VARS PULSEAUDIO_LIBRARY PULSEAUDIO_INCLUDE_DIR VERSION_VAR PULSEAUDIO_VERSION_STRING ) - -if(PULSEAUDIO_FOUND) - set(PULSEAUDIO_LIBRARIES ${PULSEAUDIO_LIBRARY}) - set(PULSEAUDIO_INCLUDE_DIRS ${PULSEAUDIO_INCLUDE_DIR}) -endif() - -mark_as_advanced(PULSEAUDIO_INCLUDE_DIR PULSEAUDIO_LIBRARY) diff --git a/libs/openal-soft/cmake/FindSDL2.cmake b/libs/openal-soft/cmake/FindSDL2.cmake deleted file mode 100644 index e808d006..00000000 --- a/libs/openal-soft/cmake/FindSDL2.cmake +++ /dev/null @@ -1,191 +0,0 @@ -# Locate SDL2 library -# This module defines -# SDL2_LIBRARY, the name of the library to link against -# SDL2_FOUND, if false, do not try to link to SDL2 -# SDL2_INCLUDE_DIR, where to find SDL.h -# -# This module responds to the the flag: -# SDL2_BUILDING_LIBRARY -# If this is defined, then no SDL2_main will be linked in because -# only applications need main(). -# Otherwise, it is assumed you are building an application and this -# module will attempt to locate and set the the proper link flags -# as part of the returned SDL2_LIBRARY variable. -# -# Don't forget to include SDL2main.h and SDL2main.m your project for the -# OS X framework based version. (Other versions link to -lSDL2main which -# this module will try to find on your behalf.) Also for OS X, this -# module will automatically add the -framework Cocoa on your behalf. -# -# -# Additional Note: If you see an empty SDL2_CORE_LIBRARY in your configuration -# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library -# (SDL2.dll, libsdl2.so, SDL2.framework, etc). -# Set SDL2_CORE_LIBRARY to point to your SDL2 library, and configure again. -# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value -# as appropriate. These values are used to generate the final SDL2_LIBRARY -# variable, but when these values are unset, SDL2_LIBRARY does not get created. -# -# -# $SDL2DIR is an environment variable that would -# correspond to the ./configure --prefix=$SDL2DIR -# used in building SDL2. -# l.e.galup 9-20-02 -# -# Modified by Eric Wing. -# Added code to assist with automated building by using environmental variables -# and providing a more controlled/consistent search behavior. -# Added new modifications to recognize OS X frameworks and -# additional Unix paths (FreeBSD, etc). -# Also corrected the header search path to follow "proper" SDL2 guidelines. -# Added a search for SDL2main which is needed by some platforms. -# Added a search for threads which is needed by some platforms. -# Added needed compile switches for MinGW. -# -# On OSX, this will prefer the Framework version (if found) over others. -# People will have to manually change the cache values of -# SDL2_LIBRARY to override this selection or set the CMake environment -# CMAKE_INCLUDE_PATH to modify the search paths. -# -# Note that the header path has changed from SDL2/SDL.h to just SDL.h -# This needed to change because "proper" SDL2 convention -# is #include "SDL.h", not . This is done for portability -# reasons because not all systems place things in SDL2/ (see FreeBSD). -# -# Ported by Johnny Patterson. This is a literal port for SDL2 of the FindSDL.cmake -# module with the minor edit of changing "SDL" to "SDL2" where necessary. This -# was not created for redistribution, and exists temporarily pending official -# SDL2 CMake modules. - -#============================================================================= -# Copyright 2003-2009 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - - -FIND_PATH(SDL2_INCLUDE_DIR SDL.h - HINTS - $ENV{SDL2DIR} - PATH_SUFFIXES include/SDL2 include - PATHS - ~/Library/Frameworks - /Library/Frameworks - /usr/local/include/SDL2 - /usr/include/SDL2 - /sw # Fink - /opt/local # DarwinPorts - /opt/csw # Blastwave - /opt -) -#MESSAGE("SDL2_INCLUDE_DIR is ${SDL2_INCLUDE_DIR}") - -FIND_LIBRARY(SDL2_CORE_LIBRARY - NAMES SDL2 - HINTS - $ENV{SDL2DIR} - PATH_SUFFIXES lib64 lib - PATHS - /sw - /opt/local - /opt/csw - /opt -) -#MESSAGE("SDL2_CORE_LIBRARY is ${SDL2_CORE_LIBRARY}") - -IF(NOT SDL2_BUILDING_LIBRARY) - IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") - # Non-OS X framework versions expect you to also dynamically link to - # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms - # seem to provide SDL2main for compatibility even though they don't - # necessarily need it. - FIND_LIBRARY(SDL2MAIN_LIBRARY - NAMES SDL2main - HINTS - $ENV{SDL2DIR} - PATH_SUFFIXES lib64 lib - PATHS - /sw - /opt/local - /opt/csw - /opt - ) - ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") -ENDIF(NOT SDL2_BUILDING_LIBRARY) - -# SDL2 may require threads on your system. -# The Apple build may not need an explicit flag because one of the -# frameworks may already provide it. -# But for non-OSX systems, I will use the CMake Threads package. -IF(NOT APPLE) - FIND_PACKAGE(Threads) -ENDIF(NOT APPLE) - -# MinGW needs an additional library, mwindows -# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows -# (Actually on second look, I think it only needs one of the m* libraries.) -IF(MINGW) - SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") -ENDIF(MINGW) - -SET(SDL2_FOUND "NO") -IF(SDL2_CORE_LIBRARY) - SET(SDL2_LIBRARY_TEMP ${SDL2_CORE_LIBRARY}) - - # For SDL2main - IF(NOT SDL2_BUILDING_LIBRARY) - IF(SDL2MAIN_LIBRARY) - SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP}) - ENDIF(SDL2MAIN_LIBRARY) - ENDIF(NOT SDL2_BUILDING_LIBRARY) - - # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. - # CMake doesn't display the -framework Cocoa string in the UI even - # though it actually is there if I modify a pre-used variable. - # I think it has something to do with the CACHE STRING. - # So I use a temporary variable until the end so I can set the - # "real" variable in one-shot. - IF(APPLE) - SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") - ENDIF(APPLE) - - # For threads, as mentioned Apple doesn't need this. - # In fact, there seems to be a problem if I used the Threads package - # and try using this line, so I'm just skipping it entirely for OS X. - IF(NOT APPLE) - SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) - ENDIF(NOT APPLE) - - # For MinGW library - IF(MINGW) - SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) - ENDIF(MINGW) - - IF(WIN32) - SET(SDL2_LIBRARY_TEMP winmm imm32 version msimg32 ${SDL2_LIBRARY_TEMP}) - ENDIF(WIN32) - - # Set the final string here so the GUI reflects the final state. - SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP}) - - SET(SDL2_FOUND "YES") -ENDIF(SDL2_CORE_LIBRARY) - -INCLUDE(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 - REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR) - -IF(SDL2_STATIC) - if (UNIX AND NOT APPLE) - EXECUTE_PROCESS(COMMAND sdl2-config --static-libs OUTPUT_VARIABLE SDL2_LINK_FLAGS) - STRING(REGEX REPLACE "(\r?\n)+$" "" SDL2_LINK_FLAGS "${SDL2_LINK_FLAGS}") - SET(SDL2_LIBRARY ${SDL2_LINK_FLAGS}) - ENDIF() -ENDIF(SDL2_STATIC) diff --git a/libs/openal-soft/cmake/bin2h.script.cmake b/libs/openal-soft/cmake/bin2h.script.cmake index 1438fde2..7e74a7a1 100644 --- a/libs/openal-soft/cmake/bin2h.script.cmake +++ b/libs/openal-soft/cmake/bin2h.script.cmake @@ -8,6 +8,5 @@ file(READ "${INPUT_FILE}" indata HEX) # per line. string(REGEX REPLACE "(..)" " 0x\\1,\n" output "${indata}") -# Write the list of hex chars to the output file in a const byte array -file(WRITE "${OUTPUT_FILE}" - "const unsigned char ${VARIABLE_NAME}[] = {\n${output}};\n") +# Write the list of hex chars to the output file +file(WRITE "${OUTPUT_FILE}" "${output}") diff --git a/libs/openal-soft/common/albit.h b/libs/openal-soft/common/albit.h index ad596208..98544fa5 100644 --- a/libs/openal-soft/common/albit.h +++ b/libs/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/libs/openal-soft/common/albyte.h b/libs/openal-soft/common/albyte.h deleted file mode 100644 index be586869..00000000 --- a/libs/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/libs/openal-soft/common/alcomplex.cpp b/libs/openal-soft/common/alcomplex.cpp index 126e2c04..954c5fad 100644 --- a/libs/openal-soft/common/alcomplex.cpp +++ b/libs/openal-soft/common/alcomplex.cpp @@ -4,9 +4,11 @@ #include "alcomplex.h" #include +#include #include -#include #include +#include +#include #include #include "albit.h" @@ -19,66 +21,65 @@ namespace { using ushort = unsigned short; using ushort2 = std::pair; +using complex_d = std::complex; -/* Because std::array doesn't have constexpr non-const accessors in C++14. */ -template -struct our_array { - T mData[N]; -}; - -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 -constexpr auto GetBitReverser() noexcept -{ + +template +struct BitReverser { static_assert(N <= sizeof(ushort)*8, "Too many bits for the bit-reversal table."); - our_array ret{}; - const size_t fftsize{1u << N}; - size_t ret_i{0}; + std::array mData{}; - /* Bit-reversal permutation applied to a sequence of fftsize items. */ - for(size_t idx{1u};idx < fftsize-1;++idx) + constexpr BitReverser() { - size_t revidx{0u}, imask{idx}; - for(size_t i{0};i < N;++i) - { - revidx = (revidx<<1) | (imask&1); - imask >>= 1; - } + const std::size_t fftsize{1u << N}; + std::size_t ret_i{0}; - if(idx < revidx) + /* Bit-reversal permutation applied to a sequence of fftsize items. */ + for(std::size_t idx{1u};idx < fftsize-1;++idx) { - ret.mData[ret_i].first = static_cast(idx); - ret.mData[ret_i].second = static_cast(revidx); - ++ret_i; + 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) + { + mData[ret_i].first = static_cast(idx); + mData[ret_i].second = static_cast(revidx); + ++ret_i; + } } + assert(ret_i == std::size(mData)); } - assert(ret_i == al::size(ret.mData)); - return ret; -} +}; -/* 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 auto BitReverser2 = GetBitReverser<2>(); -constexpr auto BitReverser3 = GetBitReverser<3>(); -constexpr auto BitReverser4 = GetBitReverser<4>(); -constexpr auto BitReverser5 = GetBitReverser<5>(); -constexpr auto BitReverser6 = GetBitReverser<6>(); -constexpr auto BitReverser7 = GetBitReverser<7>(); -constexpr auto BitReverser8 = GetBitReverser<8>(); -constexpr auto BitReverser9 = GetBitReverser<9>(); -constexpr auto BitReverser10 = GetBitReverser<10>(); -constexpr al::span gBitReverses[11]{ +constexpr BitReverser<2> BitReverser2{}; +constexpr BitReverser<3> BitReverser3{}; +constexpr BitReverser<4> BitReverser4{}; +constexpr BitReverser<5> BitReverser5{}; +constexpr BitReverser<6> BitReverser6{}; +constexpr BitReverser<7> BitReverser7{}; +constexpr BitReverser<8> BitReverser8{}; +constexpr BitReverser<9> BitReverser9{}; +constexpr BitReverser<10> BitReverser10{}; +constexpr BitReverser<11> BitReverser11{}; +constexpr std::array,12> gBitReverses{{ {}, {}, BitReverser2.mData, BitReverser3.mData, @@ -88,61 +89,114 @@ constexpr al::span gBitReverses[11]{ 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 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(unlikely(log2_size >= al::size(gBitReverses))) + 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 double pi{al::numbers::pi * sign}; - size_t step2{1u}; - for(size_t i{0};i < log2_size;++i) - { - const double 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; + } + } } } @@ -152,12 +206,11 @@ void complex_hilbert(const al::span> 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, - [inverse_size](const std::complex &c) -> std::complex - { return c * (2.0*inverse_size); }); + [scale=inverse_size*2.0](std::complex d){ return d * scale; }); *bufiter *= inverse_size; ++bufiter; std::fill(bufiter, buffer.end(), std::complex{}); diff --git a/libs/openal-soft/common/alcomplex.h b/libs/openal-soft/common/alcomplex.h index 23b8114a..9f12cef3 100644 --- a/libs/openal-soft/common/alcomplex.h +++ b/libs/openal-soft/common/alcomplex.h @@ -24,7 +24,7 @@ inline void forward_fft(const al::span> buffer) * provided buffer, which MUST BE power of two. */ inline void inverse_fft(const al::span> buffer) -{ complex_fft(buffer, 1.0); } +{ complex_fft(buffer, +1.0); } /** * Calculate the complex helical sequence (discrete-time analytical signal) of diff --git a/libs/openal-soft/common/aldeque.h b/libs/openal-soft/common/aldeque.h deleted file mode 100644 index 3f99bf00..00000000 --- a/libs/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/libs/openal-soft/common/alfstream.cpp b/libs/openal-soft/common/alfstream.cpp deleted file mode 100644 index 3beda833..00000000 --- a/libs/openal-soft/common/alfstream.cpp +++ /dev/null @@ -1,150 +0,0 @@ - -#include "config.h" - -#include "alfstream.h" - -#include "strutils.h" - -#ifdef _WIN32 - -namespace al { - -auto filebuf::underflow() -> int_type -{ - if(mFile != INVALID_HANDLE_VALUE && gptr() == egptr()) - { - // Read in the next chunk of data, and set the pointers on success - DWORD got{}; - if(ReadFile(mFile, mBuffer.data(), static_cast(mBuffer.size()), &got, nullptr)) - setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got); - } - if(gptr() == egptr()) - return traits_type::eof(); - return traits_type::to_int_type(*gptr()); -} - -auto filebuf::seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) -> pos_type -{ - if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - LARGE_INTEGER fpos{}; - switch(whence) - { - case std::ios_base::beg: - fpos.QuadPart = offset; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN)) - return traits_type::eof(); - break; - - case std::ios_base::cur: - // If the offset remains in the current buffer range, just - // update the pointer. - if((offset >= 0 && offset < off_type(egptr()-gptr())) || - (offset < 0 && -offset <= off_type(gptr()-eback()))) - { - // Get the current file offset to report the correct read - // offset. - fpos.QuadPart = 0; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT)) - return traits_type::eof(); - setg(eback(), gptr()+offset, egptr()); - return fpos.QuadPart - off_type(egptr()-gptr()); - } - // Need to offset for the file offset being at egptr() while - // the requested offset is relative to gptr(). - offset -= off_type(egptr()-gptr()); - fpos.QuadPart = offset; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT)) - return traits_type::eof(); - break; - - case std::ios_base::end: - fpos.QuadPart = offset; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_END)) - return traits_type::eof(); - break; - - default: - return traits_type::eof(); - } - setg(nullptr, nullptr, nullptr); - return fpos.QuadPart; -} - -auto filebuf::seekpos(pos_type pos, std::ios_base::openmode mode) -> pos_type -{ - // Simplified version of seekoff - if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return traits_type::eof(); - - LARGE_INTEGER fpos{}; - fpos.QuadPart = pos; - if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN)) - return traits_type::eof(); - - setg(nullptr, nullptr, nullptr); - return fpos.QuadPart; -} - -filebuf::~filebuf() -{ close(); } - -bool filebuf::open(const wchar_t *filename, std::ios_base::openmode mode) -{ - if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) - return false; - HANDLE f{CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, nullptr)}; - if(f == INVALID_HANDLE_VALUE) return false; - - if(mFile != INVALID_HANDLE_VALUE) - CloseHandle(mFile); - mFile = f; - - setg(nullptr, nullptr, nullptr); - return true; -} -bool filebuf::open(const char *filename, std::ios_base::openmode mode) -{ - std::wstring wname{utf8_to_wstr(filename)}; - return open(wname.c_str(), mode); -} - -void filebuf::close() -{ - if(mFile != INVALID_HANDLE_VALUE) - CloseHandle(mFile); - mFile = INVALID_HANDLE_VALUE; -} - - -ifstream::ifstream(const wchar_t *filename, std::ios_base::openmode mode) - : std::istream{nullptr} -{ - init(&mStreamBuf); - - // Set the failbit if the file failed to open. - if((mode&std::ios_base::out) || !mStreamBuf.open(filename, mode|std::ios_base::in)) - clear(failbit); -} - -ifstream::ifstream(const char *filename, std::ios_base::openmode mode) - : std::istream{nullptr} -{ - init(&mStreamBuf); - - // Set the failbit if the file failed to open. - if((mode&std::ios_base::out) || !mStreamBuf.open(filename, mode|std::ios_base::in)) - clear(failbit); -} - -/* This is only here to ensure the compiler doesn't define an implicit - * destructor, which it tries to automatically inline and subsequently complain - * it can't inline without excessive code growth. - */ -ifstream::~ifstream() { } - -} // namespace al - -#endif diff --git a/libs/openal-soft/common/alfstream.h b/libs/openal-soft/common/alfstream.h deleted file mode 100644 index 353fd2de..00000000 --- a/libs/openal-soft/common/alfstream.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef AL_FSTREAM_H -#define AL_FSTREAM_H - -#ifdef _WIN32 - -#define WIN32_LEAN_AND_MEAN -#include - -#include -#include -#include - - -namespace al { - -// Windows' std::ifstream fails with non-ANSI paths since the standard only -// specifies names using const char* (or std::string). MSVC has a non-standard -// extension using const wchar_t* (or std::wstring?) to handle Unicode paths, -// but not all Windows compilers support it. So we have to make our own istream -// that accepts UTF-8 paths and forwards to Unicode-aware I/O functions. -class filebuf final : public std::streambuf { - std::array mBuffer; - HANDLE mFile{INVALID_HANDLE_VALUE}; - - int_type underflow() override; - pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override; - pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override; - -public: - filebuf() = default; - ~filebuf() override; - - bool open(const wchar_t *filename, std::ios_base::openmode mode); - bool open(const char *filename, std::ios_base::openmode mode); - - bool is_open() const noexcept { return mFile != INVALID_HANDLE_VALUE; } - - void close(); -}; - -// Inherit from std::istream to use our custom streambuf -class ifstream final : public std::istream { - filebuf mStreamBuf; - -public: - ifstream(const wchar_t *filename, std::ios_base::openmode mode = std::ios_base::in); - ifstream(const std::wstring &filename, std::ios_base::openmode mode = std::ios_base::in) - : ifstream(filename.c_str(), mode) { } - ifstream(const char *filename, std::ios_base::openmode mode = std::ios_base::in); - ifstream(const std::string &filename, std::ios_base::openmode mode = std::ios_base::in) - : ifstream(filename.c_str(), mode) { } - ~ifstream() override; - - bool is_open() const noexcept { return mStreamBuf.is_open(); } - - void close() { mStreamBuf.close(); } -}; - -} // namespace al - -#else /* _WIN32 */ - -#include - -namespace al { - -using filebuf = std::filebuf; -using ifstream = std::ifstream; - -} // namespace al - -#endif /* _WIN32 */ - -#endif /* AL_FSTREAM_H */ diff --git a/libs/openal-soft/common/almalloc.cpp b/libs/openal-soft/common/almalloc.cpp deleted file mode 100644 index ad1dc6be..00000000 --- a/libs/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/libs/openal-soft/common/almalloc.h b/libs/openal-soft/common/almalloc.h index 711d02fd..8ced0871 100644 --- a/libs/openal-soft/common/almalloc.h +++ b/libs/openal-soft/common/almalloc.h @@ -3,290 +3,149 @@ #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) \ - { \ - void *ret = al_malloc(alignof(T), size); \ - if(!ret) throw std::bad_alloc(); \ - return ret; \ - } \ - 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 { }; #define DEF_FAM_NEWDEL(T, FamMem) \ static constexpr size_t Sizeof(size_t count) noexcept \ { \ + static_assert(&Sizeof == &T::Sizeof, \ + "Incorrect container type specified"); \ return std::max(decltype(FamMem)::Sizeof(count, offsetof(T, FamMem)), \ 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 { - using value_type = T; - using reference = T&; - using const_reference = const T&; - using pointer = T*; - using const_pointer = const T*; + static constexpr auto Alignment = std::max(AlignV, alignof(T)); + static constexpr auto AlignVal = std::align_val_t{Alignment}; + + 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; } + + +template +constexpr T *to_address(T *p) noexcept +{ + static_assert(!std::is_function::value, "Can't be a function type"); + return p; +} + +template +constexpr auto to_address(const T &p) noexcept +{ + return ::al::to_address(p.operator->()); +} 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/libs/openal-soft/common/alnumbers.h b/libs/openal-soft/common/alnumbers.h index 37a55410..6d201ad4 100644 --- a/libs/openal-soft/common/alnumbers.h +++ b/libs/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/libs/openal-soft/common/alnumeric.h b/libs/openal-soft/common/alnumeric.h index 9e7a7f07..0d091166 100644 --- a/libs/openal-soft/common/alnumeric.h +++ b/libs/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,74 +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. */ @@ -97,12 +60,20 @@ inline uint32_t NextPowerOf2(uint32_t value) noexcept return value+1; } -/** Round up a value to the next multiple. */ -inline size_t RoundUp(size_t value, size_t r) noexcept -{ - value += r-1; - return value - (value%r); -} +/** + * If the value is not already a multiple of r, round down to the next + * multiple. + */ +template +constexpr T RoundDown(T value, al::type_identity_t r) noexcept +{ return value - (value%r); } + +/** + * If the value is not already a multiple of r, round up to the next multiple. + */ +template +constexpr T RoundUp(T value, al::type_identity_t r) noexcept +{ return RoundDown(value + r-1, r); } /** @@ -116,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 @@ -150,22 +118,17 @@ 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 UNLIKELY(shift >= 31 || shift < -23) + if(shift >= 31 || shift < -23) UNLIKELY return 0; - mant = (conv.i&0x7fffff) | 0x800000; - if LIKELY(shift < 0) + const int mant{(conv_i&0x7fffff) | 0x800000}; + if(shift < 0) LIKELY return (mant >> -shift) * sign; return (mant << shift) * sign; @@ -186,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 UNLIKELY(shift >= 63 || shift < -52) + if(shift >= 63 || shift < -52) UNLIKELY return 0; - mant = (conv.i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64; - if LIKELY(shift < 0) - return (int)(mant >> -shift) * sign; - return (int)(mant << shift) * sign; + const int64_t mant{(conv_i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64}; + if(shift < 0) LIKELY + return static_cast(mant >> -shift) * sign; + return static_cast(mant << shift) * sign; #else @@ -237,21 +194,16 @@ 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 UNLIKELY(expo >= 150/*+23*/) + if(expo >= 150/*+23*/) UNLIKELY { /* An exponent (base-2) of 23 or higher is incapable of sub-integral * precision, so it's already an integral value. We don't need to worry @@ -266,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) { @@ -293,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/libs/openal-soft/common/aloptional.h b/libs/openal-soft/common/aloptional.h deleted file mode 100644 index 6180d161..00000000 --- a/libs/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/libs/openal-soft/common/threads.cpp b/libs/openal-soft/common/alsem.cpp similarity index 61% rename from libs/openal-soft/common/threads.cpp rename to libs/openal-soft/common/alsem.cpp index c782dc35..6a92b35c 100644 --- a/libs/openal-soft/common/threads.cpp +++ b/libs/openal-soft/common/alsem.cpp @@ -20,11 +20,12 @@ #include "config.h" -#include "opthelpers.h" -#include "threads.h" +#include "alsem.h" #include +#include "opthelpers.h" + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN @@ -32,34 +33,6 @@ #include -void althrd_setname(const char *name) -{ -#if defined(_MSC_VER) -#define MS_VC_EXCEPTION 0x406D1388 -#pragma pack(push,8) - struct { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. - } info; -#pragma pack(pop) - info.dwType = 0x1000; - info.szName = name; - info.dwThreadID = ~DWORD{0}; - info.dwFlags = 0; - - __try { - RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info); - } - __except(EXCEPTION_CONTINUE_EXECUTION) { - } -#undef MS_VC_EXCEPTION -#else - (void)name; -#endif -} - namespace al { semaphore::semaphore(unsigned int initial) @@ -76,7 +49,7 @@ semaphore::~semaphore() void semaphore::post() { - if UNLIKELY(!ReleaseSemaphore(static_cast(mSem), 1, nullptr)) + if(!ReleaseSemaphore(static_cast(mSem), 1, nullptr)) throw std::system_error(std::make_error_code(std::errc::value_too_large)); } @@ -90,44 +63,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 = 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(), "%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); -} - -#ifdef __APPLE__ +/* Do not try using libdispatch on systems where it is absent. */ +#if defined(AL_APPLE_HAVE_DISPATCH) namespace al { diff --git a/libs/openal-soft/common/alsem.h b/libs/openal-soft/common/alsem.h new file mode 100644 index 00000000..90b39319 --- /dev/null +++ b/libs/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/libs/openal-soft/common/alspan.h b/libs/openal-soft/common/alspan.h index 4a0e0430..3a78b9f1 100644 --- a/libs/openal-soft/common/alspan.h +++ b/libs/openal-soft/common/alspan.h @@ -1,165 +1,250 @@ #ifndef AL_SPAN_H #define AL_SPAN_H -#include +#include #include -#include #include +#include +#include #include +#include + +#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 - struct make_void { using type = void; }; - template - using void_t = typename make_void::type; - template struct is_span_ : std::false_type { }; - template + template struct is_span_> : std::true_type { }; template - using is_span = is_span_>; + 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 - using is_std_array = is_std_array_>; + inline constexpr bool is_std_array_v = is_std_array_>::value; template - struct has_size_and_data : std::false_type { }; + inline constexpr bool has_size_and_data = false; template - struct has_size_and_data())), decltype(al::data(std::declval()))>> - : std::true_type { }; + 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 + inline constexpr bool is_array_compatible = std::is_convertible::value; /* NOLINT(*-avoid-c-arrays) */ + + template + inline constexpr bool is_valid_container = is_valid_container_type + && is_array_compatible()))>,T>; } // namespace detail_ -#define REQUIRES(...) bool rt_=true, std::enable_if_t = true -#define IS_VALID_CONTAINER(C) \ - !detail_::is_span::value && !detail_::is_std_array::value && \ - !std::is_array::value && detail_::has_size_and_data::value && \ - std::is_convertible()))>(*)[],element_type(*)[]>::value +#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 + template constexpr span() noexcept { } - constexpr span(pointer ptr, index_type /*count*/) : mData{ptr} { } - constexpr span(pointer first, pointer /*last*/) : mData{first} { } - constexpr span(element_type (&arr)[E]) noexcept : span{al::data(arr), al::size(arr)} { } - constexpr span(std::array &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(U &cont) : span{al::data(cont), al::size(cont)} { } - template - constexpr span(const U &cont) : span{al::data(cont), al::size(cont)} { } - template::value - && std::is_convertible::value)> - constexpr span(const span &span_) noexcept : span{al::data(span_), al::size(span_)} { } + template + constexpr explicit span(U iter, size_type size_ [[maybe_unused]]) + : mData{::al::to_address(iter)} + { assert(size_ == extent); } + template::value)> + constexpr explicit span(U first, V last [[maybe_unused]]) : mData{::al::to_address(first)} + { assert(static_cast(last-first) == extent); } + + 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{std::data(cont), std::size(cont)} { } + + template::value + && detail_::is_array_compatible && N == dynamic_extent)> + constexpr explicit span(const span &span_) noexcept : mData{std::data(span_)} + { assert(std::size(span_) == extent); } + template::value + && detail_::is_array_compatible && N == extent)> + 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 -> 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 -> 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 -> 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 -> std::enable_if_t> { static_assert(E >= O, "Offset exceeds extent"); return span{mData+O, E-O}; @@ -169,10 +254,12 @@ 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 + -> span; + [[nodiscard]] constexpr auto last(std::size_t count) const + -> span; + [[nodiscard]] constexpr auto subspan(std::size_t offset, + std::size_t count=dynamic_extent) const -> span; private: pointer mData{nullptr}; @@ -183,7 +270,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*; @@ -191,115 +278,189 @@ 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; - constexpr span(pointer ptr, index_type count) : mData{ptr}, mDataEnd{ptr+count} { } - constexpr span(pointer first, pointer last) : mData{first}, mDataEnd{last} { } - template - constexpr span(element_type (&arr)[N]) noexcept : span{al::data(arr), al::size(arr)} { } - template - 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(U iter, size_type count) : mData{::al::to_address(iter)}, mDataLength{count} { } - template - constexpr span(U &cont) : span{al::data(cont), al::size(cont)} { } - template - constexpr span(const U &cont) : span{al::data(cont), al::size(cont)} { } - template::value || extent != N) - && std::is_convertible::value)> - constexpr span(const span &span_) noexcept : span{al::data(span_), al::size(span_)} { } + 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 /* NOLINT(*-avoid-c-arrays) */ + : mData{std::data(arr)}, mDataLength{std::size(arr)} + { } + template + constexpr span(std::array &arr) noexcept + : mData{std::data(arr)}, mDataLength{std::size(arr)} + { } + template::value)> + constexpr span(const std::array &arr) noexcept + : mData{std::data(arr)}, mDataLength{std::size(arr)} + { } + + template)> + constexpr span(U&& cont) : span{std::data(cont), std::size(cont)} { } + + 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 -> span { - return (offset > size()) ? span{} : - (count >= size()-offset) ? span{mData+offset, mDataEnd} : - span{mData+offset, mData+offset+count}; + if(C > mDataLength) + throw std::out_of_range{"Subspan count out of range"}; + return span{mData, C}; + } + + [[nodiscard]] constexpr auto first(std::size_t count) const -> span + { + if(count > mDataLength) + throw std::out_of_range{"Subspan count out of range"}; + return span{mData, count}; + } + + template + [[nodiscard]] constexpr auto last() const -> span + { + if(C > mDataLength) + throw std::out_of_range{"Subspan count out of range"}; + return span{mData+mDataLength-C, C}; + } + + [[nodiscard]] constexpr auto last(std::size_t count) const -> span + { + if(count > mDataLength) + throw std::out_of_range{"Subspan count out of range"}; + return span{mData+mDataLength-count, count}; + } + + template + [[nodiscard]] constexpr + auto subspan() const -> std::enable_if_t> + { + if(O > mDataLength) + throw std::out_of_range{"Subspan offset out of range"}; + if(C > mDataLength-O) + throw std::out_of_range{"Subspan length out of range"}; + return span{mData+O, C}; + } + + template + [[nodiscard]] constexpr + auto subspan() const -> std::enable_if_t> + { + if(O > mDataLength) + throw std::out_of_range{"Subspan offset out of range"}; + return span{mData+O, mDataLength-O}; + } + + [[nodiscard]] constexpr + auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const -> span + { + if(offset > mDataLength) + throw std::out_of_range{"Subspan offset out of range"}; + if(count != dynamic_extent) + { + if(count > mDataLength-offset) + throw std::out_of_range{"Subspan length out of range"}; + 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 -> span { - return (count >= size()) ? span{mData, extent} : - span{mData, count}; + if(count > size()) + throw std::out_of_range{"Subspan count out of range"}; + 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 -> span { - return (count >= size()) ? span{mData, extent} : - span{mData+extent-count, count}; + if(count > size()) + throw std::out_of_range{"Subspan count out of range"}; + 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 -> span { - return (offset > size()) ? span{} : - (count >= size()-offset) ? span{mData+offset, mData+extent} : - span{mData+offset, mData+offset+count}; + if(offset > size()) + throw std::out_of_range{"Subspan offset out of range"}; + if(count != dynamic_extent) + { + if(count > size()-offset) + throw std::out_of_range{"Subspan length out of range"}; + return span{mData+offset, count}; + } + return span{mData+offset, size()-offset}; } -#undef IS_VALID_CONTAINER + +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 } // namespace al diff --git a/libs/openal-soft/common/alstring.cpp b/libs/openal-soft/common/alstring.cpp index 4a84be1d..da3c1756 100644 --- a/libs/openal-soft/common/alstring.cpp +++ b/libs/openal-soft/common/alstring.cpp @@ -3,43 +3,41 @@ #include "alstring.h" +#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 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/libs/openal-soft/common/alstring.h b/libs/openal-soft/common/alstring.h index f5127aed..7ac8bfd1 100644 --- a/libs/openal-soft/common/alstring.h +++ b/libs/openal-soft/common/alstring.h @@ -1,15 +1,38 @@ #ifndef AL_STRING_H #define AL_STRING_H +#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 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/libs/openal-soft/common/althrd_setname.cpp b/libs/openal-soft/common/althrd_setname.cpp new file mode 100644 index 00000000..b92b05d1 --- /dev/null +++ b/libs/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/libs/openal-soft/common/althrd_setname.h b/libs/openal-soft/common/althrd_setname.h new file mode 100644 index 00000000..0e22c0a9 --- /dev/null +++ b/libs/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/libs/openal-soft/common/althreads.h b/libs/openal-soft/common/althreads.h new file mode 100644 index 00000000..96cf024f --- /dev/null +++ b/libs/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; + +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/libs/openal-soft/common/altraits.h b/libs/openal-soft/common/altraits.h new file mode 100644 index 00000000..7ce0422e --- /dev/null +++ b/libs/openal-soft/common/altraits.h @@ -0,0 +1,14 @@ +#ifndef COMMON_ALTRAITS_H +#define COMMON_ALTRAITS_H + +namespace al { + +template +struct type_identity { using type = T; }; + +template +using type_identity_t = typename type_identity::type; + +} // namespace al + +#endif /* COMMON_ALTRAITS_H */ diff --git a/libs/openal-soft/common/atomic.h b/libs/openal-soft/common/atomic.h index 5e9b04c6..7075b158 100644 --- a/libs/openal-soft/common/atomic.h +++ b/libs/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/libs/openal-soft/common/comptr.h b/libs/openal-soft/common/comptr.h index 3dc574e8..5110a6c6 100644 --- a/libs/openal-soft/common/comptr.h +++ b/libs/openal-soft/common/comptr.h @@ -2,49 +2,84 @@ #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; + + 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) + 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(likely(&rhs != this)) + if(&rhs != this) { if(mPtr) mPtr->Release(); mPtr = std::exchange(rhs.mPtr, nullptr); @@ -52,17 +87,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/libs/openal-soft/common/dynload.cpp b/libs/openal-soft/common/dynload.cpp index f1c2a7eb..333a9435 100644 --- a/libs/openal-soft/common/dynload.cpp +++ b/libs/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/libs/openal-soft/common/flexarray.h b/libs/openal-soft/common/flexarray.h new file mode 100644 index 00000000..845e7ddd --- /dev/null +++ b/libs/openal-soft/common/flexarray.h @@ -0,0 +1,138 @@ +#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(std::max(alignment, alignof(al::span))) 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(std::max(alignment, alignof(al::span))) 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&; + + using Storage_t_ = FlexArrayStorage; + + 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/libs/openal-soft/common/intrusive_ptr.h b/libs/openal-soft/common/intrusive_ptr.h index 9e206a6b..b1fa742f 100644 --- a/libs/openal-soft/common/intrusive_ptr.h +++ b/libs/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,14 +13,14 @@ namespace al { template class intrusive_ref { - RefCount mRef{1u}; + std::atomic mRef{1u}; public: unsigned int add_ref() noexcept { return IncrementRef(mRef); } - unsigned int release() noexcept + unsigned int dec_ref() noexcept { auto ref = DecrementRef(mRef); - if UNLIKELY(ref == 0) + if(ref == 0) UNLIKELY delete static_cast(this); return ref; } @@ -58,22 +60,26 @@ public: { rhs.mPtr = nullptr; } intrusive_ptr(std::nullptr_t) noexcept { } explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { } - ~intrusive_ptr() { if(mPtr) mPtr->release(); } + ~intrusive_ptr() { 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()->release()), "release must be noexcept"); + static_assert(noexcept(std::declval()->dec_ref()), "dec_ref must be noexcept"); if(rhs.mPtr) rhs.mPtr->add_ref(); - if(mPtr) mPtr->release(); + if(mPtr) mPtr->dec_ref(); mPtr = rhs.mPtr; return *this; } + /* NOLINTEND(bugprone-unhandled-self-assignment) */ intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept { - if(likely(&rhs != this)) + if(&rhs != this) LIKELY { - if(mPtr) mPtr->release(); + if(mPtr) mPtr->dec_ref(); mPtr = std::exchange(rhs.mPtr, nullptr); } return *this; @@ -81,14 +87,14 @@ 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 { if(mPtr) - mPtr->release(); + mPtr->dec_ref(); mPtr = ptr; } @@ -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/libs/openal-soft/common/opthelpers.h b/libs/openal-soft/common/opthelpers.h index e46c0f3a..ae2611da 100644 --- a/libs/openal-soft/common/opthelpers.h +++ b/libs/openal-soft/common/opthelpers.h @@ -3,7 +3,7 @@ #include #include - +#include #ifdef __has_builtin #define HAS_BUILTIN __has_builtin @@ -11,63 +11,72 @@ #define HAS_BUILTIN(x) (0) #endif +#ifdef __has_cpp_attribute +#define HAS_ATTRIBUTE __has_cpp_attribute +#else +#define HAS_ATTRIBUTE(x) (0) +#endif + #ifdef __GNUC__ -#define force_inline [[gnu::always_inline]] +#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 -#if defined(__GNUC__) || HAS_BUILTIN(__builtin_expect) -/* likely() optimizes for the case where the condition is true. The condition - * is not required to be true, but it can result in more optimal code for the - * true path at the expense of a less optimal false path. +/* Unlike the likely attribute, ASSUME requires the condition to be true or + * else it invokes undefined behavior. It's essentially an assert without + * actually checking the condition at run-time, allowing for stronger + * optimizations than the likely attribute. */ -template -force_inline constexpr bool likely(T&& expr) noexcept -{ return __builtin_expect(static_cast(std::forward(expr)), true); } -/* The opposite of likely(), optimizing for the case where the condition is - * false. - */ -template -force_inline constexpr bool unlikely(T&& expr) noexcept -{ return __builtin_expect(static_cast(std::forward(expr)), false); } - -#else - -template -force_inline constexpr bool likely(T&& expr) noexcept -{ return static_cast(std::forward(expr)); } -template -force_inline constexpr bool unlikely(T&& expr) noexcept -{ return static_cast(std::forward(expr)); } -#endif -#define LIKELY(x) (likely(x)) -#define UNLIKELY(x) (unlikely(x)) - #if HAS_BUILTIN(__builtin_assume) -/* Unlike LIKELY, ASSUME requires the condition to be true or else it invokes - * undefined behavior. It's essentially an assert without actually checking the - * condition at run-time, allowing for stronger optimizations than LIKELY. - */ #define ASSUME __builtin_assume #elif defined(_MSC_VER) #define ASSUME __assume -#elif defined(__GNUC__) +#elif __has_attribute(assume) +#define ASSUME(x) [[assume(x)]] +#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 + * versions of GCC choke on the attribute syntax in certain situations. + */ +#if HAS_ATTRIBUTE(likely) +#define LIKELY [[likely]] +#define UNLIKELY [[unlikely]] +#else +#define LIKELY +#define UNLIKELY #endif namespace al { +template +constexpr std::underlying_type_t to_underlying(T e) noexcept +{ return static_cast>(e); } + +[[noreturn]] inline void unreachable() +{ +#if HAS_BUILTIN(__builtin_unreachable) + __builtin_unreachable(); +#else + ASSUME(false); +#endif +} + template force_inline constexpr auto assume_aligned(T *ptr) noexcept { #ifdef __cpp_lib_assume_aligned return std::assume_aligned(ptr); -#elif defined(__clang__) || (defined(__GNUC__) && !defined(__ICC)) +#elif HAS_BUILTIN(__builtin_assume_aligned) return static_cast(__builtin_assume_aligned(ptr, alignment)); #elif defined(_MSC_VER) constexpr std::size_t alignment_mask{(1< +#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 { + +/* 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 +{ + v4sf tmp{vec_mergeh(in1, in2)}; + out2 = vec_mergel(in1, in2); + out1 = tmp; +} +force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + v4sf tmp{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 +{ + v4sf tmp{_mm_unpacklo_ps(in1, in2)}; + out2 = _mm_unpackhi_ps(in1, in2); + out1 = tmp; +} +force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + v4sf tmp{_mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0))}; + out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1)); + out1 = tmp; +} + +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) + +#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 +{ + v4sf tmp{unpacklo(in1, in2)}; + out2 = unpackhi(in1, in2); + out1 = tmp; +} +force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + v4sf tmp{in1[0], in1[2], in2[0], in2[2]}; + out2 = v4sf{in1[1], in1[3], in2[1], in2[3]}; + out1 = tmp; +} + +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); + +/* + 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) diff --git a/libs/openal-soft/common/pffft.h b/libs/openal-soft/common/pffft.h new file mode 100644 index 00000000..31506e9c --- /dev/null +++ b/libs/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; + + 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); } + + [[nodiscard]] operator bool() const noexcept { return mSetup != nullptr; } +}; + +#endif // PFFFT_H diff --git a/libs/openal-soft/common/phase_shifter.h b/libs/openal-soft/common/phase_shifter.h index ace92c9a..a922e8cf 100644 --- a/libs/openal-soft/common/phase_shifter.h +++ b/libs/openal-soft/common/phase_shifter.h @@ -8,17 +8,22 @@ #endif #include -#include +#include +#include +#include +#include #include "alcomplex.h" #include "alspan.h" +struct NoInit { }; + /* 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"); @@ -46,21 +51,22 @@ struct PhaseShifterT { PhaseShifterT() { using complex_d = std::complex; - constexpr size_t fft_size{FilterSize}; - constexpr size_t half_size{fft_size / 2}; + constexpr std::size_t fft_size{FilterSize}; + constexpr std::size_t half_size{fft_size / 2}; - auto fftBuffer = std::make_unique(fft_size); - std::fill_n(fftBuffer.get(), fft_size, complex_d{}); + auto fftBuffer = std::vector(fft_size, complex_d{}); fftBuffer[half_size] = 1.0; - forward_fft({fftBuffer.get(), fft_size}); - for(size_t i{0};i < half_size+1;++i) + forward_fft(al::span{fftBuffer}); + fftBuffer[0] *= std::numeric_limits::epsilon(); + for(std::size_t i{1};i < half_size;++i) fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()}; - for(size_t i{half_size+1};i < fft_size;++i) + fftBuffer[half_size] *= std::numeric_limits::epsilon(); + for(std::size_t i{half_size+1};i < fft_size;++i) fftBuffer[i] = std::conj(fftBuffer[fft_size - i]); - inverse_fft({fftBuffer.get(), fft_size}); + inverse_fft(al::span{fftBuffer}); - auto fftiter = fftBuffer.get() + half_size + (FilterSize/2 - 1); + auto fftiter = fftBuffer.data() + fft_size - 1; for(float &coeff : mCoeffs) { coeff = static_cast(fftiter->real() / double{fft_size}); @@ -68,30 +74,12 @@ struct PhaseShifterT { } } + PhaseShifterT(NoInit) { } + void process(al::span dst, const float *RESTRICT src) const; - void processAccum(al::span dst, const float *RESTRICT 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))}; @@ -113,17 +101,17 @@ private: #endif }; -template +template inline void PhaseShifterT::process(al::span dst, const float *RESTRICT src) const { #ifdef HAVE_SSE_INTRINSICS - if(size_t todo{dst.size()>>1}) + if(std::size_t todo{dst.size()>>1}) { 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) + 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])}; @@ -147,7 +135,7 @@ inline void PhaseShifterT::process(al::span dst, const float *RESTRICT if((dst.size()&1)) { __m128 r4{_mm_setzero_ps()}; - for(size_t j{0};j < mCoeffs.size();j+=4) + 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(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; @@ -161,20 +149,21 @@ inline void PhaseShifterT::process(al::span dst, const float *RESTRICT #elif defined(HAVE_NEON) - size_t pos{0}; - if(size_t todo{dst.size()>>1}) + std::size_t pos{0}; + if(std::size_t todo{dst.size()>>1}) { 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) + 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 float32x4x2_t values{vuzpq_f32(s0, s1)}; - r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs); - r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs); + r04 = vmlaq_f32(r04, values.val[0], coeffs); + r14 = vmlaq_f32(r14, values.val[1], coeffs); } src += 2; @@ -188,7 +177,7 @@ inline void PhaseShifterT::process(al::span dst, const float *RESTRICT if((dst.size()&1)) { float32x4_t r4{vdupq_n_f32(0.0f)}; - for(size_t j{0};j < mCoeffs.size();j+=4) + for(std::size_t j{0};j < mCoeffs.size();j+=4) { 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])}; @@ -203,7 +192,7 @@ inline void PhaseShifterT::process(al::span dst, const float *RESTRICT for(float &output : dst) { float ret{0.0f}; - for(size_t j{0};j < mCoeffs.size();++j) + for(std::size_t j{0};j < mCoeffs.size();++j) ret += src[j*2] * mCoeffs[j]; output = ret; @@ -212,103 +201,4 @@ inline void PhaseShifterT::process(al::span dst, const float *RESTRICT #endif } -template -inline void PhaseShifterT::processAccum(al::span dst, const float *RESTRICT src) const -{ -#ifdef HAVE_SSE_INTRINSICS - if(size_t todo{dst.size()>>1}) - { - 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) - { - 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])}; - - __m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))}; - r04 = _mm_add_ps(r04, _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)); - } - src += 2; - - __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, _mm_add_ps(_mm_loadl_pi(_mm_undefined_ps(), out), r4)); - ++out; - } while(--todo); - } - if((dst.size()&1)) - { - __m128 r4{_mm_setzero_ps()}; - for(size_t j{0};j < mCoeffs.size();j+=4) - { - 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); - } - -#elif defined(HAVE_NEON) - - size_t pos{0}; - if(size_t todo{dst.size()>>1}) - { - 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) - { - 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])}; - - r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs); - r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs); - } - src += 2; - - 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], vadd_f32(vld1_f32(&dst[pos]), r2)); - pos += 2; - } while(--todo); - } - if((dst.size()&1)) - { - float32x4_t r4{vdupq_n_f32(0.0f)}; - for(size_t j{0};j < mCoeffs.size();j+=4) - { - 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); - } - -#else - - for(float &output : dst) - { - float ret{0.0f}; - for(size_t j{0};j < mCoeffs.size();++j) - ret += src[j*2] * mCoeffs[j]; - - output += ret; - ++src; - } -#endif -} - #endif /* PHASE_SHIFTER_H */ diff --git a/libs/openal-soft/common/polyphase_resampler.cpp b/libs/openal-soft/common/polyphase_resampler.cpp index bb8f69a4..6aed84ed 100644 --- a/libs/openal-soft/common/polyphase_resampler.cpp +++ b/libs/openal-soft/common/polyphase_resampler.cpp @@ -3,45 +3,50 @@ #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; -/* This is the normalized cardinal sine (sinc) function. - * - * sinc(x) = { 1, x = 0 - * { sin(pi x) / (pi x), otherwise. - */ -double Sinc(const double x) -{ - if(unlikely(std::abs(x) < Epsilon)) - return 1.0; - return std::sin(al::numbers::pi*x) / (al::numbers::pi*x); -} +#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. */ -constexpr double BesselI_0(const double x) +template +U cyl_bessel_i(T nu, U x) { - // Start at k=1 since k=0 is trivial. + 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. + /* Let the integration converge until the term of the sum is no longer + * significant. + */ double last_sum{}; do { const double y{x2 / k}; @@ -50,7 +55,20 @@ constexpr double BesselI_0(const double x) term *= y * y; sum += term; } while(sum != last_sum); - return sum; + return static_cast(sum); +} +#endif + +/* This is the normalized cardinal sine (sinc) function. + * + * sinc(x) = { 1, x = 0 + * { sin(pi x) / (pi x), otherwise. + */ +double Sinc(const double x) +{ + if(std::abs(x) < Epsilon) UNLIKELY + return 1.0; + return std::sin(al::numbers::pi*x) / (al::numbers::pi*x); } /* Calculate a Kaiser window from the given beta value and a normalized k @@ -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 @@ -96,7 +102,7 @@ constexpr uint Gcd(uint x, uint y) constexpr uint CalcKaiserOrder(const double rejection, const double transition) { const double w_t{2.0 * al::numbers::pi * transition}; - if LIKELY(rejection > 21.0) + if(rejection > 21.0) LIKELY return static_cast(std::ceil((rejection - 7.95) / (2.285 * w_t))); return static_cast(std::ceil(5.79 / w_t)); } @@ -104,7 +110,7 @@ constexpr uint CalcKaiserOrder(const double rejection, const double transition) // Calculates the beta value of the Kaiser window. Rejection is in dB. constexpr double CalcKaiserBeta(const double rejection) { - if LIKELY(rejection > 50.0) + if(rejection > 50.0) LIKELY return 0.1102 * (rejection - 8.7); if(rejection >= 21.0) return (0.5842 * std::pow(rejection - 21.0, 0.4)) + @@ -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 UNLIKELY(outN == 0) + if(out.empty()) UNLIKELY return; // Handle in-place operation. std::vector workspace; - double *work{out}; - if UNLIKELY(work == in) + 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 LIKELY(j_f < m) + if(j_f < m) LIKELY { - size_t filt_len{(m-j_f+p-1) / p}; - if LIKELY(j_s+1 > inN) + 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)}) + 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/libs/openal-soft/common/polyphase_resampler.h b/libs/openal-soft/common/polyphase_resampler.h index e9833d12..0795c80d 100644 --- a/libs/openal-soft/common/polyphase_resampler.h +++ b/libs/openal-soft/common/polyphase_resampler.h @@ -3,6 +3,10 @@ #include +#include "alspan.h" + + +using uint = unsigned int; /* This is a polyphase sinc-filtered resampler. It is built for very high * quality results, rather than real-time performance. @@ -32,13 +36,13 @@ */ struct PPhaseResampler { - using uint = unsigned int; - 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/libs/openal-soft/common/ringbuffer.cpp b/libs/openal-soft/common/ringbuffer.cpp index 0aec1d49..f4fa49bf 100644 --- a/libs/openal-soft/common/ringbuffer.cpp +++ b/libs/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/libs/openal-soft/common/ringbuffer.h b/libs/openal-soft/common/ringbuffer.h index 2a3797b0..4493474c 100644 --- a/libs/openal-soft/common/ringbuffer.h +++ b/libs/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/libs/openal-soft/common/strutils.cpp b/libs/openal-soft/common/strutils.cpp index 18c4947a..4bcd4119 100644 --- a/libs/openal-soft/common/strutils.cpp +++ b/libs/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') - return al::make_optional(str); - return al::nullopt; +#endif + if(str && *str != '\0') + return str; + 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') - return al::make_optional(str); - return al::nullopt; + if(str && *str != L'\0') + return str; + return std::nullopt; } #endif diff --git a/libs/openal-soft/common/strutils.h b/libs/openal-soft/common/strutils.h index 0c7a0e22..7eee0c1d 100644 --- a/libs/openal-soft/common/strutils.h +++ b/libs/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 -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/libs/openal-soft/common/threads.h b/libs/openal-soft/common/threads.h deleted file mode 100644 index 1cdb5d8f..00000000 --- a/libs/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/libs/openal-soft/common/vecmat.h b/libs/openal-soft/common/vecmat.h index 78fd806e..4bb7910b 100644 --- a/libs/openal-soft/common/vecmat.h +++ b/libs/openal-soft/common/vecmat.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "alspan.h" @@ -19,14 +20,14 @@ class VectorR { public: constexpr VectorR() noexcept = default; constexpr VectorR(const VectorR&) noexcept = default; - constexpr VectorR(T a, T b, T c, T d) noexcept : mVals{{a, b, c, d}} { } + constexpr explicit VectorR(T a, T b, T c, T d) noexcept : mVals{a, b, c, d} { } constexpr VectorR& operator=(const VectorR&) noexcept = default; - T& operator[](size_t idx) noexcept { return mVals[idx]; } + constexpr T& operator[](size_t idx) noexcept { return mVals[idx]; } constexpr const T& operator[](size_t idx) const noexcept { return mVals[idx]; } - VectorR& operator+=(const VectorR &rhs) noexcept + constexpr VectorR& operator+=(const VectorR &rhs) noexcept { mVals[0] += rhs.mVals[0]; mVals[1] += rhs.mVals[1]; @@ -35,14 +36,13 @@ public: return *this; } - VectorR operator-(const VectorR &rhs) const noexcept + constexpr VectorR operator-(const VectorR &rhs) const noexcept { - const VectorR ret{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1], + return VectorR{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1], mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]}; - return ret; } - T normalize(T limit = std::numeric_limits::epsilon()) + constexpr T normalize(T limit = std::numeric_limits::epsilon()) { limit = std::max(limit, std::numeric_limits::epsilon()); const T length_sqr{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; @@ -59,17 +59,17 @@ public: return T{0}; } - constexpr VectorR cross_product(const alu::VectorR &rhs) const + [[nodiscard]] constexpr auto cross_product(const alu::VectorR &rhs) const noexcept -> VectorR { return VectorR{ - (*this)[1]*rhs[2] - (*this)[2]*rhs[1], - (*this)[2]*rhs[0] - (*this)[0]*rhs[2], - (*this)[0]*rhs[1] - (*this)[1]*rhs[0], + 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}}; } - constexpr T dot_product(const alu::VectorR &rhs) const - { return (*this)[0]*rhs[0] + (*this)[1]*rhs[1] + (*this)[2]*rhs[2]; } + [[nodiscard]] constexpr auto dot_product(const alu::VectorR &rhs) const noexcept -> T + { return mVals[0]*rhs.mVals[0] + mVals[1]*rhs.mVals[1] + mVals[2]*rhs.mVals[2]; } }; using Vector = VectorR; @@ -81,16 +81,17 @@ class MatrixR { public: constexpr MatrixR() noexcept = default; constexpr MatrixR(const MatrixR&) noexcept = default; - constexpr 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 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 MatrixR& operator=(const MatrixR&) noexcept = default; - auto operator[](size_t idx) noexcept { return al::span{&mVals[idx*4], 4}; } + 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}; } @@ -106,7 +107,7 @@ public: using Matrix = MatrixR; template -inline VectorR operator*(const MatrixR &mtx, const VectorR &vec) noexcept +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], @@ -115,13 +116,6 @@ inline VectorR operator*(const MatrixR &mtx, const VectorR &vec) noexce vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]}; } -template -inline VectorR cast_to(const VectorR &vec) noexcept -{ - return VectorR{static_cast(vec[0]), static_cast(vec[1]), - static_cast(vec[2]), static_cast(vec[3])}; -} - } // namespace alu #endif /* COMMON_VECMAT_H */ diff --git a/libs/openal-soft/common/vector.h b/libs/openal-soft/common/vector.h index 1b69d6a7..364735c6 100644 --- a/libs/openal-soft/common/vector.h +++ b/libs/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/libs/openal-soft/common/win_main_utf8.h b/libs/openal-soft/common/win_main_utf8.h index e40c2158..81734242 100644 --- a/libs/openal-soft/common/win_main_utf8.h +++ b/libs/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/libs/openal-soft/config.h.in b/libs/openal-soft/config.h.in index 416b87d4..19cb79e0 100644 --- a/libs/openal-soft/config.h.in +++ b/libs/openal-soft/config.h.in @@ -1,15 +1,12 @@ +/* Define the alignment attribute for externally callable functions. */ +#define FORCE_ALIGN @ALSOFT_FORCE_ALIGN@ + /* Define if deprecated EAX extensions are enabled */ #cmakedefine ALSOFT_EAX /* 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 @@ -114,3 +111,9 @@ /* Define if we have pthread_set_name_np() */ #cmakedefine HAVE_PTHREAD_SET_NAME_NP + +/* Define the installation data directory */ +#cmakedefine ALSOFT_INSTALL_DATADIR "@ALSOFT_INSTALL_DATADIR@" + +/* Define whether build alsoft for winuwp */ +#cmakedefine ALSOFT_UWP diff --git a/libs/openal-soft/core/ambdec.cpp b/libs/openal-soft/core/ambdec.cpp index 0df22bc9..ba3a4b8b 100644 --- a/libs/openal-soft/core/ambdec.cpp +++ b/libs/openal-soft/core/ambdec.cpp @@ -5,51 +5,22 @@ #include #include +#include #include +#include +#include +#include #include #include #include -#include "alfstream.h" -#include "core/logging.h" +#include "albit.h" +#include "alspan.h" +#include "opthelpers.h" namespace { -template -constexpr inline std::size_t size(const T(&)[N]) noexcept -{ return N; } - -int readline(std::istream &f, std::string &output) -{ - while(f.good() && f.peek() == '\n') - f.ignore(); - - return std::getline(f, output) && !output.empty(); -} - -bool read_clipped_line(std::istream &f, std::string &buffer) -{ - while(readline(f, buffer)) - { - std::size_t pos{0}; - while(pos < buffer.length() && std::isspace(buffer[pos])) - pos++; - buffer.erase(0, pos); - - std::size_t cmtpos{buffer.find_first_of('#')}; - if(cmtpos < buffer.length()) - buffer.resize(cmtpos); - while(!buffer.empty() && std::isspace(buffer.back())) - buffer.pop_back(); - - if(!buffer.empty()) - return true; - } - return false; -} - - std::string read_word(std::istream &f) { std::string ret; @@ -61,120 +32,47 @@ bool is_at_end(const std::string &buffer, std::size_t endpos) { while(endpos < buffer.length() && std::isspace(buffer[endpos])) ++endpos; - return !(endpos < buffer.length()); + return !(endpos < buffer.length() && buffer[endpos] != '#'); } -al::optional load_ambdec_speakers(AmbDecConf::SpeakerConf *spkrs, - const std::size_t num_speakers, std::istream &f, std::string &buffer) +enum class ReaderScope { + Global, + Speakers, + LFMatrix, + HFMatrix, +}; + +#ifdef __MINGW32__ +[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] +#else +[[gnu::format(printf,2,3)]] +#endif +std::optional make_error(size_t linenum, const char *fmt, ...) { - size_t cur_speaker{0}; - while(cur_speaker < num_speakers) + std::optional ret; + auto &str = ret.emplace(); + + str.resize(256); + 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); + const int msglen{std::vsnprintf(&str[plen], str.size()-plen, fmt, args)}; + if(msglen >= 0 && static_cast(msglen) >= str.size()-plen) { - std::istringstream istr{buffer}; - - std::string cmd{read_word(istr)}; - if(cmd.empty()) - { - if(!read_clipped_line(f, buffer)) - return al::make_optional("Unexpected end of file"); - continue; - } - - if(cmd == "add_spkr") - { - AmbDecConf::SpeakerConf &spkr = spkrs[cur_speaker++]; - const size_t spkr_num{cur_speaker}; - - istr >> spkr.Name; - if(istr.fail()) WARN("Name not specified for speaker %zu\n", spkr_num); - istr >> spkr.Distance; - if(istr.fail()) WARN("Distance not specified for speaker %zu\n", spkr_num); - istr >> spkr.Azimuth; - if(istr.fail()) WARN("Azimuth not specified for speaker %zu\n", spkr_num); - istr >> spkr.Elevation; - if(istr.fail()) WARN("Elevation not specified for speaker %zu\n", spkr_num); - istr >> spkr.Connection; - if(istr.fail()) TRACE("Connection not specified for speaker %zu\n", spkr_num); - } - else - return al::make_optional("Unexpected speakers command: "+cmd); - - istr.clear(); - const auto endpos = static_cast(istr.tellg()); - if(!is_at_end(buffer, endpos)) - return al::make_optional("Extra junk on line: " + buffer.substr(endpos)); - buffer.clear(); + str.resize(static_cast(msglen) + plen + 1u); + std::vsnprintf(&str[plen], str.size()-plen, fmt, args2); } + va_end(args2); + va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ - return al::nullopt; -} - -al::optional load_ambdec_matrix(float (&gains)[MaxAmbiOrder+1], - AmbDecConf::CoeffArray *matrix, const std::size_t maxrow, std::istream &f, std::string &buffer) -{ - bool gotgains{false}; - std::size_t cur{0u}; - while(cur < maxrow) - { - std::istringstream istr{buffer}; - - std::string cmd{read_word(istr)}; - if(cmd.empty()) - { - if(!read_clipped_line(f, buffer)) - return al::make_optional("Unexpected end of file"); - continue; - } - - if(cmd == "order_gain") - { - std::size_t curgain{0u}; - float value; - while(istr.good()) - { - istr >> value; - if(istr.fail()) break; - if(!istr.eof() && !std::isspace(istr.peek())) - return al::make_optional("Extra junk on gain "+std::to_string(curgain+1)+": "+ - buffer.substr(static_cast(istr.tellg()))); - if(curgain < size(gains)) - gains[curgain++] = value; - } - std::fill(std::begin(gains)+curgain, std::end(gains), 0.0f); - gotgains = true; - } - else if(cmd == "add_row") - { - AmbDecConf::CoeffArray &mtxrow = matrix[cur++]; - std::size_t curidx{0u}; - float value{}; - while(istr.good()) - { - istr >> value; - if(istr.fail()) break; - if(!istr.eof() && !std::isspace(istr.peek())) - return al::make_optional("Extra junk on matrix element "+ - std::to_string(curidx)+"x"+std::to_string(cur-1)+": "+ - buffer.substr(static_cast(istr.tellg()))); - if(curidx < mtxrow.size()) - mtxrow[curidx++] = value; - } - std::fill(mtxrow.begin()+curidx, mtxrow.end(), 0.0f); - } - else - return al::make_optional("Unexpected matrix command: "+cmd); - - istr.clear(); - const auto endpos = static_cast(istr.tellg()); - if(!is_at_end(buffer, endpos)) - return al::make_optional("Extra junk on line: " + buffer.substr(endpos)); - buffer.clear(); - } - - if(!gotgains) - return al::make_optional("Matrix order_gain not specified"); - return al::nullopt; + return ret; } } // namespace @@ -182,197 +80,231 @@ al::optional load_ambdec_matrix(float (&gains)[MaxAmbiOrder+1], 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 al::make_optional("Failed to open file"); + return std::string("Failed to open file \"")+fname+"\""; + + ReaderScope scope{ReaderScope::Global}; + size_t speaker_pos{0}; + size_t lfmatrix_pos{0}; + size_t hfmatrix_pos{0}; + size_t linenum{0}; - bool speakers_loaded{false}; - bool matrix_loaded{false}; - bool lfmatrix_loaded{false}; std::string buffer; - while(read_clipped_line(f, buffer)) + while(f.good() && std::getline(f, buffer)) { + ++linenum; + std::istringstream istr{buffer}; - std::string command{read_word(istr)}; - if(command.empty()) - return al::make_optional("Malformed line: "+buffer); + if(command.empty() || command[0] == '#') + continue; - if(command == "/description") - readline(istr, Description); + if(command == "/}") + { + if(scope == ReaderScope::Global) + return make_error(linenum, "Unexpected /} in global scope"); + scope = ReaderScope::Global; + continue; + } + + if(scope == ReaderScope::Speakers) + { + if(command == "add_spkr") + { + if(speaker_pos == Speakers.size()) + return make_error(linenum, "Too many speakers specified"); + + AmbDecConf::SpeakerConf &spkr = Speakers[speaker_pos++]; + istr >> spkr.Name; + istr >> spkr.Distance; + istr >> spkr.Azimuth; + istr >> spkr.Elevation; + istr >> spkr.Connection; + } + else + return make_error(linenum, "Unexpected speakers command: %s", command.c_str()); + } + else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix) + { + auto &gains = (scope == ReaderScope::LFMatrix) ? LFOrderGain : HFOrderGain; + auto matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix; + auto &pos = (scope == ReaderScope::LFMatrix) ? lfmatrix_pos : hfmatrix_pos; + + if(command == "order_gain") + { + size_t toread{(ChanMask > Ambi3OrderMask) ? 5u : 4u}; + std::size_t curgain{0u}; + float value{}; + while(toread) + { + --toread; + istr >> value; + if(curgain < std::size(gains)) + gains[curgain++] = value; + } + } + else if(command == "add_row") + { + if(pos == Speakers.size()) + return make_error(linenum, "Too many matrix rows specified"); + + unsigned int mask{ChanMask}; + + AmbDecConf::CoeffArray &mtxrow = matrix[pos++]; + mtxrow.fill(0.0f); + + float value{}; + while(mask) + { + auto idx = static_cast(al::countr_zero(mask)); + mask &= ~(1u << idx); + + istr >> value; + if(idx < mtxrow.size()) + mtxrow[idx] = value; + } + } + else + return make_error(linenum, "Unexpected matrix command: %s", command.c_str()); + } + // Global scope commands + else if(command == "/description") + { + while(istr.good() && std::isspace(istr.peek())) + istr.ignore(); + std::getline(istr, Description); + while(!Description.empty() && std::isspace(Description.back())) + Description.pop_back(); + } else if(command == "/version") { + if(Version) + return make_error(linenum, "Duplicate version definition"); istr >> Version; - if(!istr.eof() && !std::isspace(istr.peek())) - return al::make_optional("Extra junk after version: " + - buffer.substr(static_cast(istr.tellg()))); if(Version != 3) - return al::make_optional("Unsupported version: "+std::to_string(Version)); + return make_error(linenum, "Unsupported version: %d", Version); } else if(command == "/dec/chan_mask") { if(ChanMask) - return al::make_optional("Duplicate chan_mask definition"); - + return make_error(linenum, "Duplicate chan_mask definition"); istr >> std::hex >> ChanMask >> std::dec; - if(!istr.eof() && !std::isspace(istr.peek())) - return al::make_optional("Extra junk after mask: " + - buffer.substr(static_cast(istr.tellg()))); - if(!ChanMask) - return al::make_optional("Invalid chan_mask: "+std::to_string(ChanMask)); + if(!ChanMask || ChanMask > Ambi4OrderMask) + return make_error(linenum, "Invalid chan_mask: 0x%x", ChanMask); + if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa) + return make_error(linenum, "FuMa not compatible with over third-order"); } else if(command == "/dec/freq_bands") { if(FreqBands) - return al::make_optional("Duplicate freq_bands"); - + return make_error(linenum, "Duplicate freq_bands"); istr >> FreqBands; - if(!istr.eof() && !std::isspace(istr.peek())) - return al::make_optional("Extra junk after freq_bands: " + - buffer.substr(static_cast(istr.tellg()))); - if(FreqBands != 1 && FreqBands != 2) - return al::make_optional("Invalid freq_bands: "+std::to_string(FreqBands)); + return make_error(linenum, "Invalid freq_bands: %u", FreqBands); } else if(command == "/dec/speakers") { - if(NumSpeakers) - return al::make_optional("Duplicate speakers"); - - istr >> NumSpeakers; - if(!istr.eof() && !std::isspace(istr.peek())) - return al::make_optional("Extra junk after speakers: " + - buffer.substr(static_cast(istr.tellg()))); - - if(!NumSpeakers) - return al::make_optional("Invalid speakers: "+std::to_string(NumSpeakers)); - Speakers = std::make_unique(NumSpeakers); + if(!Speakers.empty()) + return make_error(linenum, "Duplicate speakers"); + size_t numspeakers{}; + istr >> numspeakers; + if(!numspeakers) + return make_error(linenum, "Invalid speakers: %zu", numspeakers); + Speakers.resize(numspeakers); } else if(command == "/dec/coeff_scale") { - std::string scale = read_word(istr); + if(CoeffScale != AmbDecScale::Unset) + return make_error(linenum, "Duplicate coeff_scale"); + + std::string scale{read_word(istr)}; if(scale == "n3d") CoeffScale = AmbDecScale::N3D; else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D; else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa; else - return al::make_optional("Unexpected coeff_scale: "+scale); + return make_error(linenum, "Unexpected coeff_scale: %s", scale.c_str()); + + if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa) + return make_error(linenum, "FuMa not compatible with over third-order"); } else if(command == "/opt/xover_freq") { istr >> XOverFreq; - if(!istr.eof() && !std::isspace(istr.peek())) - return al::make_optional("Extra junk after xover_freq: " + - buffer.substr(static_cast(istr.tellg()))); } else if(command == "/opt/xover_ratio") { istr >> XOverRatio; - if(!istr.eof() && !std::isspace(istr.peek())) - return al::make_optional("Extra junk after xover_ratio: " + - buffer.substr(static_cast(istr.tellg()))); } - else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp" || - command == "/opt/delay_comp" || command == "/opt/level_comp") + else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp" + || command == "/opt/delay_comp" || command == "/opt/level_comp") { /* Unused */ read_word(istr); } else if(command == "/speakers/{") { - if(!NumSpeakers) - return al::make_optional("Speakers defined without a count"); - - const auto endpos = static_cast(istr.tellg()); - if(!is_at_end(buffer, endpos)) - return al::make_optional("Extra junk on line: " + buffer.substr(endpos)); - buffer.clear(); - - if(auto err = load_ambdec_speakers(Speakers.get(), NumSpeakers, f, buffer)) - return err; - speakers_loaded = true; - - if(!read_clipped_line(f, buffer)) - return al::make_optional("Unexpected end of file"); - std::istringstream istr2{buffer}; - std::string endmark{read_word(istr2)}; - if(endmark != "/}") - return al::make_optional("Expected /} after speaker definitions, got "+endmark); - istr.swap(istr2); + 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) - return al::make_optional("Matrix defined without a count"); - const auto endpos = static_cast(istr.tellg()); - if(!is_at_end(buffer, endpos)) - return al::make_optional("Extra junk on line: " + buffer.substr(endpos)); - buffer.clear(); + 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) { if(command != "/matrix/{") - return al::make_optional( - "Unexpected \""+command+"\" type for a single-band decoder"); - if(auto err = load_ambdec_matrix(HFOrderGain, HFMatrix, NumSpeakers, f, buffer)) - return err; - matrix_loaded = true; + return make_error(linenum, "Unexpected \"%s\" for a single-band decoder", + command.c_str()); + scope = ReaderScope::HFMatrix; } else { if(command == "/lfmatrix/{") - { - if(auto err=load_ambdec_matrix(LFOrderGain, LFMatrix, NumSpeakers, f, buffer)) - return err; - lfmatrix_loaded = true; - } + scope = ReaderScope::LFMatrix; else if(command == "/hfmatrix/{") - { - if(auto err=load_ambdec_matrix(HFOrderGain, HFMatrix, NumSpeakers, f, buffer)) - return err; - matrix_loaded = true; - } + scope = ReaderScope::HFMatrix; else - return al::make_optional( - "Unexpected \""+command+"\" type for a dual-band decoder"); + return make_error(linenum, "Unexpected \"%s\" for a dual-band decoder", + command.c_str()); } - - if(!read_clipped_line(f, buffer)) - return al::make_optional("Unexpected end of file"); - std::istringstream istr2{buffer}; - std::string endmark{read_word(istr2)}; - if(endmark != "/}") - return al::make_optional("Expected /} after matrix definitions, got "+endmark); - istr.swap(istr2); } else if(command == "/end") { const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) - return al::make_optional("Extra junk on end: " + buffer.substr(endpos)); + return make_error(linenum, "Extra junk on end: %s", buffer.substr(endpos).c_str()); - if(!speakers_loaded || !matrix_loaded || (FreqBands == 2 && !lfmatrix_loaded)) - return al::make_optional("No decoder defined"); + 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 al::make_optional("Unexpected command: " + command); + return make_error(linenum, "Unexpected command: %s", command.c_str()); istr.clear(); const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) - return al::make_optional("Extra junk on line: " + buffer.substr(endpos)); + return make_error(linenum, "Extra junk on line: %s", buffer.substr(endpos).c_str()); buffer.clear(); } - return al::make_optional("Unexpected end of file"); + return make_error(linenum, "Unexpected end of file"); } diff --git a/libs/openal-soft/core/ambdec.h b/libs/openal-soft/core/ambdec.h index e1bcde26..587a30c6 100644 --- a/libs/openal-soft/core/ambdec.h +++ b/libs/openal-soft/core/ambdec.h @@ -3,14 +3,17 @@ #include #include +#include #include +#include -#include "aloptional.h" +#include "alspan.h" #include "core/ambidefs.h" /* Helpers to read .ambdec configuration files. */ enum class AmbDecScale { + Unset, N3D, SN3D, FuMa, @@ -21,7 +24,7 @@ struct AmbDecConf { unsigned int ChanMask{0u}; unsigned int FreqBands{0u}; /* Must be 1 or 2 */ - AmbDecScale CoeffScale{}; + AmbDecScale CoeffScale{AmbDecScale::Unset}; float XOverFreq{0.0f}; float XOverRatio{0.0f}; @@ -33,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/libs/openal-soft/core/ambidefs.cpp b/libs/openal-soft/core/ambidefs.cpp index 2725748e..2389ce6b 100644 --- a/libs/openal-soft/core/ambidefs.cpp +++ b/libs/openal-soft/core/ambidefs.cpp @@ -3,42 +3,306 @@ #include "ambidefs.h" -#include +#include "alnumbers.h" namespace { -constexpr std::array Ambi3DDecoderHFScale{{ - 1.00000000e+00f, 1.00000000e+00f -}}; -constexpr std::array Ambi3DDecoderHFScale2O{{ - 7.45355990e-01f, 1.00000000e+00f, 1.00000000e+00f -}}; -constexpr std::array Ambi3DDecoderHFScale3O{{ - 5.89792205e-01f, 8.79693856e-01f, 1.00000000e+00f, 1.00000000e+00f -}}; +using AmbiChannelFloatArray = std::array; -inline auto& GetDecoderHFScales(uint order) noexcept +constexpr auto inv_sqrt2f = static_cast(1.0/al::numbers::sqrt2); +constexpr auto inv_sqrt3f = static_cast(1.0/al::numbers::sqrt3); + + +/* These HF gains are derived from the same 32-point speaker array. The scale + * factor between orders represents the same scale factors for any (regular) + * speaker array decoder. e.g. Given a first-order source and second-order + * output, applying an HF scale of HFScales[1][0] / HFScales[2][0] to channel 0 + * 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 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 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 + * decoder matrix with a max-order encoder matrix, creating a matrix that + * behaves as if the B-Format input signal is first decoded to a speaker array + * at first-order, then those speaker feeds are encoded to a higher-order + * signal. While not perfect, this should accurately encode a lower-order + * signal into a higher-order signal. + */ +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), + CalcAmbiCoeffs(-inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f), + CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f), + 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 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"); + + +/* This calculates a second-order "upsampler" matrix. Same as the first-order + * matrix, just using a slightly more dense speaker array suitable for second- + * order content. + */ +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), + 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), + 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), + 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 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"); + + +/* This calculates a third-order "upsampler" matrix. Same as the first-order + * matrix, just using a more dense speaker array suitable for third-order + * content. + */ +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), + CalcAmbiCoeffs(-0.35682208976f, -0.93417235897f, 0.00000000000f), + CalcAmbiCoeffs( 0.93417235897f, 0.00000000000f, 0.35682208976f), + CalcAmbiCoeffs( 0.93417235897f, 0.00000000000f, -0.35682208976f), + CalcAmbiCoeffs(-0.93417235897f, 0.00000000000f, 0.35682208976f), + CalcAmbiCoeffs(-0.93417235897f, 0.00000000000f, -0.35682208976f), + CalcAmbiCoeffs( 0.00000000000f, 0.35682208976f, 0.93417235897f), + CalcAmbiCoeffs( 0.00000000000f, 0.35682208976f, -0.93417235897f), + CalcAmbiCoeffs( 0.00000000000f, -0.35682208976f, 0.93417235897f), + CalcAmbiCoeffs( 0.00000000000f, -0.35682208976f, -0.93417235897f), + CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, inv_sqrt3f), + CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f), + CalcAmbiCoeffs( -inv_sqrt3f, inv_sqrt3f, inv_sqrt3f), + CalcAmbiCoeffs( -inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f), + CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f), + 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 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), + CalcAmbiCoeffs(-0.38268343237f, 0.0f, -0.92387953251f), + CalcAmbiCoeffs( 0.38268343237f, 0.0f, -0.92387953251f), + 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"); + + +/* This calculates a 2D fourth-order "upsampler" matrix. There is no 3D fourth- + * order upsampler since fourth-order is the max order we'll be supporting for + * the foreseeable future. This is only necessary for mixing horizontal-only + * fourth-order content to 3D. + */ +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), + CalcAmbiCoeffs( 8.090169944e-01f, 0.000000000e+00f, -5.877852523e-01f), + CalcAmbiCoeffs( 3.090169944e-01f, 0.000000000e+00f, -9.510565163e-01f), + 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), + 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 +constexpr auto CalcAmbiUpsampler(const std::array,M> &decoder, + const std::array &encoder) { - if(order >= 3) return Ambi3DDecoderHFScale3O; - if(order == 2) return Ambi3DDecoderHFScale2O; - return Ambi3DDecoderHFScale; + std::array res{}; + + for(size_t i{0};i < decoder[0].size();++i) + { + for(size_t j{0};j < encoder[0].size();++j) + { + double sum{0.0}; + for(size_t k{0};k < decoder.size();++k) + sum += double{decoder[k][i]} * encoder[k][j]; + res[i][j] = static_cast(sum); + } + } + + return res; } } // namespace -auto AmbiScale::GetHFOrderScales(const uint in_order, const uint out_order) noexcept - -> std::array +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, + const uint dev_order, const bool horizontalOnly) noexcept { - std::array ret{}; + std::array res{}; - assert(out_order >= in_order); + if(!horizontalOnly) + { + for(size_t i{0};i < MaxAmbiOrder+1;++i) + res[i] = HFScales[src_order][i] / HFScales[dev_order][i]; + } + else + { + for(size_t i{0};i < MaxAmbiOrder+1;++i) + res[i] = HFScales2D[src_order][i] / HFScales2D[dev_order][i]; + } - const auto &target = GetDecoderHFScales(out_order); - const auto &input = GetDecoderHFScales(in_order); - - for(size_t i{0};i < in_order+1;++i) - ret[i] = input[i] / target[i]; - - return ret; + return res; } diff --git a/libs/openal-soft/core/ambidefs.h b/libs/openal-soft/core/ambidefs.h index 82a1a4e5..bea1a312 100644 --- a/libs/openal-soft/core/ambidefs.h +++ b/libs/openal-soft/core/ambidefs.h @@ -5,32 +5,35 @@ #include #include +#include "alnumbers.h" + + using uint = unsigned int; /* The maximum number of Ambisonics channels. For a given order (o), the size * needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second- * order has 9, third-order has 16, and fourth-order has 25. */ -constexpr uint8_t MaxAmbiOrder{3}; +inline constexpr uint8_t MaxAmbiOrder{3}; constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept { return (order+1) * (order+1); } -constexpr size_t MaxAmbiChannels{AmbiChannelsFromOrder(MaxAmbiOrder)}; +inline constexpr size_t MaxAmbiChannels{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- @@ -38,150 +41,171 @@ constexpr uint AmbiPeriphonicMask{0xfe7ce4}; */ constexpr inline size_t Ambi2DChannelsFromOrder(size_t order) noexcept { return order*2 + 1; } -constexpr size_t MaxAmbi2DChannels{Ambi2DChannelsFromOrder(MaxAmbiOrder)}; +inline constexpr size_t MaxAmbi2DChannels{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 in_order, - const uint out_order) noexcept; + static std::array GetHFOrderScales(const uint src_order, + const uint dev_order, const bool horizontalOnly) noexcept; + + static const std::array,4> FirstOrderUp; + static const std::array,4> FirstOrder2DUp; + static const std::array,9> SecondOrderUp; + static const std::array,9> SecondOrder2DUp; + static const std::array,16> ThirdOrderUp; + static const std::array,16> ThirdOrder2DUp; + static const std::array,25> FourthOrder2DUp; }; 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, + }}; }; + +/** + * Calculates ambisonic encoder coefficients using the X, Y, and Z direction + * components, which must represent a normalized (unit length) vector. + * + * NOTE: The components use ambisonic coordinates. As a result: + * + * Ambisonic Y = OpenAL -X + * Ambisonic Z = OpenAL Y + * Ambisonic X = OpenAL -Z + * + * The components are ordered such that OpenAL's X, Y, and Z are the first, + * second, and third parameters respectively -- simply negate X and Z. + */ +constexpr auto CalcAmbiCoeffs(const float y, const float z, const float x) +{ + const float xx{x*x}, yy{y*y}, zz{z*z}, xy{x*y}, yz{y*z}, xz{x*z}; + + return std::array{{ + /* Zeroth-order */ + 1.0f, /* ACN 0 = 1 */ + /* First-order */ + al::numbers::sqrt3_v * y, /* ACN 1 = sqrt(3) * Y */ + al::numbers::sqrt3_v * z, /* ACN 2 = sqrt(3) * Z */ + al::numbers::sqrt3_v * x, /* ACN 3 = sqrt(3) * X */ + /* Second-order */ + 3.872983346e+00f * xy, /* ACN 4 = sqrt(15) * X * Y */ + 3.872983346e+00f * yz, /* ACN 5 = sqrt(15) * Y * Z */ + 1.118033989e+00f * (3.0f*zz - 1.0f), /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */ + 3.872983346e+00f * xz, /* ACN 7 = sqrt(15) * X * Z */ + 1.936491673e+00f * (xx - yy), /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */ + /* Third-order */ + 2.091650066e+00f * (y*(3.0f*xx - yy)), /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */ + 1.024695076e+01f * (z*xy), /* ACN 10 = sqrt(105) * Z * X * Y */ + 1.620185175e+00f * (y*(5.0f*zz - 1.0f)), /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */ + 1.322875656e+00f * (z*(5.0f*zz - 3.0f)), /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */ + 1.620185175e+00f * (x*(5.0f*zz - 1.0f)), /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */ + 5.123475383e+00f * (z*(xx - yy)), /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */ + 2.091650066e+00f * (x*(xx - 3.0f*yy)), /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */ + /* Fourth-order */ + /* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */ + /* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */ + /* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */ + /* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */ + /* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */ + /* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */ + /* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */ + /* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */ + /* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */ + }}; +} + #endif /* CORE_AMBIDEFS_H */ diff --git a/libs/openal-soft/core/async_event.h b/libs/openal-soft/core/async_event.h index 750f38c9..20857c9c 100644 --- a/libs/openal-soft/core/async_event.h +++ b/libs/openal-soft/core/async_event.h @@ -1,6 +1,10 @@ #ifndef CORE_EVENT_H #define CORE_EVENT_H +#include +#include +#include + #include "almalloc.h" struct EffectState; @@ -8,48 +12,53 @@ struct EffectState; using uint = unsigned int; -struct AsyncEvent { - enum : uint { - /* End event thread processing. */ - KillThread = 0, - - /* User event types. */ - SourceStateChange = 1<<0, - BufferCompleted = 1<<1, - Disconnected = 1<<2, - - /* Internal events. */ - ReleaseEffectState = 65536, - }; - - enum class SrcState { - Reset, - Stop, - Play, - Pause - }; - - uint EnumType{0u}; - union { - char dummy; - struct { - uint id; - SrcState state; - } srcstate; - struct { - uint id; - uint count; - } bufcomp; - struct { - char msg[244]; - } disconnect; - EffectState *mEffectState; - } u{}; - - AsyncEvent() noexcept = default; - constexpr AsyncEvent(uint type) noexcept : EnumType{type} { } - - DISABLE_ALLOC() +enum class AsyncEnableBits : uint8_t { + SourceState, + BufferCompleted, + Disconnected, + Count }; + +enum class AsyncSrcState : 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::array 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/libs/openal-soft/core/bformatdec.cpp b/libs/openal-soft/core/bformatdec.cpp index 606093c0..395ee48e 100644 --- a/libs/openal-soft/core/bformatdec.cpp +++ b/libs/openal-soft/core/bformatdec.cpp @@ -6,153 +6,145 @@ #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({input->data(), SamplesToDo}, hfSamples, lfSamples); + MixSamples(hfSamples, OutBuffer, chandec.mGains[sHFBand].data(), + chandec.mGains[sHFBand].data(), 0, 0); + MixSamples(lfSamples, OutBuffer, chandec.mGains[sLFBand].data(), + chandec.mGains[sLFBand].data(), 0, 0); + ++input; } - } - 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.data(), + chandec.mGains.data(), 0, 0); + ++input; } - } + }; + + 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. Add a delay to it so it stays aligned with the stablizer - * delay. + * 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[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] + OutBuffer[ridx][i]; - side[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] - OutBuffer[ridx][i]; - } - std::fill_n(OutBuffer[lidx].begin(), SamplesToDo, 0.0f); - std::fill_n(OutBuffer[ridx].begin(), SamplesToDo, 0.0f); + 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); - /* Apply a delay to all channels, except the front-left and front-right, so - * they maintain correct timing. + /* Include the decoded side signal with the direct side signal. */ + for(size_t i{0};i < SamplesToDo;++i) + side[i] += leftout[i] - rightout[i]; + + /* Get the decoded mid signal and band-split it. */ + const auto tmpsamples = al::span{mStablizer->Temp}.first(SamplesToDo); + std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), tmpsamples.begin(), + std::plus{}); + + 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 + * signal and the split mid signal. */ const size_t NumChannels{OutBuffer.size()}; for(size_t i{0u};i < NumChannels;i++) { - if(i == lidx || i == ridx) - continue; - - auto &DelayBuf = mStablizer->DelayBuf[i]; - auto buffer_end = OutBuffer[i].begin() + SamplesToDo; - if LIKELY(SamplesToDo >= FrontStablizer::DelayLength) - { - auto delay_end = std::rotate(OutBuffer[i].begin(), - buffer_end - FrontStablizer::DelayLength, buffer_end); - std::swap_ranges(OutBuffer[i].begin(), delay_end, DelayBuf.begin()); - } + /* Skip the left and right channels, which are going to get overwritten, + * and substitute the direct mid signal and direct+decoded side signal. + */ + if(i == lidx) + mStablizer->ChannelFilters[i].processAllPass(mid); + else if(i == ridx) + mStablizer->ChannelFilters[i].processAllPass(side); else - { - auto delay_start = std::swap_ranges(OutBuffer[i].begin(), buffer_end, - DelayBuf.begin()); - std::rotate(DelayBuf.begin(), delay_start, DelayBuf.end()); - } + mStablizer->ChannelFilters[i].processAllPass({OutBuffer[i].data(), SamplesToDo}); } - /* Include the side signal for what was just decoded. */ - for(size_t i{0};i < SamplesToDo;++i) - side[FrontStablizer::DelayLength+i] += OutBuffer[lidx][i] - OutBuffer[ridx][i]; - - /* Combine the delayed mid signal with the decoded mid signal. */ - float *tmpbuf{mStablizer->TempBuf.data()}; - auto tmpiter = std::copy(mStablizer->MidDelay.cbegin(), mStablizer->MidDelay.cend(), tmpbuf); - for(size_t i{0};i < SamplesToDo;++i,++tmpiter) - *tmpiter = OutBuffer[lidx][i] + OutBuffer[ridx][i]; - /* Save the newest samples for next time. */ - std::copy_n(tmpbuf+SamplesToDo, mStablizer->MidDelay.size(), mStablizer->MidDelay.begin()); - - /* Apply an all-pass on the signal in reverse. The future samples are - * included with the all-pass to reduce the error in the output samples - * (the smaller the delay, the more error is introduced). - */ - mStablizer->MidFilter.applyAllpassRev({tmpbuf, SamplesToDo+FrontStablizer::DelayLength}); - - /* Now apply the band-splitter, combining its phase shift with the reversed - * phase shift, restoring the original phase on the split signal. - */ - mStablizer->MidFilter.process({tmpbuf, SamplesToDo}, mStablizer->MidHF.data(), - mStablizer->MidLF.data()); - /* This pans the separate low- and high-frequency signals between being on * the center channel and the left+right channels. The low-frequency signal * is panned 1/3rd toward center and the high-frequency signal is panned @@ -162,8 +154,12 @@ 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 + * properly combined with the direct+decoded side signal. + */ const float m{mStablizer->MidLF[i]*cos_lf + mStablizer->MidHF[i]*cos_hf + mid[i]}; const float c{mStablizer->MidLF[i]*sin_lf + mStablizer->MidHF[i]*sin_hf}; const float s{side[i]}; @@ -171,15 +167,10 @@ 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; } - /* Move the delayed mid/side samples to the front for next time. */ - auto mid_end = mStablizer->MidDirect.cbegin() + SamplesToDo; - std::copy(mid_end, mid_end+FrontStablizer::DelayLength, mStablizer->MidDirect.begin()); - auto side_end = mStablizer->Side.cbegin() + SamplesToDo; - std::copy(side_end, side_end+FrontStablizer::DelayLength, mStablizer->Side.begin()); } diff --git a/libs/openal-soft/core/bformatdec.h b/libs/openal-soft/core/bformatdec.h index 7a27a5a4..5bb2d3b4 100644 --- a/libs/openal-soft/core/bformatdec.h +++ b/libs/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/libs/openal-soft/core/bs2b.cpp b/libs/openal-soft/core/bs2b.cpp index 303bf9bd..aaac25f4 100644 --- a/libs/openal-soft/core/bs2b.cpp +++ b/libs/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/libs/openal-soft/core/bs2b.h b/libs/openal-soft/core/bs2b.h index 4d0b9dd8..6fb54c0c 100644 --- a/libs/openal-soft/core/bs2b.h +++ b/libs/openal-soft/core/bs2b.h @@ -24,66 +24,65 @@ #ifndef CORE_BS2B_H #define CORE_BS2B_H -#include "almalloc.h" +#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, 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/libs/openal-soft/core/bsinc_defs.h b/libs/openal-soft/core/bsinc_defs.h index f2958231..01bd3c29 100644 --- a/libs/openal-soft/core/bsinc_defs.h +++ b/libs/openal-soft/core/bsinc_defs.h @@ -1,7 +1,9 @@ #ifndef CORE_BSINC_DEFS_H #define CORE_BSINC_DEFS_H -/* The number of distinct scale and phase intervals within the filter table. */ +/* The number of distinct scale and phase intervals within the bsinc filter + * tables. + */ constexpr unsigned int BSincScaleBits{4}; constexpr unsigned int BSincScaleCount{1 << BSincScaleBits}; constexpr unsigned int BSincPhaseBits{5}; diff --git a/libs/openal-soft/core/bsinc_tables.cpp b/libs/openal-soft/core/bsinc_tables.cpp index a81167d2..d321a983 100644 --- a/libs/openal-soft/core/bsinc_tables.cpp +++ b/libs/openal-soft/core/bsinc_tables.cpp @@ -5,18 +5,62 @@ #include #include #include +#include #include #include #include #include "alnumbers.h" -#include "core/mixer/defs.h" +#include "alnumeric.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 +75,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 +93,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 +112,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 +122,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 +151,20 @@ 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 filterptr = std::make_unique>(); + const auto filter = filterptr->begin(); + + 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 -> const float* { return mTable.data(); } }; -#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; @@ -291,5 +303,5 @@ constexpr BSincTable GenerateBSincTable(const T &filter) } // namespace -const BSincTable bsinc12{GenerateBSincTable(bsinc12_filter)}; -const BSincTable bsinc24{GenerateBSincTable(bsinc24_filter)}; +const BSincTable gBSinc12{GenerateBSincTable(bsinc12_filter)}; +const BSincTable gBSinc24{GenerateBSincTable(bsinc24_filter)}; diff --git a/libs/openal-soft/core/bsinc_tables.h b/libs/openal-soft/core/bsinc_tables.h index f52cda66..6c33bd56 100644 --- a/libs/openal-soft/core/bsinc_tables.h +++ b/libs/openal-soft/core/bsinc_tables.h @@ -1,17 +1,19 @@ #ifndef CORE_BSINC_TABLES_H #define CORE_BSINC_TABLES_H +#include + #include "bsinc_defs.h" struct BSincTable { float scaleBase, scaleRange; - unsigned int m[BSincScaleCount]; - unsigned int filterOffset[BSincScaleCount]; + std::array m; + std::array filterOffset; const float *Tab; }; -extern const BSincTable bsinc12; -extern const BSincTable bsinc24; +extern const BSincTable gBSinc12; +extern const BSincTable gBSinc24; #endif /* CORE_BSINC_TABLES_H */ diff --git a/libs/openal-soft/core/buffer_storage.cpp b/libs/openal-soft/core/buffer_storage.cpp index 1c80e7ef..780e388a 100644 --- a/libs/openal-soft/core/buffer_storage.cpp +++ b/libs/openal-soft/core/buffer_storage.cpp @@ -3,40 +3,3 @@ #include "buffer_storage.h" -#include - - -uint BytesFromFmt(FmtType type) noexcept -{ - switch(type) - { - case FmtUByte: return sizeof(uint8_t); - case FmtShort: return sizeof(int16_t); - case FmtFloat: return sizeof(float); - case FmtDouble: return sizeof(double); - case FmtMulaw: return sizeof(uint8_t); - case FmtAlaw: return sizeof(uint8_t); - } - return 0; -} - -uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept -{ - switch(chans) - { - case FmtMono: return 1; - case FmtStereo: return 2; - case FmtRear: return 2; - case FmtQuad: return 4; - case FmtX51: return 6; - case FmtX61: return 7; - case FmtX71: return 8; - case FmtBFormat2D: return (ambiorder*2) + 1; - case FmtBFormat3D: return (ambiorder+1) * (ambiorder+1); - case FmtUHJ2: return 2; - case FmtUHJ3: return 3; - case FmtUHJ4: return 4; - case FmtSuperStereo: return 2; - } - return 0; -} diff --git a/libs/openal-soft/core/buffer_storage.h b/libs/openal-soft/core/buffer_storage.h index ec934681..ab097cf1 100644 --- a/libs/openal-soft/core/buffer_storage.h +++ b/libs/openal-soft/core/buffer_storage.h @@ -2,55 +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, -}; -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, -}; - -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; } @@ -79,21 +40,32 @@ struct BufferStorage { CallbackType mCallback{nullptr}; void *mUserData{nullptr}; + al::span mData; + uint mSampleRate{0u}; FmtChannels mChannels{FmtMono}; FmtType mType{FmtShort}; uint mSampleLen{0u}; + uint mBlockAlign{0u}; AmbiLayout mAmbiLayout{AmbiLayout::FuMa}; AmbiScaling mAmbiScaling{AmbiScaling::FuMa}; uint mAmbiOrder{0u}; - inline uint bytesFromFmt() const noexcept { return BytesFromFmt(mType); } - inline uint channelsFromFmt() const noexcept + [[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 bool isBFormat() const noexcept { return IsBFormat(mChannels); } + [[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(); + }; + + [[nodiscard]] auto isBFormat() const noexcept -> bool { return IsBFormat(mChannels); } }; #endif /* CORE_BUFFER_STORAGE_H */ diff --git a/libs/openal-soft/core/bufferline.h b/libs/openal-soft/core/bufferline.h index 8b445f3f..309fb778 100644 --- a/libs/openal-soft/core/bufferline.h +++ b/libs/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/libs/openal-soft/core/context.cpp b/libs/openal-soft/core/context.cpp index 39fd8522..1e19e8fa 100644 --- a/libs/openal-soft/core/context.cpp +++ b/libs/openal-soft/core/context.cpp @@ -1,7 +1,12 @@ #include "config.h" +#include +#include +#include #include +#include +#include #include "async_event.h" #include "context.h" @@ -13,57 +18,34 @@ #include "voice_change.h" +#ifdef __cpp_lib_atomic_is_always_lock_free +static_assert(std::atomic::is_always_lock_free, "atomic isn't lock-free"); +#endif + ContextBase::ContextBase(DeviceBase *device) : mDevice{device} -{ } +{ assert(mEnabledEvts.is_lock_free()); } 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"); + if(auto curarray = mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed)) + std::destroy_n(curarray->end(), curarray->size()); - 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); + 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) @@ -75,64 +57,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) + voice_iter = std::transform(cluster->begin(), cluster->end(), voice_iter, + [](Voice &voice) noexcept -> Voice* { return &voice; }); + + 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& clusterptr : mEffectSlotClusters) { - for(size_t i{0};i < clustersize;++i) - *(voice_iter++) = &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(auto *oldvoices = mVoices.exchange(newarray.release(), std::memory_order_acq_rel)) - { - mDevice->waitForMix(); - delete oldvoices; - } + 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) * clusterptr->size()}; + TRACE("Increasing allocated effect slots to %zu\n", totalcount); + + 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/libs/openal-soft/core/context.h b/libs/openal-soft/core/context.h index f5768629..901ed1a1 100644 --- a/libs/openal-soft/core/context.h +++ b/libs/openal-soft/core/context.h @@ -3,17 +3,20 @@ #include #include +#include #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; @@ -23,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, @@ -40,17 +41,6 @@ enum class DistanceModel : unsigned char { }; -struct WetBuffer { - bool mInUse; - al::FlexArray mBuffer; - - WetBuffer(size_t count) : mBuffer{count} { } - - DEF_FAM_NEWDEL(WetBuffer, mBuffer) -}; -using WetBufferPtr = std::unique_ptr; - - struct ContextProps { std::array Position; std::array Velocity; @@ -67,8 +57,6 @@ struct ContextProps { DistanceModel mDistanceModel; std::atomic next; - - DEF_NEWDEL(ContextProps) }; struct ContextParams { @@ -96,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}; @@ -107,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 @@ -119,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)}; @@ -141,26 +130,42 @@ struct ContextBase { using EffectSlotArray = al::FlexArray; - std::atomic mActiveAuxSlots{nullptr}; + al::atomic_unique_ptr mActiveAuxSlots; std::thread mEventThread; al::semaphore mEventSem; std::unique_ptr mAsyncEvents; - std::atomic mEnabledEvts{0u}; + using AsyncEventBitset = std::bitset; + std::atomic mEnabledEvts{0u}; /* Asynchronous voice change actions are processed as a linked list of * VoiceChange objects by the mixer, which is atomically appended to. * However, to avoid allocating each object individually, they're allocated * in clusters that are stored in a vector for easy automatic cleanup. */ - using VoiceChangeCluster = std::unique_ptr; - 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; + + + EffectSlot *getEffectSlot(); + + 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/libs/openal-soft/core/converter.cpp b/libs/openal-soft/core/converter.cpp index 6a06b464..104d1394 100644 --- a/libs/openal-soft/core/converter.cpp +++ b/libs/openal-soft/core/converter.cpp @@ -4,19 +4,17 @@ #include "converter.h" #include +#include #include +#include #include #include -#include +#include #include "albit.h" -#include "albyte.h" #include "alnumeric.h" #include "fpu_ctrl.h" -struct CTag; -struct CopyTag; - namespace { @@ -26,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); @@ -83,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 @@ -98,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); @@ -127,35 +133,40 @@ 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 LIKELY((chanmask&1)) + 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 -SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans, +SampleConverterPtr SampleConverter::Create(DevFmtType srcType, DevFmtType dstType, size_t numchans, uint srcRate, uint dstRate, Resampler resampler) { if(numchans < 1 || srcRate < 1 || dstRate < 1) @@ -167,16 +178,19 @@ SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, converter->mSrcTypeSize = BytesFromDevFmt(srcType); converter->mDstTypeSize = BytesFromDevFmt(dstType); - converter->mSrcPrepCount = 0; + converter->mSrcPrepCount = MaxResamplerPadding; converter->mFracOffset = 0; + for(auto &chan : converter->mChan) + 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 = Resample_; + converter->mResample = [](const InterpState*, const float *RESTRICT src, uint, const uint, + const al::span dst) { std::copy_n(src, dst.size(), dst.begin()); }; else converter->mResample = PrepareResampler(resampler, converter->mIncrement, &converter->mState); @@ -186,149 +200,220 @@ SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, uint SampleConverter::availableOut(uint srcframes) const { - int prepcount{mSrcPrepCount}; - if(prepcount < 0) - { - /* Negative prepcount means we need to skip that many input samples. */ - if(static_cast(-prepcount) >= srcframes) - return 0; - srcframes -= static_cast(-prepcount); - prepcount = 0; - } - if(srcframes < 1) { /* No output samples if there's no input samples. */ return 0; } - if(prepcount < MaxResamplerPadding - && static_cast(MaxResamplerPadding - prepcount) >= srcframes) + const uint prepcount{mSrcPrepCount}; + if(prepcount < MaxResamplerPadding && MaxResamplerPadding - prepcount >= srcframes) { /* Not enough input samples to generate an output sample. */ return 0; } - auto DataSize64 = static_cast(prepcount); + uint64_t DataSize64{prepcount}; DataSize64 += srcframes; DataSize64 -= MaxResamplerPadding; DataSize64 <<= MixerFracBits; DataSize64 -= mFracOffset; /* If we have a full prep, we can generate at least one sample. */ - return static_cast(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}; + 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{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++) + LoadSamples(al::span{mChan[chan].PrevSamples}.subspan(prepcount, readable), + SamplesIn.data(), chan, mChan.size(), mSrcType); + + 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. + */ + 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. + */ + 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, al::to_address(SrcData.begin()+MaxResamplerEdge), DataPosFrac, + increment, DstData.first(DstSize)); + + StoreSamples(SamplesOut.data(), DstData.first(DstSize), chan, mChan.size(), 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)}; + 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}; - auto SamplesIn = static_cast(*src); uint NumSrcSamples{*srcframes}; FPUCtl mixer_mode{}; uint pos{0}; while(pos < dstframes && NumSrcSamples > 0) { - int prepcount{mSrcPrepCount}; - if(prepcount < 0) - { - /* Negative prepcount means we need to skip that many input samples. */ - if(static_cast(-prepcount) >= NumSrcSamples) - { - mSrcPrepCount = static_cast(NumSrcSamples) + prepcount; - NumSrcSamples = 0; - break; - } - SamplesIn += SrcFrameSize*static_cast(-prepcount); - NumSrcSamples -= static_cast(-prepcount); - mSrcPrepCount = 0; - continue; - } - const uint toread{minu(NumSrcSamples, BufferLineSize - MaxResamplerPadding)}; + const uint prepcount{mSrcPrepCount}; + const uint readable{std::min(NumSrcSamples, uint{BufferLineSize} - prepcount)}; - if(prepcount < MaxResamplerPadding - && static_cast(MaxResamplerPadding - prepcount) >= toread) + 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++) - LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan, - mChan.size(), mSrcType, toread); + { + 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 + static_cast(toread); + 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}; - auto DataSize64 = static_cast(prepcount); - DataSize64 += toread; + 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( - 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{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, toread); + 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. */ - uint SrcDataEnd{(DstSize*increment + DataPosFrac)>>MixerFracBits}; - if(SrcDataEnd >= static_cast(prepcount)+toread) - std::fill(std::begin(mChan[chan].PrevSamples), - std::end(mChan[chan].PrevSamples), 0.0f); - else - { - const size_t len{minz(al::size(mChan[chan].PrevSamples), - static_cast(prepcount)+toread-SrcDataEnd)}; - std::copy_n(SrcData+SrcDataEnd, len, mChan[chan].PrevSamples); - std::fill(std::begin(mChan[chan].PrevSamples)+len, - std::end(mChan[chan].PrevSamples), 0.0f); - } + 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. */ - const float *ResampledData{mResample(&mState, SrcData+(MaxResamplerPadding>>1), - DataPosFrac, increment, {DstData, DstSize})}; + mResample(&mState, al::to_address(SrcData.begin()+MaxResamplerEdge), DataPosFrac, + increment, DstData.first(DstSize)); - StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, 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. */ - DataPosFrac += increment*DstSize; - mSrcPrepCount = mini(prepcount + static_cast(toread - (DataPosFrac>>MixerFracBits)), - MaxResamplerPadding); - mFracOffset = DataPosFrac & MixerFracMask; + mSrcPrepCount = nextprep; + mFracOffset = DataPosEnd & MixerFracMask; /* Update the src and dst pointers in case there's still more to do. */ - SamplesIn += SrcFrameSize*(DataPosFrac>>MixerFracBits); - NumSrcSamples -= minu(NumSrcSamples, (DataPosFrac>>MixerFracBits)); + 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; @@ -342,7 +427,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); @@ -357,7 +442,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/libs/openal-soft/core/converter.h b/libs/openal-soft/core/converter.h index 2d22ae38..01649c36 100644 --- a/libs/openal-soft/core/converter.h +++ b/libs/openal-soft/core/converter.h @@ -1,12 +1,15 @@ #ifndef CORE_CONVERTER_H #define CORE_CONVERTER_H +#include #include #include #include "almalloc.h" #include "devformat.h" +#include "flexarray.h" #include "mixer/defs.h" +#include "resampler_limits.h" using uint = unsigned int; @@ -17,41 +20,48 @@ struct SampleConverter { uint mSrcTypeSize{}; uint mDstTypeSize{}; - int mSrcPrepCount{}; + uint mSrcPrepCount{}; uint mFracOffset{}; uint mIncrement{}; 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>; + [[nodiscard]] auto currentInputDelay() const noexcept -> SampleOffset + { + const int64_t prep{int64_t{mSrcPrepCount} - MaxResamplerEdge}; + return SampleOffset{(prep< Create(DevFmtType srcType, DevFmtType dstType, + size_t numchans, uint srcRate, uint dstRate, Resampler resampler); DEF_FAM_NEWDEL(SampleConverter, mChan) }; using SampleConverterPtr = std::unique_ptr; -SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans, - uint srcRate, uint dstRate, Resampler resampler); - - struct ChannelConverter { DevFmtType mSrcType{}; uint mSrcStep{}; uint mChanMask{}; DevFmtChannels mDstChans{}; - bool is_active() const noexcept { return mChanMask != 0; } + [[nodiscard]] auto is_active() const noexcept -> bool { return mChanMask != 0; } void convert(const void *src, float *dst, uint frames) const; }; diff --git a/libs/openal-soft/core/cpu_caps.cpp b/libs/openal-soft/core/cpu_caps.cpp index 8211b331..1a064cf4 100644 --- a/libs/openal-soft/core/cpu_caps.cpp +++ b/libs/openal-soft/core/cpu_caps.cpp @@ -11,13 +11,13 @@ #endif #endif -#ifdef HAVE_INTRIN_H +#if defined(HAVE_CPUID_H) +#include +#elif defined(HAVE_INTRIN_H) #include #endif -#ifdef HAVE_CPUID_H -#include -#endif +#include #include #include #include @@ -33,7 +33,7 @@ using reg_type = unsigned int; inline std::array get_cpuid(unsigned int f) { std::array ret{}; - __get_cpuid(f, &ret[0], &ret[1], &ret[2], &ret[3]); + __get_cpuid(f, ret.data(), &ret[1], &ret[2], &ret[3]); return ret; } #define CAN_GET_CPUID @@ -51,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]}; @@ -138,5 +138,5 @@ al::optional GetCPUInfo() #endif #endif - return al::make_optional(ret); + return ret; } diff --git a/libs/openal-soft/core/cpu_caps.h b/libs/openal-soft/core/cpu_caps.h index ffd671d0..0826a49b 100644 --- a/libs/openal-soft/core/cpu_caps.h +++ b/libs/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/libs/openal-soft/core/cubic_defs.h b/libs/openal-soft/core/cubic_defs.h new file mode 100644 index 00000000..a3820816 --- /dev/null +++ b/libs/openal-soft/core/cubic_defs.h @@ -0,0 +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 { + alignas(16) std::array mCoeffs; + alignas(16) std::array mDeltas; +}; + +#endif /* CORE_CUBIC_DEFS_H */ diff --git a/libs/openal-soft/core/cubic_tables.cpp b/libs/openal-soft/core/cubic_tables.cpp new file mode 100644 index 00000000..adb7abfb --- /dev/null +++ b/libs/openal-soft/core/cubic_tables.cpp @@ -0,0 +1,91 @@ + +#include "cubic_tables.h" + +#include +#include +#include + +#include "alnumbers.h" +#include "alnumeric.h" +#include "cubic_defs.h" + +/* These 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 phase coefficients to more cleanly transition from the + * end of one sample set to the start of the next. + */ +namespace { + +[[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 + +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]; +} + +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;++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/libs/openal-soft/core/cubic_tables.h b/libs/openal-soft/core/cubic_tables.h new file mode 100644 index 00000000..366923b6 --- /dev/null +++ b/libs/openal-soft/core/cubic_tables.h @@ -0,0 +1,38 @@ +#ifndef CORE_CUBIC_TABLES_H +#define CORE_CUBIC_TABLES_H + +#include +#include + +#include "cubic_defs.h" + + +struct CubicTable { + std::array mTable{}; +}; + +struct GaussianTable : CubicTable { GaussianTable(); }; +inline const GaussianTable gGaussianFilter; + + +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/libs/openal-soft/core/dbus_wrap.cpp b/libs/openal-soft/core/dbus_wrap.cpp index 7f221706..05d9fc06 100644 --- a/libs/openal-soft/core/dbus_wrap.cpp +++ b/libs/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/libs/openal-soft/core/dbus_wrap.h b/libs/openal-soft/core/dbus_wrap.h index 09eaacf9..65f08942 100644 --- a/libs/openal-soft/core/dbus_wrap.h +++ b/libs/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 diff --git a/libs/openal-soft/core/devformat.cpp b/libs/openal-soft/core/devformat.cpp index c841b634..acdabc4f 100644 --- a/libs/openal-soft/core/devformat.cpp +++ b/libs/openal-soft/core/devformat.cpp @@ -28,6 +28,8 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept case DevFmtX51: return 6; case DevFmtX61: return 7; case DevFmtX71: return 8; + case DevFmtX714: return 12; + case DevFmtX3D71: return 8; case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1); } return 0; @@ -57,6 +59,8 @@ const char *DevFmtChannelsString(DevFmtChannels chans) noexcept case DevFmtX51: return "5.1 Surround"; case DevFmtX61: return "6.1 Surround"; case DevFmtX71: return "7.1 Surround"; + case DevFmtX714: return "7.1.4 Surround"; + case DevFmtX3D71: return "3D7.1 Surround"; case DevFmtAmbi3D: return "Ambisonic 3D"; } return "(unknown channels)"; diff --git a/libs/openal-soft/core/devformat.h b/libs/openal-soft/core/devformat.h index e6d30924..d918e531 100644 --- a/libs/openal-soft/core/devformat.h +++ b/libs/openal-soft/core/devformat.h @@ -2,6 +2,7 @@ #define CORE_DEVFORMAT_H #include +#include using uint = unsigned int; @@ -25,6 +26,23 @@ enum Channel : unsigned char { TopBackCenter, TopBackRight, + Aux0, + Aux1, + Aux2, + Aux3, + Aux4, + Aux5, + Aux6, + Aux7, + Aux8, + Aux9, + Aux10, + Aux11, + Aux12, + Aux13, + Aux14, + Aux15, + MaxChannels }; @@ -48,11 +66,13 @@ enum DevFmtChannels : unsigned char { DevFmtX51, DevFmtX61, DevFmtX71, + DevFmtX714, + DevFmtX3D71, DevFmtAmbi3D, DevFmtChannelsDefault = DevFmtStereo }; -#define MAX_OUTPUT_CHANNELS 16 +inline constexpr size_t MaxOutputChannels{16}; /* DevFmtType traits, providing the type, etc given a DevFmtType. */ template diff --git a/libs/openal-soft/core/device.cpp b/libs/openal-soft/core/device.cpp index 2766c5e4..795a9601 100644 --- a/libs/openal-soft/core/device.cpp +++ b/libs/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/libs/openal-soft/core/device.h b/libs/openal-soft/core/device.h index 58a30f1b..1e8ec507 100644 --- a/libs/openal-soft/core/device.h +++ b/libs/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 44100 +inline constexpr size_t MinOutputRate{8000}; +inline constexpr size_t MaxOutputRate{192000}; +inline constexpr size_t DefaultOutputRate{48000}; -#define DEFAULT_UPDATE_SIZE 882 /* 20ms */ -#define DEFAULT_NUM_UPDATES 3 +inline constexpr size_t DefaultUpdateSize{960}; /* 20ms */ +inline constexpr size_t DefaultNumUpdates{3}; -enum class DeviceType : unsigned char { +enum class DeviceType : uint8_t { Playback, Capture, Loopback }; -enum class RenderMode : unsigned char { +enum class RenderMode : uint8_t { Normal, Pairwise, Hrtf }; -enum class StereoEncoding : unsigned char { +enum class StereoEncoding : uint8_t { Basic, Uhj, Hrtf, @@ -69,21 +71,20 @@ struct InputRemixMap { struct TargetMix { Channel channel; float mix; }; Channel channel; - std::array targets; + al::span targets; }; -/* Maximum delay in samples for speaker distance compensation. */ -#define MAX_DELAY_LENGTH 1024 - struct DistanceComp { + /* Maximum delay in samples for speaker distance compensation. */ + 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...MAX_DELAY_LENGTH). */ - float *Buffer{nullptr}; }; - std::array mChannels; + std::array mChannels; al::FlexArray mSamples; DistanceComp(size_t count) : mSamples{count} { } @@ -95,52 +96,90 @@ struct DistanceComp { }; +constexpr uint8_t InvalidChannelIndex{static_cast(~0u)}; + struct BFChannelConfig { float Scale; uint Index; }; - struct MixParams { /* Coefficient channel mapping for mixing to the buffer. */ - std::array AmbiMap{}; + std::array AmbiMap{}; al::span Buffer; + + /** + * Helper to set an identity/pass-through panning for ambisonic mixing. The + * 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 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) + { + uint8_t idx{InvalidChannelIndex}; + float gain{0.0f}; + + for(size_t j{0};j < numOut;++j) + { + if(AmbiMap[j].Index == inmix.AmbiMap[i].Index) + { + idx = static_cast(j); + gain = AmbiMap[j].Scale * gainbase; + break; + } + } + func(i, idx, gain); + } + } }; struct RealMixParams { al::span RemixMap; - std::array ChannelIndex{}; + std::array ChannelIndex{}; al::span Buffer; }; +using AmbiRotateMatrix = std::array,MaxAmbiChannels>; + enum { // Frequency was requested by the app or config file FrequencyRequest, - // Channel configuration was requested by the config file + // Channel configuration was requested by the app or config file ChannelsRequest, // Sample type was requested by the config file SampleTypeRequest, // Specifies if the DSP is paused at user request DevicePaused, - // Specifies if the device is currently running - DeviceRunning, // 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 : uint8_t { + Unprepared, + Configured, + Playing +}; +struct DeviceBase { std::atomic Connected{true}; const DeviceType Type{}; @@ -152,6 +191,8 @@ struct DeviceBase { DevFmtType FmtType{}; uint mAmbiOrder{0}; float mXOverFreq{400.0f}; + /* If the main device mix is horizontal/2D only. */ + bool m2DMixing{false}; /* For DevFmtAmbi* output only, specifies the channel order and * normalization. */ @@ -162,6 +203,7 @@ struct DeviceBase { // Device flags std::bitset Flags{}; + DeviceState mDeviceState{DeviceState::Unprepared}; uint NumAuxSends{}; @@ -178,33 +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}; - /* Temp storage used for mixer processing. */ - static constexpr size_t MixerLineSize{BufferLineSize + MaxResamplerPadding + - UhjDecoder::sFilterDelay}; - static constexpr size_t MixerChannelsMax{16}; - using MixerBufferLine = std::array; - alignas(16) std::array mSampleData; + AmbiRotateMatrix mAmbiRotateMatrix{}; + AmbiRotateMatrix mAmbiRotateMatrix2{}; - alignas(16) float ResampledData[BufferLineSize]; - alignas(16) float FilteredData[BufferLineSize]; - union { - alignas(16) float HrtfSourceData[BufferLineSize + HrtfHistoryLength]; - alignas(16) float NfcSampleData[BufferLineSize]; - }; + /* Temp storage used for mixer processing. */ + static constexpr size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding}; + static constexpr size_t MixerChannelsMax{16}; + alignas(16) std::array mSampleData{}; + alignas(16) std::array mResampleData{}; + + 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. @@ -217,13 +257,13 @@ struct DeviceBase { uint mIrSize{0}; /* Ambisonic-to-UHJ encoder */ - std::unique_ptr mUhjEncoder; + std::unique_ptr mUhjEncoder; /* Ambisonic decoder for speakers */ 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}; @@ -242,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); @@ -253,18 +293,54 @@ 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; } + /** + * 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; + + auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / Frequency; + return mClockBase.load(std::memory_order_relaxed) + ns; + } + void ProcessHrtf(const size_t SamplesToDo); void ProcessAmbiDec(const size_t SamplesToDo); void ProcessAmbiDecStablized(const size_t SamplesToDo); @@ -272,39 +348,36 @@ struct DeviceBase { void ProcessBs2b(const size_t SamplesToDo); inline void postProcess(const size_t SamplesToDo) - { if LIKELY(PostProcess) (this->*PostProcess)(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); /* 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 void handleDisconnect(const char *msg, ...); - DISABLE_ALLOC() + /** + * Returns the index for the given channel name (e.g. FrontCenter), or + * InvalidChannelIndex if it doesn't exist. + */ + [[nodiscard]] auto channelIdxByName(Channel chan) const noexcept -> uint8_t + { return RealOut.ChannelIndex[chan]; } 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" - - -/** - * Returns the index for the given channel name (e.g. FrontCenter), or - * INVALID_CHANNEL_INDEX if it doesn't exist. - */ -inline uint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcept -{ return real.ChannelIndex[chan]; } -#define INVALID_CHANNEL_INDEX ~0u +[[nodiscard]] constexpr +auto GetRecordThreadName() noexcept -> const char* { return "alsoft-record"; } #endif /* CORE_DEVICE_H */ diff --git a/libs/openal-soft/core/effects/base.h b/libs/openal-soft/core/effects/base.h index 3094f627..2e1eaa75 100644 --- a/libs/openal-soft/core/effects/base.h +++ b/libs/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,118 +58,148 @@ enum class VMorpherWaveform { Sawtooth }; -union EffectProps { - struct { - // Shared Reverb Properties - float Density; - float Diffusion; - float Gain; - float GainHF; - float DecayTime; - float DecayHFRatio; - float ReflectionsGain; - float ReflectionsDelay; - float LateReverbGain; - float LateReverbDelay; - float AirAbsorptionGainHF; - float RoomRolloffFactor; - bool DecayHFLimit; - - // Additional EAX Reverb Properties - float GainLF; - float DecayLFRatio; - float ReflectionsPan[3]; - float LateReverbPan[3]; - float EchoTime; - float EchoDepth; - float ModulationTime; - float ModulationDepth; - float HFReference; - float LFReference; - } Reverb; - - struct { - float AttackTime; - float ReleaseTime; - float Resonance; - float PeakGain; - } Autowah; - - struct { - ChorusWaveform Waveform; - int Phase; - float Rate; - float Depth; - float Feedback; - float Delay; - } Chorus; /* Also Flanger */ - - struct { - bool OnOff; - } Compressor; - - struct { - float Edge; - float Gain; - float LowpassCutoff; - float EQCenter; - float EQBandwidth; - } Distortion; - - struct { - float Delay; - float LRDelay; - - float Damping; - float Feedback; - - float Spread; - } Echo; - - struct { - float LowCutoff; - float LowGain; - float Mid1Center; - float Mid1Gain; - float Mid1Width; - float Mid2Center; - float Mid2Gain; - float Mid2Width; - float HighCutoff; - float HighGain; - } Equalizer; - - struct { - float Frequency; - FShifterDirection LeftDirection; - FShifterDirection RightDirection; - } Fshifter; - - struct { - float Frequency; - float HighPassCutoff; - ModulatorWaveform Waveform; - } Modulator; - - struct { - int CoarseTune; - int FineTune; - } Pshifter; - - struct { - float Rate; - VMorpherPhenome PhonemeA; - VMorpherPhenome PhonemeB; - int PhonemeACoarseTuning; - int PhonemeBCoarseTuning; - VMorpherWaveform Waveform; - } Vmorpher; - - struct { - float Gain; - } Dedicated; +struct 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 FlangerProps { + 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 DedicatedDialogProps { + float Gain; +}; + +struct DedicatedLfeProps { + float Gain; +}; + +struct ConvolutionProps { + std::array OrientAt; + std::array OrientUp; +}; + +using EffectProps = std::variant; + struct EffectTarget { MixParams *Main; @@ -178,17 +207,12 @@ struct EffectTarget { }; struct EffectState : public al::intrusive_ref { - struct Buffer { - const BufferStorage *storage; - al::span samples; - }; - al::span mOutTarget; virtual ~EffectState() = default; - virtual void deviceUpdate(const DeviceBase *device, const Buffer &buffer) = 0; + virtual void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) = 0; virtual void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) = 0; virtual void process(const size_t samplesToDo, const al::span samplesIn, diff --git a/libs/openal-soft/core/effectslot.cpp b/libs/openal-soft/core/effectslot.cpp index 51fb8d46..ed737f49 100644 --- a/libs/openal-soft/core/effectslot.cpp +++ b/libs/openal-soft/core/effectslot.cpp @@ -3,23 +3,16 @@ #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); -} - -EffectSlot::~EffectSlot() -{ - if(mWetBuffer) - mWetBuffer->mInUse = false; + return std::unique_ptr{new(FamCount{count*2}) EffectSlotArray(count)}; } diff --git a/libs/openal-soft/core/effectslot.h b/libs/openal-soft/core/effectslot.h index 8b7b977c..65116ce2 100644 --- a/libs/openal-soft/core/effectslot.h +++ b/libs/openal-soft/core/effectslot.h @@ -2,10 +2,12 @@ #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; @@ -45,12 +47,12 @@ struct EffectSlotProps { al::intrusive_ptr State; std::atomic next; - - DEF_NEWDEL(EffectSlotProps) }; struct EffectSlot { + bool InUse{false}; + std::atomic Update{nullptr}; /* Wet buffer configuration is ACN channel order with N3D scaling. @@ -66,7 +68,7 @@ struct EffectSlot { EffectSlotType EffectType{EffectSlotType::None}; EffectProps mEffectProps{}; - EffectState *mEffectState{nullptr}; + al::intrusive_ptr mEffectState; float RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */ float DecayTime{0.0f}; @@ -76,13 +78,10 @@ struct EffectSlot { float AirAbsorptionGainHF{1.0f}; /* Mixing buffer used by the Wet mix. */ - WetBuffer *mWetBuffer{nullptr}; + al::vector mWetBuffer; - ~EffectSlot(); - static EffectSlotArray *CreatePtrArray(size_t count) noexcept; - - DISABLE_ALLOC() + static std::unique_ptr CreatePtrArray(size_t count); }; #endif /* CORE_EFFECTSLOT_H */ diff --git a/libs/openal-soft/core/except.cpp b/libs/openal-soft/core/except.cpp index 07bb410a..f338e9ae 100644 --- a/libs/openal-soft/core/except.cpp +++ b/libs/openal-soft/core/except.cpp @@ -11,21 +11,22 @@ namespace al { -/* Defined here to avoid inlining it. */ -base_exception::~base_exception() { } +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 LIKELY(msglen > 0) + if(msglen > 0) LIKELY { mMessage.resize(static_cast(msglen)+1); - std::vsnprintf(&mMessage[0], 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/libs/openal-soft/core/except.h b/libs/openal-soft/core/except.h index 0e28e9df..5013893a 100644 --- a/libs/openal-soft/core/except.h +++ b/libs/openal-soft/core/except.h @@ -13,13 +13,13 @@ 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() override; + + [[nodiscard]] auto what() const noexcept -> const char* override { return mMessage.c_str(); } }; } // namespace al diff --git a/libs/openal-soft/core/filters/biquad.cpp b/libs/openal-soft/core/filters/biquad.cpp index 470b1cd3..c1faf5e9 100644 --- a/libs/openal-soft/core/filters/biquad.cpp +++ b/libs/openal-soft/core/filters/biquad.cpp @@ -3,6 +3,7 @@ #include "biquad.h" +#include #include #include #include @@ -14,8 +15,12 @@ template void BiquadFilterR::setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ) { - // Limit gain to -100dB - assert(gain > 0.00001f); + /* HACK: Limit gain to -100dB. This shouldn't ever happen, all callers + * already clamp to minimum of 0.001, or have a limited range of values + * that don't go below 0.126. But it seems to with some callers. This needs + * to be investigated. + */ + gain = std::max(gain, Real(0.00001)); const Real w0{al::numbers::pi_v*2.0f * f0norm}; const Real sin_w0{std::sin(w0)}; @@ -23,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) @@ -90,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}; @@ -115,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; @@ -123,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}; @@ -152,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/libs/openal-soft/core/filters/nfc.cpp b/libs/openal-soft/core/filters/nfc.cpp index aa64c613..c3c313db 100644 --- a/libs/openal-soft/core/filters/nfc.cpp +++ b/libs/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/libs/openal-soft/core/filters/nfc.h b/libs/openal-soft/core/filters/nfc.h index 33f67a5f..00ab4dd7 100644 --- a/libs/openal-soft/core/filters/nfc.h +++ b/libs/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/libs/openal-soft/core/filters/splitter.cpp b/libs/openal-soft/core/filters/splitter.cpp index e7f03756..fbb6b2b7 100644 --- a/libs/openal-soft/core/filters/splitter.cpp +++ b/libs/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; @@ -160,17 +164,18 @@ void BandSplitterR::processScale(const al::span samples, const Real } template -void BandSplitterR::applyAllpassRev(const al::span samples) const +void BandSplitterR::processAllPass(const al::span samples) { const Real coeff{mCoeff}; - Real z1{0.0f}; + Real z1{mApZ1}; auto proc_sample = [coeff,&z1](const Real in) noexcept -> Real { const Real out{in*coeff + z1}; z1 = in - out*coeff; return out; }; - std::transform(samples.rbegin(), samples.rend(), samples.rbegin(), proc_sample); + std::transform(samples.cbegin(), samples.cend(), samples.begin(), proc_sample); + mApZ1 = z1; } diff --git a/libs/openal-soft/core/filters/splitter.h b/libs/openal-soft/core/filters/splitter.h index fa15bd50..e3658c19 100644 --- a/libs/openal-soft/core/filters/splitter.h +++ b/libs/openal-soft/core/filters/splitter.h @@ -22,21 +22,20 @@ 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); /** * The all-pass portion of the band splitter. Applies the same phase shift - * without splitting the signal, in reverse. It starts from the back of the - * span and works toward the front, creating a phase shift of -n degrees - * instead of +n. Note that each use of this method is indepedent, it does - * not track history between calls. + * without splitting or scaling the signal. */ - void applyAllpassRev(const al::span samples) const; + void processAllPass(const al::span samples); }; using BandSplitter = BandSplitterR; diff --git a/libs/openal-soft/core/fmt_traits.cpp b/libs/openal-soft/core/fmt_traits.cpp index 054d8766..9d79287d 100644 --- a/libs/openal-soft/core/fmt_traits.cpp +++ b/libs/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/libs/openal-soft/core/fmt_traits.h b/libs/openal-soft/core/fmt_traits.h index f797f836..e87ea57c 100644 --- a/libs/openal-soft/core/fmt_traits.h +++ b/libs/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/libs/openal-soft/core/fpu_ctrl.cpp b/libs/openal-soft/core/fpu_ctrl.cpp index 0cf0d6e7..28e60c04 100644 --- a/libs/openal-soft/core/fpu_ctrl.cpp +++ b/libs/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/libs/openal-soft/core/fpu_ctrl.h b/libs/openal-soft/core/fpu_ctrl.h index 9554313a..d4f75ec3 100644 --- a/libs/openal-soft/core/fpu_ctrl.h +++ b/libs/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/libs/openal-soft/core/front_stablizer.h b/libs/openal-soft/core/front_stablizer.h index 3d328a8d..8eeb6d74 100644 --- a/libs/openal-soft/core/front_stablizer.h +++ b/libs/openal-soft/core/front_stablizer.h @@ -7,30 +7,26 @@ #include "almalloc.h" #include "bufferline.h" #include "filters/splitter.h" +#include "flexarray.h" struct FrontStablizer { - static constexpr size_t DelayLength{256u}; + FrontStablizer(size_t numchans) : ChannelFilters{numchans} { } - FrontStablizer(size_t numchans) : DelayBuf{numchans} { } - - alignas(16) std::array Side{}; - alignas(16) std::array MidDirect{}; - alignas(16) std::array MidDelay{}; - - alignas(16) std::array TempBuf{}; + alignas(16) std::array MidDirect{}; + alignas(16) std::array Side{}; + alignas(16) std::array Temp{}; BandSplitter MidFilter; alignas(16) FloatBufferLine MidLF{}; alignas(16) FloatBufferLine MidHF{}; - using DelayLine = std::array; - al::FlexArray DelayBuf; + al::FlexArray ChannelFilters; static std::unique_ptr Create(size_t numchans) { return std::unique_ptr{new(FamCount(numchans)) FrontStablizer{numchans}}; } - DEF_FAM_NEWDEL(FrontStablizer, DelayBuf) + DEF_FAM_NEWDEL(FrontStablizer, ChannelFilters) }; #endif /* CORE_FRONT_STABLIZER_H */ diff --git a/libs/openal-soft/core/helpers.cpp b/libs/openal-soft/core/helpers.cpp index e4a94fe5..1898f5e0 100644 --- a/libs/openal-soft/core/helpers.cpp +++ b/libs/openal-soft/core/helpers.cpp @@ -3,103 +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 = al::make_optional(); - return *procbin; - } - - fullpath.resize(len); - if(fullpath.back() != 0) - fullpath.push_back(0); - - auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\'); - sep = std::find(fullpath.rbegin()+1, sep, '/'); - if(sep != fullpath.rend()) - { - *sep = 0; - procbin = al::make_optional(wstr_to_utf8(fullpath.data()), - wstr_to_utf8(&*sep + 1)); - } - else - procbin = al::make_optional(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) +using namespace std::string_view_literals; + +std::mutex gSearchLock; + +void DirectorySearch(const std::filesystem::path &path, const std::string_view ext, + std::vector *const results) { - std::string pathstr{path}; - pathstr += "\\*"; - pathstr += ext; - TRACE("Searching %s\n", pathstr.c_str()); + namespace fs = std::filesystem; - std::wstring wpath{utf8_to_wstr(pathstr.c_str())}; - WIN32_FIND_DATAW fdata; - HANDLE hdl{FindFirstFileW(wpath.c_str(), &fdata)}; - if(hdl == INVALID_HANDLE_VALUE) return; + const auto base = static_cast>(results->size()); - const auto base = results->size(); + try { + auto fpath = path.lexically_normal(); + if(!fs::exists(fpath)) + return; - do { - results->emplace_back(); - std::string &str = results->back(); - str = path; - str += '\\'; - str += wstr_to_utf8(fdata.cFileName); - } while(FindNextFileW(hdl, &fdata)); - FindClose(hdl); + 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; - const al::span newlist{results->data()+base, results->size()-base}; + 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 al::span newlist{results->begin()+base, results->end()}; std::sort(newlist.begin(), newlist.end()); for(const auto &name : newlist) TRACE(" got %s\n", name.c_str()); @@ -107,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 @@ -198,7 +203,6 @@ void SetRTPriority(void) #include #endif #ifdef HAVE_RTKIT -#include #include #include "dbus_wrap.h" @@ -210,185 +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 = al::make_optional(std::string(pathname.cbegin(), sep.base()-1), - std::string(sep.base(), pathname.cend())); - else - procbin = al::make_optional(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}; @@ -396,24 +327,27 @@ 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 */ + if(auto instpath = std::filesystem::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty()) + DirectorySearch(instpath/path, ext, &results); +#endif + return results; } namespace { -bool SetRTPriorityPthread(int prio) +bool SetRTPriorityPthread(int prio [[maybe_unused]]) { int err{ENOTSUP}; #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) @@ -426,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()) @@ -461,6 +392,17 @@ bool SetRTPriorityRTKit(int prio) /* Don't stupidly exit if the connection dies while doing this. */ dbus_connection_set_exit_on_disconnect(conn.get(), false); + int nicemin{}; + int err{rtkit_get_min_nice_level(conn.get(), &nicemin)}; + if(err == -ENOENT) + { + err = std::abs(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())}; + TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin); + auto limit_rttime = [](DBusConnection *c) -> int { using ulonglong = unsigned long long; @@ -472,29 +414,18 @@ bool SetRTPriorityRTKit(int prio) if(getrlimit(RLIMIT_RTTIME, &rlim) != 0) return errno; - TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime, ulonglong{rlim.rlim_max}, - ulonglong{rlim.rlim_cur}); + TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime, + static_cast(rlim.rlim_max), static_cast(rlim.rlim_cur)); if(rlim.rlim_max > umaxtime) { - rlim.rlim_max = static_cast(umaxtime); + rlim.rlim_max = static_cast(std::min(umaxtime, + std::numeric_limits::max())); rlim.rlim_cur = std::min(rlim.rlim_cur, rlim.rlim_max); if(setrlimit(RLIMIT_RTTIME, &rlim) != 0) return errno; } return 0; }; - - int nicemin{}; - int err{rtkit_get_min_nice_level(conn.get(), &nicemin)}; - if(err == -ENOENT) - { - err = std::abs(err); - ERR("Could not query RTKit: %s (%d)\n", std::strerror(err), err); - return false; - } - int rtmax{rtkit_get_max_realtime_priority(conn.get())}; - TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin); - if(rtmax > 0) { if(AllowRTTimeLimit) @@ -502,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 @@ -529,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/libs/openal-soft/core/helpers.h b/libs/openal-soft/core/helpers.h index f0bfcf1b..3a987c9d 100644 --- a/libs/openal-soft/core/helpers.h +++ b/libs/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/libs/openal-soft/core/hrtf.cpp b/libs/openal-soft/core/hrtf.cpp index d4d69815..6bbdf262 100644 --- a/libs/openal-soft/core/hrtf.cpp +++ b/libs/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,20 +37,42 @@ #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(); }; +HrtfEntry::~HrtfEntry() = default; struct LoadedHrtf { std::string mFilename; + uint mSampleRate{}; std::unique_ptr mEntry; + + template + 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. */ + ~LoadedHrtf(); + + LoadedHrtf& operator=(LoadedHrtf&&) = default; }; +LoadedHrtf::~LoadedHrtf() = default; + /* Data set limits must be the same as or more flexible than those defined in * the makemhr utility. @@ -70,22 +95,29 @@ 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; class databuf final : public std::streambuf { @@ -97,34 +129,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 @@ -136,15 +166,14 @@ 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())); } }; @@ -152,8 +181,7 @@ 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); } }; @@ -164,11 +192,11 @@ struct IdxBlend { uint idx; float blend; }; */ IdxBlend CalcEvIndex(uint evcount, float ev) { - ev = (al::numbers::pi_v*0.5f + ev) * static_cast(evcount-1) / - al::numbers::pi_v; + ev = (al::numbers::pi_v*0.5f + ev) * static_cast(evcount-1) * + 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 @@ -176,8 +204,8 @@ IdxBlend CalcEvIndex(uint evcount, float ev) */ IdxBlend CalcAzIndex(uint azcount, float az) { - az = (al::numbers::pi_v*2.0f + az) * static_cast(azcount) / - (al::numbers::pi_v*2.0f); + az = (al::numbers::pi_v*2.0f + az) * static_cast(azcount) * + (al::numbers::inv_pi_v*0.5f); uint idx{float2uint(az)}; return IdxBlend{idx%azcount, az-static_cast(idx)}; @@ -189,68 +217,69 @@ IdxBlend CalcAzIndex(uint azcount, float az) /* Calculates static HRIR coefficients and delays for the given polar elevation * and azimuth in radians. The coefficients are normalized. */ -void GetHrtfCoeffs(const HrtfStore *Hrtf, float elevation, float azimuth, float distance, - float spread, HrirArray &coeffs, const al::span delays) +void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float spread, + HrirArray &coeffs, const al::span delays) const { const float dirfact{1.0f - (al::numbers::inv_pi_v/2.0f * spread)}; - const auto *field = Hrtf->field; - const auto *field_end = field + Hrtf->fdCount-1; size_t ebase{0}; - while(distance < field->distance && field != field_end) + auto match_field = [&ebase,distance](const Field &field) noexcept -> bool { - ebase += field->evCount; - ++field; - } + if(distance >= field.distance) + return true; + ebase += field.evCount; + return false; + }; + auto field = std::find_if(mFields.begin(), mFields.end()-1, match_field); /* 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 ir0offset{Hrtf->elev[ebase + elev0.idx].irOffset}; - const size_t ir1offset{Hrtf->elev[ebase + elev1_idx].irOffset}; + 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}; /* Calculate azimuth indices. */ - const auto az0 = CalcAzIndex(Hrtf->elev[ebase + elev0.idx].azCount, azimuth); - const auto az1 = CalcAzIndex(Hrtf->elev[ebase + elev1_idx].azCount, azimuth); + const auto az0 = CalcAzIndex(mElev[ebase + elev0.idx].azCount, azimuth); + 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) % Hrtf->elev[ebase + elev0.idx].azCount), + ir0offset + ((az0.idx+1) % mElev[ebase + elev0.idx].azCount), ir1offset + az1.idx, - ir1offset + ((az1.idx+1) % Hrtf->elev[ebase + elev1_idx].azCount) - }; + 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{Hrtf->delays[idx[0]][0]*blend[0] + Hrtf->delays[idx[1]][0]*blend[1] + - Hrtf->delays[idx[2]][0]*blend[2] + Hrtf->delays[idx[3]][0]*blend[3]}; + 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 = Hrtf->delays[idx[0]][1]*blend[0] + Hrtf->delays[idx[1]][1]*blend[1] + - Hrtf->delays[idx[2]][1]*blend[2] + Hrtf->delays[idx[3]][1]*blend[3]; + 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][0])}; + 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); for(size_t c{0};c < 4;c++) { - const float *srccoeffs{al::assume_aligned<16>(Hrtf->coeffs[idx[c]][0].data())}; + 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); + std::transform(srccoeffs, srccoeffs + HrirLength*2_uz, coeffout, coeffout, blend_coeffs); } } @@ -258,8 +287,9 @@ void GetHrtfCoeffs(const HrtfStore *Hrtf, float elevation, float azimuth, float 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 al::span AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels], +void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin, + const al::span AmbiPoints, + const al::span> AmbiMatrix, const float XOverFreq, const al::span AmbiOrderHFGain) { using double2 = std::array; @@ -268,48 +298,43 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, uint ldelay, rdelay; }; - const double xover_norm{double{XOverFreq} / Hrtf->sampleRate}; - for(size_t i{0};i < mChannels.size();++i) + const double xover_norm{double{XOverFreq} / Hrtf->mSampleRate}; + mChannels[0].mSplitter.init(static_cast(xover_norm)); + mChannels[0].mHfScale = AmbiOrderHFGain[0]; + for(size_t i{1};i < mChannels.size();++i) { - const size_t order{AmbiIndex::OrderFromChannel()[i]}; - mChannels[i].mSplitter.init(static_cast(xover_norm)); + 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->field[0]; + 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 ir0offset{Hrtf->elev[elev0.idx].irOffset}; - const size_t ir1offset{Hrtf->elev[elev1_idx].irOffset}; + 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->elev[elev0.idx].azCount, pt.Azim.value); - const auto az1 = CalcAzIndex(Hrtf->elev[elev1_idx].azCount, pt.Azim.value); + 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->elev[elev0.idx].azCount), + ir0offset + ((az0.idx+1) % Hrtf->mElev[elev0.idx].azCount), ir1offset + az1.idx, - ir1offset + ((az1.idx+1) % Hrtf->elev[elev1_idx].azCount) + ir1offset + ((az1.idx+1) % Hrtf->mElev[elev1_idx].azCount) }; - const std::array blend{{ - (1.0-elev0.blend) * (1.0-az0.blend), - (1.0-elev0.blend) * ( az0.blend), - ( elev0.blend) * (1.0-az1.blend), - ( elev0.blend) * ( az1.blend) - }}; - /* The largest blend factor serves as the closest HRIR. */ - const size_t irOffset{idx[std::max_element(blend.begin(), blend.end()) - blend.begin()]}; - ImpulseResponse res{Hrtf->coeffs[irOffset], - Hrtf->delays[irOffset][0], Hrtf->delays[irOffset][1]}; + const size_t irOffset{idx[(elev0.blend >= 0.5f)*2 + (az1.blend >= 0.5f)]}; + 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; }; @@ -320,41 +345,44 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, TRACE("Min delay: %.2f, max delay: %.2f, FIR length: %u\n", min_delay/double{HrirDelayFracOne}, max_delay/double{HrirDelayFracOne}, irSize); - const bool per_hrir_min{mChannels.size() > AmbiChannelsFromOrder(1)}; - 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{per_hrir_min ? 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; @@ -363,66 +391,67 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, namespace { -std::unique_ptr CreateHrtfStore(uint rate, ushort irSize, +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 */ - total += sizeof(std::declval().field[0])*fields.size(); + total += sizeof(std::declval().mFields[0])*fields.size(); total = RoundUp(total, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ - total += sizeof(std::declval().elev[0])*elevs.size(); + total += sizeof(std::declval().mElev[0])*elevs.size(); total = RoundUp(total, 16); /* Align for coefficients using SIMD */ - total += sizeof(std::declval().coeffs[0])*irCount; - total += sizeof(std::declval().delays[0])*irCount; + total += sizeof(std::declval().mCoeffs[0])*irCount; + total += sizeof(std::declval().mDelays[0])*irCount; - void *ptr{al_calloc(16, total)}; - std::unique_ptr Hrtf{al::construct_at(static_cast(ptr))}; - if(!Hrtf) - ERR("Out of memory allocating storage for %s.\n", filename); - else - { - InitRef(Hrtf->mRef, 1u); - Hrtf->sampleRate = rate; - Hrtf->irSize = irSize; - Hrtf->fdCount = static_cast(fields.size()); + 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. */ + char *base = reinterpret_cast(Hrtf.get()); + size_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_ = reinterpret_cast(base + offset); + offset += 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_ = reinterpret_cast(base + offset); + offset += 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_ = reinterpret_cast(base + offset); + offset += sizeof(coeffs_[0])*irCount; - auto delays_ = reinterpret_cast(base + offset); - offset += sizeof(delays_[0])*irCount; + auto delays_ = reinterpret_cast(base + offset); + offset += sizeof(delays_[0])*irCount; - if(unlikely(offset != total)) - throw std::runtime_error{"HrtfStore allocation size mismatch"}; + if(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_); + std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_); + std::uninitialized_copy_n(coeffs, irCount, coeffs_); + std::uninitialized_copy_n(delays, irCount, delays_); - /* Finally, assign the storage pointers. */ - Hrtf->field = field_; - Hrtf->elev = elev_; - Hrtf->coeffs = coeffs_; - Hrtf->delays = delays_; - } + /* Finally, assign the storage pointers. */ + Hrtf->mFields = {field_, fields.size()}; + Hrtf->mElev = elev_; + Hrtf->mCoeffs = coeffs_; + Hrtf->mDelays = delays_; return Hrtf; } @@ -468,11 +497,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 @@ -482,13 +511,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<> @@ -496,17 +524,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) { @@ -520,14 +545,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) @@ -562,20 +585,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; + 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) @@ -589,21 +610,18 @@ 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()); - 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, 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)}; - ushort irSize{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) { @@ -617,14 +635,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) @@ -640,20 +656,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; + 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) @@ -667,28 +681,24 @@ 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()); - 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)}; ubyte channelType{readle(data)}; - ushort irSize{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(sampleType > SampleType_S24) { @@ -713,17 +723,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) { @@ -738,7 +745,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) { @@ -752,10 +759,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) for(auto &elev : al::span(elevs.data()+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++) { @@ -778,8 +782,8 @@ 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) @@ -787,7 +791,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) for(auto &hrir : coeffs) { for(auto &val : al::span{hrir.data(), irSize}) - val[0] = readle(data) / 32768.0f; + val[0] = float(readle(data)) / 32768.0f; } } else if(sampleType == SampleType_S24) @@ -801,10 +805,8 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) 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) @@ -826,8 +828,8 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) { for(auto &val : al::span{hrir.data(), 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; } } } @@ -848,10 +850,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) { @@ -872,10 +871,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()); @@ -884,16 +883,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. @@ -913,12 +912,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); @@ -927,7 +927,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); @@ -937,24 +937,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)}; - ushort irSize{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) { @@ -974,17 +970,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) { @@ -999,7 +992,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) { @@ -1013,10 +1006,7 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) for(auto &elev : al::span(elevs.data()+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++) { @@ -1039,8 +1029,8 @@ 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) @@ -1051,10 +1041,8 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) 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) 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) { @@ -1106,39 +1091,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}; @@ -1148,8 +1132,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()); } @@ -1157,9 +1140,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 @@ -1181,8 +1165,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()); } @@ -1197,7 +1180,10 @@ al::span GetResource(int /*name*/) #else -#include "hrtf_default.h" +/* NOLINTNEXTLINE(*-avoid-c-arrays) */ +constexpr unsigned char hrtf_default[]{ +#include "default_hrtf.txt" +}; al::span GetResource(int name) { @@ -1210,56 +1196,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); @@ -1267,28 +1249,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->sampleRate == 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; @@ -1300,15 +1289,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()); @@ -1318,63 +1309,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->sampleRate != devrate) + if(hrtf->mSampleRate != devrate) { - TRACE("Resampling HRTF %s (%uhz -> %uhz)\n", name.c_str(), hrtf->sampleRate, 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->field, hrtf->field+hrtf->fdCount, 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->elev[lastEv].irOffset} + hrtf->elev[lastEv].azCount}; + const size_t irCount{size_t{hrtf->mElev[lastEv].irOffset} + hrtf->mElev[lastEv].azCount}; /* Resample all the IRs. */ std::array,2> inout; PPhaseResampler rs; - rs.init(hrtf->sampleRate, devrate); + rs.init(hrtf->mSampleRate, devrate); for(size_t i{0};i < irCount;++i) { - HrirArray &coeffs = const_cast(hrtf->coeffs[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]); } @@ -1383,15 +1373,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); - const float rate_scale{static_cast(devrate)/static_cast(hrtf->sampleRate)}; + 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->delays[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; } } @@ -1409,25 +1399,31 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) for(size_t i{0};i < irCount;++i) { - ubyte2 &delays = const_cast(hrtf->delays[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->irSize) * rate_scale)}; - hrtf->irSize = static_cast(minf(HrirLength, newIrSize)); - hrtf->sampleRate = devrate; + const float newIrSize{std::round(static_cast(hrtf->mIrSize) * rate_scale)}; + 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->sampleRate, hrtf->irSize); - handle = LoadedHrtfs.emplace(handle, LoadedHrtf{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() @@ -1436,21 +1432,21 @@ void HrtfStore::add_ref() TRACE("HrtfStore %p increasing refcount to %u\n", decltype(std::declval()){this}, ref); } -void HrtfStore::release() +void HrtfStore::dec_ref() { auto ref = DecrementRef(mRef); 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/libs/openal-soft/core/hrtf.h b/libs/openal-soft/core/hrtf.h index 9cf11efb..b0c769cc 100644 --- a/libs/openal-soft/core/hrtf.h +++ b/libs/openal-soft/core/hrtf.h @@ -4,24 +4,26 @@ #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 sampleRate; - uint irSize; + uint mSampleRate : 24; + uint mIrSize : 8; struct Field { float distance; @@ -30,21 +32,30 @@ struct HrtfStore { /* NOTE: Fields are stored *backwards*. field[0] is the farthest field, and * field[fdCount-1] is the nearest. */ - uint fdCount; - const Field *field; + al::span mFields; struct Elevation { ushort azCount; ushort irOffset; }; - Elevation *elev; - const HrirArray *coeffs; - const ubyte2 *delays; + Elevation *mElev; + const HrirArray *mCoeffs; + const ubyte2 *mDelays; + + void getCoeffs(float elevation, float azimuth, float distance, float spread, HrirArray &coeffs, + const al::span delays) const; void add_ref(); - void release(); + 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; @@ -58,7 +69,7 @@ struct AngularPoint { struct DirectHrtfState { - std::array mTemp; + std::array mTemp{}; /* HRTF filter state for dry buffer content */ uint mIrSize{0}; @@ -71,8 +82,9 @@ struct DirectHrtfState { * high-frequency gains for the decoder. The calculated impulse responses * are ordered and scaled according to the matrix input. */ - void build(const HrtfStore *Hrtf, const uint irSize, - const al::span AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels], + void build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin, + const al::span AmbiPoints, + const al::span> AmbiMatrix, const float XOverFreq, const al::span AmbiOrderHFGain); static std::unique_ptr Create(size_t num_chans); @@ -81,10 +93,7 @@ struct DirectHrtfState { }; -al::vector EnumerateHrtf(al::optional pathopt); -HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate); - -void GetHrtfCoeffs(const HrtfStore *Hrtf, float elevation, float azimuth, float distance, - float spread, HrirArray &coeffs, const al::span delays); +std::vector EnumerateHrtf(std::optional pathopt); +HrtfStorePtr GetLoadedHrtf(const std::string_view name, const uint devrate); #endif /* CORE_HRTF_H */ diff --git a/libs/openal-soft/core/logging.cpp b/libs/openal-soft/core/logging.cpp index 7ee7ff23..91752a65 100644 --- a/libs/openal-soft/core/logging.cpp +++ b/libs/openal-soft/core/logging.cpp @@ -3,84 +3,140 @@ #include "logging.h" +#include +#include #include #include +#include +#include +#include #include +#include +#include "alspan.h" #include "strutils.h" -#include "vector.h" -#ifdef _WIN32 - +#if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include +#elif defined(__ANDROID__) +#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 { - al::vector dynmsg; - char stcmsg[256]; - char *str{stcmsg}; + 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::span{"[ALSOFT] (--) "}.first<14>(); + switch(level) + { + case LogLevel::Disable: 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; + } + + 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(str, sizeof(stcmsg), fmt, args)}; - if(unlikely(msglen >= 0 && static_cast(msglen) >= sizeof(stcmsg))) + const int msglen{std::vsnprintf(msg.data(), msg.size(), fmt, args)}; + if(msglen >= 0) { - dynmsg.resize(static_cast(msglen) + 1u); - str = dynmsg.data(); - std::vsnprintf(str, dynmsg.size(), fmt, args2); + 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()}; + + 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); } +#if defined(_WIN32) && !defined(NDEBUG) /* OutputDebugStringW has no 'level' property to distinguish between * informational, warning, or error debug messages. So only print them for * non-Release builds. */ -#ifndef NDEBUG std::wstring wstr{utf8_to_wstr(str)}; OutputDebugStringW(wstr.c_str()); -#endif -} - -#else - -#ifdef __ANDROID__ -#include -#endif - -void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) -{ - al::vector dynmsg; - char stcmsg[256]; - char *str{stcmsg}; - - std::va_list args, args2; - va_start(args, fmt); - va_copy(args2, args); - const int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)}; - if(unlikely(msglen >= 0 && static_cast(msglen) >= sizeof(stcmsg))) - { - dynmsg.resize(static_cast(msglen) + 1u); - str = dynmsg.data(); - std::vsnprintf(str, dynmsg.size(), fmt, args2); - } - va_end(args2); - va_end(args); - - if(gLogLevel >= level) - { - std::fputs(str, logfile); - std::fflush(logfile); - } -#ifdef __ANDROID__ +#elif defined(__ANDROID__) auto android_severity = [](LogLevel l) noexcept { switch(l) @@ -96,6 +152,24 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) }; __android_log_print(android_severity(level), "openal", "%s", str); #endif -} -#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/libs/openal-soft/core/logging.h b/libs/openal-soft/core/logging.h index 81465929..b8d5f583 100644 --- a/libs/openal-soft/core/logging.h +++ b/libs/openal-soft/core/logging.h @@ -17,36 +17,22 @@ extern LogLevel gLogLevel; extern FILE *gLogFile; -#if !defined(_WIN32) && !defined(__ANDROID__) -#define TRACE(...) do { \ - if UNLIKELY(gLogLevel >= LogLevel::Trace) \ - fprintf(gLogFile, "[ALSOFT] (II) " __VA_ARGS__); \ -} while(0) +using LogCallbackFunc = void(*)(void *userptr, char level, const char *message, int length) noexcept; -#define WARN(...) do { \ - if UNLIKELY(gLogLevel >= LogLevel::Warning) \ - fprintf(gLogFile, "[ALSOFT] (WW) " __VA_ARGS__); \ -} while(0) +void al_set_log_callback(LogCallbackFunc callback, void *userptr); -#define ERR(...) do { \ - if UNLIKELY(gLogLevel >= LogLevel::Error) \ - fprintf(gLogFile, "[ALSOFT] (EE) " __VA_ARGS__); \ -} while(0) +#ifdef __MINGW32__ +[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] #else - -#ifdef __USE_MINGW_ANSI_STDIO -[[gnu::format(gnu_printf,3,4)]] -#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; -#define TRACE(...) al_print(LogLevel::Trace, gLogFile, "[ALSOFT] (II) " __VA_ARGS__) +#define TRACE(...) al_print(LogLevel::Trace, __VA_ARGS__) -#define WARN(...) al_print(LogLevel::Warning, gLogFile, "[ALSOFT] (WW) " __VA_ARGS__) +#define WARN(...) al_print(LogLevel::Warning, __VA_ARGS__) -#define ERR(...) al_print(LogLevel::Error, gLogFile, "[ALSOFT] (EE) " __VA_ARGS__) -#endif +#define ERR(...) al_print(LogLevel::Error, __VA_ARGS__) #endif /* CORE_LOGGING_H */ diff --git a/libs/openal-soft/core/mastering.cpp b/libs/openal-soft/core/mastering.cpp index 99850477..468a286d 100644 --- a/libs/openal-soft/core/mastering.cpp +++ b/libs/openal-soft/core/mastering.cpp @@ -21,8 +21,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; @@ -44,8 +44,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 +60,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(1); - found_place: lowerIndex = (lowerIndex + 1) & mask; values[lowerIndex] = in; @@ -82,38 +84,38 @@ 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, - std::bind(std::minus<>{}, _1, 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, std::bind(std::minus<>{}, _1, 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 FloatBufferLine *OutBuffer) { - const size_t numChans{Comp->mNumChans}; - ASSUME(SamplesToDo > 0); - ASSUME(numChans > 0); - auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; + const auto side_begin = mSideChain.begin() + mLookAhead; std::fill(side_begin, side_begin+SamplesToDo, 0.0f); auto fill_max = [SamplesToDo,side_begin](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)); + auto max_abs = [](const float s0, const float s1) noexcept -> float + { return std::max(s0, std::fabs(s1)); }; std::transform(side_begin, side_begin+SamplesToDo, buffer, side_begin, max_abs); }; - std::for_each(OutBuffer, OutBuffer+numChans, fill_max); + std::for_each(OutBuffer, OutBuffer+mNumChans, fill_max); } /* This calculates the squared crest factor of the control signal for the @@ -121,59 +123,59 @@ 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. */ -static 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); 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 side_begin = mSideChain.begin() + mLookAhead; + std::transform(side_begin, side_begin+SamplesToDo, 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); - /* Clamp the minimum amplitude to near-zero and convert to logarithm. */ - auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; + /* Clamp the minimum amplitude to near-zero and convert to logarithmic. */ + const auto side_begin = mSideChain.begin() + mLookAhead; std::transform(side_begin, side_begin+SamplesToDo, side_begin, - [](const float s) -> float { return std::log(maxf(0.000001f, s)); }); + [](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); - 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; + auto side_begin = mSideChain.begin() + mLookAhead; std::transform(side_begin, side_begin+SamplesToDo, side_begin, detect_peak); ShiftSlidingHold(hold, SamplesToDo); @@ -184,46 +186,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 +245,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 +264,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,10 +284,10 @@ 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, FloatBufferLine *OutBuffer) { - const size_t numChans{Comp->mNumChans}; - const uint lookAhead{Comp->mLookAhead}; + const size_t numChans{mNumChans}; + const uint lookAhead{mLookAhead}; ASSUME(SamplesToDo > 0); ASSUME(numChans > 0); @@ -292,10 +296,10 @@ void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutB for(size_t c{0};c < numChans;c++) { float *inout{al::assume_aligned<16>(OutBuffer[c].data())}; - float *delaybuf{al::assume_aligned<16>(Comp->mDelay[c].data())}; + float *delaybuf{al::assume_aligned<16>(mDelay[c].data())}; auto inout_end = inout + SamplesToDo; - if LIKELY(SamplesToDo >= lookAhead) + if(SamplesToDo >= lookAhead) LIKELY { auto delay_end = std::rotate(inout, inout_end - lookAhead, inout_end); std::swap_ranges(inout, delay_end, delaybuf); @@ -308,8 +312,6 @@ void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutB } } -} // 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 +319,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 +333,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 +349,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 +370,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) @@ -404,36 +387,36 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) { float *buffer{al::assume_aligned<16>(input.data())}; std::transform(buffer, buffer+SamplesToDo, buffer, - std::bind(std::multiplies{}, _1, preGain)); + [preGain](const float s) noexcept { return s * preGain; }); }; std::for_each(OutBuffer, OutBuffer+numChans, apply_gain); } - LinkChannels(this, SamplesToDo, OutBuffer); + linkChannels(SamplesToDo, OutBuffer); 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, OutBuffer); - const float (&sideChain)[BufferLineSize*2] = mSideChain; - auto apply_comp = [SamplesToDo,&sideChain](FloatBufferLine &input) noexcept -> void + const auto sideChain = al::span{mSideChain}; + auto apply_comp = [SamplesToDo,sideChain](FloatBufferLine &input) noexcept -> void { float *buffer{al::assume_aligned<16>(input.data())}; - const float *gains{al::assume_aligned<16>(&sideChain[0])}; + const float *gains{al::assume_aligned<16>(sideChain.data())}; std::transform(gains, gains+SamplesToDo, buffer, buffer, - std::bind(std::multiplies{}, _1, _2)); + [](const float g, const float s) noexcept { return g * s; }); }; std::for_each(OutBuffer, OutBuffer+numChans, apply_comp); - auto side_begin = std::begin(mSideChain) + SamplesToDo; - std::copy(side_begin, side_begin+mLookAhead, std::begin(mSideChain)); + auto side_begin = mSideChain.begin() + SamplesToDo; + std::copy(side_begin, side_begin+mLookAhead, mSideChain.begin()); } diff --git a/libs/openal-soft/core/mastering.h b/libs/openal-soft/core/mastering.h index 1a36937c..f2cca857 100644 --- a/libs/openal-soft/core/mastering.h +++ b/libs/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 FloatBufferLine *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, FloatBufferLine *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/libs/openal-soft/core/mixer.cpp b/libs/openal-soft/core/mixer.cpp index 4618406b..806ac8b8 100644 --- a/libs/openal-soft/core/mixer.cpp +++ b/libs/openal-soft/core/mixer.cpp @@ -13,45 +13,14 @@ struct CTag; -MixerFunc MixSamples{Mix_}; +MixerOutFunc MixSamplesOut{Mix_}; +MixerOneFunc MixSamplesOne{Mix_}; std::array CalcAmbiCoeffs(const float y, const float z, const float x, const float spread) { - std::array coeffs; - - /* Zeroth-order */ - coeffs[0] = 1.0f; /* ACN 0 = 1 */ - /* First-order */ - coeffs[1] = al::numbers::sqrt3_v * y; /* ACN 1 = sqrt(3) * Y */ - coeffs[2] = al::numbers::sqrt3_v * z; /* ACN 2 = sqrt(3) * Z */ - coeffs[3] = al::numbers::sqrt3_v * x; /* ACN 3 = sqrt(3) * X */ - /* Second-order */ - const float xx{x*x}, yy{y*y}, zz{z*z}, xy{x*y}, yz{y*z}, xz{x*z}; - coeffs[4] = 3.872983346f * xy; /* ACN 4 = sqrt(15) * X * Y */ - coeffs[5] = 3.872983346f * yz; /* ACN 5 = sqrt(15) * Y * Z */ - coeffs[6] = 1.118033989f * (3.0f*zz - 1.0f); /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */ - coeffs[7] = 3.872983346f * xz; /* ACN 7 = sqrt(15) * X * Z */ - coeffs[8] = 1.936491673f * (xx - yy); /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */ - /* Third-order */ - coeffs[9] = 2.091650066f * (y*(3.0f*xx - yy)); /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */ - coeffs[10] = 10.246950766f * (z*xy); /* ACN 10 = sqrt(105) * Z * X * Y */ - coeffs[11] = 1.620185175f * (y*(5.0f*zz - 1.0f)); /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */ - coeffs[12] = 1.322875656f * (z*(5.0f*zz - 3.0f)); /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */ - coeffs[13] = 1.620185175f * (x*(5.0f*zz - 1.0f)); /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */ - coeffs[14] = 5.123475383f * (z*(xx - yy)); /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */ - coeffs[15] = 2.091650066f * (x*(xx - 3.0f*yy)); /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */ - /* Fourth-order */ - /* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */ - /* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */ - /* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */ - /* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */ - /* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */ - /* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */ - /* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */ - /* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */ - /* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */ + std::array coeffs{CalcAmbiCoeffs(y, z, x)}; if(spread > 0.0f) { @@ -113,14 +82,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 iter = std::transform(ambimap, ambimap+mix->Buffer.size(), 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/libs/openal-soft/core/mixer.h b/libs/openal-soft/core/mixer.h index 309f4224..9062ebac 100644 --- a/libs/openal-soft/core/mixer.h +++ b/libs/openal-soft/core/mixer.h @@ -13,11 +13,25 @@ struct MixParams; -using MixerFunc = void(*)(const al::span InSamples, +/* 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); -extern MixerFunc MixSamples; +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) +{ 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); + +extern MixerOneFunc MixSamplesOne; +inline void MixSamples(const al::span InSamples, float *OutBuffer, float &CurrentGain, + const float TargetGain, const size_t Counter) +{ MixSamplesOne(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); } /** @@ -44,13 +58,25 @@ 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. */ return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread); } +/** + * CalcDirectionCoeffs + * + * Calculates ambisonic coefficients based on an OpenAL direction vector. The + * vector must be normalized (unit length). + */ +constexpr std::array CalcDirectionCoeffs(const al::span dir) +{ + /* Convert from OpenAL coords to Ambisonics. */ + return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2]); +} + /** * CalcAngleCoeffs * @@ -77,25 +103,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); - - -/** Helper to set an identity/pass-through panning for ambisonic mixing (3D input). */ -template -auto SetAmbiPanIdentity(T iter, I count, F func) -> std::enable_if_t::value> -{ - if(count < 1) return; - - std::array coeffs{{1.0f}}; - func(*iter, coeffs); - ++iter; - for(I i{1};i < count;++i,++iter) - { - coeffs[i-1] = 0.0f; - coeffs[i ] = 1.0f; - func(*iter, coeffs); - } -} +void ComputePanGains(const MixParams *mix, const al::span coeffs, + const float ingain, const al::span gains); #endif /* CORE_MIXER_H */ diff --git a/libs/openal-soft/core/mixer/defs.h b/libs/openal-soft/core/mixer/defs.h index ba304f22..ba10a146 100644 --- a/libs/openal-soft/core/mixer/defs.h +++ b/libs/openal-soft/core/mixer/defs.h @@ -2,11 +2,14 @@ #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 HrtfChannelState; struct HrtfFilter; @@ -16,14 +19,15 @@ using uint = unsigned int; using float2 = std::array; -constexpr int MixerFracBits{12}; -constexpr int MixerFracOne{1 << MixerFracBits}; -constexpr int MixerFracMask{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 { +enum class Resampler : std::uint8_t { Point, Linear, Cubic, @@ -50,23 +54,32 @@ struct BsincState { const float *filter; }; -union InterpState { - BsincState bsinc; +struct CubicState { + /* Filter coefficients, and coefficient deltas. Starting at phase index 0, + * each subsequent phase index follows contiguously. + */ + al::span filter; + CubicState(al::span f) : filter{f} { } }; -using ResamplerFunc = float*(*)(const InterpState *state, float *RESTRICT src, uint frac, - uint increment, const al::span dst); +using InterpState = std::variant; + +using ResamplerFunc = void(*)(const InterpState *state, const float *src, uint frac, + const uint increment, const al::span dst); ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state); template -float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, uint increment, +void Resample_(const InterpState *state, const float *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); +template +void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, + const float TargetGain, const size_t Counter); template void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, @@ -77,15 +90,18 @@ void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSi 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 TempBuf, HrtfChannelState *ChanState, const size_t IrSize, + const size_t BufferSize); /* Vectorized resampler helpers */ template -inline void InitPosArrays(uint frac, uint increment, uint (&frac_arr)[N], uint (&pos_arr)[N]) +constexpr void InitPosArrays(uint frac, const uint increment, const al::span frac_arr, + const al::span pos_arr) { + static_assert(pos_arr.size() == frac_arr.size()); pos_arr[0] = 0; 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/libs/openal-soft/core/mixer/hrtfbase.h b/libs/openal-soft/core/mixer/hrtfbase.h index 606f9d4e..868f09d2 100644 --- a/libs/openal-soft/core/mixer/hrtfbase.h +++ b/libs/openal-soft/core/mixer/hrtfbase.h @@ -50,7 +50,7 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl const ConstHrirSpan NewCoeffs{newparams->Coeffs}; const float newGainStep{newparams->GainStep}; - if LIKELY(oldparams->Gain > GainSilenceThreshold) + if(oldparams->Gain > GainSilenceThreshold) LIKELY { size_t ldelay{HrtfHistoryLength - oldparams->Delay[0]}; size_t rdelay{HrtfHistoryLength - oldparams->Delay[1]}; @@ -66,7 +66,7 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl } } - if LIKELY(newGainStep*static_cast(BufferSize) > GainSilenceThreshold) + if(newGainStep*static_cast(BufferSize) > GainSilenceThreshold) LIKELY { size_t ldelay{HrtfHistoryLength+1 - newparams->Delay[0]}; size_t rdelay{HrtfHistoryLength+1 - newparams->Delay[1]}; @@ -86,7 +86,8 @@ 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 TempBuf, HrtfChannelState *ChanState, const size_t IrSize, + const size_t BufferSize) { ASSUME(BufferSize > 0); @@ -100,7 +101,7 @@ inline void MixDirectHrtfBase(const FloatBufferSpan LeftOut, const FloatBufferSp ChanState->mHfScale); /* Now apply the HRIR coefficients to this channel. */ - const float *RESTRICT tempbuf{al::assume_aligned<16>(TempBuf)}; + const float *RESTRICT tempbuf{al::assume_aligned<16>(TempBuf.data())}; const ConstHrirSpan Coeffs{ChanState->mCoeffs}; for(size_t i{0u};i < BufferSize;++i) { diff --git a/libs/openal-soft/core/mixer/mixer_c.cpp b/libs/openal-soft/core/mixer/mixer_c.cpp index f3e6aa6a..5efa9cc3 100644 --- a/libs/openal-soft/core/mixer/mixer_c.cpp +++ b/libs/openal-soft/core/mixer/mixer_c.cpp @@ -1,16 +1,22 @@ #include "config.h" -#include -#include +#include +#include +#include #include +#include #include "alnumeric.h" -#include "core/bsinc_tables.h" +#include "alspan.h" +#include "core/bsinc_defs.h" +#include "core/bufferline.h" +#include "core/cubic_defs.h" +#include "core/mixer/hrtfdefs.h" #include "defs.h" #include "hrtfbase.h" +#include "opthelpers.h" struct CTag; -struct CopyTag; struct PointTag; struct LerpTag; struct CubicTag; @@ -20,73 +26,105 @@ struct FastBSincTag; namespace { -constexpr uint FracPhaseBitDiff{MixerFracBits - BSincPhaseBits}; -constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff}; +constexpr uint BsincPhaseDiffBits{MixerFracBits - BSincPhaseBits}; +constexpr uint BsincPhaseDiffOne{1 << BsincPhaseDiffBits}; +constexpr uint BsincPhaseDiffMask{BsincPhaseDiffOne - 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) +constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; +constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; +constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; + +constexpr +auto do_point(const float *vals, const uint) noexcept -> float { return vals[0]; } +constexpr +auto do_lerp(const float *vals, const uint frac) noexcept -> float { return lerpf(vals[0], vals[1], static_cast(frac)*(1.0f/MixerFracOne)); } -inline float do_cubic(const InterpState&, const float *RESTRICT vals, const uint frac) -{ return cubic(vals[0], vals[1], vals[2], vals[3], static_cast(frac)*(1.0f/MixerFracOne)); } -inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac) +constexpr +auto do_cubic(const CubicState &istate, const float *vals, const uint frac) noexcept -> float { - const size_t m{istate.bsinc.m}; + /* Calculate the phase index and factor. */ + const uint pi{frac >> CubicPhaseDiffBits}; + const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; + + 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]; +} +constexpr +auto do_bsinc(const BsincState &istate, const float *vals, const uint frac) noexcept -> float +{ + const size_t m{istate.m}; ASSUME(m > 0); - // Calculate the phase index and factor. - const uint pi{frac >> FracPhaseBitDiff}; - const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + /* 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}; - const float *RESTRICT scd{fil + BSincPhaseCount*2*m}; - const float *RESTRICT spd{scd + m}; + const float *fil{istate.filter + m*pi*2_uz}; + const float *phd{fil + m}; + const float *scd{fil + BSincPhaseCount*2_uz*m}; + const float *spd{scd + m}; - // Apply the scale and phase interpolated filter. + /* 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]; + r += (fil[j_f] + istate.sf*scd[j_f] + pf*(phd[j_f] + istate.sf*spd[j_f])) * vals[j_f]; return r; } -inline float do_fastbsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac) +constexpr +auto do_fastbsinc(const BsincState &istate, const float *vals, const uint frac) noexcept -> float { - const size_t m{istate.bsinc.m}; + const size_t m{istate.m}; ASSUME(m > 0); - // Calculate the phase index and factor. - const uint pi{frac >> FracPhaseBitDiff}; - const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + /* 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}; + const float *fil{istate.filter + m*pi*2_uz}; + const float *phd{fil + m}; - // Apply the phase interpolated filter. + /* Apply the phase interpolated filter. */ float r{0.0f}; for(size_t j_f{0};j_f < m;j_f++) r += (fil[j_f] + pf*phd[j_f]) * vals[j_f]; return r; } -using SamplerT = float(&)(const InterpState&, const float*RESTRICT, const uint); -template -float *DoResample(const InterpState *state, float *RESTRICT src, uint frac, uint increment, - const al::span dst) +template +void DoResample(const float *src, uint frac, const uint increment, const al::span dst) { - const InterpState istate{*state}; - for(float &out : dst) + ASSUME(frac < MixerFracOne); + std::generate(dst.begin(), dst.end(), [&src,&frac,increment]() -> float { - out = Sampler(istate, src, frac); - + const float output{Sampler(src, frac)}; frac += increment; src += frac>>MixerFracBits; frac &= MixerFracMask; - } - return dst.data(); + return output; + }); } -inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, - const float left, const float right) +template +void DoResample(const U istate, const float *src, uint frac, const uint increment, + const al::span dst) +{ + ASSUME(frac < MixerFracOne); + std::generate(dst.begin(), dst.end(), [istate,&src,&frac,increment]() -> float + { + const float output{Sampler(istate, src, frac)}; + frac += increment; + src += frac>>MixerFracBits; + frac &= MixerFracMask; + return output; + }); +} + +constexpr void ApplyCoeffs(float2 *RESTRICT 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) @@ -96,45 +134,69 @@ 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, + size_t Counter) +{ + float gain{CurrentGain}; + const float step{(TargetGain-gain) * delta}; + + size_t pos{0}; + if(!(std::abs(step) > std::numeric_limits::epsilon())) + gain = TargetGain; + else + { + 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; + + if(!(std::abs(gain) > GainSilenceThreshold)) + return; + for(;pos != InSamples.size();++pos) + dst[pos] += InSamples[pos] * gain; +} + } // namespace template<> -float *Resample_(const InterpState*, float *RESTRICT src, uint, uint, +void Resample_(const InterpState*, const float *src, uint frac, + const uint increment, const al::span dst) +{ DoResample(src, frac, increment, dst); } + +template<> +void Resample_(const InterpState*, const float *src, uint frac, const uint increment, const al::span dst) +{ DoResample(src, frac, increment, dst); } + +template<> +void Resample_(const InterpState *state, const float *src, uint frac, + const uint increment, const al::span dst) +{ DoResample(std::get(*state), src-1, frac, increment, dst); } + +template<> +void Resample_(const InterpState *state, const float *src, uint frac, + const uint increment, const al::span dst) { -#if defined(HAVE_SSE) || defined(HAVE_NEON) - /* Avoid copying the source data if it's aligned like the destination. */ - if((reinterpret_cast(src)&15) == (reinterpret_cast(dst.data())&15)) - return src; -#endif - std::copy_n(src, dst.size(), dst.begin()); - return dst.data(); + const auto istate = std::get(*state); + DoResample(istate, src-istate.l, frac, increment, dst); } template<> -float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, - uint increment, const al::span dst) -{ return DoResample(state, src, frac, increment, dst); } - -template<> -float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, - uint increment, const al::span dst) -{ return DoResample(state, src, frac, increment, dst); } - -template<> -float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, - uint increment, const al::span dst) -{ return DoResample(state, src-1, frac, increment, dst); } - -template<> -float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, - uint increment, const al::span dst) -{ return DoResample(state, src-state->bsinc.l, frac, increment, dst); } - -template<> -float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, - uint increment, const al::span dst) -{ return DoResample(state, src-state->bsinc.l, frac, increment, dst); } +void Resample_(const InterpState *state, const float *src, uint frac, + const uint increment, const al::span dst) +{ + const auto istate = std::get(*state); + DoResample(istate, src-istate.l, frac, increment, dst); +} template<> @@ -153,7 +215,8 @@ void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uin 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 TempBuf, HrtfChannelState *ChanState, const size_t IrSize, + const size_t BufferSize) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, IrSize, BufferSize); @@ -165,36 +228,20 @@ void Mix_(const al::span InSamples, const al::span 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); + const auto min_len = std::min(Counter, InSamples.size()); + for(FloatBufferLine &output : OutBuffer) - { - float *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)}; - float gain{*CurrentGains}; - const float step{(*TargetGains-gain) * delta}; - - size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = *TargetGains; - else - { - 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 = *TargetGains; - else - gain += step*step_count; - } - *CurrentGains = gain; - ++CurrentGains; - ++TargetGains; - - if(!(std::abs(gain) > GainSilenceThreshold)) - continue; - for(;pos != InSamples.size();++pos) - dst[pos] += InSamples[pos] * gain; - } + MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, + *TargetGains++, delta, min_len, Counter); +} + +template<> +void Mix_(const al::span InSamples, float *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 = std::min(Counter, InSamples.size()); + + MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, + TargetGain, delta, min_len, Counter); } diff --git a/libs/openal-soft/core/mixer/mixer_neon.cpp b/libs/openal-soft/core/mixer/mixer_neon.cpp index a3468926..b44b7830 100644 --- a/libs/openal-soft/core/mixer/mixer_neon.cpp +++ b/libs/openal-soft/core/mixer/mixer_neon.cpp @@ -2,16 +2,25 @@ #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 "defs.h" #include "hrtfbase.h" +#include "opthelpers.h" struct NEONTag; struct LerpTag; +struct CubicTag; struct BSincTag; struct FastBSincTag; @@ -20,8 +29,31 @@ struct FastBSincTag; #pragma GCC target("fpu=neon") #endif +using uint = unsigned int; + namespace { +constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits}; +constexpr uint BSincPhaseDiffOne{1 << BSincPhaseDiffBits}; +constexpr uint BSincPhaseDiffMask{BSincPhaseDiffOne - 1u}; + +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)}; @@ -31,18 +63,15 @@ inline float32x4_t set_f4(float l0, float l1, float l2, float l3) return ret; } -constexpr uint FracPhaseBitDiff{MixerFracBits - BSincPhaseBits}; -constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff}; - inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, const float left, const float right) { - float32x4_t leftright4; + auto dup_samples = [left,right] { - float32x2_t leftright2{vmov_n_f32(left)}; - leftright2 = vset_lane_f32(right, leftright2, 1); - leftright4 = vcombine_f32(leftright2, leftright2); - } + float32x2_t leftright2{vset_lane_f32(right, vmov_n_f32(left), 1)}; + return vcombine_f32(leftright2, leftright2); + }; + const float32x4_t leftright4{dup_samples()}; ASSUME(IrSize >= MinIrLength); for(size_t c{0};c < IrSize;c += 2) @@ -56,85 +85,247 @@ 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) +{ + float gain{CurrentGain}; + const float step{(TargetGain-gain) * delta}; + + size_t pos{0}; + if(!(std::abs(step) > std::numeric_limits::epsilon())) + gain = TargetGain; + else + { + float step_count{0.0f}; + /* Mix with applying gain steps in aligned multiples of 4. */ + if(size_t todo{min_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); + + 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. + */ + 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) + { + dst[pos] += InSamples[pos] * (gain + step*step_count); + step_count += 1.0f; + } + 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(!(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); + } + for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) + dst[pos] += InSamples[pos] * gain; +} + } // namespace template<> -float *Resample_(const InterpState*, float *RESTRICT src, uint frac, - uint increment, const al::span dst) +void Resample_(const InterpState*, const float *src, uint frac, + const uint increment, const al::span dst) { - const int32x4_t increment4 = vdupq_n_s32(static_cast(increment*4)); + ASSUME(frac < MixerFracOne); + + 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(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)}; 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)); + src += 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)); + std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment] + { + const float out{lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; src += frac>>MixerFracBits; frac &= MixerFracMask; - } while(--todo); + return out; + }); } - return dst.data(); } template<> -float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, - uint increment, const al::span dst) +void Resample_(const InterpState *state, const float *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}; - ASSUME(m > 0); + ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + const auto filter = std::get(*state).filter; + + 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(frac, increment, al::span{frac_}, al::span{pos_}); + uint32x4_t frac4{vld1q_u32(frac_.data())}; + uint32x4_t pos4{vld1q_u32(pos_.data())}; + + src -= 1; + auto vecout = al::span{reinterpret_cast(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> float32x4_t + { + 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)}; + 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)}; + + 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); + + const float32x4_t pf4{vmulq_f32(vcvtq_f32_u32(vandq_u32(frac4, fracDiffMask4)), + fracDiffOne4)}; + + 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())))}; + + 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}) + { + src += vgetq_lane_u32(pos4, 0); + frac = vgetq_lane_u32(frac4, 0); + + std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,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))}; + + 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; + src += frac>>MixerFracBits; + frac &= MixerFracMask; + return output; + }); + } +} + +template<> +void Resample_(const InterpState *state, const float *src, uint frac, + const uint increment, const al::span dst) +{ + const auto &bsinc = std::get(*state); + const float *const filter{bsinc.filter}; + const float32x4_t sf4{vdupq_n_f32(bsinc.sf)}; + const size_t m{bsinc.m}; + ASSUME(m > 0); + ASSUME(frac < MixerFracOne); + + src -= bsinc.l; + std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,sf4,m]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> FracPhaseBitDiff}; - const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + const uint pi{frac >> BSincPhaseDiffBits}; + 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 float *fil{filter + m*pi*2_uz}; + const float *phd{fil + m}; + const float *scd{fil + BSincPhaseCount*2_uz*m}; + const float *spd{scd + m}; size_t td{m >> 2}; size_t j{0u}; @@ -149,36 +340,38 @@ float *Resample_(const InterpState *state, float *RESTRICT src } 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; frac &= MixerFracMask; - } - return dst.data(); + return output; + }); } template<> -float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, - uint increment, const al::span dst) +void Resample_(const InterpState *state, const float *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 float *const filter{bsinc.filter}; + const size_t m{bsinc.m}; ASSUME(m > 0); + ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + src -= bsinc.l; + std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,m]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> FracPhaseBitDiff}; - const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + const uint pi{frac >> BSincPhaseDiffBits}; + 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 float *fil{filter + m*pi*2_uz}; + const float *phd{fil + m}; size_t td{m >> 2}; size_t j{0u}; @@ -191,13 +384,13 @@ float *Resample_(const InterpState *state, float *RESTRICT } 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; frac &= MixerFracMask; - } - return dst.data(); + return output; + }); } @@ -217,7 +410,8 @@ void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const 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 TempBuf, HrtfChannelState *ChanState, const size_t IrSize, + const size_t BufferSize) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, IrSize, BufferSize); @@ -229,79 +423,22 @@ void Mix_(const al::span InSamples, const al::span 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 min_len = std::min(Counter, InSamples.size()); + const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len; for(FloatBufferLine &output : OutBuffer) - { - float *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)}; - float gain{*CurrentGains}; - const float step{(*TargetGains-gain) * delta}; - - size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = *TargetGains; - else - { - float step_count{0.0f}; - /* Mix with applying gain steps in aligned multiples of 4. */ - if(size_t todo{min_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); - - 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. - */ - 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) - { - dst[pos] += InSamples[pos] * (gain + step*step_count); - step_count += 1.0f; - } - if(pos == Counter) - gain = *TargetGains; - 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; - } - *CurrentGains = gain; - ++CurrentGains; - ++TargetGains; - - if(!(std::abs(gain) > GainSilenceThreshold)) - continue; - 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); - } - for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; - } + MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, + *TargetGains++, delta, min_len, aligned_len, Counter); +} + +template<> +void Mix_(const al::span InSamples, float *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 = std::min(Counter, InSamples.size()); + const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len; + + MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len, + aligned_len, Counter); } diff --git a/libs/openal-soft/core/mixer/mixer_sse.cpp b/libs/openal-soft/core/mixer/mixer_sse.cpp index fc4d8cc6..74e13a31 100644 --- a/libs/openal-soft/core/mixer/mixer_sse.cpp +++ b/libs/openal-soft/core/mixer/mixer_sse.cpp @@ -1,16 +1,27 @@ #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 "defs.h" #include "hrtfbase.h" +#include "opthelpers.h" struct SSETag; +struct CubicTag; struct BSincTag; struct FastBSincTag; @@ -21,10 +32,16 @@ struct FastBSincTag; namespace { -constexpr uint FracPhaseBitDiff{MixerFracBits - BSincPhaseBits}; -constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff}; +constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits}; +constexpr uint BSincPhaseDiffOne{1 << BSincPhaseDiffBits}; +constexpr uint BSincPhaseDiffMask{BSincPhaseDiffOne - 1u}; -#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z)) +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)); } inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, const float left, const float right) @@ -40,130 +57,239 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Cons { for(size_t i{0};i < IrSize;i += 2) { - const __m128 coeffs{_mm_load_ps(&Coeffs[i][0])}; - __m128 vals{_mm_load_ps(&Values[i][0])}; - vals = MLA4(vals, lrlr, coeffs); - _mm_store_ps(&Values[i][0], vals); + const __m128 coeffs{_mm_load_ps(Coeffs[i].data())}; + __m128 vals{_mm_load_ps(Values[i].data())}; + vals = vmadd(vals, lrlr, coeffs); + _mm_store_ps(Values[i].data(), vals); } } else { __m128 imp0, imp1; - __m128 coeffs{_mm_load_ps(&Coeffs[0][0])}; - __m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(&Values[0][0]))}; + __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); vals = _mm_add_ps(imp0, vals); - _mm_storel_pi(reinterpret_cast<__m64*>(&Values[0][0]), vals); + _mm_storel_pi(reinterpret_cast<__m64*>(Values[0].data()), vals); size_t td{((IrSize+1)>>1) - 1}; size_t i{1}; do { - coeffs = _mm_load_ps(&Coeffs[i+1][0]); - vals = _mm_load_ps(&Values[i][0]); + coeffs = _mm_load_ps(Coeffs[i+1].data()); + vals = _mm_load_ps(Values[i].data()); imp1 = _mm_mul_ps(lrlr, coeffs); imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2)); vals = _mm_add_ps(imp0, vals); - _mm_store_ps(&Values[i][0], vals); + _mm_store_ps(Values[i].data(), vals); imp0 = imp1; i += 2; } while(--td); - vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(&Values[i][0])); + vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(Values[i].data())); imp0 = _mm_movehl_ps(imp0, imp0); vals = _mm_add_ps(imp0, vals); - _mm_storel_pi(reinterpret_cast<__m64*>(&Values[i][0]), vals); + _mm_storel_pi(reinterpret_cast<__m64*>(Values[i].data()), vals); } } +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) +{ + float gain{CurrentGain}; + const float step{(TargetGain-gain) * delta}; + + size_t pos{0}; + if(!(std::abs(step) > std::numeric_limits::epsilon())) + gain = TargetGain; + else + { + float step_count{0.0f}; + /* Mix with applying gain steps in aligned multiples of 4. */ + if(size_t todo{min_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])}; + + /* dry += val * (gain + step*step_count) */ + dry4 = vmadd(dry4, val4, vmadd(gain4, step4, step_count4)); + + _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. + */ + 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) + { + dst[pos] += InSamples[pos] * (gain + step*step_count); + step_count += 1.0f; + } + 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(!(std::abs(gain) > 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); + } + for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) + dst[pos] += InSamples[pos] * gain; +} + } // namespace template<> -float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, - uint increment, const al::span dst) +void Resample_(const InterpState *state, const float *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}; - ASSUME(m > 0); + ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + const auto filter = std::get(*state).filter; + + src -= 1; + std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter]() -> float + { + 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 = 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))}; + + 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; + frac &= MixerFracMask; + return output; + }); +} + +template<> +void Resample_(const InterpState *state, const float *src, uint frac, + const uint increment, const al::span dst) +{ + const auto &bsinc = std::get(*state); + const float *const filter{bsinc.filter}; + const __m128 sf4{_mm_set1_ps(bsinc.sf)}; + const size_t m{bsinc.m}; + ASSUME(m > 0); + ASSUME(frac < MixerFracOne); + + src -= bsinc.l; + std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,sf4,m]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> FracPhaseBitDiff}; - const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + const uint pi{frac >> BSincPhaseDiffBits}; + const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the scale and phase interpolated filter. __m128 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}; + const float *fil{filter + m*pi*2_uz}; + const float *phd{fil + m}; + const float *scd{fil + BSincPhaseCount*2_uz*m}; + const float *spd{scd + m}; size_t td{m >> 2}; size_t j{0u}; do { /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ - const __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[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 float output{_mm_cvtss_f32(r4)}; frac += increment; src += frac>>MixerFracBits; frac &= MixerFracMask; - } - return dst.data(); + return output; + }); } template<> -float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, - uint increment, const al::span dst) +void Resample_(const InterpState *state, const float *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 float *const filter{bsinc.filter}; + const size_t m{bsinc.m}; ASSUME(m > 0); + ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + src -= bsinc.l; + std::generate(dst.begin(), dst.end(), [&src,&frac,increment,filter,m]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> FracPhaseBitDiff}; - const float pf{static_cast(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)}; + const uint pi{frac >> BSincPhaseDiffBits}; + const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the phase interpolated filter. __m128 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 *fil{filter + m*pi*2_uz}; + const float *phd{fil + m}; size_t td{m >> 2}; size_t j{0u}; do { /* f = fil + pf*phd */ - const __m128 f4 = MLA4(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j])); + const __m128 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[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 float output{_mm_cvtss_f32(r4)}; frac += increment; src += frac>>MixerFracBits; frac &= MixerFracMask; - } - return dst.data(); + return output; + }); } @@ -183,7 +309,8 @@ void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const u 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 TempBuf, HrtfChannelState *ChanState, const size_t IrSize, + const size_t BufferSize) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, IrSize, BufferSize); @@ -195,78 +322,22 @@ void Mix_(const al::span InSamples, const al::span 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 min_len = std::min(Counter, InSamples.size()); + const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len; for(FloatBufferLine &output : OutBuffer) - { - float *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)}; - float gain{*CurrentGains}; - const float step{(*TargetGains-gain) * delta}; - - size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = *TargetGains; - else - { - float step_count{0.0f}; - /* Mix with applying gain steps in aligned multiples of 4. */ - if(size_t todo{min_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])}; - - /* dry += val * (gain + step*step_count) */ - dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4)); - - _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. - */ - 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) - { - dst[pos] += InSamples[pos] * (gain + step*step_count); - step_count += 1.0f; - } - if(pos == Counter) - gain = *TargetGains; - 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; - } - *CurrentGains = gain; - ++CurrentGains; - ++TargetGains; - - if(!(std::abs(gain) > GainSilenceThreshold)) - continue; - 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); - } - for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; - } + MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, + *TargetGains++, delta, min_len, aligned_len, Counter); +} + +template<> +void Mix_(const al::span InSamples, float *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 = std::min(Counter, InSamples.size()); + const auto aligned_len = std::min((min_len+3_uz) & ~3_uz, InSamples.size()) - min_len; + + MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len, + aligned_len, Counter); } diff --git a/libs/openal-soft/core/mixer/mixer_sse2.cpp b/libs/openal-soft/core/mixer/mixer_sse2.cpp index 2c0adb8a..5108ee56 100644 --- a/libs/openal-soft/core/mixer/mixer_sse2.cpp +++ b/libs/openal-soft/core/mixer/mixer_sse2.cpp @@ -23,67 +23,187 @@ #include #include +#include +#include +#include +#include + #include "alnumeric.h" +#include "alspan.h" +#include "core/cubic_defs.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<> -float *Resample_(const InterpState*, float *RESTRICT src, uint frac, - uint increment, const al::span dst) +void Resample_(const InterpState*, const float *src, uint frac, + const uint increment, const al::span dst) { + ASSUME(frac < MixerFracOne); + const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; 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_); + alignas(16) std::array pos_, frac_; + InitPosArrays(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<__m128>{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))); + 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}) { src += 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)); + std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment]() + { + const float out{lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; src += frac>>MixerFracBits; frac &= MixerFracMask; - } while(--todo); + return out; + }); + } +} + +template<> +void Resample_(const InterpState *state, const float *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)}; + + alignas(16) std::array pos_, frac_; + InitPosArrays(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]))}; + + src -= 1; + auto vecout = al::span<__m128>{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128 + { + 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))); + 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(const size_t todo{dst.size()&3}) + { + src += static_cast(_mm_cvtsi128_si32(pos4)); + frac = static_cast(_mm_cvtsi128_si32(frac4)); + + std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,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))}; + + 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; + frac &= MixerFracMask; + return output; + }); } - return dst.data(); } diff --git a/libs/openal-soft/core/mixer/mixer_sse41.cpp b/libs/openal-soft/core/mixer/mixer_sse41.cpp index cfedfd65..4a834d59 100644 --- a/libs/openal-soft/core/mixer/mixer_sse41.cpp +++ b/libs/openal-soft/core/mixer/mixer_sse41.cpp @@ -24,54 +24,76 @@ #include #include +#include +#include +#include +#include + #include "alnumeric.h" +#include "alspan.h" +#include "core/cubic_defs.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<> -float *Resample_(const InterpState*, float *RESTRICT src, uint frac, - uint increment, const al::span dst) +void Resample_(const InterpState*, const float *src, uint frac, + const uint increment, const al::span dst) { + ASSUME(frac < MixerFracOne); + const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; 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_); + alignas(16) std::array pos_, frac_; + InitPosArrays(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<__m128>{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128 { - 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)); + 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}) { @@ -82,13 +104,111 @@ float *Resample_(const InterpState*, float *RESTRICT src, uint src += 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)); + std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,increment] + { + const float out{lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; src += frac>>MixerFracBits; frac &= MixerFracMask; - } while(--todo); + return out; + }); + } +} + +template<> +void Resample_(const InterpState *state, const float *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)}; + + alignas(16) std::array pos_, frac_; + InitPosArrays(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]))}; + + src -= 1; + auto vecout = al::span<__m128>{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128 + { + 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)); + 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}) + { + src += static_cast(_mm_cvtsi128_si32(pos4)); + frac = static_cast(_mm_cvtsi128_si32(frac4)); + + std::generate(dst.end()-ptrdiff_t(todo), dst.end(), [&src,&frac,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))}; + + 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; + frac &= MixerFracMask; + return output; + }); } - return dst.data(); } diff --git a/libs/openal-soft/core/resampler_limits.h b/libs/openal-soft/core/resampler_limits.h index 9d4cefda..a32807e8 100644 --- a/libs/openal-soft/core/resampler_limits.h +++ b/libs/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/libs/openal-soft/core/rtkit.cpp b/libs/openal-soft/core/rtkit.cpp index 9210220e..70b5e12b 100644 --- a/libs/openal-soft/core/rtkit.cpp +++ b/libs/openal-soft/core/rtkit.cpp @@ -30,17 +30,21 @@ #include "rtkit.h" -#include +#include #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include -#include +#include #include #include +#ifdef __linux__ #include +#elif defined(__FreeBSD__) +#include +#endif namespace dbus { @@ -149,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(); @@ -191,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); @@ -201,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(); @@ -221,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/libs/openal-soft/core/storage_formats.cpp b/libs/openal-soft/core/storage_formats.cpp new file mode 100644 index 00000000..51f64644 --- /dev/null +++ b/libs/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/libs/openal-soft/core/storage_formats.h b/libs/openal-soft/core/storage_formats.h new file mode 100644 index 00000000..acced258 --- /dev/null +++ b/libs/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/libs/openal-soft/core/uhjfilter.cpp b/libs/openal-soft/core/uhjfilter.cpp index 7a68f1b1..5b40a981 100644 --- a/libs/openal-soft/core/uhjfilter.cpp +++ b/libs/openal-soft/core/uhjfilter.cpp @@ -5,19 +5,169 @@ #include #include +#include #include "alcomplex.h" #include "alnumeric.h" #include "opthelpers.h" +#include "pffft.h" #include "phase_shifter.h" +#include "vector.h" + + +UhjQualityType UhjDecodeQuality{UhjQualityType::Default}; +UhjQualityType UhjEncodeQuality{UhjQualityType::Default}; namespace { -const PhaseShifterT PShift{}; +/* 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} + { + using complex_d = std::complex; + constexpr size_t fft_size{N}; + constexpr size_t half_size{fft_size / 2}; + + /* To set up the filter, we need to generate the desired response. + * Start with a pure delay that passes all frequencies through. + */ + auto fftBuffer = std::vector(fft_size, complex_d{}); + fftBuffer[half_size] = 1.0; + + /* Convert to the frequency domain, shift the phase of each bin by +90 + * degrees, then convert back to the time domain. + * + * NOTE: The 0- and half-frequency are always real for a real signal. + * To maintain that and their phase (0 or pi), they're heavily + * attenuated instead of shifted like the others. + */ + forward_fft(al::span{fftBuffer}); + fftBuffer[0] *= std::numeric_limits::epsilon(); + for(size_t i{1};i < half_size;++i) + fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()}; + fftBuffer[half_size] *= std::numeric_limits::epsilon(); + for(size_t i{half_size+1};i < fft_size;++i) + fftBuffer[i] = std::conj(fftBuffer[fft_size - i]); + inverse_fft(al::span{fftBuffer}); + + /* The segments of the filter are converted back to the frequency + * domain, each on their own (0 stuffed). + */ + auto fftBuffer2 = std::vector(sFftLength); + auto fftTmp = al::vector(sFftLength); + float *filter{mFilterData.data()}; + for(size_t s{0};s < sNumSegments;++s) + { + for(size_t i{0};i < sSampleLength;++i) + fftBuffer2[i] = fftBuffer[sSampleLength*s + i].real() / double{fft_size}; + std::fill_n(fftBuffer2.data()+sSampleLength, sSampleLength, complex_d{}); + forward_fft(al::span{fftBuffer2}); + + /* 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(fftBuffer2[i].real()) / float{sFftLength}; + fftTmp[i*2 + 1] = static_cast((i == 0) ? fftBuffer2[sSampleLength].real() + : fftBuffer2[i].imag()) / float{sFftLength}; + } + mFft.zreorder(fftTmp.data(), filter, PFFFT_BACKWARD); + filter += sFftLength; + } + } +}; + +template +const SegmentedFilter gSegmentedFilter; + +template +const PhaseShifterT PShifter; + + +/* 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{{ + 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{{ + 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) +{ + auto state = mState; + + auto proc_sample = [&state,coeffs](float x) noexcept -> float + { + 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; + } + return x; + }; + std::transform(src.begin(), src.end(), dst, proc_sample); + if(updateState) LIKELY mState = state; +} + /* Encoding UHJ from B-Format is done as: * @@ -36,55 +186,199 @@ const PhaseShifterT PShift{}; * impulse with the desired shift. */ -void UhjEncoder::encode(float *LeftOut, float *RightOut, const FloatBufferLine *InSamples, - const size_t SamplesToDo) +template +void UhjEncoder::encode(float *LeftOut, float *RightOut, + const al::span InSamples, const size_t SamplesToDo) +{ + static constexpr auto &Filter = gSegmentedFilter; + static_assert(sFftLength == Filter.sFftLength); + static_assert(sSegmentSize == Filter.sSampleLength); + static_assert(sNumSegments == Filter.sNumSegments); + + ASSUME(SamplesToDo > 0); + + 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])}; + + std::copy_n(winput, SamplesToDo, mW.begin()+sFilterDelay); + std::copy_n(xinput, SamplesToDo, mX.begin()+sFilterDelay); + std::copy_n(yinput, 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]; + + /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */ + size_t curseg{mCurrentSegment}; + for(size_t base{0};base < SamplesToDo;) + { + const size_t todo{std::min(sSegmentSize-mFifoPos, SamplesToDo-base)}; + + /* Copy out the samples that were previously processed by the FFT. */ + std::copy_n(mWXInOut.begin()+mFifoPos, todo, mD.begin()+base); + + /* Transform the non-delayed input and store in the front half of the + * filter input. + */ + std::transform(winput+base, winput+base+todo, xinput+base, mWXInOut.begin()+mFifoPos, + [](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. + */ + float *input{mWXHistory.data() + curseg*sFftLength}; + std::copy_n(mWXInOut.begin(), sSegmentSize, input); + std::fill_n(input+sSegmentSize, sSegmentSize, 0.0f); + + Filter.mFft.transform(input, 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); + const float *filter{Filter.mFilterData.data()}; + for(size_t s{curseg};s < sNumSegments;++s) + { + Filter.mFft.zconvolve_accumulate(input, filter, mFftBuffer.data()); + input += sFftLength; + filter += sFftLength; + } + input = mWXHistory.data(); + for(size_t s{0};s < curseg;++s) + { + Filter.mFft.zconvolve_accumulate(input, 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); + + for(size_t i{0};i < sSegmentSize;++i) + mWXInOut[i] = mFftBuffer[i] + mWXInOut[sSegmentSize+i]; + for(size_t i{0};i < sSegmentSize;++i) + mWXInOut[sSegmentSize+i] = mFftBuffer[sSegmentSize+i]; + + /* 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]; + + /* 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()); + + /* Apply a delay to the existing output to align with the input delay. */ + auto *delayBuffer = mDirectDelay.data(); + for(float *buffer : {LeftOut, RightOut}) + { + float *distbuf{al::assume_aligned<16>(delayBuffer->data())}; + ++delayBuffer; + + float *inout{al::assume_aligned<16>(buffer)}; + auto inout_end = inout + SamplesToDo; + if(SamplesToDo >= sFilterDelay) + { + auto delay_end = std::rotate(inout, inout_end - sFilterDelay, inout_end); + std::swap_ranges(inout, delay_end, distbuf); + } + else + { + auto delay_start = std::swap_ranges(inout, inout_end, distbuf); + std::rotate(distbuf, delay_start, distbuf + 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++) + 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++) + right[i] += (mS[i] - mD[i]) * 0.5f; +} + +/* This encoding implementation uses two sets of four chained IIR filters to + * produce the desired relative phase shift. The first filter chain produces a + * phase shift of varying degrees over a wide range of frequencies, while the + * second filter chain produces a phase shift 90 degrees ahead of the first + * over the same range. Further details are described here: + * + * https://web.archive.org/web/20060708031958/http://www.biochem.oulu.fi/~oniemita/dsp/hilbert/ + * + * 2-channel UHJ output requires the use of three filter chains. The S channel + * output uses a Filter1 chain on the W and X channel mix, while the D channel + * output uses a Filter1 chain on the Y channel plus a Filter2 chain on the W + * and X channel mix. This results in the W and X input mix on the D channel + * output having the required +90 degree phase shift relative to the other + * inputs. + */ +void UhjEncoderIIR::encode(float *LeftOut, float *RightOut, + const al::span InSamples, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); - float *RESTRICT left{al::assume_aligned<16>(LeftOut)}; - float *RESTRICT right{al::assume_aligned<16>(RightOut)}; - - const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0].data())}; - const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1].data())}; - const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())}; - - /* Combine the previously delayed S/D signal with the input. Include any - * existing direct signal with it. - */ + 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])}; /* S = 0.9396926*W + 0.1855740*X */ - auto miditer = mS.begin() + sFilterDelay; - std::transform(winput, winput+SamplesToDo, xinput, miditer, - [](const float w, const float x) noexcept -> float - { return 0.9396926f*w + 0.1855740f*x; }); - for(size_t i{0};i < SamplesToDo;++i,++miditer) - *miditer += left[i] + right[i]; + std::transform(winput, winput+SamplesToDo, xinput, 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); + mS[0] = mDelayWX; mDelayWX = mS[SamplesToDo]; - /* D = 0.6554516*Y */ - auto sideiter = mD.begin() + sFilterDelay; - std::transform(yinput, yinput+SamplesToDo, sideiter, - [](const float y) noexcept -> float { return 0.6554516f*y; }); - for(size_t i{0};i < SamplesToDo;++i,++sideiter) - *sideiter += left[i] - right[i]; + /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mWX. */ + std::transform(winput, winput+SamplesToDo, xinput, 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()); - /* D += j(-0.3420201*W + 0.5098604*X) */ - auto tmpiter = std::copy(mWXHistory.cbegin(), mWXHistory.cend(), mTemp.begin()); - std::transform(winput, winput+SamplesToDo, xinput, tmpiter, - [](const float w, const float x) noexcept -> float - { return -0.3420201f*w + 0.5098604f*x; }); - std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory.size(), mWXHistory.begin()); - PShift.processAccum({mD.data(), SamplesToDo}, mTemp.data()); + /* Apply filter1 to Y and store in mD. */ + mFilter1Y.process(Filter1Coeff, {yinput, SamplesToDo}, SamplesToDo, mD.data()+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]; + + /* Apply the base filter to the existing output to align with the processed + * signal. + */ + mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true, mTemp.data()+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++) - left[i] = (mS[i] + mD[i]) * 0.5f; - /* Right = (S - D)/2.0 */ - for(size_t i{0};i < SamplesToDo;i++) - right[i] = (mS[i] - mD[i]) * 0.5f; + left[i] = (mS[i] + mD[i])*0.5f + mTemp[i]; - /* Copy the future samples to the front for next time. */ - std::copy(mS.cbegin()+SamplesToDo, mS.cbegin()+SamplesToDo+sFilterDelay, mS.begin()); - std::copy(mD.cbegin()+SamplesToDo, mD.cbegin()+SamplesToDo+sFilterDelay, mD.begin()); + mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, true, mTemp.data()+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++) + right[i] = (mS[i] - mD[i])*0.5f + mTemp[i]; } @@ -101,9 +395,14 @@ void UhjEncoder::encode(float *LeftOut, float *RightOut, const FloatBufferLine * * where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2- * channel excludes Q and T. */ -void UhjDecoder::decode(const al::span samples, const size_t samplesToDo, - const size_t forwardSamples) +template +void UhjDecoder::decode(const al::span samples, const size_t samplesToDo, + const bool updateState) { + static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); + + constexpr auto &PShift = PShifter; + ASSUME(samplesToDo > 0); { @@ -112,15 +411,15 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesToDo const float *RESTRICT t{al::assume_aligned<16>(samples[2])}; /* S = Left + Right */ - for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + for(size_t i{0};i < samplesToDo+sInputPadding;++i) mS[i] = left[i] + right[i]; /* D = Left - Right */ - for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + for(size_t i{0};i < samplesToDo+sInputPadding;++i) mD[i] = left[i] - right[i]; /* T */ - for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + for(size_t i{0};i < samplesToDo+sInputPadding;++i) mT[i] = t[i]; } @@ -130,9 +429,10 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesToDo /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); - std::transform(mD.cbegin(), mD.cbegin()+samplesToDo+sFilterDelay, mT.cbegin(), tmpiter, + std::transform(mD.cbegin(), mD.cbegin()+samplesToDo+sInputPadding, mT.cbegin(), tmpiter, [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); - std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.begin()); + if(updateState) LIKELY + std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin()); PShift.process({xoutput, samplesToDo}, mTemp.data()); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ @@ -144,8 +444,9 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesToDo /* Precompute j*S and store in youtput. */ tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); - std::copy_n(mS.cbegin(), samplesToDo+sFilterDelay, tmpiter); - std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.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()); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ @@ -161,6 +462,76 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesToDo } } +void UhjDecoderIIR::decode(const al::span samples, const size_t samplesToDo, + const bool updateState) +{ + static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); + + ASSUME(samplesToDo > 0); + + { + const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; + const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + + /* S = Left + Right */ + for(size_t i{0};i < samplesToDo+sInputPadding;++i) + mS[i] = left[i] + right[i]; + + /* D = Left - Right */ + for(size_t i{0};i < samplesToDo+sInputPadding;++i) + mD[i] = left[i] - right[i]; + } + + 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])}; + + /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ + std::transform(mD.cbegin(), mD.cbegin()+sInputPadding+samplesToDo, youtput, mTemp.begin(), + [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); + if(mFirstRun) mFilter2DT.processOne(Filter2Coeff, mTemp[0]); + mFilter2DT.process(Filter2Coeff, {mTemp.data()+1, samplesToDo}, updateState, xoutput); + + /* Apply filter1 to S and store in mTemp. */ + mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()); + + /* 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]; + /* 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]; + + + /* Apply filter1 to (0.795968*D - 0.676392*T) and store in mTemp. */ + std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, youtput, + [](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; }); + mFilter1DT.process(Filter1Coeff, {youtput, samplesToDo}, updateState, mTemp.data()); + + /* Precompute j*S and store in youtput. */ + if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]); + mFilter2S.process(Filter2Coeff, {mS.data()+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]; + + + if(samples.size() > 3) + { + float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])}; + + /* Apply filter1 to Q and store in mTemp. */ + mFilter1Q.process(Filter1Coeff, {zoutput, samplesToDo}, updateState, mTemp.data()); + + /* Z = 1.023332*Q */ + for(size_t i{0};i < samplesToDo;++i) + zoutput[i] = 1.023332f*mTemp[i]; + } + + mFirstRun = false; +} + /* Super Stereo processing is done as: * @@ -174,39 +545,44 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesToDo * where j is a +90 degree phase shift. w is a variable control for the * resulting stereo width, with the range 0 <= w <= 0.7. */ -void UhjDecoder::decodeStereo(const al::span samples, const size_t samplesToDo, - const size_t forwardSamples) +template +void UhjStereoDecoder::decode(const al::span samples, const size_t samplesToDo, + const bool updateState) { + static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); + + constexpr auto &PShift = PShifter; + ASSUME(samplesToDo > 0); { const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; - for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + for(size_t i{0};i < samplesToDo+sInputPadding;++i) mS[i] = left[i] + right[i]; /* Pre-apply the width factor to the difference signal D. Smoothly * interpolate when it changes. */ const float wtarget{mWidthControl}; - const float wcurrent{unlikely(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; - if(likely(wtarget == wcurrent) || unlikely(forwardSamples == 0)) + const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; + if(wtarget == wcurrent || !updateState) { - for(size_t i{0};i < samplesToDo+sFilterDelay;++i) + for(size_t i{0};i < samplesToDo+sInputPadding;++i) mD[i] = (left[i] - right[i]) * wcurrent; + mCurrentWidth = wcurrent; } else { - const float wstep{(wtarget - wcurrent) / static_cast(forwardSamples)}; + const float wstep{(wtarget - wcurrent) / static_cast(samplesToDo)}; float fi{0.0f}; - size_t i{0}; - for(;i < forwardSamples;++i) + for(size_t i{0};i < samplesToDo;++i) { mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi); fi += 1.0f; } - for(;i < samplesToDo+sFilterDelay;++i) + for(size_t i{samplesToDo};i < samplesToDo+sInputPadding;++i) mD[i] = (left[i] - right[i]) * wtarget; mCurrentWidth = wtarget; } @@ -218,8 +594,9 @@ void UhjDecoder::decodeStereo(const al::span samples, const size_t sampl /* Precompute j*D and store in xoutput. */ auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); - std::copy_n(mD.cbegin(), samplesToDo+sFilterDelay, tmpiter); - std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.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()); /* W = 0.6098637*S - 0.6896511*j*w*D */ @@ -231,11 +608,93 @@ void UhjDecoder::decodeStereo(const al::span samples, const size_t sampl /* Precompute j*S and store in youtput. */ tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); - std::copy_n(mS.cbegin(), samplesToDo+sFilterDelay, tmpiter); - std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.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()); /* 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]; } + +void UhjStereoDecoderIIR::decode(const al::span samples, const size_t samplesToDo, + const bool updateState) +{ + static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); + + ASSUME(samplesToDo > 0); + + { + const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; + const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + + for(size_t i{0};i < samplesToDo+sInputPadding;++i) + mS[i] = left[i] + right[i]; + + /* Pre-apply the width factor to the difference signal D. Smoothly + * interpolate when it changes. + */ + const float wtarget{mWidthControl}; + 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; + 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; + 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])}; + + /* Apply filter1 to S and store in mTemp. */ + mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()); + + /* Precompute j*D and store in xoutput. */ + if(mFirstRun) mFilter2D.processOne(Filter2Coeff, mD[0]); + mFilter2D.process(Filter2Coeff, {mD.data()+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]; + /* 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]; + + /* Precompute j*S and store in youtput. */ + if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]); + mFilter2S.process(Filter2Coeff, {mS.data()+1, samplesToDo}, updateState, youtput); + + /* Apply filter1 to D and store in mTemp. */ + mFilter1D.process(Filter1Coeff, {mD.data(), samplesToDo}, updateState, mTemp.data()); + + /* 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]; + + mFirstRun = false; +} + + +template struct UhjEncoder; +template struct UhjDecoder; +template struct UhjStereoDecoder; + +template struct UhjEncoder; +template struct UhjDecoder; +template struct UhjStereoDecoder; diff --git a/libs/openal-soft/core/uhjfilter.h b/libs/openal-soft/core/uhjfilter.h index 0453def5..eb822ebf 100644 --- a/libs/openal-soft/core/uhjfilter.h +++ b/libs/openal-soft/core/uhjfilter.h @@ -2,58 +2,150 @@ #define CORE_UHJFILTER_H #include +#include #include "almalloc.h" +#include "alspan.h" #include "bufferline.h" -#include "resampler_limits.h" -struct UhjFilterBase { - /* The filter delay is half it's effective size, so a delay of 128 has a - * FIR length of 256. - */ - static constexpr size_t sFilterDelay{128}; +inline constexpr std::size_t UhjLength256{256}; +inline constexpr std::size_t UhjLength512{512}; + +enum class UhjQualityType : std::uint8_t { + IIR = 0, + FIR256, + FIR512, + Default = IIR }; -struct UhjEncoder : public UhjFilterBase { - /* Delays and processing storage for the unfiltered signal. */ - alignas(16) std::array mS{}; - alignas(16) std::array mD{}; +extern UhjQualityType UhjDecodeQuality; +extern UhjQualityType UhjEncodeQuality; - /* History for the FIR filter. */ - alignas(16) std::array mWXHistory{}; - alignas(16) std::array mTemp{}; +struct UhjAllPassFilter { + struct AllPassState { + /* Last two delayed components for direct form II. */ + 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); +}; + + +struct UhjEncoderBase { + virtual ~UhjEncoderBase() = default; + + virtual std::size_t getDelay() noexcept = 0; /** * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input * signal. The input must use FuMa channel ordering and UHJ scaling (FuMa * with an additional +3dB boost). */ - void encode(float *LeftOut, float *RightOut, const FloatBufferLine *InSamples, - const size_t SamplesToDo); + virtual void encode(float *LeftOut, float *RightOut, + const al::span InSamples, const std::size_t SamplesToDo) = 0; +}; - DEF_NEWDEL(UhjEncoder) +template +struct UhjEncoder final : public UhjEncoderBase { + 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{}; + alignas(16) std::array mX{}; + alignas(16) std::array mY{}; + + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + + /* 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{}; + + std::size_t getDelay() noexcept override { return sFilterDelay; } + + /** + * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input + * signal. The input must use FuMa channel ordering and UHJ scaling (FuMa + * with an additional +3dB boost). + */ + void encode(float *LeftOut, float *RightOut, const al::span InSamples, + const std::size_t SamplesToDo) override; +}; + +struct UhjEncoderIIR final : public UhjEncoderBase { + static constexpr std::size_t sFilterDelay{1}; + + /* Processing storage for the input signal. */ + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mWX{}; + alignas(16) std::array mTemp{}; + float mDelayWX{}, mDelayY{}; + + UhjAllPassFilter mFilter1WX; + UhjAllPassFilter mFilter2WX; + UhjAllPassFilter mFilter1Y; + + std::array mFilter1Direct; + std::array mDirectDelay{}; + + std::size_t getDelay() noexcept override { return sFilterDelay; } + + /** + * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input + * signal. The input must use FuMa channel ordering and UHJ scaling (FuMa + * with an additional +3dB boost). + */ + void encode(float *LeftOut, float *RightOut, const al::span InSamples, + const std::size_t SamplesToDo) override; }; -struct UhjDecoder : public UhjFilterBase { - alignas(16) std::array mS{}; - alignas(16) std::array mD{}; - alignas(16) std::array mT{}; +struct DecoderBase { + static constexpr std::size_t sMaxPadding{256}; - alignas(16) std::array mDTHistory{}; - alignas(16) std::array mSHistory{}; + /* For 2-channel UHJ, shelf filters should use these LF responses. */ + static constexpr float sWLFScale{0.661f}; + static constexpr float sXYLFScale{1.293f}; - alignas(16) std::array mTemp{}; + virtual ~DecoderBase() = default; - float mCurrentWidth{-1.0f}; + virtual void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) = 0; /** * The width factor for Super Stereo processing. Can be changed in between - * calls to decodeStereo, with valid values being between 0...0.7. + * calls to decode, with valid values being between 0...0.7. */ float mWidthControl{0.593f}; +}; + +template +struct UhjDecoder final : public DecoderBase { + /* The number of extra sample frames needed for input. */ + static constexpr std::size_t sInputPadding{N/2}; + + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mT{}; + + alignas(16) std::array mDTHistory{}; + alignas(16) std::array mSHistory{}; + + alignas(16) std::array mTemp{}; /** * Decodes a 3- or 4-channel UHJ signal into a B-Format signal with FuMa @@ -63,8 +155,47 @@ struct UhjDecoder : public UhjFilterBase { * 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 size_t forwardSamples); + void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) override; +}; + +struct UhjDecoderIIR final : public DecoderBase { + /* 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 std::size_t sInputPadding{1}; + + bool mFirstRun{true}; + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mTemp{}; + + UhjAllPassFilter mFilter1S; + UhjAllPassFilter mFilter2DT; + UhjAllPassFilter mFilter1DT; + UhjAllPassFilter mFilter2S; + UhjAllPassFilter mFilter1Q; + + void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) override; +}; + +template +struct UhjStereoDecoder final : public DecoderBase { + static constexpr std::size_t sInputPadding{N/2}; + + float mCurrentWidth{-1.0f}; + + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + + alignas(16) std::array mDTHistory{}; + alignas(16) std::array mSHistory{}; + + alignas(16) std::array mTemp{}; /** * Applies Super Stereo processing on a stereo signal to create a B-Format @@ -72,13 +203,27 @@ struct UhjDecoder : public UhjFilterBase { * should contain 3 channels, the first two being the left and right stereo * channels, and the third left empty. */ - void decodeStereo(const al::span samples, const size_t samplesToDo, - const size_t forwardSamples); + void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) override; +}; - using DecoderFunc = void (UhjDecoder::*)(const al::span samples, - const size_t samplesToDo, const size_t forwardSamples); +struct UhjStereoDecoderIIR final : public DecoderBase { + static constexpr std::size_t sInputPadding{1}; - DEF_NEWDEL(UhjDecoder) + bool mFirstRun{true}; + float mCurrentWidth{-1.0f}; + + 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 std::size_t samplesToDo, + const bool updateState) override; }; #endif /* CORE_UHJFILTER_H */ diff --git a/libs/openal-soft/core/uiddefs.cpp b/libs/openal-soft/core/uiddefs.cpp index 244c01a5..9471bba5 100644 --- a/libs/openal-soft/core/uiddefs.cpp +++ b/libs/openal-soft/core/uiddefs.cpp @@ -19,12 +19,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/libs/openal-soft/core/voice.cpp b/libs/openal-soft/core/voice.cpp index e269c4a9..a2251a7f 100644 --- a/libs/openal-soft/core/voice.cpp +++ b/libs/openal-soft/core/voice.cpp @@ -7,17 +7,17 @@ #include #include #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" @@ -48,18 +48,22 @@ struct SSETag; #ifdef HAVE_NEON struct NEONTag; #endif -struct CopyTag; -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"); -Resampler ResamplerDefault{Resampler::Linear}; +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); @@ -70,7 +74,20 @@ using HrtfMixerBlendFunc = void(*)(const float *InSamples, float2 *AccumSamples, HrtfMixerFunc MixHrtfSamples{MixHrtf_}; HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_}; -inline MixerFunc SelectMixer() +inline MixerOutFunc SelectMixer() +{ +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return Mix_; +#endif +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return Mix_; +#endif + return Mix_; +} + +inline MixerOneFunc SelectMixerOne() { #ifdef HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) @@ -111,47 +128,49 @@ 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{"cubic"sv, Resampler::Cubic}, + 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, "bsinc"sv) == 0) { - WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str); - str = "bsinc12"; + WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", resopt->c_str()); + resampler = "bsinc12"sv; } - else if(al::strcasecmp(str, "sinc4") == 0 || al::strcasecmp(str, "sinc8") == 0) + else if(al::case_compare(resampler, "sinc4"sv) == 0 + || al::case_compare(resampler, "sinc8"sv) == 0) { - WARN("Resampler option \"%s\" is deprecated, using cubic\n", str); - str = "cubic"; + WARN("Resampler option \"%s\" is deprecated, using cubic\n", resopt->c_str()); + resampler = "cubic"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; } - MixSamples = SelectMixer(); + MixSamplesOut = SelectMixer(); + MixSamplesOne = SelectMixerOne(); MixHrtfBlendSamples = SelectHrtfBlendMixer(); MixHrtfSamples = SelectHrtfMixer(); } @@ -159,23 +178,65 @@ void Voice::InitMixer(al::optional resampler) namespace { +/* IMA ADPCM Stepsize table */ +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, + 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, + 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, + 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, + 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442, + 11487,12635,13899,15289,16818,18500,20350,22358,24633,27086,29794, + 32767 +}}; + +/* IMA4 ADPCM Codeword decode table */ +constexpr 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 std::arrayIMA4Index_adjust{{ + -1,-1,-1,-1, 2, 4, 6, 8, + -1,-1,-1,-1, 2, 4, 6, 8 +}}; + +/* MSADPCM Adaption table */ +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 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} +}; + + void SendSourceStoppedEvent(ContextBase *context, uint id) { RingBuffer *ring{context->mAsyncEvents.get()}; 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) { @@ -187,134 +248,337 @@ 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(const al::span dstSamples, const size_t dstOffset, - const al::byte *src, const size_t srcOffset, const FmtChannels srcChans, const size_t srcStep, - const size_t samples) 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*sampleSize; - if(srcChans == FmtUHJ2 || srcChans == FmtSuperStereo) + using TypeTraits = al::FmtTypeTraits; + using SampleType = typename TypeTraits::Type; + static constexpr size_t sampleSize{sizeof(SampleType)}; + assert(srcChan < srcStep); + auto converter = TypeTraits{}; + + 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] { - al::LoadSampleArray(dstSamples[0]+dstOffset, s, srcStep, samples); - al::LoadSampleArray(dstSamples[1]+dstOffset, s+sampleSize, srcStep, samples); - std::fill_n(dstSamples[2]+dstOffset, samples, 0.0f); - } - else + auto ret = converter(ssrc[srcChan]); + ssrc += ptrdiff_t(srcStep); + return ret; + }); +} + +template<> +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) 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 = 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. */ + while(!dstSamples.empty()) { - for(auto *dst : dstSamples) + 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{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 = std::clamp((index^0x8000) - 32768, 0, MaxStepIndex); + + if(skip == 0) { - al::LoadSampleArray(dst+dstOffset, s, srcStep, samples); - s += sampleSize; + 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[static_cast(index)] / 8; + sample = std::clamp(sample, -32768, 32767); + + index += IMA4Index_adjust[nibble]; + index = std::clamp(index, 0, MaxStepIndex); + + return sample; + }; + + /* The rest of the block is arranged as a series of nibbles, contained + * in 4 *bytes* per channel interleaved. So every 8 nibbles we need to + * skip 4 bytes per channel to get the next nibbles for this channel. + * + * First, decode the samples that we need to skip in the block (will + * always be less than the block size). They need to be decoded despite + * being ignored for proper state on the remaining samples. + */ + 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) & ~3_uz}; + const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; + ++nibbleOffset; + + 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{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) & ~3_uz}; + const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; + ++nibbleOffset; + + const int result{decode_sample(uint(nibbleData[byteOffset]>>byteShift) & 15u)}; + return static_cast(result) / 32768.0f; + }); + dstSamples = dstSamples.subspan(todo); } } -void LoadSamples(const al::span dstSamples, const size_t dstOffset, const al::byte *src, - const size_t srcOffset, const FmtType srcType, const FmtChannels srcChans, - const size_t srcStep, const size_t samples) noexcept +template<> +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) noexcept +{ + assert(srcStep > 0 || srcStep <= 2); + assert(srcChan < srcStep); + assert(samplesPerBlock > 2); + const size_t blockBytes{((samplesPerBlock-2)/2 + 7)*srcStep}; + + src = src.subspan(srcOffset/samplesPerBlock*blockBytes); + size_t skip{srcOffset % samplesPerBlock}; + + 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 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); + + 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 al::span coeffs{MSADPCMAdaptionCoeff[blockpred]}; + delta = (delta^0x8000) - 32768; + sampleHistory[0] = (sampleHistory[0]^0x8000) - 32768; + sampleHistory[1] = (sampleHistory[1]^0x8000) - 32768; + + /* The second history sample is "older", so it's the first to be + * written out. + */ + if(skip == 0) + { + 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[0] = static_cast(sampleHistory[0]) / 32768.0f; + dstSamples = dstSamples.subspan<1>(); + if(dstSamples.empty()) return; + } + else + skip -= 2; + + auto decode_sample = [&sampleHistory,&delta,coeffs](const int nibble) + { + int pred{(sampleHistory[0]*coeffs[0] + sampleHistory[1]*coeffs[1]) / 256}; + pred += ((nibble^0x08) - 0x08) * delta; + pred = std::clamp(pred, -32768, 32767); + + sampleHistory[1] = sampleHistory[0]; + sampleHistory[0] = pred; + + delta = (MSADPCMAdaption[static_cast(nibble)] * delta) / 256; + delta = std::max(16, delta); + + return pred; + }; + + /* The rest of the block is a series of nibbles, interleaved per- + * channel. First, skip samples. + */ + const size_t startOffset{skip + 2}; + size_t nibbleOffset{srcChan}; + for(;skip;--skip) + { + const size_t byteOffset{nibbleOffset>>1}; + const size_t byteShift{((nibbleOffset&1)^1) * 4}; + nibbleOffset += srcStep; + + 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{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(int(input[byteOffset]>>byteShift) & 15)}; + return static_cast(sample) / 32768.0f; + }); + dstSamples = dstSamples.subspan(todo); + } +} + +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, dstOffset, src, srcOffset, srcChans, srcStep, \ - samples); \ + LoadSamples(dstSamples, src, srcChan, srcOffset, srcStep, \ + samplesPerBlock); \ break switch(srcType) { HANDLE_FMT(FmtUByte); HANDLE_FMT(FmtShort); + HANDLE_FMT(FmtInt); HANDLE_FMT(FmtFloat); HANDLE_FMT(FmtDouble); HANDLE_FMT(FmtMulaw); HANDLE_FMT(FmtAlaw); + HANDLE_FMT(FmtIMA4); + HANDLE_FMT(FmtMSADPCM); } #undef HANDLE_FMT } void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, - const size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels, - const size_t srcStep, const size_t samplesToLoad, const al::span voiceSamples) + const size_t dataPosInt, const FmtType sampleType, const size_t srcChannel, + const size_t srcStep, al::span voiceSamples) { - const uint loopStart{buffer->mLoopStart}; - const uint loopEnd{buffer->mLoopEnd}; - ASSUME(loopEnd > loopStart); - - /* If current pos is beyond the loop range, do not loop */ - if(!bufferLoopItem || dataPosInt >= loopEnd) + if(!bufferLoopItem) { + float lastSample{0.0f}; /* Load what's left to play from the buffer */ - const size_t remaining{minz(samplesToLoad, buffer->mSampleLen-dataPosInt)}; - LoadSamples(voiceSamples, 0, buffer->mSamples, dataPosInt, sampleType, sampleChannels, - srcStep, remaining); - - if(const size_t toFill{samplesToLoad - remaining}) + if(buffer->mSampleLen > dataPosInt) LIKELY { - for(auto *chanbuffer : voiceSamples) - { - auto srcsamples = chanbuffer + remaining - 1; - std::fill_n(srcsamples + 1, toFill, *srcsamples); - } + const size_t buffer_remaining{buffer->mSampleLen - dataPosInt}; + 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{voiceSamples.size()}) + std::fill_n(voiceSamples.begin(), toFill, lastSample); } else { + const size_t loopStart{buffer->mLoopStart}; + const size_t loopEnd{buffer->mLoopEnd}; + ASSUME(loopEnd > loopStart); + + const size_t intPos{(dataPosInt < loopEnd) ? dataPosInt + : (((dataPosInt-loopStart)%(loopEnd-loopStart)) + loopStart)}; + /* Load what's left of this loop iteration */ - const size_t remaining{minz(samplesToLoad, loopEnd-dataPosInt)}; - LoadSamples(voiceSamples, 0, buffer->mSamples, dataPosInt, sampleType, sampleChannels, - srcStep, 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 auto loopSize = static_cast(loopEnd - loopStart); - size_t samplesLoaded{remaining}; - while(const size_t toFill{minz(samplesToLoad - samplesLoaded, loopSize)}) + const size_t loopSize{loopEnd - loopStart}; + while(const size_t toFill{std::min(voiceSamples.size(), loopSize)}) { - LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, loopStart, sampleType, - sampleChannels, srcStep, 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 numCallbackSamples, - const FmtType sampleType, const FmtChannels sampleChannels, const size_t srcStep, - const size_t samplesToLoad, const al::span voiceSamples) +void LoadBufferCallback(VoiceBufferItem *buffer, const size_t dataPosInt, + const size_t numCallbackSamples, const FmtType sampleType, const size_t srcChannel, + const size_t srcStep, al::span voiceSamples) { - /* Load what's left to play from the buffer */ - const size_t remaining{minz(samplesToLoad, numCallbackSamples)}; - LoadSamples(voiceSamples, 0, buffer->mSamples, 0, sampleType, sampleChannels, srcStep, - remaining); - - if(const size_t toFill{samplesToLoad - remaining}) + float lastSample{0.0f}; + if(numCallbackSamples > dataPosInt) LIKELY { - for(auto *chanbuffer : voiceSamples) - { - auto srcsamples = chanbuffer + remaining - 1; - std::fill_n(srcsamples + 1, toFill, *srcsamples); - } + 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{voiceSamples.size()}) + std::fill_n(voiceSamples.begin(), toFill, lastSample); } void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, - size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels, - const size_t srcStep, const size_t samplesToLoad, const al::span voiceSamples) + size_t dataPosInt, const FmtType sampleType, const size_t srcChannel, + const size_t srcStep, al::span voiceSamples) { + float lastSample{0.0f}; /* Crawl the buffer queue to fill in the temp buffer */ - size_t samplesLoaded{0}; - while(buffer && samplesLoaded != samplesToLoad) + while(buffer && !voiceSamples.empty()) { if(dataPosInt >= buffer->mSampleLen) { @@ -324,53 +588,45 @@ void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, continue; } - const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer->mSampleLen-dataPosInt)}; - LoadSamples(voiceSamples, samplesLoaded, buffer->mSamples, dataPosInt, sampleType, - sampleChannels, srcStep, 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}) - { - size_t chanidx{0}; - for(auto *chanbuffer : voiceSamples) - { - auto srcsamples = chanbuffer + samplesLoaded - 1; - std::fill_n(srcsamples + 1, toFill, *srcsamples); - ++chanidx; - } - } + 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, uint 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(likely(IsPlaying)) - std::copy_n(std::begin(HrtfSamples) + DstBufferSize, parms.Hrtf.History.size(), + if(IsPlaying) LIKELY + std::copy_n(HrtfSamples.begin() + ptrdiff_t(samples.size()), parms.Hrtf.History.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}; @@ -389,8 +645,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.data(), AccumSamples.data()+OutPos, IrSize, + &parms.Hrtf.Old, &hrtfparams, fademix); /* Update the old parameters with the result. */ parms.Hrtf.Old = parms.Hrtf.Target; @@ -398,15 +654,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); @@ -417,33 +673,37 @@ 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.data()+fademix, AccumSamples.data()+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 float *TargetGains, 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; + auto CurrentGains = parms.Gains.Current.begin(); + MixSamples(samples, OutBuffer.first<1>(), al::to_address(CurrentGains), TargetGains, Counter, + OutPos); + OutBuffer = OutBuffer.subspan<1>(); ++CurrentGains; ++TargetGains; - const al::span nfcsamples{Device->NfcSampleData, samples.size()}; + const auto nfcsamples = al::span{Device->ExtraSampleData.begin(), 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; + (parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples); + MixSamples(nfcsamples, OutBuffer.first(chancount), al::to_address(CurrentGains), + TargetGains, Counter, OutPos); + OutBuffer = OutBuffer.subspan(chancount); CurrentGains += chancount; TargetGains += chancount; if(++order == MaxAmbiOrder+1) @@ -453,19 +713,23 @@ void DoNfcMix(const al::span samples, FloatBufferLine *OutBuffer, D } // namespace -void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo) +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); + DeviceBase *Device{Context->mDevice}; + const uint NumSends{Device->NumAuxSends}; + /* Get voice info */ - uint DataPosInt{mPosition.load(std::memory_order_relaxed)}; + int DataPosInt{mPosition.load(std::memory_order_relaxed)}; uint DataPosFrac{mPositionFrac.load(std::memory_order_relaxed)}; VoiceBufferItem *BufferListItem{mCurrentBuffer.load(std::memory_order_relaxed)}; VoiceBufferItem *BufferLoopItem{mLoopBuffer.load(std::memory_order_relaxed)}; const uint increment{mStep}; - if UNLIKELY(increment < 1) + if(increment < 1) UNLIKELY { /* If the voice is supposed to be stopping but can't be mixed, just * stop it before bailing. @@ -475,13 +739,292 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo return; } - DeviceBase *Device{Context->mDevice}; - const uint NumSends{Device->NumAuxSends}; + /* If the static voice's current position is beyond the buffer loop end + * position, disable looping. + */ + if(mFlags.test(VoiceIsStatic) && BufferLoopItem) + { + if(DataPosInt >= 0 && static_cast(DataPosInt) >= BufferListItem->mLoopEnd) + BufferLoopItem = nullptr; + } - ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0) ? - Resample_ : mResampler}; + uint OutPos{0u}; - uint Counter{mFlags.test(VoiceIsFading) ? SamplesToDo : 0}; + /* Check if we're doing a delayed start, and we start in this update. */ + if(mStartTime > deviceTime) UNLIKELY + { + /* If the voice is supposed to be stopping but hasn't actually started + * yet, make sure its stopped. + */ + if(vstate == Stopping) + { + mPlayState.store(Stopped, std::memory_order_release); + return; + } + + /* If the start time is too far ahead, don't bother. */ + auto diff = mStartTime - deviceTime; + if(diff >= seconds{1}) + return; + + /* Get the number of samples ahead of the current time that output + * should start at. Skip this update if it's beyond the output sample + * count. + * + * Round the start position to a multiple of 4, which some mixers want. + * This makes the start time accurate to 4 samples. This could be made + * sample-accurate by forcing non-SIMD functions on the first run. + */ + seconds::rep sampleOffset{duration_cast(diff * Device->Frequency).count()}; + sampleOffset = (sampleOffset+2) & ~seconds::rep{3}; + if(sampleOffset >= SamplesToDo) + return; + + OutPos = static_cast(sampleOffset); + } + + /* Calculate the number of samples to mix, and the number of (resampled) + * samples that need to be loaded (mixing samples and decoder padding). + */ + const uint samplesToMix{SamplesToDo - OutPos}; + const uint samplesToLoad{samplesToMix + mDecoderPadding}; + + /* 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()}; + { + 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). MonoDup only has 1 buffer + * channel, but 2 mixing channels (2nd channel is just duplicated). + */ + const size_t realChannels{(mFmtChannels == FmtMonoDup) ? 1u + : (mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 2u + : MixingSamples.size()}; + for(size_t chan{0};chan < realChannels;++chan) + { + static constexpr uint ResBufSize{std::tuple_size_v}; + static constexpr uint srcSizeMax{ResBufSize - 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}; + + /* Load samples for this channel from the available buffer(s), with + * resampling. + */ + for(uint samplesLoaded{0};samplesLoaded < samplesToLoad;) + { + /* Calculate the number of dst samples that can be loaded this + * iteration, given the available resampler buffer size, and the + * number of src samples that are needed to load it. + */ + auto calc_buffer_sizes = [fracPos,increment](uint dstBufferSize) + { + /* If ext=true, calculate the last written dst pos from the dst + * count, convert to the last read src pos, then add one to get + * the src count. + * + * If ext=false, convert the dst count to src count directly. + * + * Without this, the src count could be short by one when + * increment < 1.0, or not have a full src at the end when + * increment > 1.0. + */ + const bool ext{increment <= MixerFracOne}; + uint64_t dataSize64{dstBufferSize - ext}; + dataSize64 = (dataSize64*increment + fracPos) >> MixerFracBits; + /* Also include resampler padding. */ + dataSize64 += ext + MaxResamplerEdge; + + if(dataSize64 <= srcSizeMax) + return std::make_pair(dstBufferSize, static_cast(dataSize64)); + + /* If the source size got saturated, we can't fill the desired + * dst size. Figure out how many dst samples we can fill. + */ + dataSize64 = srcSizeMax - MaxResamplerEdge; + dataSize64 = ((dataSize64<(dataSize64) & ~3u; + } + return std::make_pair(dstBufferSize, srcSizeMax); + }; + 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) UNLIKELY + { + const uint avail{std::min(srcBufferSize, MaxResamplerEdge)}; + const uint tofill{std::max(srcBufferSize, MaxResamplerEdge)}; + + /* 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.begin(), + resampleBuffer.begin()+ptrdiff_t(avail), abs_lt); + + std::fill(srciter+1, resampleBuffer.begin()+ptrdiff_t(tofill), *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 + { + 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); + } + + /* 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, resampleBuffer.data(), fracPos, increment, + {MixingSamples[chan]+samplesLoaded, dstBufferSize}); + + /* Store the last source samples used for next time. */ + if(vstate == Playing) LIKELY + { + /* Only store samples for the end of the mix, excluding what + * gets loaded for decoder padding. + */ + const uint loadEnd{samplesLoaded + dstBufferSize}; + if(samplesToMix > samplesLoaded && samplesToMix <= loadEnd) LIKELY + { + const size_t dstOffset{samplesToMix - samplesLoaded}; + const size_t srcOffset{(dstOffset*increment + fracPos) >> MixerFracBits}; + std::copy_n(Device->mResampleData.cbegin()+srcOffset, prevSamples.size(), + prevSamples.begin()); + } + } + + skip_resample: + samplesLoaded += dstBufferSize; + if(samplesLoaded < samplesToLoad) + { + fracPos += dstBufferSize*increment; + const uint srcOffset{fracPos >> MixerFracBits}; + fracPos &= MixerFracMask; + 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_n(Device->mResampleData.cbegin()+srcOffset, MaxResamplerPadding, + Device->mResampleData.begin()); + } + } + } + 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) + mDecoder->decode(MixingSamples, samplesToMix, (vstate==Playing)); + + if(mFlags.test(VoiceIsAmbisonic)) + { + auto voiceSamples = MixingSamples.begin(); + for(auto &chandata : mChans) + { + chandata.mAmbiSplitter.processScale({*voiceSamples, samplesToMix}, + chandata.mAmbiHFScale, chandata.mAmbiLFScale); + ++voiceSamples; + } + } + + const uint Counter{mFlags.test(VoiceIsFading) ? std::min(samplesToMix, 64u) : 0u}; if(!Counter) { /* No fading, just overwrite the old/current params. */ @@ -504,292 +1047,131 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo } } } - else if UNLIKELY(!BufferListItem) - Counter = std::min(Counter, 64u); - std::array SamplePointers; - const al::span MixingSamples{SamplePointers.data(), mChans.size()}; - auto offset_bufferline = [](DeviceBase::MixerBufferLine &bufline) noexcept -> float* - { return bufline.data() + MaxResamplerEdge; }; - std::transform(Device->mSampleData.end() - mChans.size(), Device->mSampleData.end(), - MixingSamples.begin(), offset_bufferline); - - const uint PostPadding{MaxResamplerEdge + - (mDecoder ? uint{UhjDecoder::sFilterDelay} : 0u)}; - uint buffers_done{0u}; - uint OutPos{0u}; - do { - /* Figure out how many buffer samples will be needed */ - uint DstBufferSize{SamplesToDo - OutPos}; - uint SrcBufferSize; - - if(increment <= MixerFracOne) + auto voiceSamples = MixingSamples.begin(); + for(auto &chandata : mChans) + { + /* Now filter and mix to the appropriate outputs. */ + const al::span FilterBuf{Device->FilteredData}; { - /* Calculate the last written dst sample pos. */ - uint64_t DataSize64{DstBufferSize - 1}; - /* Calculate the last read src sample pos. */ - DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits; - /* +1 to get the src sample count, include padding. */ - DataSize64 += 1 + PostPadding; + DirectParams &parms = chandata.mDryParams; + const auto samples = DoFilters(parms.LowPass, parms.HighPass, FilterBuf, + {*voiceSamples, samplesToMix}, mDirect.FilterType); - /* Result is guaranteed to be <= BufferLineSize+PostPadding since - * we won't use more src samples than dst samples+padding. - */ - SrcBufferSize = static_cast(DataSize64); - } - else - { - uint64_t DataSize64{DstBufferSize}; - /* Calculate the end src sample pos, include padding. */ - DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits; - DataSize64 += PostPadding; - - if(DataSize64 <= DeviceBase::MixerLineSize - MaxResamplerEdge) - SrcBufferSize = static_cast(DataSize64); + if(mFlags.test(VoiceHasHrtf)) + { + const float TargetGain{parms.Hrtf.Target.Gain * float(vstate == Playing)}; + DoHrtfMix(samples, parms, TargetGain, Counter, OutPos, (vstate == Playing), + Device); + } else { - /* If the source size got saturated, we can't fill the desired - * dst size. Figure out how many samples we can actually mix. - */ - SrcBufferSize = DeviceBase::MixerLineSize - MaxResamplerEdge; - - DataSize64 = SrcBufferSize - PostPadding; - DataSize64 = ((DataSize64<(DataSize64) & ~3u; - /* If the voice is stopping, only one mixing iteration will - * be done, so ensure it fades out completely this mix. - */ - if(unlikely(vstate == Stopping)) - Counter = std::min(Counter, DstBufferSize); - } - ASSUME(DstBufferSize > 0); - } - } - - if(unlikely(!BufferListItem)) - { - const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; - auto prevSamples = mPrevSamples.data(); - SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge; - for(auto *chanbuffer : MixingSamples) - { - auto srcend = std::copy_n(prevSamples->data(), MaxResamplerPadding, - chanbuffer-MaxResamplerEdge); - - /* 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(chanbuffer, srcend, abs_lt); - - std::fill(srciter+1, chanbuffer + SrcBufferSize, *srciter); - - std::copy_n(chanbuffer-MaxResamplerEdge+srcOffset, prevSamples->size(), - prevSamples->data()); - ++prevSamples; - } - } - else - { - auto prevSamples = mPrevSamples.data(); - for(auto *chanbuffer : MixingSamples) - { - std::copy_n(prevSamples->data(), MaxResamplerEdge, chanbuffer-MaxResamplerEdge); - ++prevSamples; - } - if(mFlags.test(VoiceIsStatic)) - LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, - mFmtChannels, mFrameStep, SrcBufferSize, MixingSamples); - else if(mFlags.test(VoiceIsCallback)) - { - if(!mFlags.test(VoiceCallbackStopped) && SrcBufferSize > mNumCallbackSamples) - { - const size_t byteOffset{mNumCallbackSamples*mFrameSize}; - const size_t needBytes{SrcBufferSize*mFrameSize - byteOffset}; - - 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); - mNumCallbackSamples += static_cast(gotBytes) / mFrameSize; - } - else - mNumCallbackSamples = SrcBufferSize; - } - LoadBufferCallback(BufferListItem, mNumCallbackSamples, mFmtType, mFmtChannels, - mFrameStep, SrcBufferSize, MixingSamples); - } - else - LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels, - mFrameStep, SrcBufferSize, MixingSamples); - - const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits}; - if(mDecoder) - { - SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge; - ((*mDecoder).*mDecoderFunc)(MixingSamples, SrcBufferSize, - srcOffset * likely(vstate == Playing)); - } - /* Store the last source samples used for next time. */ - if(likely(vstate == Playing)) - { - prevSamples = mPrevSamples.data(); - for(auto *chanbuffer : MixingSamples) - { - /* Store the last source samples used for next time. */ - std::copy_n(chanbuffer-MaxResamplerEdge+srcOffset, prevSamples->size(), - prevSamples->data()); - ++prevSamples; - } - } - } - - auto voiceSamples = MixingSamples.begin(); - for(auto &chandata : mChans) - { - /* Resample, then apply ambisonic upsampling as needed. */ - float *ResampledData{Resample(&mResampleState, *voiceSamples, DataPosFrac, increment, - {Device->ResampledData, DstBufferSize})}; - ++voiceSamples; - - if(mFlags.test(VoiceIsAmbisonic)) - chandata.mAmbiSplitter.processScale({ResampledData, DstBufferSize}, - chandata.mAmbiHFScale, chandata.mAmbiLFScale); - - /* Now filter and mix to the appropriate outputs. */ - const al::span FilterBuf{Device->FilteredData}; - { - DirectParams &parms = chandata.mDryParams; - const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(), - {ResampledData, DstBufferSize}, mDirect.FilterType)}; - - if(mFlags.test(VoiceHasHrtf)) - { - const float TargetGain{parms.Hrtf.Target.Gain * likely(vstate == Playing)}; - DoHrtfMix(samples, DstBufferSize, parms, TargetGain, Counter, OutPos, - (vstate == Playing), Device); - } - else - { - const float *TargetGains{likely(vstate == Playing) ? parms.Gains.Target.data() - : SilentTarget.data()}; - if(mFlags.test(VoiceHasNfc)) - DoNfcMix({samples, DstBufferSize}, mDirect.Buffer.data(), parms, - TargetGains, Counter, OutPos, Device); - else - MixSamples({samples, DstBufferSize}, mDirect.Buffer, - parms.Gains.Current.data(), TargetGains, Counter, OutPos); - } - } - - for(uint send{0};send < NumSends;++send) - { - if(mSend[send].Buffer.empty()) - continue; - - SendParams &parms = chandata.mWetParams[send]; - const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(), - {ResampledData, DstBufferSize}, mSend[send].FilterType)}; - - const float *TargetGains{likely(vstate == Playing) ? parms.Gains.Target.data() + const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data() : SilentTarget.data()}; - MixSamples({samples, DstBufferSize}, mSend[send].Buffer, - parms.Gains.Current.data(), TargetGains, Counter, OutPos); + if(mFlags.test(VoiceHasNfc)) + DoNfcMix(samples, mDirect.Buffer, parms, TargetGains, Counter, OutPos, Device); + else + MixSamples(samples, mDirect.Buffer, parms.Gains.Current.data(), TargetGains, + Counter, OutPos); } } - /* If the voice is stopping, we're now done. */ - if(unlikely(vstate == Stopping)) - break; - /* Update positions */ - DataPosFrac += increment*DstBufferSize; - const uint SrcSamplesDone{DataPosFrac>>MixerFracBits}; - DataPosInt += SrcSamplesDone; - DataPosFrac &= MixerFracMask; - - OutPos += DstBufferSize; - Counter = maxu(DstBufferSize, Counter) - DstBufferSize; - - if(unlikely(!BufferListItem)) + for(uint send{0};send < NumSends;++send) { - /* Do nothing extra when there's no buffers. */ + if(mSend[send].Buffer.empty()) + continue; + + SendParams &parms = chandata.mWetParams[send]; + 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, mSend[send].Buffer, parms.Gains.Current.data(), TargetGains, + Counter, OutPos); } - else if(mFlags.test(VoiceIsStatic)) + + ++voiceSamples; + } + + mFlags.set(VoiceIsFading); + + /* Don't update positions and buffers if we were stopping. */ + if(vstate == Stopping) UNLIKELY + { + mPlayState.store(Stopped, std::memory_order_release); + return; + } + + /* Update voice positions and buffers as needed. */ + DataPosFrac += increment*samplesToMix; + DataPosInt += static_cast(DataPosFrac>>MixerFracBits); + DataPosFrac &= MixerFracMask; + + uint buffers_done{0u}; + if(BufferListItem && DataPosInt > 0) LIKELY + { + if(mFlags.test(VoiceIsStatic)) { if(BufferLoopItem) { /* Handle looping static source */ const uint LoopStart{BufferListItem->mLoopStart}; const uint LoopEnd{BufferListItem->mLoopEnd}; - if(DataPosInt >= LoopEnd) + uint DataPosUInt{static_cast(DataPosInt)}; + if(DataPosUInt >= LoopEnd) { assert(LoopEnd > LoopStart); - DataPosInt = ((DataPosInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart; + DataPosUInt = ((DataPosUInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart; + DataPosInt = static_cast(DataPosUInt); } } else { /* Handle non-looping static source */ - if(DataPosInt >= BufferListItem->mSampleLen) - { + if(static_cast(DataPosInt) >= BufferListItem->mSampleLen) BufferListItem = nullptr; - break; - } } } else if(mFlags.test(VoiceIsCallback)) { /* Handle callback buffer source */ - if(SrcSamplesDone < mNumCallbackSamples) + const uint currentBlock{static_cast(DataPosInt) / mSamplesPerBlock}; + const uint blocksDone{currentBlock - mCallbackBlockBase}; + if(blocksDone < mNumCallbackBlocks) { - const size_t byteOffset{SrcSamplesDone*mFrameSize}; - const size_t byteEnd{mNumCallbackSamples*mFrameSize}; - al::byte *data{BufferListItem->mSamples}; - std::copy(data+byteOffset, data+byteEnd, data); - mNumCallbackSamples -= SrcSamplesDone; + 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; } else { BufferListItem = nullptr; - mNumCallbackSamples = 0; + mNumCallbackBlocks = 0; + mCallbackBlockBase += blocksDone; } } else { /* Handle streaming source */ do { - if(BufferListItem->mSampleLen > DataPosInt) + 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); if(!BufferListItem) BufferListItem = BufferLoopItem; } while(BufferListItem); } - } while(OutPos < SamplesToDo); - - mFlags.set(VoiceIsFading); - - /* Don't update positions and buffers if we were stopping. */ - if(unlikely(vstate == Stopping)) - { - mPlayState.store(Stopped, std::memory_order_release); - return; } - /* Capture the source ID in case it's reset for stopping. */ + /* Capture the source ID in case it gets reset for stopping. */ const uint SourceID{mSourceID.load(std::memory_order_relaxed)}; /* Update voice info */ @@ -804,17 +1186,16 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo std::atomic_thread_fence(std::memory_order_release); /* Send any events now, after the position/buffer info was updated. */ - const uint enabledevt{Context->mEnabledEvts.load(std::memory_order_acquire)}; - if(buffers_done > 0 && (enabledevt&AsyncEvent::BufferCompleted)) + const auto enabledevt = Context->mEnabledEvts.load(std::memory_order_acquire); + 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); } } @@ -825,7 +1206,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const uint SamplesToDo * ensures any residual noise fades to 0 amplitude. */ mPlayState.store(Stopping, std::memory_order_release); - if((enabledevt&AsyncEvent::SourceStateChange)) + if(enabledevt.test(al::to_underlying(AsyncEnableBits::SourceState))) SendSourceStoppedEvent(Context, SourceID); } } @@ -835,34 +1216,62 @@ 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(unlikely(num_channels > device->mSampleData.size())) + 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); - if(IsUHJ(mFmtChannels)) + mDecoder = nullptr; + mDecoderPadding = 0; + if(mFmtChannels == FmtSuperStereo) { - mDecoder = std::make_unique(); - mDecoderFunc = (mFmtChannels == FmtSuperStereo) ? &UhjDecoder::decodeStereo - : &UhjDecoder::decode; + switch(UhjDecodeQuality) + { + case UhjQualityType::IIR: + mDecoder = std::make_unique(); + mDecoderPadding = UhjStereoDecoderIIR::sInputPadding; + break; + case UhjQualityType::FIR256: + mDecoder = std::make_unique>(); + mDecoderPadding = UhjStereoDecoder::sInputPadding; + break; + case UhjQualityType::FIR512: + mDecoder = std::make_unique>(); + mDecoderPadding = UhjStereoDecoder::sInputPadding; + break; + } } - else + else if(IsUHJ(mFmtChannels)) { - mDecoder = nullptr; - mDecoderFunc = nullptr; + switch(UhjDecodeQuality) + { + case UhjQualityType::IIR: + mDecoder = std::make_unique(); + mDecoderPadding = UhjDecoderIIR::sInputPadding; + break; + case UhjQualityType::FIR256: + mDecoder = std::make_unique>(); + mDecoderPadding = UhjDecoder::sInputPadding; + break; + case UhjQualityType::FIR512: + mDecoder = std::make_unique>(); + mDecoderPadding = UhjDecoder::sInputPadding; + break; + } } /* Clear the stepping value explicitly so the mixer knows not to mix this @@ -873,48 +1282,18 @@ void Voice::prepare(DeviceBase *device) /* Make sure the sample history is cleared. */ std::fill(mPrevSamples.begin(), mPrevSamples.end(), HistoryLine{}); - /* Don't need to set the VoiceIsAmbisonic flag if the device is not higher - * order than the voice. No HF scaling is necessary to mix it. - */ - if(mAmbiOrder && device->mAmbiOrder > mAmbiOrder) + if(mFmtChannels == FmtUHJ2 && !device->mUhjEncoder) { - const uint8_t *OrderFromChan{Is2DAmbisonic(mFmtChannels) ? - AmbiIndex::OrderFrom2DChannel().data() : AmbiIndex::OrderFromChannel().data()}; - const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder); - - const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; - for(auto &chandata : mChans) - { - chandata.mAmbiHFScale = scales[*(OrderFromChan++)]; - chandata.mAmbiLFScale = 1.0f; - chandata.mAmbiSplitter = splitter; - chandata.mDryParams = DirectParams{}; - chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; - std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); - } /* 2-channel UHJ needs different shelf filters. However, we can't just - * use different shelf filters after mixing it and with any old speaker + * use different shelf filters after mixing it, given any old speaker * setup the user has. To make this work, we apply the expected shelf * filters for decoding UHJ2 to quad (only needs LF scaling), and act - * as if those 4 quad channels are encoded right back onto first-order - * B-Format, which then upsamples to higher order as normal (only needs - * HF scaling). + * as if those 4 quad channels are encoded right back into B-Format. * * This isn't perfect, but without an entirely separate and limited * UHJ2 path, it's better than nothing. - */ - if(mFmtChannels == FmtUHJ2) - { - mChans[0].mAmbiLFScale = 0.661f; - mChans[1].mAmbiLFScale = 1.293f; - mChans[2].mAmbiLFScale = 1.293f; - } - mFlags.set(VoiceIsAmbisonic); - } - else if(mFmtChannels == FmtUHJ2 && !device->mUhjEncoder) - { - /* 2-channel UHJ with first-order output also needs the shelf filter - * correction applied, except with UHJ output (UHJ2->B-Format->UHJ2 is + * + * Note this isn't needed with UHJ output (UHJ2->B-Format->UHJ2 is * identity, so don't mess with it). */ const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; @@ -927,9 +1306,33 @@ void Voice::prepare(DeviceBase *device) chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); } - mChans[0].mAmbiLFScale = 0.661f; - mChans[1].mAmbiLFScale = 1.293f; - mChans[2].mAmbiLFScale = 1.293f; + mChans[0].mAmbiLFScale = DecoderBase::sWLFScale; + mChans[1].mAmbiLFScale = DecoderBase::sXYLFScale; + mChans[2].mAmbiLFScale = DecoderBase::sXYLFScale; + mFlags.set(VoiceIsAmbisonic); + } + /* Don't need to set the VoiceIsAmbisonic flag if the device is not higher + * order than the voice. No HF scaling is necessary to mix it. + */ + else if(mAmbiOrder && device->mAmbiOrder > mAmbiOrder) + { + 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); + + const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; + for(auto &chandata : mChans) + { + chandata.mAmbiHFScale = scales[*(OrderFromChan++)]; + chandata.mAmbiLFScale = 1.0f; + chandata.mAmbiSplitter = splitter; + chandata.mDryParams = DirectParams{}; + chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; + std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); + } mFlags.set(VoiceIsAmbisonic); } else diff --git a/libs/openal-soft/core/voice.h b/libs/openal-soft/core/voice.h index 70b80841..3d5385bb 100644 --- a/libs/openal-soft/core/voice.h +++ b/libs/openal-soft/core/voice.h @@ -4,13 +4,13 @@ #include #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" @@ -32,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 { @@ -48,10 +48,7 @@ enum class DirectMode : unsigned char { }; -/* Maximum number of extra source samples that may need to be loaded, for - * resampling or conversion purposes. - */ -constexpr uint MaxPostVoiceLoad{MaxResamplerEdge + UhjDecoder::sFilterDelay}; +inline constexpr uint MaxPitch{10}; enum { @@ -68,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; }; @@ -97,11 +97,12 @@ struct VoiceBufferItem { CallbackType mCallback{nullptr}; void *mUserData{nullptr}; + uint mBlockAlign{0u}; uint mSampleLen{0u}; uint mLoopStart{0u}; uint mLoopEnd{0u}; - al::byte *mSamples{nullptr}; + al::span mSamples{}; }; @@ -140,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; @@ -156,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 { @@ -187,7 +190,7 @@ struct Voice { std::atomic mUpdate{nullptr}; - VoiceProps mProps; + VoiceProps mProps{}; std::atomic mSourceID{0u}; std::atomic mPlayState{Stopped}; @@ -197,47 +200,51 @@ 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 mFrameSize; /**< In bytes. */ - 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; - UhjDecoder::DecoderFunc mDecoderFunc{}; + std::unique_ptr mDecoder; + uint mDecoderPadding{}; /** Current target parameters used for mixing. */ uint mStep{0}; - ResamplerFunc mResampler; + ResamplerFunc mResampler{}; - InterpState mResampleState; + InterpState mResampleState{}; std::bitset mFlags{}; - uint mNumCallbackSamples{0}; + 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 @@ -248,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}; @@ -262,13 +269,12 @@ struct Voice { Voice(const Voice&) = delete; Voice& operator=(const Voice&) = delete; - void mix(const State vstate, ContextBase *Context, const uint SamplesToDo); + void mix(const State vstate, ContextBase *Context, const std::chrono::nanoseconds deviceTime, + const uint SamplesToDo); void prepare(DeviceBase *device); - static void InitMixer(al::optional resampler); - - DEF_NEWDEL(Voice) + static void InitMixer(std::optional resopt); }; extern Resampler ResamplerDefault; diff --git a/libs/openal-soft/core/voice_change.h b/libs/openal-soft/core/voice_change.h index ddc6186f..e97c48f3 100644 --- a/libs/openal-soft/core/voice_change.h +++ b/libs/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/libs/openal-soft/docs/3D7.1.txt b/libs/openal-soft/docs/3D7.1.txt index 1d40bec6..b7249c22 100644 --- a/libs/openal-soft/docs/3D7.1.txt +++ b/libs/openal-soft/docs/3D7.1.txt @@ -58,17 +58,10 @@ Software Setup ============== To enable 3D7.1 on OpenAL Soft, first make sure the audio device is configured -for 7.1 output. Then in the alsoft-config utility, under the Renderer tab, -select the 3D7.1.ambdec preset for the 7.1 Surround decoder configuration. And -that's it. Any applications using OpenAL Soft can take advantage of fully 3D -audio, and multi-channel sounds will be properly remixed for the speaker -layout. - -Playback can be improved by (copying and) modifying the 3D7.1.ambdec preset, -changing the specified speaker distances to match the the real distance (in -meters) from the center of the speaker array, then enable High Quality Mode in -alsoft-config. That will improve the quality when the speakers are not all -equidistant. +for 7.1 output. Then in the alsoft-config utility, for the Channels setting +choose "3D7.1 Surround" from the drop-down list. And that's it. Any application +using OpenAL Soft can take advantage of fully 3D audio, and multi-channel +sounds will be properly remixed for the speaker layout. Note that care must be taken that the audio device is not treated as a "true" 7.1 device by non-3D7.1-capable applications. In particular, the audio server diff --git a/libs/openal-soft/docs/ambdec.txt b/libs/openal-soft/docs/ambdec.txt index 1f328937..a301004c 100644 --- a/libs/openal-soft/docs/ambdec.txt +++ b/libs/openal-soft/docs/ambdec.txt @@ -13,7 +13,7 @@ about ambisonics. Starting with OpenAL Soft 1.18, version 3 of the file format is supported as a means of specifying custom surround sound speaker layouts. These configuration -files are also used to enable the high-quality ambisonic decoder. +files are also used to enable per-speaker distance compensation. File Format @@ -155,11 +155,14 @@ LB = Back left RB = Back right CE = Front center CB = Back center +LFT = Top front left +RFT = Top front right +LBT = Top back left +RBT = Top back right Additionally, configuration files for surround51 will acknowledge back speakers -for side channels, and surround51rear will acknowledge side speakers for back -channels, to avoid issues with a configuration expecting 5.1 to use the side -channels when the device is configured for back, or vice-versa. +for side channels, to avoid issues with a configuration expecting 5.1 to use +the side channels when the device is configured for back, or vice-versa. Furthermore, OpenAL Soft does not require a speaker definition for each output channel the configuration is used with. So for example a 5.1 configuration may diff --git a/libs/openal-soft/docs/ambisonics.txt b/libs/openal-soft/docs/ambisonics.txt index b03e3bed..7798c8f9 100644 --- a/libs/openal-soft/docs/ambisonics.txt +++ b/libs/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. @@ -72,19 +72,12 @@ How Is It Used? =============== As a 3D audio API, OpenAL is tasked with playing 3D sound as best it can with -the speaker setup the user has. Since the OpenAL API does not explicitly handle -the output channel configuration, it has a lot of leeway in how to deal with -the audio before it's played back for the user to hear. Consequently, OpenAL -Soft (or any other OpenAL implementation that wishes to) can render using -Ambisonics and decode the ambisonic mix for a high level of accuracy over what -simple pan-pot could provide. - -When given an appropriate decoder configuration for the channel layout, the -ambisonic mix can be decoded utilizing the benefits available to ambisonic -processing, including frequency-dependent processing and near-field effects. -Without a decoder configuration, the ambisonic mix can still be decoded for -good stereo or surround sound output, although without near-field effects as -there's no speaker distance information. +the speaker setup the user has. Since the OpenAL API doesn't expose discrete +playback speaker feeds, an implementation has a lot of leeway with how to deal +with the audio before it's played back for the user to hear. Consequently, +OpenAL Soft (or any other OpenAL implementation that wishes to) can render +using Ambisonics and decode the ambisonic mix for a high level of accuracy over +what simple pan-pot could provide. In addition to surround sound output, Ambisonics also has benefits with stereo output. 2-channel UHJ is a stereo-compatible format that encodes some surround diff --git a/libs/openal-soft/docs/env-vars.txt b/libs/openal-soft/docs/env-vars.txt index 77a30c58..0c15cbe9 100644 --- a/libs/openal-soft/docs/env-vars.txt +++ b/libs/openal-soft/docs/env-vars.txt @@ -58,6 +58,12 @@ between 0 and 180 degrees, instead of the expected range of 0 to 360 degrees. Setting this to "true" or "1" restores the old buggy behavior, for apps that were written to expect the incorrect range. +__ALSOFT_ENABLE_SUB_DATA_EXT +The more widely used AL_EXT_SOURCE_RADIUS extension is incompatible with the +now-defunct AL_SOFT_buffer_sub_data extension. Setting this to "true" or "1" +restores the AL_SOFT_buffer_sub_data extension for apps that require it, +disabling AL_EXT_SOURCE_RADIUS. + __ALSOFT_REVERSE_Z Applications that don't natively use OpenAL's coordinate system have to convert to it before passing in 3D coordinates. Depending on how exactly this is done, @@ -72,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/libs/openal-soft/docs/hrtf.txt b/libs/openal-soft/docs/hrtf.txt index 7a1a500f..0ea27caa 100644 --- a/libs/openal-soft/docs/hrtf.txt +++ b/libs/openal-soft/docs/hrtf.txt @@ -73,9 +73,7 @@ calculation (ie. left = angle, right = 360-angle). The actual coefficients follow. Each coefficient is a signed 24-bit sample. Stereo HRTFs interleave left/right ear coefficients. The HRIRs must be minimum-phase. This allows the use of a smaller filter length, reducing -computation. For reference, the default data set uses a 32-point filter while -even the smallest data set provided by MIT used a 128-sample filter (a 4x -reduction by applying minimum-phase reconstruction). +computation. After the coefficients is an array of unsigned 8-bit delay values as 6.2 fixed- point integers, one for each HRIR (with stereo HRTFs interleaving left/right diff --git a/libs/openal-soft/examples/alconvolve.c b/libs/openal-soft/examples/alconvolve.c index 93fd2eb4..597d6ea2 100644 --- a/libs/openal-soft/examples/alconvolve.c +++ b/libs/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/libs/openal-soft/examples/alffplay.cpp b/libs/openal-soft/examples/alffplay.cpp index c3f4c505..fb0a18da 100644 --- a/libs/openal-soft/examples/alffplay.cpp +++ b/libs/openal-soft/examples/alffplay.cpp @@ -28,12 +28,12 @@ #include #include -extern "C" { #ifdef __GNUC__ _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wconversion\"") _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") #endif +extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavformat/avio.h" @@ -55,12 +55,13 @@ constexpr auto AVNoPtsValue = AV_NOPTS_VALUE; constexpr auto AVErrorEOF = AVERROR_EOF; struct SwsContext; +} + +#define SDL_MAIN_HANDLED +#include "SDL.h" #ifdef __GNUC__ _Pragma("GCC diagnostic pop") #endif -} - -#include "SDL.h" #include "AL/alc.h" #include "AL/al.h" @@ -89,6 +90,7 @@ const std::string AppName{"alffplay"}; ALenum DirectOutMode{AL_FALSE}; bool EnableWideStereo{false}; +bool EnableUhj{false}; bool EnableSuperStereo{false}; bool DisableVideo{false}; LPALGETSOURCEI64VSOFT alGetSourcei64vSOFT; @@ -97,9 +99,6 @@ LPALEVENTCONTROLSOFT alEventControlSOFT; LPALEVENTCALLBACKSOFT alEventCallbackSOFT; LPALBUFFERCALLBACKSOFT alBufferCallbackSOFT; -ALenum FormatStereo8{AL_FORMAT_STEREO8}; -ALenum FormatStereo16{AL_FORMAT_STEREO16}; -ALenum FormatStereo32F{AL_FORMAT_STEREO_FLOAT32}; const seconds AVNoSyncThreshold{10}; @@ -169,6 +168,12 @@ struct SwsContextDeleter { using SwsContextPtr = std::unique_ptr; +struct ChannelLayout : public AVChannelLayout { + ChannelLayout() : AVChannelLayout{} { } + ~ChannelLayout() { av_channel_layout_uninit(this); } +}; + + template class DataQueue { std::mutex mPacketMutex, mFrameMutex; @@ -232,7 +237,7 @@ public: void setFinished() { { - std::lock_guard _{mPacketMutex}; + std::lock_guard packetlock{mPacketMutex}; mFinished = true; } mPacketCond.notify_one(); @@ -241,7 +246,7 @@ public: void flush() { { - std::lock_guard _{mPacketMutex}; + std::lock_guard packetlock{mPacketMutex}; mFinished = true; mPackets.clear(); @@ -253,7 +258,7 @@ public: bool put(const AVPacket *pkt) { { - std::unique_lock lock{mPacketMutex}; + std::lock_guard packet_lock{mPacketMutex}; if(mTotalSize >= SizeLimit || mFinished) return false; @@ -280,7 +285,7 @@ struct AudioState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - DataQueue<2*1024*1024> mQueue; + DataQueue mQueue; /* Used for clock difference average computation */ seconds_d64 mClockDiffAvg{0}; @@ -305,8 +310,9 @@ struct AudioState { int mSamplesPos{0}; int mSamplesMax{0}; - std::unique_ptr mBufferData; - size_t mBufferDataSize{0}; + std::vector mBufferData_; + //std::unique_ptr mBufferData; + //size_t mBufferDataSize{0}; std::atomic mReadPos{0}; std::atomic mWritePos{0}; @@ -316,7 +322,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}; @@ -334,14 +340,14 @@ struct AudioState { } 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() @@ -366,7 +372,7 @@ struct VideoState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - DataQueue<14*1024*1024> mQueue; + DataQueue mQueue; /* The pts of the currently displayed frame, and the time (av_gettime) it * was last updated - used to have running video pts @@ -404,7 +410,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(); }; @@ -444,14 +450,12 @@ 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(); - - nanoseconds getMasterClock(); - - nanoseconds getDuration(); + [[nodiscard]] nanoseconds getClock() const; + [[nodiscard]] nanoseconds getMasterClock(); + [[nodiscard]] nanoseconds getDuration() const; int streamComponentOpen(unsigned int stream_index); int parse_handler(); @@ -469,8 +473,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]}; @@ -480,7 +484,7 @@ nanoseconds AudioState::getClockNoLock() return device_time - mDeviceStartTime - latency; } - if(mBufferDataSize > 0) + if(!mBufferData_.empty()) { if(mDeviceStartTime == nanoseconds::min()) return nanoseconds::zero(); @@ -489,15 +493,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 @@ -518,7 +521,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; @@ -545,15 +548,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); @@ -581,10 +583,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; @@ -605,8 +607,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; @@ -617,7 +619,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}; @@ -676,13 +678,17 @@ int AudioState::decodeFrame() if(mDecodedFrame->nb_samples > mSamplesMax) { av_freep(&mSamples); - av_samples_alloc(&mSamples, nullptr, mCodecCtx->channels, mDecodedFrame->nb_samples, - mDstSampleFmt, 0); + av_samples_alloc(&mSamples, nullptr, mCodecCtx->ch_layout.nb_channels, + mDecodedFrame->nb_samples, mDstSampleFmt, 0); mSamplesMax = mDecodedFrame->nb_samples; } + /* 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, mDecodedFrame->nb_samples, + cdata.data(), mDecodedFrame->nb_samples)}; av_frame_unref(mDecodedFrame.get()); return data_size; @@ -692,28 +698,33 @@ 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(uint8_t *out, const uint8_t *in, size_t count, size_t frame_size) { auto *sample = reinterpret_cast(in); auto *dst = reinterpret_cast(out); - if(frame_size == sizeof(T)) + + /* NOTE: frame_size is a multiple of sizeof(T). */ + size_t type_mult{frame_size / sizeof(T)}; + if(type_mult == 1) std::fill_n(dst, count, *sample); - else + else for(size_t i{0};i < count;++i) { - /* NOTE: frame_size is a multiple of sizeof(T). */ - size_t type_mult{frame_size / sizeof(T)}; - size_t i{0}; - std::generate_n(dst, count*type_mult, - [sample,type_mult,&i]() -> T - { - T ret = sample[i]; - i = (i+1)%type_mult; - return ret; - } - ); + for(size_t j{0};j < type_mult;++j) + dst[i*type_mult + j] = sample[j]; } } +void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t frame_size) +{ + if((frame_size&7) == 0) + sample_dup(out, in, count, frame_size); + else if((frame_size&3) == 0) + sample_dup(out, in, count, frame_size); + else if((frame_size&1) == 0) + sample_dup(out, in, count, frame_size); + else + sample_dup(out, in, count, frame_size); +} bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_skip) { @@ -729,27 +740,20 @@ 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); + std::copy_n(mSamples + static_cast(mSamplesPos)*size_t{mFrameSize}, + rem*size_t{mFrameSize}, samples); } else { rem = std::min(rem, static_cast(-mSamplesPos)); /* Add samples by copying the first sample */ - if((mFrameSize&7) == 0) - sample_dup(samples, mSamples, rem, mFrameSize); - else if((mFrameSize&3) == 0) - sample_dup(samples, mSamples, rem, mFrameSize); - else if((mFrameSize&1) == 0) - sample_dup(samples, mSamples, rem, mFrameSize); - else - sample_dup(samples, mSamples, rem, mFrameSize); + sample_dup(samples, mSamples, rem, mFrameSize); } - mSamplesPos += rem; + mSamplesPos += static_cast(rem); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; - samples += rem*mFrameSize; + samples += rem*size_t{mFrameSize}; audio_size += rem; while(mSamplesPos >= mSamplesLen) @@ -766,7 +770,6 @@ bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_sk auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate; mDeviceStartTime -= skip; mCurrentPts += skip; - continue; } } if(audio_size <= 0) @@ -785,71 +788,42 @@ bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_sk bool AudioState::readAudio(int sample_skip) { size_t woffset{mWritePos.load(std::memory_order_acquire)}; + const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; while(mSamplesLen > 0) { - const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; + const size_t nsamples{((roffset > woffset) ? roffset-woffset-1 + : (roffset == 0) ? (mBufferData_.size()-woffset-1) + : (mBufferData_.size()-woffset)) / mFrameSize}; + if(!nsamples) break; if(mSamplesPos < 0) { - size_t rem{(((roffset > woffset) ? roffset-1 - : ((roffset == 0) ? mBufferDataSize-1 - : mBufferDataSize)) - woffset) / mFrameSize}; - rem = std::min(rem, static_cast(-mSamplesPos)); - if(rem == 0) break; + const size_t rem{std::min(nsamples, static_cast(-mSamplesPos))}; - auto *splout{&mBufferData[woffset]}; - if((mFrameSize&7) == 0) - sample_dup(splout, mSamples, rem, mFrameSize); - else if((mFrameSize&3) == 0) - sample_dup(splout, mSamples, rem, mFrameSize); - else if((mFrameSize&1) == 0) - sample_dup(splout, mSamples, rem, mFrameSize); - else - sample_dup(splout, mSamples, rem, mFrameSize); + sample_dup(&mBufferData_[woffset], mSamples, rem, mFrameSize); woffset += rem * mFrameSize; - if(woffset == mBufferDataSize) - woffset = 0; + if(woffset == mBufferData_.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); - mSamplesPos += static_cast(rem); + mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; + mSamplesPos += static_cast(rem); continue; } + const size_t rem{std::min(nsamples, static_cast(mSamplesLen-mSamplesPos))}; const size_t boffset{static_cast(mSamplesPos) * size_t{mFrameSize}}; - const size_t nbytes{static_cast(mSamplesLen)*size_t{mFrameSize} - - boffset}; - if(roffset > woffset) - { - const size_t writable{roffset-woffset-1}; - if(writable < nbytes) break; + const size_t nbytes{rem * mFrameSize}; - memcpy(&mBufferData[woffset], mSamples+boffset, nbytes); - woffset += nbytes; - } - else - { - const size_t writable{mBufferDataSize+roffset-woffset-1}; - if(writable < nbytes) break; - - const size_t todo1{std::min(nbytes, mBufferDataSize-woffset)}; - const size_t todo2{nbytes - todo1}; - - memcpy(&mBufferData[woffset], mSamples+boffset, todo1); - woffset += todo1; - if(woffset == mBufferDataSize) - { - woffset = 0; - if(todo2 > 0) - { - memcpy(&mBufferData[woffset], mSamples+boffset+todo1, todo2); - woffset += todo2; - } - } - } + memcpy(&mBufferData_[woffset], mSamples + boffset, nbytes); + woffset += nbytes; + if(woffset == mBufferData_.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); - mCurrentPts += nanoseconds{seconds{mSamplesLen-mSamplesPos}} / mCodecCtx->sample_rate; - do { + mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; + mSamplesPos += static_cast(rem); + + while(mSamplesPos >= mSamplesLen) + { mSamplesLen = decodeFrame(); mSamplesPos = std::min(mSamplesLen, sample_skip); if(mSamplesLen <= 0) return false; @@ -859,7 +833,7 @@ bool AudioState::readAudio(int sample_skip) auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate; mDeviceStartTime -= skip; mCurrentPts += skip; - } while(mSamplesPos >= mSamplesLen); + } } return true; @@ -867,7 +841,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) { @@ -905,7 +879,7 @@ 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 { ALsizei got{0}; @@ -915,15 +889,15 @@ ALsizei AudioState::bufferCallback(void *data, ALsizei size) const size_t woffset{mWritePos.load(std::memory_order_relaxed)}; if(woffset == roffset) break; - size_t todo{((woffset < roffset) ? mBufferDataSize : woffset) - roffset}; + size_t todo{((woffset < roffset) ? mBufferData_.size() : woffset) - roffset}; todo = std::min(todo, static_cast(size-got)); - memcpy(data, &mBufferData[roffset], todo); + memcpy(data, &mBufferData_[roffset], todo); data = static_cast(data) + todo; got += static_cast(todo); roffset += todo; - if(roffset == mBufferDataSize) + if(roffset == mBufferData_.size()) roffset = 0; } mReadPos.store(roffset, std::memory_order_release); @@ -963,14 +937,11 @@ int AudioState::handler() }; EventControlManager event_controller{sleep_time}; - const bool has_bfmt_ex{alIsExtensionPresent("AL_SOFT_bformat_ex") != AL_FALSE}; - ALenum ambi_layout{AL_FUMA_SOFT}; - ALenum ambi_scale{AL_FUMA_SOFT}; - - 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 @@ -984,40 +955,37 @@ int AudioState::handler() { mDstSampleFmt = AV_SAMPLE_FMT_FLT; mFrameSize = 4; - if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_NATIVE) { - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1) + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 8; - mFormat = alGetEnumValue("AL_FORMAT_71CHN32"); + if(layoutmask == AV_CH_LAYOUT_7POINT1) + { + mDstChanLayout = layoutmask; + mFrameSize *= 8; + mFormat = alGetEnumValue("AL_FORMAT_71CHN32"); + } + if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) + { + mDstChanLayout = layoutmask; + mFrameSize *= 6; + mFormat = alGetEnumValue("AL_FORMAT_51CHN32"); + } + if(layoutmask == AV_CH_LAYOUT_QUAD) + { + mDstChanLayout = layoutmask; + mFrameSize *= 4; + mFormat = alGetEnumValue("AL_FORMAT_QUAD32"); + } } - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 - || mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) + if(layoutmask == AV_CH_LAYOUT_MONO) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 6; - mFormat = alGetEnumValue("AL_FORMAT_51CHN32"); - } - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_QUAD) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 4; - mFormat = alGetEnumValue("AL_FORMAT_QUAD32"); + mDstChanLayout = layoutmask; + mFrameSize *= 1; + mFormat = AL_FORMAT_MONO_FLOAT32; } } - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 1; - mFormat = AL_FORMAT_MONO_FLOAT32; - } - /* Assume 3D B-Format (ambisonics) if the channel layout is blank and - * there's 4 or more channels. FFmpeg/libavcodec otherwise seems to - * have no way to specify if the source is actually B-Format (let alone - * if it's 2D or 3D). - */ - if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 + else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC && alIsExtensionPresent("AL_EXT_BFORMAT")) { /* Calculate what should be the ambisonic order from the number of @@ -1025,9 +993,10 @@ int AudioState::handler() * an optional non-diegetic stereo stream with the B-Format stream, * which we can ignore, so check for that too. */ - auto order = static_cast(std::sqrt(mCodecCtx->channels)) - 1; + auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; int channels{(order+1) * (order+1)}; - if(channels == mCodecCtx->channels || channels+2 == mCodecCtx->channels) + if(channels == mCodecCtx->ch_layout.nb_channels + || channels+2 == mCodecCtx->ch_layout.nb_channels) { /* OpenAL only supports first-order with AL_EXT_BFORMAT, which * is 4 channels for 3D buffers. @@ -1040,47 +1009,50 @@ int AudioState::handler() { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; - mFormat = FormatStereo32F; + mFormat = EnableUhj ? AL_FORMAT_UHJ2CHN_FLOAT32_SOFT : AL_FORMAT_STEREO_FLOAT32; } } if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8 || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) { mDstSampleFmt = AV_SAMPLE_FMT_U8; mFrameSize = 1; - if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_NATIVE) { - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1) + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 8; - mFormat = alGetEnumValue("AL_FORMAT_71CHN8"); + if(layoutmask == AV_CH_LAYOUT_7POINT1) + { + mDstChanLayout = layoutmask; + mFrameSize *= 8; + mFormat = alGetEnumValue("AL_FORMAT_71CHN8"); + } + if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) + { + mDstChanLayout = layoutmask; + mFrameSize *= 6; + mFormat = alGetEnumValue("AL_FORMAT_51CHN8"); + } + if(layoutmask == AV_CH_LAYOUT_QUAD) + { + mDstChanLayout = layoutmask; + mFrameSize *= 4; + mFormat = alGetEnumValue("AL_FORMAT_QUAD8"); + } } - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 - || mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) + if(layoutmask == AV_CH_LAYOUT_MONO) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 6; - mFormat = alGetEnumValue("AL_FORMAT_51CHN8"); - } - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_QUAD) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 4; - mFormat = alGetEnumValue("AL_FORMAT_QUAD8"); + mDstChanLayout = layoutmask; + mFrameSize *= 1; + mFormat = AL_FORMAT_MONO8; } } - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 1; - mFormat = AL_FORMAT_MONO8; - } - if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 + else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC && alIsExtensionPresent("AL_EXT_BFORMAT")) { - auto order = static_cast(std::sqrt(mCodecCtx->channels)) - 1; + auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; int channels{(order+1) * (order+1)}; - if(channels == mCodecCtx->channels || channels+2 == mCodecCtx->channels) + if(channels == mCodecCtx->ch_layout.nb_channels + || channels+2 == mCodecCtx->ch_layout.nb_channels) { mFrameSize *= 4; mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_8"); @@ -1090,47 +1062,50 @@ int AudioState::handler() { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; - mFormat = FormatStereo8; + mFormat = EnableUhj ? AL_FORMAT_UHJ2CHN8_SOFT : AL_FORMAT_STEREO8; } } if(!mFormat || mFormat == -1) { mDstSampleFmt = AV_SAMPLE_FMT_S16; mFrameSize = 2; - if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_NATIVE) { - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1) + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 8; - mFormat = alGetEnumValue("AL_FORMAT_71CHN16"); + if(layoutmask == AV_CH_LAYOUT_7POINT1) + { + mDstChanLayout = layoutmask; + mFrameSize *= 8; + mFormat = alGetEnumValue("AL_FORMAT_71CHN16"); + } + if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) + { + mDstChanLayout = layoutmask; + mFrameSize *= 6; + mFormat = alGetEnumValue("AL_FORMAT_51CHN16"); + } + if(layoutmask == AV_CH_LAYOUT_QUAD) + { + mDstChanLayout = layoutmask; + mFrameSize *= 4; + mFormat = alGetEnumValue("AL_FORMAT_QUAD16"); + } } - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1 - || mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) + if(layoutmask == AV_CH_LAYOUT_MONO) { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 6; - mFormat = alGetEnumValue("AL_FORMAT_51CHN16"); - } - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_QUAD) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 4; - mFormat = alGetEnumValue("AL_FORMAT_QUAD16"); + mDstChanLayout = layoutmask; + mFrameSize *= 1; + mFormat = AL_FORMAT_MONO16; } } - if(mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) - { - mDstChanLayout = mCodecCtx->channel_layout; - mFrameSize *= 1; - mFormat = AL_FORMAT_MONO16; - } - if(mCodecCtx->channel_layout == 0 && mCodecCtx->channels >= 4 + else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC && alIsExtensionPresent("AL_EXT_BFORMAT")) { - auto order = static_cast(std::sqrt(mCodecCtx->channels)) - 1; + auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; int channels{(order+1) * (order+1)}; - if(channels == mCodecCtx->channels || channels+2 == mCodecCtx->channels) + if(channels == mCodecCtx->ch_layout.nb_channels + || channels+2 == mCodecCtx->ch_layout.nb_channels) { mFrameSize *= 4; mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_16"); @@ -1140,7 +1115,7 @@ int AudioState::handler() { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; - mFormat = FormatStereo16; + mFormat = EnableUhj ? AL_FORMAT_UHJ2CHN16_SOFT : AL_FORMAT_STEREO16; } } @@ -1156,32 +1131,33 @@ int AudioState::handler() return 0; } + /* Note that ffmpeg assumes AmbiX (ACN layout, SN3D normalization). */ + const bool has_bfmt_ex{alIsExtensionPresent("AL_SOFT_bformat_ex") != AL_FALSE}; + const ALenum ambi_layout{AL_ACN_SOFT}; + const ALenum ambi_scale{AL_SN3D_SOFT}; + if(!mDstChanLayout) { /* OpenAL only supports first-order ambisonics with AL_EXT_BFORMAT, so * we have to drop any extra channels. */ - mSwresCtx.reset(swr_alloc_set_opts(nullptr, - (1_i64<<4)-1, mDstSampleFmt, mCodecCtx->sample_rate, - (1_i64<channels)-1, mCodecCtx->sample_fmt, mCodecCtx->sample_rate, - 0, nullptr)); + ChannelLayout layout{}; + av_channel_layout_from_string(&layout, "ambisonic 1"); - /* Note that ffmpeg/libavcodec has no method to check the ambisonic - * channel order and normalization, so we can only assume AmbiX as the - * defacto-standard. This is not true for .amb files, which use FuMa. - */ - std::vector mtx(64*64, 0.0); - ambi_layout = AL_ACN_SOFT; - ambi_scale = AL_SN3D_SOFT; - if(has_bfmt_ex) + SwrContext *ps{}; + int err{swr_alloc_set_opts2(&ps, &layout, mDstSampleFmt, mCodecCtx->sample_rate, + &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, mCodecCtx->sample_rate, 0, nullptr)}; + mSwresCtx.reset(ps); + if(err != 0) { - /* An identity matrix that doesn't remix any channels. */ - std::cout<< "Found AL_SOFT_bformat_ex" < errstr{}; + std::cerr<< "Failed to allocate SwrContext: " + < mtx(size_t{64}*64, 0.0); mtx[0 + 0*64] = std::sqrt(0.5); mtx[3 + 1*64] = 1.0; mtx[1 + 2*64] = 1.0; mtx[2 + 3*64] = 1.0; + swr_set_matrix(mSwresCtx.get(), mtx.data(), 64); } - swr_set_matrix(mSwresCtx.get(), mtx.data(), 64); } else - mSwresCtx.reset(swr_alloc_set_opts(nullptr, - static_cast(mDstChanLayout), mDstSampleFmt, mCodecCtx->sample_rate, - mCodecCtx->channel_layout ? static_cast(mCodecCtx->channel_layout) - : av_get_default_channel_layout(mCodecCtx->channels), - mCodecCtx->sample_fmt, mCodecCtx->sample_rate, - 0, nullptr)); - if(!mSwresCtx || swr_init(mSwresCtx.get()) != 0) { - std::cerr<< "Failed to initialize audio converter" <sample_rate, + &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, mCodecCtx->sample_rate, 0, nullptr)}; + mSwresCtx.reset(ps); + if(err != 0) + { + 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); + const std::array angles{static_cast(M_PI / 3.0), static_cast(-M_PI / 3.0)}; + alSourcefv(mSource, AL_STEREO_ANGLES, angles.data()); } if(has_bfmt_ex) { @@ -1247,13 +1236,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); @@ -1265,12 +1253,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; @@ -1297,21 +1285,30 @@ int AudioState::handler() mCurrentPts += skip; } - while(1) + while(true) { + if(mMovie.mQuit.load(std::memory_order_relaxed)) + { + /* If mQuit is set, drain frames until we can't get more audio, + * indicating we've reached the flush packet and the packet sender + * will also quit. + */ + do { + mSamplesLen = decodeFrame(); + mSamplesPos = mSamplesLen; + } while(mSamplesLen > 0); + break; + } + ALenum state; - if(mBufferDataSize > 0) + if(!mBufferData_.empty()) { alGetSourcei(mSource, AL_SOURCE_STATE, &state); - /* If mQuit is set, don't actually quit until we can't get more - * audio, indicating we've reached the flush packet and the packet - * sender will also quit. - * - * If mQuit is not set, don't quit even if there's no more audio, + + /* If mQuit is not set, don't quit even if there's no more audio, * so what's buffered has a chance to play to the real end. */ - if(!readAudio(getSync()) && mMovie.mQuit.load(std::memory_order_relaxed)) - goto finish; + readAudio(getSync()); } else { @@ -1334,19 +1331,13 @@ int AudioState::handler() /* Read the next chunk of data, filling the buffer, and queue * it on the source. */ - const bool got_audio{readAudio(samples.get(), static_cast(buffer_len), - sync_skip)}; - if(!got_audio) - { - if(mMovie.mQuit.load(std::memory_order_relaxed)) - goto finish; + if(!readAudio(samples.data(), 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; } @@ -1387,7 +1378,6 @@ int AudioState::handler() mSrcCond.wait_for(srclock, sleep_time); } -finish: alSourceRewind(mSource); alSourcei(mSource, AL_BUFFER, 0); @@ -1400,7 +1390,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; @@ -1408,7 +1398,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; @@ -1458,7 +1448,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)) @@ -1518,9 +1508,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); } @@ -1553,18 +1543,15 @@ 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 std::array pict_data{ + static_cast(pixels), + static_cast(pixels) + ptrdiff_t{w}*h, + static_cast(pixels) + 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); } @@ -1582,7 +1569,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; } @@ -1606,7 +1593,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; @@ -1615,12 +1602,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]}; @@ -1714,7 +1701,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('\\'); @@ -1724,7 +1711,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(); @@ -1740,7 +1727,7 @@ 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) @@ -1907,6 +1894,8 @@ std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) int main(int argc, char *argv[]) { + SDL_SetMainReady(); + std::unique_ptr movState; if(argc < 2) @@ -2046,9 +2035,7 @@ int main(int argc, char *argv[]) else { std::cout<< "Found AL_SOFT_UHJ" <{new MovieState{argv[fileidx++]}}; + movState = std::make_unique(argv[fileidx++]); if(!movState->prepare()) movState = nullptr; } if(!movState) @@ -2084,7 +2071,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}); @@ -2152,7 +2139,7 @@ int main(int argc, char *argv[]) movState = nullptr; while(fileidx < argc && !movState) { - movState = std::unique_ptr{new MovieState{argv[fileidx++]}}; + movState = std::make_unique(argv[fileidx++]); if(!movState->prepare()) movState = nullptr; } if(movState) diff --git a/libs/openal-soft/examples/alhrtf.c b/libs/openal-soft/examples/alhrtf.c index d878870e..a2f1824a 100644 --- a/libs/openal-soft/examples/alhrtf.c +++ b/libs/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/libs/openal-soft/examples/allatency.c b/libs/openal-soft/examples/allatency.c index ab4a4ebc..9a535442 100644 --- a/libs/openal-soft/examples/allatency.c +++ b/libs/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/libs/openal-soft/examples/alloopback.c b/libs/openal-soft/examples/alloopback.c index 7513458b..964a0cdb 100644 --- a/libs/openal-soft/examples/alloopback.c +++ b/libs/openal-soft/examples/alloopback.c @@ -30,6 +30,7 @@ #include #include +#define SDL_MAIN_HANDLED #include "SDL.h" #include "SDL_audio.h" #include "SDL_error.h" @@ -117,7 +118,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) { @@ -141,6 +142,8 @@ int main(int argc, char *argv[]) (void)argc; (void)argv; + SDL_SetMainReady(); + /* Print out error if extension is missing. */ if(!alcIsExtensionPresent(NULL, "ALC_SOFT_loopback")) { @@ -197,6 +200,10 @@ int main(int argc, char *argv[]) attrs[3] = ALC_UNSIGNED_SHORT_SOFT; else if(obtained.format == AUDIO_S16SYS) attrs[3] = ALC_SHORT_SOFT; + else if(obtained.format == AUDIO_S32SYS) + attrs[3] = ALC_INT_SOFT; + else if(obtained.format == AUDIO_F32SYS) + attrs[3] = ALC_FLOAT_SOFT; else { fprintf(stderr, "Unhandled SDL format: 0x%04x\n", obtained.format); diff --git a/libs/openal-soft/examples/almultireverb.c b/libs/openal-soft/examples/almultireverb.c index a77cc59e..f9a46c4f 100644 --- a/libs/openal-soft/examples/almultireverb.c +++ b/libs/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/libs/openal-soft/examples/alplay.c b/libs/openal-soft/examples/alplay.c index 56d5434b..9af7ca40 100644 --- a/libs/openal-soft/examples/alplay.c +++ b/libs/openal-soft/examples/alplay.c @@ -37,19 +37,31 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + + +enum FormatType { + Int16, + Float, + IMA4, + MSADPCM +}; /* LoadBuffer loads the named audio file into an OpenAL buffer object, and * returns the new buffer ID. */ static ALuint LoadSound(const char *filename) { + enum FormatType sample_format = Int16; + ALint byteblockalign = 0; + ALint splblockalign = 0; + sf_count_t num_frames; ALenum err, format; - ALuint buffer; + ALsizei num_bytes; SNDFILE *sndfile; SF_INFO sfinfo; - short *membuf; - sf_count_t num_frames; - ALsizei num_bytes; + ALuint buffer; + void *membuf; /* Open the audio file and check that it's usable. */ sndfile = sf_open(filename, SFM_READ, &sfinfo); @@ -58,28 +70,151 @@ static ALuint LoadSound(const char *filename) fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); return 0; } - if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(short))/sfinfo.channels) + if(sfinfo.frames < 1) { fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); sf_close(sndfile); return 0; } - /* Get the sound format, and figure out the OpenAL format */ + /* Detect a suitable format to load. Formats like Vorbis and Opus use float + * natively, so load as float to avoid clipping when possible. Formats + * larger than 16-bit can also use float to preserve a bit more precision. + */ + switch((sfinfo.format&SF_FORMAT_SUBMASK)) + { + case SF_FORMAT_PCM_24: + case SF_FORMAT_PCM_32: + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + case SF_FORMAT_VORBIS: + case SF_FORMAT_OPUS: + case SF_FORMAT_ALAC_20: + case SF_FORMAT_ALAC_24: + case SF_FORMAT_ALAC_32: + case 0x0080/*SF_FORMAT_MPEG_LAYER_I*/: + case 0x0081/*SF_FORMAT_MPEG_LAYER_II*/: + case 0x0082/*SF_FORMAT_MPEG_LAYER_III*/: + if(alIsExtensionPresent("AL_EXT_FLOAT32")) + sample_format = Float; + break; + case SF_FORMAT_IMA_ADPCM: + /* ADPCM formats require setting a block alignment as specified in the + * file, which needs to be read from the wave 'fmt ' chunk manually + * since libsndfile doesn't provide it in a format-agnostic way. + */ + if(sfinfo.channels <= 2 && (sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV + && alIsExtensionPresent("AL_EXT_IMA4") + && alIsExtensionPresent("AL_SOFT_block_alignment")) + sample_format = IMA4; + break; + case SF_FORMAT_MS_ADPCM: + if(sfinfo.channels <= 2 && (sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV + && alIsExtensionPresent("AL_SOFT_MSADPCM") + && alIsExtensionPresent("AL_SOFT_block_alignment")) + sample_format = MSADPCM; + break; + } + + if(sample_format == IMA4 || sample_format == MSADPCM) + { + /* For ADPCM, lookup the wave file's "fmt " chunk, which is a + * WAVEFORMATEX-based structure for the audio format. + */ + SF_CHUNK_INFO inf = { "fmt ", 4, 0, NULL }; + SF_CHUNK_ITERATOR *iter = sf_get_chunk_iterator(sndfile, &inf); + + /* If there's an issue getting the chunk or block alignment, load as + * 16-bit and have libsndfile do the conversion. + */ + if(!iter || sf_get_chunk_size(iter, &inf) != SF_ERR_NO_ERROR || inf.datalen < 14) + sample_format = Int16; + else + { + ALubyte *fmtbuf = calloc(inf.datalen, 1); + inf.data = fmtbuf; + if(sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR) + sample_format = 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 == IMA4) + { + splblockalign = (byteblockalign/sfinfo.channels - 4)/4*8 + 1; + if(splblockalign < 1 + || ((splblockalign-1)/2 + 4)*sfinfo.channels != byteblockalign) + sample_format = Int16; + } + else + { + splblockalign = (byteblockalign/sfinfo.channels - 7)*2 + 2; + if(splblockalign < 2 + || ((splblockalign-2)/2 + 7)*sfinfo.channels != byteblockalign) + sample_format = Int16; + } + } + free(fmtbuf); + } + } + + if(sample_format == Int16) + { + splblockalign = 1; + byteblockalign = sfinfo.channels * 2; + } + else if(sample_format == Float) + { + splblockalign = 1; + byteblockalign = sfinfo.channels * 4; + } + + /* Figure out the OpenAL format from the file and desired sample type. */ format = AL_NONE; if(sfinfo.channels == 1) - format = AL_FORMAT_MONO16; + { + if(sample_format == Int16) + format = AL_FORMAT_MONO16; + else if(sample_format == Float) + format = AL_FORMAT_MONO_FLOAT32; + else if(sample_format == IMA4) + format = AL_FORMAT_MONO_IMA4; + else if(sample_format == MSADPCM) + format = AL_FORMAT_MONO_MSADPCM_SOFT; + } else if(sfinfo.channels == 2) - format = AL_FORMAT_STEREO16; + { + if(sample_format == Int16) + format = AL_FORMAT_STEREO16; + else if(sample_format == Float) + format = AL_FORMAT_STEREO_FLOAT32; + else if(sample_format == IMA4) + format = AL_FORMAT_STEREO_IMA4; + else if(sample_format == MSADPCM) + format = AL_FORMAT_STEREO_MSADPCM_SOFT; + } else if(sfinfo.channels == 3) { if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) - format = AL_FORMAT_BFORMAT2D_16; + { + if(sample_format == Int16) + format = AL_FORMAT_BFORMAT2D_16; + else if(sample_format == Float) + format = AL_FORMAT_BFORMAT2D_FLOAT32; + } } else if(sfinfo.channels == 4) { if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) - format = AL_FORMAT_BFORMAT3D_16; + { + if(sample_format == Int16) + format = AL_FORMAT_BFORMAT3D_16; + else if(sample_format == Float) + format = AL_FORMAT_BFORMAT3D_FLOAT32; + } } if(!format) { @@ -88,10 +223,27 @@ static ALuint LoadSound(const char *filename) return 0; } - /* Decode the whole audio file to a buffer. */ - membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(short)); + if(sfinfo.frames/splblockalign > (sf_count_t)(INT_MAX/byteblockalign)) + { + fprintf(stderr, "Too many samples in %s (%" PRId64 ")\n", filename, sfinfo.frames); + sf_close(sndfile); + return 0; + } - num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames); + /* Decode the whole audio file to a buffer. */ + membuf = malloc((size_t)(sfinfo.frames / splblockalign * byteblockalign)); + + if(sample_format == Int16) + num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames); + else if(sample_format == Float) + num_frames = sf_readf_float(sndfile, membuf, sfinfo.frames); + else + { + sf_count_t count = sfinfo.frames / splblockalign * byteblockalign; + num_frames = sf_read_raw(sndfile, membuf, count); + if(num_frames > 0) + num_frames = num_frames / byteblockalign * splblockalign; + } if(num_frames < 1) { free(membuf); @@ -99,19 +251,24 @@ static ALuint LoadSound(const char *filename) fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); return 0; } - num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(short); + num_bytes = (ALsizei)(num_frames / splblockalign * byteblockalign); + + printf("Loading: %s (%s, %dhz)\n", filename, FormatName(format), sfinfo.samplerate); + fflush(stdout); /* Buffer the audio data into a new buffer object, then free the data and * close the file. */ buffer = 0; alGenBuffers(1, &buffer); + if(splblockalign > 1) + alBufferi(buffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, splblockalign); alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); 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/libs/openal-soft/examples/alrecord.c b/libs/openal-soft/examples/alrecord.c index 03894493..2a1fbd2f 100644 --- a/libs/openal-soft/examples/alrecord.c +++ b/libs/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/libs/openal-soft/examples/alreverb.c b/libs/openal-soft/examples/alreverb.c index 11a3ac6b..15b92235 100644 --- a/libs/openal-soft/examples/alreverb.c +++ b/libs/openal-soft/examples/alreverb.c @@ -40,6 +40,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + /* Effect object functions */ static LPALGENEFFECTS alGenEffects; @@ -132,7 +134,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 +221,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/libs/openal-soft/examples/alstream.c b/libs/openal-soft/examples/alstream.c index 2e427a32..028290f5 100644 --- a/libs/openal-soft/examples/alstream.c +++ b/libs/openal-soft/examples/alstream.c @@ -37,22 +37,35 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + /* Define the number of buffers and buffer size (in milliseconds) to use. 4 - * buffers with 8192 samples each gives a nice per-chunk size, and lets the - * queue last for almost one second at 44.1khz. */ -#define NUM_BUFFERS 4 -#define BUFFER_SAMPLES 8192 + * buffers at 200ms each gives a nice per-chunk size, and lets the queue last + * for almost one second. + */ +enum { NumBuffers = 4 }; +enum { BufferMillisec = 200 }; + +typedef enum SampleType { + Int16, Float, IMA4, MSADPCM +} SampleType; typedef struct StreamPlayer { - /* These are the buffers and source to play out through OpenAL with */ - ALuint buffers[NUM_BUFFERS]; + /* These are the buffers and source to play out through OpenAL with. */ + ALuint buffers[NumBuffers]; ALuint source; /* Handle for the audio file */ SNDFILE *sndfile; SF_INFO sfinfo; - short *membuf; + void *membuf; + + /* The sample type and block/frame size being read for the buffer. */ + SampleType sample_type; + int byteblockalign; + int sampleblockalign; + int block_count; /* The format of the output stream (sample rate is in sfinfo) */ ALenum format; @@ -67,7 +80,8 @@ static int UpdatePlayer(StreamPlayer *player); /* Creates a new player object, and allocates the needed OpenAL source and * buffer objects. Error checking is simplified for the purposes of this - * example, and will cause an abort if needed. */ + * example, and will cause an abort if needed. + */ static StreamPlayer *NewPlayer(void) { StreamPlayer *player; @@ -76,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); @@ -99,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); } @@ -112,7 +126,7 @@ static void DeletePlayer(StreamPlayer *player) * it will be closed first. */ static int OpenPlayerFile(StreamPlayer *player, const char *filename) { - size_t frame_size; + int byteblockalign=0, splblockalign=0; ClosePlayerFile(player); @@ -124,20 +138,151 @@ static int OpenPlayerFile(StreamPlayer *player, const char *filename) return 0; } - /* Get the sound format, and figure out the OpenAL format */ + /* Detect a suitable format to load. Formats like Vorbis and Opus use float + * natively, so load as float to avoid clipping when possible. Formats + * larger than 16-bit can also use float to preserve a bit more precision. + */ + switch((player->sfinfo.format&SF_FORMAT_SUBMASK)) + { + case SF_FORMAT_PCM_24: + case SF_FORMAT_PCM_32: + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + case SF_FORMAT_VORBIS: + case SF_FORMAT_OPUS: + case SF_FORMAT_ALAC_20: + case SF_FORMAT_ALAC_24: + case SF_FORMAT_ALAC_32: + case 0x0080/*SF_FORMAT_MPEG_LAYER_I*/: + case 0x0081/*SF_FORMAT_MPEG_LAYER_II*/: + case 0x0082/*SF_FORMAT_MPEG_LAYER_III*/: + if(alIsExtensionPresent("AL_EXT_FLOAT32")) + player->sample_type = Float; + break; + case SF_FORMAT_IMA_ADPCM: + /* ADPCM formats require setting a block alignment as specified in the + * file, which needs to be read from the wave 'fmt ' chunk manually + * since libsndfile doesn't provide it in a format-agnostic way. + */ + if(player->sfinfo.channels <= 2 + && (player->sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV + && alIsExtensionPresent("AL_EXT_IMA4") + && alIsExtensionPresent("AL_SOFT_block_alignment")) + player->sample_type = IMA4; + break; + case SF_FORMAT_MS_ADPCM: + if(player->sfinfo.channels <= 2 + && (player->sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV + && alIsExtensionPresent("AL_SOFT_MSADPCM") + && alIsExtensionPresent("AL_SOFT_block_alignment")) + player->sample_type = MSADPCM; + break; + } + + if(player->sample_type == IMA4 || player->sample_type == MSADPCM) + { + /* For ADPCM, lookup the wave file's "fmt " chunk, which is a + * WAVEFORMATEX-based structure for the audio format. + */ + SF_CHUNK_INFO inf = { "fmt ", 4, 0, NULL }; + SF_CHUNK_ITERATOR *iter = sf_get_chunk_iterator(player->sndfile, &inf); + + /* If there's an issue getting the chunk or block alignment, load as + * 16-bit and have libsndfile do the conversion. + */ + if(!iter || sf_get_chunk_size(iter, &inf) != SF_ERR_NO_ERROR || inf.datalen < 14) + player->sample_type = Int16; + else + { + ALubyte *fmtbuf = calloc(inf.datalen, 1); + inf.data = fmtbuf; + if(sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR) + player->sample_type = 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(player->sample_type == IMA4) + { + splblockalign = (byteblockalign/player->sfinfo.channels - 4)/4*8 + 1; + if(splblockalign < 1 + || ((splblockalign-1)/2 + 4)*player->sfinfo.channels != byteblockalign) + player->sample_type = Int16; + } + else + { + splblockalign = (byteblockalign/player->sfinfo.channels - 7)*2 + 2; + if(splblockalign < 2 + || ((splblockalign-2)/2 + 7)*player->sfinfo.channels != byteblockalign) + player->sample_type = Int16; + } + } + free(fmtbuf); + } + } + + if(player->sample_type == Int16) + { + player->sampleblockalign = 1; + player->byteblockalign = player->sfinfo.channels * 2; + } + else if(player->sample_type == Float) + { + player->sampleblockalign = 1; + player->byteblockalign = player->sfinfo.channels * 4; + } + else + { + player->sampleblockalign = splblockalign; + player->byteblockalign = byteblockalign; + } + + /* Figure out the OpenAL format from the file and desired sample type. */ + player->format = AL_NONE; if(player->sfinfo.channels == 1) - player->format = AL_FORMAT_MONO16; + { + if(player->sample_type == Int16) + player->format = AL_FORMAT_MONO16; + else if(player->sample_type == Float) + player->format = AL_FORMAT_MONO_FLOAT32; + else if(player->sample_type == IMA4) + player->format = AL_FORMAT_MONO_IMA4; + else if(player->sample_type == MSADPCM) + player->format = AL_FORMAT_MONO_MSADPCM_SOFT; + } else if(player->sfinfo.channels == 2) - player->format = AL_FORMAT_STEREO16; + { + if(player->sample_type == Int16) + player->format = AL_FORMAT_STEREO16; + else if(player->sample_type == Float) + player->format = AL_FORMAT_STEREO_FLOAT32; + else if(player->sample_type == IMA4) + player->format = AL_FORMAT_STEREO_IMA4; + else if(player->sample_type == MSADPCM) + player->format = AL_FORMAT_STEREO_MSADPCM_SOFT; + } else if(player->sfinfo.channels == 3) { if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) - player->format = AL_FORMAT_BFORMAT2D_16; + { + if(player->sample_type == Int16) + player->format = AL_FORMAT_BFORMAT2D_16; + else if(player->sample_type == Float) + player->format = AL_FORMAT_BFORMAT2D_FLOAT32; + } } else if(player->sfinfo.channels == 4) { if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) - player->format = AL_FORMAT_BFORMAT3D_16; + { + if(player->sample_type == Int16) + player->format = AL_FORMAT_BFORMAT3D_16; + else if(player->sample_type == Float) + player->format = AL_FORMAT_BFORMAT3D_FLOAT32; + } } if(!player->format) { @@ -147,8 +292,9 @@ static int OpenPlayerFile(StreamPlayer *player, const char *filename) return 0; } - frame_size = (size_t)(BUFFER_SAMPLES * player->sfinfo.channels) * sizeof(short); - player->membuf = malloc(frame_size); + player->block_count = player->sfinfo.samplerate / player->sampleblockalign; + player->block_count = player->block_count * BufferMillisec / 1000; + player->membuf = malloc((size_t)player->block_count * (size_t)player->byteblockalign); return 1; } @@ -162,6 +308,15 @@ static void ClosePlayerFile(StreamPlayer *player) free(player->membuf); player->membuf = NULL; + + if(player->sampleblockalign > 1) + { + ALsizei i; + for(i = 0;i < NumBuffers;i++) + alBufferi(player->buffers[i], AL_UNPACK_BLOCK_ALIGNMENT_SOFT, 0); + player->sampleblockalign = 0; + player->byteblockalign = 0; + } } @@ -175,13 +330,37 @@ 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++) { - /* Get some data to give it to the buffer */ - sf_count_t slen = sf_readf_short(player->sndfile, player->membuf, BUFFER_SAMPLES); - if(slen < 1) break; + sf_count_t slen; + + /* Get some data to give it to the buffer */ + if(player->sample_type == Int16) + { + slen = sf_readf_short(player->sndfile, player->membuf, + (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, + (sf_count_t)player->block_count * player->sampleblockalign); + if(slen < 1) break; + slen *= player->byteblockalign; + } + else + { + slen = sf_read_raw(player->sndfile, player->membuf, + (sf_count_t)player->block_count * player->byteblockalign); + if(slen > 0) slen -= slen%player->byteblockalign; + if(slen < 1) break; + } + + if(player->sampleblockalign > 1) + alBufferi(player->buffers[i], AL_UNPACK_BLOCK_ALIGNMENT_SOFT, + player->sampleblockalign); - slen *= player->sfinfo.channels * (sf_count_t)sizeof(short); alBufferData(player->buffers[i], player->format, player->membuf, (ALsizei)slen, player->sfinfo.samplerate); } @@ -227,10 +406,27 @@ static int UpdatePlayer(StreamPlayer *player) /* Read the next chunk of data, refill the buffer, and queue it * back on the source */ - slen = sf_readf_short(player->sndfile, player->membuf, BUFFER_SAMPLES); + if(player->sample_type == Int16) + { + slen = sf_readf_short(player->sndfile, player->membuf, + (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, + (sf_count_t)player->block_count * player->sampleblockalign); + if(slen > 0) slen *= player->byteblockalign; + } + else + { + slen = sf_read_raw(player->sndfile, player->membuf, + (sf_count_t)player->block_count * player->byteblockalign); + if(slen > 0) slen -= slen%player->byteblockalign; + } + if(slen > 0) { - slen *= player->sfinfo.channels * (sf_count_t)sizeof(short); alBufferData(bufid, player->format, player->membuf, (ALsizei)slen, player->sfinfo.samplerate); alSourceQueueBuffers(player->source, 1, &bufid); @@ -292,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/libs/openal-soft/examples/alstreamcb.cpp b/libs/openal-soft/examples/alstreamcb.cpp index e0dff4aa..ebf3d3b0 100644 --- a/libs/openal-soft/examples/alstreamcb.cpp +++ b/libs/openal-soft/examples/alstreamcb.cpp @@ -24,12 +24,12 @@ /* This file contains a streaming audio player using a callback buffer. */ -#include -#include -#include #include #include +#include +#include +#include #include #include #include @@ -44,6 +44,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + namespace { @@ -56,10 +58,16 @@ 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}; + size_t mBytesPerBlock{1}; + + enum class SampleType { + Int16, Float, IMA4, MSADPCM + }; + SampleType mSampleFormat{SampleType::Int16}; /* The buffer to get the callback, and source to play with. */ ALuint mBuffer{0}, mSource{0}; @@ -71,15 +79,15 @@ struct StreamPlayer { size_t mDecoderOffset{0}; /* The format of the callback samples. */ - ALenum mFormat; + ALenum mFormat{}; StreamPlayer() { alGenBuffers(1, &mBuffer); - if(ALenum err{alGetError()}) + if(alGetError() != AL_NO_ERROR) throw std::runtime_error{"alGenBuffers failed"}; alGenSources(1, &mSource); - if(ALenum err{alGetError()}) + if(alGetError() != AL_NO_ERROR) { alDeleteBuffers(1, &mBuffer); throw std::runtime_error{"alGenSources failed"}; @@ -95,6 +103,9 @@ struct StreamPlayer { void close() { + if(mSamplesPerBlock > 1) + alBufferi(mBuffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, 0); + if(mSndfile) { alSourceRewind(mSource); @@ -116,20 +127,129 @@ struct StreamPlayer { return false; } + switch((mSfInfo.format&SF_FORMAT_SUBMASK)) + { + case SF_FORMAT_PCM_24: + case SF_FORMAT_PCM_32: + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + case SF_FORMAT_VORBIS: + case SF_FORMAT_OPUS: + case SF_FORMAT_ALAC_20: + case SF_FORMAT_ALAC_24: + case SF_FORMAT_ALAC_32: + case 0x0080/*SF_FORMAT_MPEG_LAYER_I*/: + case 0x0081/*SF_FORMAT_MPEG_LAYER_II*/: + case 0x0082/*SF_FORMAT_MPEG_LAYER_III*/: + if(alIsExtensionPresent("AL_EXT_FLOAT32")) + mSampleFormat = SampleType::Float; + break; + case SF_FORMAT_IMA_ADPCM: + if(mSfInfo.channels <= 2 && (mSfInfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV + && alIsExtensionPresent("AL_EXT_IMA4") + && alIsExtensionPresent("AL_SOFT_block_alignment")) + mSampleFormat = SampleType::IMA4; + break; + case SF_FORMAT_MS_ADPCM: + if(mSfInfo.channels <= 2 && (mSfInfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV + && alIsExtensionPresent("AL_SOFT_MSADPCM") + && alIsExtensionPresent("AL_SOFT_block_alignment")) + mSampleFormat = SampleType::MSADPCM; + break; + } + + int splblocksize{}, byteblocksize{}; + if(mSampleFormat == SampleType::IMA4 || mSampleFormat == SampleType::MSADPCM) + { + SF_CHUNK_INFO inf{ "fmt ", 4, 0, nullptr }; + SF_CHUNK_ITERATOR *iter = sf_get_chunk_iterator(mSndfile, &inf); + if(!iter || sf_get_chunk_size(iter, &inf) != SF_ERR_NO_ERROR || inf.datalen < 14) + mSampleFormat = SampleType::Int16; + else + { + 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 + { + byteblocksize = fmtbuf[12] | (fmtbuf[13]<<8u); + if(mSampleFormat == SampleType::IMA4) + { + splblocksize = (byteblocksize/mSfInfo.channels - 4)/4*8 + 1; + if(splblocksize < 1 + || ((splblocksize-1)/2 + 4)*mSfInfo.channels != byteblocksize) + mSampleFormat = SampleType::Int16; + } + else + { + splblocksize = (byteblocksize/mSfInfo.channels - 7)*2 + 2; + if(splblocksize < 2 + || ((splblocksize-2)/2 + 7)*mSfInfo.channels != byteblocksize) + mSampleFormat = SampleType::Int16; + } + } + } + } + + if(mSampleFormat == SampleType::Int16) + { + mSamplesPerBlock = 1; + mBytesPerBlock = static_cast(mSfInfo.channels) * 2; + } + else if(mSampleFormat == SampleType::Float) + { + mSamplesPerBlock = 1; + mBytesPerBlock = static_cast(mSfInfo.channels) * 4; + } + else + { + mSamplesPerBlock = static_cast(splblocksize); + mBytesPerBlock = static_cast(byteblocksize); + } + mFormat = AL_NONE; if(mSfInfo.channels == 1) - mFormat = AL_FORMAT_MONO_FLOAT32; + { + if(mSampleFormat == SampleType::Int16) + mFormat = AL_FORMAT_MONO16; + else if(mSampleFormat == SampleType::Float) + mFormat = AL_FORMAT_MONO_FLOAT32; + else if(mSampleFormat == SampleType::IMA4) + mFormat = AL_FORMAT_MONO_IMA4; + else if(mSampleFormat == SampleType::MSADPCM) + mFormat = AL_FORMAT_MONO_MSADPCM_SOFT; + } else if(mSfInfo.channels == 2) - mFormat = AL_FORMAT_STEREO_FLOAT32; + { + if(mSampleFormat == SampleType::Int16) + mFormat = AL_FORMAT_STEREO16; + else if(mSampleFormat == SampleType::Float) + mFormat = AL_FORMAT_STEREO_FLOAT32; + else if(mSampleFormat == SampleType::IMA4) + mFormat = AL_FORMAT_STEREO_IMA4; + else if(mSampleFormat == SampleType::MSADPCM) + mFormat = AL_FORMAT_STEREO_MSADPCM_SOFT; + } else if(mSfInfo.channels == 3) { - if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) - mFormat = AL_FORMAT_BFORMAT2D_FLOAT32; + if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) + { + if(mSampleFormat == SampleType::Int16) + mFormat = AL_FORMAT_BFORMAT2D_16; + else if(mSampleFormat == SampleType::Float) + mFormat = AL_FORMAT_BFORMAT2D_FLOAT32; + } } else if(mSfInfo.channels == 4) { - if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) - mFormat = AL_FORMAT_BFORMAT3D_FLOAT32; + if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) + { + if(mSampleFormat == SampleType::Int16) + mFormat = AL_FORMAT_BFORMAT3D_16; + else if(mSampleFormat == SampleType::Float) + mFormat = AL_FORMAT_BFORMAT3D_FLOAT32; + } } if(!mFormat) { @@ -141,8 +261,9 @@ struct StreamPlayer { } /* Set a 1s ring buffer size. */ - mBufferDataSize = static_cast(mSfInfo.samplerate*mSfInfo.channels) * sizeof(float); - mBufferData.reset(new ALbyte[mBufferDataSize]); + size_t numblocks{(static_cast(mSfInfo.samplerate) + mSamplesPerBlock-1) + / mSamplesPerBlock}; + mBufferData.resize(static_cast(numblocks * mBytesPerBlock)); mReadPos.store(0, std::memory_order_relaxed); mWritePos.store(0, std::memory_order_relaxed); mDecoderOffset = 0; @@ -155,9 +276,9 @@ 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 { /* 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 @@ -182,7 +303,7 @@ 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}; + size_t todo{((woffset < roffset) ? mBufferData.size() : woffset) - roffset}; todo = std::min(todo, static_cast(size-got)); /* Copy from the ring buffer to the provided output buffer. Wrap @@ -194,7 +315,7 @@ struct StreamPlayer { 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 @@ -207,6 +328,8 @@ struct StreamPlayer { bool prepare() { + if(mSamplesPerBlock > 1) + alBufferi(mBuffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, static_cast(mSamplesPerBlock)); alBufferCallbackSOFT(mBuffer, mFormat, mSfInfo.samplerate, bufferCallbackC, this); alSourcei(mSource, AL_BUFFER, static_cast(mBuffer)); if(ALenum err{alGetError()}) @@ -224,22 +347,22 @@ struct StreamPlayer { alGetSourcei(mSource, AL_SAMPLE_OFFSET, &pos); alGetSourcei(mSource, AL_SOURCE_STATE, &state); - const size_t frame_size{static_cast(mSfInfo.channels) * sizeof(float)}; size_t woffset{mWritePos.load(std::memory_order_acquire)}; 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. * For a playing/paused source, it's the source's offset including * the playback offset the source was started with. */ - const size_t curtime{((state==AL_STOPPED) ? (mDecoderOffset-readable) / frame_size - : (static_cast(pos) + mStartOffset/frame_size)) + const size_t curtime{((state == AL_STOPPED) + ? (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); @@ -256,15 +379,33 @@ struct StreamPlayer { * at the read offset would be interpreted as being empty * instead of full. */ - const size_t writable{roffset-woffset-1}; - if(writable < frame_size) break; + const size_t writable{(roffset-woffset-1) / mBytesPerBlock}; + if(!writable) break; - sf_count_t num_frames{sf_readf_float(mSndfile, - reinterpret_cast(&mBufferData[woffset]), - static_cast(writable/frame_size))}; - if(num_frames < 1) break; + if(mSampleFormat == SampleType::Int16) + { + sf_count_t num_frames{sf_readf_short(mSndfile, + reinterpret_cast(&mBufferData[woffset]), + static_cast(writable*mSamplesPerBlock))}; + if(num_frames < 1) break; + read_bytes = static_cast(num_frames) * mBytesPerBlock; + } + else if(mSampleFormat == SampleType::Float) + { + sf_count_t num_frames{sf_readf_float(mSndfile, + reinterpret_cast(&mBufferData[woffset]), + static_cast(writable*mSamplesPerBlock))}; + if(num_frames < 1) break; + read_bytes = static_cast(num_frames) * mBytesPerBlock; + } + else + { + sf_count_t numbytes{sf_read_raw(mSndfile, &mBufferData[woffset], + static_cast(writable*mBytesPerBlock))}; + if(numbytes < 1) break; + read_bytes = static_cast(numbytes); + } - read_bytes = static_cast(num_frames) * frame_size; woffset += read_bytes; } else @@ -274,18 +415,36 @@ 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)}; - if(writable < frame_size) break; + const size_t writable{(!roffset ? mBufferData.size()-woffset-1 : + (mBufferData.size()-woffset)) / mBytesPerBlock}; + if(!writable) break; - sf_count_t num_frames{sf_readf_float(mSndfile, - reinterpret_cast(&mBufferData[woffset]), - static_cast(writable/frame_size))}; - if(num_frames < 1) break; + if(mSampleFormat == SampleType::Int16) + { + sf_count_t num_frames{sf_readf_short(mSndfile, + reinterpret_cast(&mBufferData[woffset]), + static_cast(writable*mSamplesPerBlock))}; + if(num_frames < 1) break; + read_bytes = static_cast(num_frames) * mBytesPerBlock; + } + else if(mSampleFormat == SampleType::Float) + { + sf_count_t num_frames{sf_readf_float(mSndfile, + reinterpret_cast(&mBufferData[woffset]), + static_cast(writable*mSamplesPerBlock))}; + if(num_frames < 1) break; + read_bytes = static_cast(num_frames) * mBytesPerBlock; + } + else + { + sf_count_t numbytes{sf_read_raw(mSndfile, &mBufferData[woffset], + static_cast(writable*mBytesPerBlock))}; + if(numbytes < 1) break; + read_bytes = static_cast(numbytes); + } - read_bytes = static_cast(num_frames) * frame_size; woffset += read_bytes; - if(woffset == mBufferDataSize) + if(woffset == mBufferData.size()) woffset = 0; } mWritePos.store(woffset, std::memory_order_release); @@ -300,7 +459,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; @@ -363,7 +522,8 @@ int main(int argc, char **argv) /* Get the name portion, without the path, for display. */ const char *namepart{strrchr(argv[i], '/')}; - if(namepart || (namepart=strrchr(argv[i], '\\'))) + if(!namepart) namepart = strrchr(argv[i], '\\'); + if(namepart) ++namepart; else namepart = argv[i]; diff --git a/libs/openal-soft/examples/altonegen.c b/libs/openal-soft/examples/altonegen.c index 75db2d6b..52a57875 100644 --- a/libs/openal-soft/examples/altonegen.c +++ b/libs/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/libs/openal-soft/examples/common/alhelpers.c b/libs/openal-soft/examples/common/alhelpers.c index 1d498ffa..6627f704 100644 --- a/libs/openal-soft/examples/common/alhelpers.c +++ b/libs/openal-soft/examples/common/alhelpers.c @@ -111,15 +111,50 @@ const char *FormatName(ALenum format) case AL_FORMAT_MONO8: return "Mono, U8"; case AL_FORMAT_MONO16: return "Mono, S16"; case AL_FORMAT_MONO_FLOAT32: return "Mono, Float32"; + case AL_FORMAT_MONO_MULAW: return "Mono, muLaw"; + case AL_FORMAT_MONO_ALAW_EXT: return "Mono, aLaw"; + case AL_FORMAT_MONO_IMA4: return "Mono, IMA4 ADPCM"; + case AL_FORMAT_MONO_MSADPCM_SOFT: return "Mono, MS ADPCM"; case AL_FORMAT_STEREO8: return "Stereo, U8"; case AL_FORMAT_STEREO16: return "Stereo, S16"; case AL_FORMAT_STEREO_FLOAT32: return "Stereo, Float32"; + case AL_FORMAT_STEREO_MULAW: return "Stereo, muLaw"; + case AL_FORMAT_STEREO_ALAW_EXT: return "Stereo, aLaw"; + case AL_FORMAT_STEREO_IMA4: return "Stereo, IMA4 ADPCM"; + case AL_FORMAT_STEREO_MSADPCM_SOFT: return "Stereo, MS ADPCM"; + case AL_FORMAT_QUAD8: return "Quadraphonic, U8"; + case AL_FORMAT_QUAD16: return "Quadraphonic, S16"; + case AL_FORMAT_QUAD32: return "Quadraphonic, Float32"; + case AL_FORMAT_QUAD_MULAW: return "Quadraphonic, muLaw"; + case AL_FORMAT_51CHN8: return "5.1 Surround, U8"; + case AL_FORMAT_51CHN16: return "5.1 Surround, S16"; + case AL_FORMAT_51CHN32: return "5.1 Surround, Float32"; + case AL_FORMAT_51CHN_MULAW: return "5.1 Surround, muLaw"; + case AL_FORMAT_61CHN8: return "6.1 Surround, U8"; + case AL_FORMAT_61CHN16: return "6.1 Surround, S16"; + case AL_FORMAT_61CHN32: return "6.1 Surround, Float32"; + case AL_FORMAT_61CHN_MULAW: return "6.1 Surround, muLaw"; + case AL_FORMAT_71CHN8: return "7.1 Surround, U8"; + case AL_FORMAT_71CHN16: return "7.1 Surround, S16"; + case AL_FORMAT_71CHN32: return "7.1 Surround, Float32"; + case AL_FORMAT_71CHN_MULAW: return "7.1 Surround, muLaw"; case AL_FORMAT_BFORMAT2D_8: return "B-Format 2D, U8"; case AL_FORMAT_BFORMAT2D_16: return "B-Format 2D, S16"; case AL_FORMAT_BFORMAT2D_FLOAT32: return "B-Format 2D, Float32"; + case AL_FORMAT_BFORMAT2D_MULAW: return "B-Format 2D, muLaw"; case AL_FORMAT_BFORMAT3D_8: return "B-Format 3D, U8"; case AL_FORMAT_BFORMAT3D_16: return "B-Format 3D, S16"; case AL_FORMAT_BFORMAT3D_FLOAT32: return "B-Format 3D, Float32"; + case AL_FORMAT_BFORMAT3D_MULAW: return "B-Format 3D, muLaw"; + case AL_FORMAT_UHJ2CHN8_SOFT: return "UHJ 2-channel, U8"; + case AL_FORMAT_UHJ2CHN16_SOFT: return "UHJ 2-channel, S16"; + case AL_FORMAT_UHJ2CHN_FLOAT32_SOFT: return "UHJ 2-channel, Float32"; + case AL_FORMAT_UHJ3CHN8_SOFT: return "UHJ 3-channel, U8"; + case AL_FORMAT_UHJ3CHN16_SOFT: return "UHJ 3-channel, S16"; + case AL_FORMAT_UHJ3CHN_FLOAT32_SOFT: return "UHJ 3-channel, Float32"; + case AL_FORMAT_UHJ4CHN8_SOFT: return "UHJ 4-channel, U8"; + case AL_FORMAT_UHJ4CHN16_SOFT: return "UHJ 4-channel, S16"; + case AL_FORMAT_UHJ4CHN_FLOAT32_SOFT: return "UHJ 4-channel, Float32"; } return "Unknown Format"; } diff --git a/libs/openal-soft/examples/common/alhelpers.h b/libs/openal-soft/examples/common/alhelpers.h index 34f73864..1a56e6dc 100644 --- a/libs/openal-soft/examples/common/alhelpers.h +++ b/libs/openal-soft/examples/common/alhelpers.h @@ -8,7 +8,7 @@ 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); diff --git a/libs/openal-soft/hrtf/Default HRTF.mhr b/libs/openal-soft/hrtf/Default HRTF.mhr index 516a67863d0a1cd2a3bf688f345f3e00a1116d8c..495516758d5b23dd1b681ab5f1c19f09e3420c54 100644 GIT binary patch literal 159841 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

C+ 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 z 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 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 @@ -15,7 +35,7 @@ extern "C" { #endif #endif -#if defined(_WIN32) +#ifdef _WIN32 #define AL_APIENTRY __cdecl #else #define AL_APIENTRY @@ -40,43 +60,43 @@ typedef char ALboolean; /** character */ typedef char ALchar; -/** signed 8-bit 2's complement integer */ +/** signed 8-bit integer */ typedef signed char ALbyte; /** unsigned 8-bit integer */ typedef unsigned char ALubyte; -/** signed 16-bit 2's complement integer */ +/** signed 16-bit integer */ typedef short ALshort; /** unsigned 16-bit integer */ typedef unsigned short ALushort; -/** signed 32-bit 2's complement integer */ +/** signed 32-bit integer */ typedef int ALint; /** unsigned 32-bit integer */ typedef unsigned int ALuint; -/** non-negative 32-bit binary integer size */ +/** non-negative 32-bit integer size */ typedef int ALsizei; -/** enumerated 32-bit value */ +/** 32-bit enumeration value */ typedef int ALenum; -/** 32-bit IEEE754 floating-point */ +/** 32-bit IEEE-754 floating-point */ typedef float ALfloat; -/** 64-bit IEEE754 floating-point */ +/** 64-bit IEEE-754 floating-point */ typedef double ALdouble; -/** void type (for opaque pointers only) */ +/** void type (opaque pointers only) */ typedef void ALvoid; -/* Enumerant values begin at column 50. No tabs. */ +/* Enumeration values begin at column 50. Do not use tabs. */ -/** "no distance model" or "no buffer" */ +/** No distance model or no buffer */ #define AL_NONE 0 /** Boolean False. */ @@ -89,10 +109,10 @@ typedef void ALvoid; /** * Relative source. * Type: ALboolean - * Range: [AL_TRUE, AL_FALSE] + * Range: [AL_FALSE, AL_TRUE] * Default: AL_FALSE * - * Specifies if the Source has relative coordinates. + * Specifies if the source uses relative coordinates. */ #define AL_SOURCE_RELATIVE 0x202 @@ -103,7 +123,8 @@ typedef void ALvoid; * Range: [0 - 360] * Default: 360 * - * The angle covered by the inner cone, where the source will not attenuate. + * The angle covered by the inner cone, the area within which the source will + * not be attenuated by direction. */ #define AL_CONE_INNER_ANGLE 0x1001 @@ -112,8 +133,8 @@ typedef void ALvoid; * Range: [0 - 360] * Default: 360 * - * The angle covered by the outer cone, where the source will be fully - * attenuated. + * The angle covered by the outer cone, the area outside of which the source + * will be fully attenuated by direction. */ #define AL_CONE_OUTER_ANGLE 0x1002 @@ -123,7 +144,7 @@ typedef void ALvoid; * Range: [0.5 - 2.0] * Default: 1.0 * - * A multiplier for the frequency (sample rate) of the source's buffer. + * A multiplier for the sample rate of the source's buffer. */ #define AL_PITCH 0x1003 @@ -134,12 +155,12 @@ typedef void ALvoid; * * The source or listener location in three dimensional space. * - * OpenAL, like OpenGL, uses a right handed coordinate system, where in a - * frontal default view X (thumb) points right, Y points up (index finger), and - * Z points towards the viewer/camera (middle finger). + * OpenAL uses a right handed coordinate system, like OpenGL, where with a + * default view, X points right (thumb), Y points up (index finger), and Z + * points towards the viewer/camera (middle finger). * - * To switch from a left handed coordinate system, flip the sign on the Z - * coordinate. + * To change from or to a left handed coordinate system, negate the Z + * component. */ #define AL_POSITION 0x1004 @@ -148,8 +169,11 @@ typedef void ALvoid; * Type: ALfloat[3], ALint[3] * Default: {0, 0, 0} * - * Specifies the current direction in local space. - * A zero-length vector specifies an omni-directional source (cone is ignored). + * Specifies the current direction in local space. A zero-length vector + * specifies an omni-directional source (cone is ignored). + * + * To change from or to a left handed coordinate system, negate the Z + * component. */ #define AL_DIRECTION 0x1005 @@ -158,26 +182,30 @@ typedef void ALvoid; * Type: ALfloat[3], ALint[3] * Default: {0, 0, 0} * - * Specifies the current velocity in local space. + * Specifies the current velocity, relative to the position. + * + * To change from or to a left handed coordinate system, negate the Z + * component. */ #define AL_VELOCITY 0x1006 /** * Source looping. * Type: ALboolean - * Range: [AL_TRUE, AL_FALSE] + * Range: [AL_FALSE, AL_TRUE] * Default: AL_FALSE * - * Specifies whether source is looping. + * Specifies whether source playback loops. */ #define AL_LOOPING 0x1007 /** * Source buffer. - * Type: ALuint - * Range: any valid Buffer. + * Type: ALuint + * Range: any valid Buffer ID + * Default: AL_NONE * - * Specifies the buffer to provide sound samples. + * Specifies the buffer to provide sound samples for a source. */ #define AL_BUFFER 0x1009 @@ -186,12 +214,12 @@ typedef void ALvoid; * Type: ALfloat * Range: [0.0 - ] * - * A value of 1.0 means unattenuated. Each division by 2 equals an attenuation - * of about -6dB. Each multiplicaton by 2 equals an amplification of about - * +6dB. + * For sources, an initial linear gain value (before attenuation is applied). + * For the listener, an output linear gain adjustment. * - * A value of 0.0 is meaningless with respect to a logarithmic scale; it is - * silent. + * A value of 1.0 means unattenuated. Each division by 2 equals an attenuation + * of about -6dB. Each multiplication by 2 equals an amplification of about + * +6dB. */ #define AL_GAIN 0x100A @@ -200,8 +228,8 @@ typedef void ALvoid; * Type: ALfloat * Range: [0.0 - 1.0] * - * The minimum gain allowed for a source, after distance and cone attenation is - * applied (if applicable). + * The minimum gain allowed for a source, after distance and cone attenuation + * are applied (if applicable). */ #define AL_MIN_GAIN 0x100D @@ -210,26 +238,28 @@ typedef void ALvoid; * Type: ALfloat * Range: [0.0 - 1.0] * - * The maximum gain allowed for a source, after distance and cone attenation is - * applied (if applicable). + * The maximum gain allowed for a source, after distance and cone attenuation + * are applied (if applicable). */ #define AL_MAX_GAIN 0x100E /** * Listener orientation. - * Type: ALfloat[6] + * Type: ALfloat[6] * Default: {0.0, 0.0, -1.0, 0.0, 1.0, 0.0} * * Effectively two three dimensional vectors. The first vector is the front (or - * "at") and the second is the top (or "up"). + * "at") and the second is the top (or "up"). Both vectors are relative to the + * listener position. * - * Both vectors are in local space. + * To change from or to a left handed coordinate system, negate the Z + * component of both vectors. */ #define AL_ORIENTATION 0x100F /** * Source state (query only). - * Type: ALint + * Type: ALenum * Range: [AL_INITIAL, AL_PLAYING, AL_PAUSED, AL_STOPPED] */ #define AL_SOURCE_STATE 0x1010 @@ -267,9 +297,9 @@ typedef void ALvoid; * Range: [0.0 - ] * Default: 1.0 * - * The distance in units that no attenuation occurs. + * The distance in units that no distance attenuation occurs. * - * At 0.0, no distance attenuation ever occurs on non-linear attenuation models. + * At 0.0, no distance attenuation occurs with non-linear attenuation models. */ #define AL_REFERENCE_DISTANCE 0x1020 @@ -292,7 +322,7 @@ typedef void ALvoid; * Default: 0.0 * * The gain attenuation applied when the listener is outside of the source's - * outer cone. + * outer cone angle. */ #define AL_CONE_OUTER_GAIN 0x1022 @@ -308,16 +338,16 @@ typedef void ALvoid; */ #define AL_MAX_DISTANCE 0x1023 -/** Source buffer position, in seconds */ +/** Source buffer offset, in seconds */ #define AL_SEC_OFFSET 0x1024 -/** Source buffer position, in sample frames */ +/** Source buffer offset, in sample frames */ #define AL_SAMPLE_OFFSET 0x1025 -/** Source buffer position, in bytes */ +/** Source buffer offset, in bytes */ #define AL_BYTE_OFFSET 0x1026 /** * Source type (query only). - * Type: ALint + * Type: ALenum * Range: [AL_STATIC, AL_STREAMING, AL_UNDETERMINED] * * A Source is Static if a Buffer has been attached using AL_BUFFER. @@ -344,13 +374,13 @@ typedef void ALvoid; /** Signed 16-bit stereo buffer format. */ #define AL_FORMAT_STEREO16 0x1103 -/** Buffer frequency (query only). */ +/** Buffer frequency/sample rate (query only). */ #define AL_FREQUENCY 0x2001 /** Buffer bits per sample (query only). */ #define AL_BITS 0x2002 /** Buffer channel count (query only). */ #define AL_CHANNELS 0x2003 -/** Buffer data size (query only). */ +/** Buffer data size in bytes (query only). */ #define AL_SIZE 0x2004 /* Buffer state. Not for public use. */ @@ -362,32 +392,31 @@ typedef void ALvoid; /** No error. */ #define AL_NO_ERROR 0 -/** Invalid name paramater passed to AL call. */ +/** Invalid name (ID) passed to an AL call. */ #define AL_INVALID_NAME 0xA001 -/** Invalid enum parameter passed to AL call. */ +/** Invalid enumeration passed to AL call. */ #define AL_INVALID_ENUM 0xA002 -/** Invalid value parameter passed to AL call. */ +/** Invalid value passed to AL call. */ #define AL_INVALID_VALUE 0xA003 /** Illegal AL call. */ #define AL_INVALID_OPERATION 0xA004 -/** Not enough memory. */ +/** Not enough memory to execute the AL call. */ #define AL_OUT_OF_MEMORY 0xA005 -/** Context string: Vendor ID. */ +/** Context string: Vendor name. */ #define AL_VENDOR 0xB001 /** Context string: Version. */ #define AL_VERSION 0xB002 -/** Context string: Renderer ID. */ +/** Context string: Renderer name. */ #define AL_RENDERER 0xB003 /** Context string: Space-separated extension list. */ #define AL_EXTENSIONS 0xB004 - /** * Doppler scale. * Type: ALfloat @@ -397,7 +426,6 @@ typedef void ALvoid; * Scale for source and listener velocities. */ #define AL_DOPPLER_FACTOR 0xC000 -AL_API void AL_APIENTRY alDopplerFactor(ALfloat value); /** * Doppler velocity (deprecated). @@ -405,7 +433,6 @@ AL_API void AL_APIENTRY alDopplerFactor(ALfloat value); * A multiplier applied to the Speed of Sound. */ #define AL_DOPPLER_VELOCITY 0xC001 -AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value); /** * Speed of Sound, in units per second. @@ -414,14 +441,13 @@ AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value); * Default: 343.3 * * The speed at which sound waves are assumed to travel, when calculating the - * doppler effect. + * doppler effect from source and listener velocities. */ #define AL_SPEED_OF_SOUND 0xC003 -AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value); /** * Distance attenuation model. - * Type: ALint + * Type: ALenum * Range: [AL_NONE, AL_INVERSE_DISTANCE, AL_INVERSE_DISTANCE_CLAMPED, * AL_LINEAR_DISTANCE, AL_LINEAR_DISTANCE_CLAMPED, * AL_EXPONENT_DISTANCE, AL_EXPONENT_DISTANCE_CLAMPED] @@ -438,7 +464,6 @@ AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value); * distance calculated is clamped between the reference and max distances. */ #define AL_DISTANCE_MODEL 0xD000 -AL_API void AL_APIENTRY alDistanceModel(ALenum distanceModel); /* Distance model values. */ #define AL_INVERSE_DISTANCE 0xD001 @@ -448,208 +473,223 @@ AL_API void AL_APIENTRY alDistanceModel(ALenum distanceModel); #define AL_EXPONENT_DISTANCE 0xD005 #define AL_EXPONENT_DISTANCE_CLAMPED 0xD006 +#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; -/* 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); +/* Context state setting. */ +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; -/* Error retrieval. */ +/* Context state retrieval. */ +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 check. */ -AL_API ALenum AL_APIENTRY alGetError(void); +/** + * 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_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); +/* Set listener parameters. */ +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); +/* Get listener parameters. */ +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); -/** Delete Source objects. */ -AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources); -/** Verify a handle is a valid Source. */ -AL_API ALboolean AL_APIENTRY alIsSource(ALuint source); +/** Create source objects. */ +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_NOEXCEPT; +/** Verify an ID is for a valid 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); +/* Set source parameters. */ +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); +/* Get source parameters. */ +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, replay, or resume (if paused) a list of Sources */ -AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources); -/** Stop a list of Sources */ -AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources); -/** Rewind a list of Sources */ -AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources); -/** Pause a list of Sources */ -AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources); +/** Play, restart, or resume a source, setting its state to AL_PLAYING. */ +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_NOEXCEPT; +/** Rewind a source, setting its state to AL_INITIAL. */ +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_NOEXCEPT; -/** Play, replay, or resume a Source */ -AL_API void AL_APIENTRY alSourcePlay(ALuint source); -/** Stop a Source */ -AL_API void AL_APIENTRY alSourceStop(ALuint source); -/** Rewind a Source (set playback postiton to beginning) */ -AL_API void AL_APIENTRY alSourceRewind(ALuint source); -/** Pause a Source */ -AL_API void AL_APIENTRY alSourcePause(ALuint source); +/** Play, restart, or resume a list of sources atomically. */ +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_NOEXCEPT; +/** Rewind a list of sources atomically. */ +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_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); -/** Delete Buffer objects */ -AL_API void AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers); -/** Verify a handle is a valid Buffer */ -AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer); +/** Create buffer objects */ +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_NOEXCEPT; +/** Verify an ID is a valid buffer (including the NULL buffer) */ +AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer) AL_API_NOEXCEPT; -/** Specifies the data to be copied into a buffer */ -AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq); +/** + * 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_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); +/* Set buffer parameters. */ +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); +/* Get buffer parameters. */ +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 type, useful for dynamically getting 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 freq); -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); +/* Pointer-to-function types, useful for storing dynamically loaded AL entry + * points. + */ +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; -#if defined(__cplusplus) +#ifdef __cplusplus } /* extern "C" */ #endif +/* NOLINTEND */ #endif /* AL_AL_H */ diff --git a/libs/openal-soft/include/AL/alc.h b/libs/openal-soft/include/AL/alc.h index c73b6e91..3311b57f 100644 --- a/libs/openal-soft/include/AL/alc.h +++ b/libs/openal-soft/include/AL/alc.h @@ -1,8 +1,28 @@ #ifndef AL_ALC_H #define AL_ALC_H -#if defined(__cplusplus) +/* 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 @@ -15,7 +35,7 @@ extern "C" { #endif #endif -#if defined(_WIN32) +#ifdef _WIN32 #define ALC_APIENTRY __cdecl #else #define ALC_APIENTRY @@ -41,41 +61,41 @@ typedef char ALCboolean; /** character */ typedef char ALCchar; -/** signed 8-bit 2's complement integer */ +/** signed 8-bit integer */ typedef signed char ALCbyte; /** unsigned 8-bit integer */ typedef unsigned char ALCubyte; -/** signed 16-bit 2's complement integer */ +/** signed 16-bit integer */ typedef short ALCshort; /** unsigned 16-bit integer */ typedef unsigned short ALCushort; -/** signed 32-bit 2's complement integer */ +/** signed 32-bit integer */ typedef int ALCint; /** unsigned 32-bit integer */ typedef unsigned int ALCuint; -/** non-negative 32-bit binary integer size */ +/** non-negative 32-bit integer size */ typedef int ALCsizei; -/** enumerated 32-bit value */ +/** 32-bit enumeration value */ typedef int ALCenum; -/** 32-bit IEEE754 floating-point */ +/** 32-bit IEEE-754 floating-point */ typedef float ALCfloat; -/** 64-bit IEEE754 floating-point */ +/** 64-bit IEEE-754 floating-point */ typedef double ALCdouble; /** void type (for opaque pointers only) */ typedef void ALCvoid; -/* Enumerant values begin at column 50. No tabs. */ +/* Enumeration values begin at column 50. Do not use tabs. */ /** Boolean False. */ #define ALC_FALSE 0 @@ -107,10 +127,10 @@ typedef void ALCvoid; /** Invalid context handle. */ #define ALC_INVALID_CONTEXT 0xA002 -/** Invalid enum parameter passed to an ALC call. */ +/** Invalid enumeration passed to an ALC call. */ #define ALC_INVALID_ENUM 0xA003 -/** Invalid value parameter passed to an ALC call. */ +/** Invalid value passed to an ALC call. */ #define ALC_INVALID_VALUE 0xA004 /** Out of memory. */ @@ -130,9 +150,9 @@ typedef void ALCvoid; /** String for the default device specifier. */ #define ALC_DEFAULT_DEVICE_SPECIFIER 0x1004 /** - * String for the given device's specifier. + * Device specifier string. * - * If device handle is NULL, it is instead a null-char separated list of + * If device handle is NULL, it is instead a null-character separated list of * strings of known device specifiers (list ends with an empty string). */ #define ALC_DEVICE_SPECIFIER 0x1005 @@ -143,9 +163,9 @@ typedef void ALCvoid; /** Capture extension */ #define ALC_EXT_CAPTURE 1 /** - * String for the given capture device's specifier. + * Capture device specifier string. * - * If device handle is NULL, it is instead a null-char separated list of + * If device handle is NULL, it is instead a null-character separated list of * strings of known capture device specifiers (list ends with an empty string). */ #define ALC_CAPTURE_DEVICE_SPECIFIER 0x310 @@ -160,45 +180,46 @@ typedef void ALCvoid; /** String for the default extended device specifier. */ #define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012 /** - * String for the given extended device's specifier. + * Device's extended specifier string. * - * If device handle is NULL, it is instead a null-char separated list of + * If device handle is NULL, it is instead a null-character separated list of * strings of known extended device specifiers (list ends with an empty string). */ #define ALC_ALL_DEVICES_SPECIFIER 0x1013 +#ifndef ALC_NO_PROTOTYPES /* 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. */ @@ -206,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. */ @@ -231,40 +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); -/** Starts capturing samples into the device buffer. */ -ALC_API void ALC_APIENTRY alcCaptureStart(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_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 type, useful for dynamically getting 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); +/* Pointer-to-function types, useful for storing dynamically loaded ALC entry + * points. + */ +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; -#if defined(__cplusplus) -} +#ifdef __cplusplus +} /* extern "C" */ #endif +/* NOLINTEND */ #endif /* AL_ALC_H */ diff --git a/libs/openal-soft/include/AL/alext.h b/libs/openal-soft/include/AL/alext.h index 1757f341..37680fb6 100644 --- a/libs/openal-soft/include/AL/alext.h +++ b/libs/openal-soft/include/AL/alext.h @@ -1,26 +1,7 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2008 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - #ifndef AL_ALEXT_H #define AL_ALEXT_H +/* NOLINTBEGIN */ #include /* Define int64 and uint64 types */ #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ @@ -45,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 @@ -161,9 +144,9 @@ extern "C" { #ifndef AL_EXT_STATIC_BUFFER #define AL_EXT_STATIC_BUFFER 1 -typedef void (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALint,ALenum,ALvoid*,ALsizei,ALsizei); +typedef void (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALuint,ALenum,ALvoid*,ALsizei,ALsizei) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferDataStatic(const ALint buffer, ALenum format, ALvoid *data, ALsizei len, ALsizei freq); +void AL_APIENTRY alBufferDataStatic(const ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT; #endif #endif @@ -179,11 +162,11 @@ AL_API void AL_APIENTRY alBufferDataStatic(const ALint buffer, ALenum format, AL #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 @@ -196,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 @@ -215,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 @@ -283,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 @@ -322,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 @@ -348,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 @@ -384,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 @@ -411,13 +394,20 @@ AL_API void AL_APIENTRY alProcessUpdatesSOFT(void); /*#define AL_SEC_LENGTH_SOFT 0x200B*/ #endif +#ifndef AL_SOFT_buffer_length_query +#define AL_SOFT_buffer_length_query 1 +/*#define AL_BYTE_LENGTH_SOFT 0x2009*/ +/*#define AL_SAMPLE_LENGTH_SOFT 0x200A*/ +/*#define AL_SEC_LENGTH_SOFT 0x200B*/ +#endif + #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 @@ -461,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 @@ -480,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 @@ -506,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 @@ -565,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 @@ -593,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 @@ -624,6 +613,18 @@ AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid #define AL_SUPER_STEREO_WIDTH_SOFT 0x19B1 #endif +#ifndef AL_SOFT_UHJ_ex +#define AL_SOFT_UHJ_ex +#define AL_FORMAT_UHJ2CHN_MULAW_SOFT 0x19B3 +#define AL_FORMAT_UHJ2CHN_ALAW_SOFT 0x19B4 +#define AL_FORMAT_UHJ2CHN_IMA4_SOFT 0x19B5 +#define AL_FORMAT_UHJ2CHN_MSADPCM_SOFT 0x19B6 +#define AL_FORMAT_UHJ3CHN_MULAW_SOFT 0x19B7 +#define AL_FORMAT_UHJ3CHN_ALAW_SOFT 0x19B8 +#define AL_FORMAT_UHJ4CHN_MULAW_SOFT 0x19B9 +#define AL_FORMAT_UHJ4CHN_ALAW_SOFT 0x19BA +#endif + #ifndef ALC_SOFT_output_mode #define ALC_SOFT_output_mode #define ALC_OUTPUT_MODE_SOFT 0x19AC @@ -639,8 +640,440 @@ AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid #define ALC_SURROUND_7_1_SOFT 0x1506 #endif +#ifndef AL_SOFT_source_start_delay +#define AL_SOFT_source_start_delay +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) 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 *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 *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 *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/libs/openal-soft/include/AL/efx-presets.h b/libs/openal-soft/include/AL/efx-presets.h index 8539fd51..acd5bf39 100644 --- a/libs/openal-soft/include/AL/efx-presets.h +++ b/libs/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/libs/openal-soft/include/AL/efx.h b/libs/openal-soft/include/AL/efx.h index 5ab64a64..1e93bf22 100644 --- a/libs/openal-soft/include/AL/efx.h +++ b/libs/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/libs/openal-soft/libopenal.version b/libs/openal-soft/libopenal.version new file mode 100644 index 00000000..dd998e14 --- /dev/null +++ b/libs/openal-soft/libopenal.version @@ -0,0 +1,192 @@ +{ +global: +alBuffer3f; +alBuffer3i; +alBufferData; +alBufferf; +alBufferfv; +alBufferi; +alBufferiv; +alcCaptureCloseDevice; +alcCaptureOpenDevice; +alcCaptureSamples; +alcCaptureStart; +alcCaptureStop; +alcCloseDevice; +alcCreateContext; +alcDestroyContext; +alcGetContextsDevice; +alcGetCurrentContext; +alcGetEnumValue; +alcGetError; +alcGetIntegerv; +alcGetProcAddress; +alcGetString; +alcIsExtensionPresent; +alcMakeContextCurrent; +alcOpenDevice; +alcProcessContext; +alcSuspendContext; +alDeleteBuffers; +alDeleteSources; +alDisable; +alDistanceModel; +alDopplerFactor; +alEnable; +alGenBuffers; +alGenSources; +alGetBoolean; +alGetBooleanv; +alGetBuffer3f; +alGetBuffer3i; +alGetBufferf; +alGetBufferfv; +alGetBufferi; +alGetBufferiv; +alGetDouble; +alGetDoublev; +alGetEnumValue; +alGetError; +alGetFloat; +alGetFloatv; +alGetInteger; +alGetIntegerv; +alGetListener3f; +alGetListener3i; +alGetListenerf; +alGetListenerfv; +alGetListeneri; +alGetListeneriv; +alGetProcAddress; +alGetSource3f; +alGetSource3i; +alGetSourcef; +alGetSourcefv; +alGetSourcei; +alGetSourceiv; +alGetString; +alIsBuffer; +alIsEnabled; +alIsExtensionPresent; +alIsSource; +alListener3f; +alListener3i; +alListenerf; +alListenerfv; +alListeneri; +alListeneriv; +alSource3f; +alSource3i; +alSourcef; +alSourcefv; +alSourcei; +alSourceiv; +alSourcePause; +alSourcePausev; +alSourcePlay; +alSourcePlayv; +alSourceQueueBuffers; +alSourceRewind; +alSourceRewindv; +alSourceStop; +alSourceStopv; +alSourceUnqueueBuffers; +alSpeedOfSound; + +# Deprecated in AL 1.1, kept for compatibility. +alDopplerVelocity; + +# EFX, effectively standard at this point. +alAuxiliaryEffectSlotf; +alAuxiliaryEffectSlotfv; +alAuxiliaryEffectSloti; +alAuxiliaryEffectSlotiv; +alDeleteAuxiliaryEffectSlots; +alDeleteEffects; +alDeleteFilters; +alEffectf; +alEffectfv; +alEffecti; +alEffectiv; +alFilterf; +alFilterfv; +alFilteri; +alFilteriv; +alGenAuxiliaryEffectSlots; +alGenEffects; +alGenFilters; +alGetAuxiliaryEffectSlotf; +alGetAuxiliaryEffectSlotfv; +alGetAuxiliaryEffectSloti; +alGetAuxiliaryEffectSlotiv; +alGetEffectf; +alGetEffectfv; +alGetEffecti; +alGetEffectiv; +alGetFilterf; +alGetFilterfv; +alGetFilteri; +alGetFilteriv; +alIsAuxiliaryEffectSlot; +alIsEffect; +alIsFilter; + +# Non-standard +alsoft_get_version; + +# These extension functions shouldn't be exported here, but they were exported +# by mistake in previous releases, so need to stay for compatibility with apps +# that may have directly linked to them. Remove them if it can be done without +# breaking anything. +alAuxiliaryEffectSlotPlaySOFT; +alAuxiliaryEffectSlotPlayvSOFT; +alAuxiliaryEffectSlotStopSOFT; +alAuxiliaryEffectSlotStopvSOFT; +alBufferCallbackSOFT; +alBufferSamplesSOFT; +alBufferStorageSOFT; +alBufferSubDataSOFT; +alBufferSubSamplesSOFT; +alcDevicePauseSOFT; +alcDeviceResumeSOFT; +alcGetInteger64vSOFT; +alcGetStringiSOFT; +alcGetThreadContext; +alcIsRenderFormatSupportedSOFT; +alcLoopbackOpenDeviceSOFT; +alcRenderSamplesSOFT; +alcResetDeviceSOFT; +alcSetThreadContext; +alDeferUpdatesSOFT; +alEventCallbackSOFT; +alEventControlSOFT; +alFlushMappedBufferSOFT; +alGetBuffer3PtrSOFT; +alGetBufferPtrSOFT; +alGetBufferPtrvSOFT; +alGetBufferSamplesSOFT; +alGetInteger64SOFT; +alGetInteger64vSOFT; +alGetPointerSOFT; +alGetPointervSOFT; +alGetSource3dSOFT; +alGetSource3i64SOFT; +alGetSourcedSOFT; +alGetSourcedvSOFT; +alGetSourcei64SOFT; +alGetSourcei64vSOFT; +alGetStringiSOFT; +alIsBufferFormatSupportedSOFT; +alMapBufferSOFT; +alProcessUpdatesSOFT; +alSource3dSOFT; +alSource3i64SOFT; +alSourcedSOFT; +alSourcedvSOFT; +alSourcei64SOFT; +alSourcei64vSOFT; +alSourceQueueBufferLayersSOFT; +alUnmapBufferSOFT; + +local: *; +}; diff --git a/libs/openal-soft/presets/3D7.1.ambdec b/libs/openal-soft/presets/3D7.1.ambdec index 66e56501..ec3b787d 100644 --- a/libs/openal-soft/presets/3D7.1.ambdec +++ b/libs/openal-soft/presets/3D7.1.ambdec @@ -5,6 +5,11 @@ /description 3D7-noCenter_1h1v_pinv_even_energy_rV_max_rE_2_band +# In OpenAL Soft, 3D7.1 is a distinct configuration that uses the standard 5.1 +# channels (LF, RF, CE, LS, RS), plus two auxiliary channels (AUX0, AUX1) in +# place of the rear speakers. AUX0 corresponds to the LB speaker (upper back +# center), and AUX1 corresponds to the RB speaker (lower front center). + # Similar to the the ITU-5.1-nocenter configuration, the front-center is # declared here so that an appropriate distance may be set (for proper delaying # or attenuating of dialog and such which feed it directly). It otherwise does @@ -25,37 +30,37 @@ /opt/xover_ratio 0.000000 /speakers/{ -# id dist azim elev conn +# id dist azim elev conn #----------------------------------------------------------------------- -add_spkr FL 1.828800 51.000000 24.000000 -add_spkr FR 1.828800 -51.000000 24.000000 -add_spkr FC 1.828800 0.000000 0.000000 -add_spkr BL 1.828800 180.000000 55.000000 -add_spkr BR 1.828800 0.000000 -55.000000 -add_spkr SL 1.828800 129.000000 -24.000000 -add_spkr SR 1.828800 -129.000000 -24.000000 +add_spkr LF 1.828800 51.000000 24.000000 +add_spkr RF 1.828800 -51.000000 24.000000 +add_spkr CE 1.828800 0.000000 0.000000 +add_spkr AUX0 1.828800 180.000000 55.000000 +add_spkr AUX1 1.828800 0.000000 -55.000000 +add_spkr LS 1.828800 129.000000 -24.000000 +add_spkr RS 1.828800 -129.000000 -24.000000 /} /lfmatrix/{ order_gain 1.00000000e+00 1.00000000e+00 0.000000 0.000000 -add_row 1.66669447e-01 2.04127551e-01 1.17487922e-01 1.66927066e-01 -add_row 1.66669447e-01 -2.04127551e-01 1.17487922e-01 1.66927066e-01 -add_row 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 -add_row 1.66669447e-01 0.00000000e+00 2.36070520e-01 -1.66153012e-01 -add_row 1.66669447e-01 0.00000000e+00 -2.36070520e-01 1.66153012e-01 -add_row 1.66669447e-01 2.04127551e-01 -1.17487922e-01 -1.66927066e-01 -add_row 1.66669447e-01 -2.04127551e-01 -1.17487922e-01 -1.66927066e-01 +add_row 1.666666667e-01 2.033043281e-01 1.175581508e-01 1.678904388e-01 +add_row 1.666666667e-01 -2.033043281e-01 1.175581508e-01 1.678904388e-01 +add_row 0.000000000e+00 0.000000000e+00 0.000000000e+00 0.000000000e+00 +add_row 1.666666667e-01 0.000000000e+00 2.356640879e-01 -1.667265410e-01 +add_row 1.666666667e-01 0.000000000e+00 -2.356640879e-01 1.667265410e-01 +add_row 1.666666667e-01 2.033043281e-01 -1.175581508e-01 -1.678904388e-01 +add_row 1.666666667e-01 -2.033043281e-01 -1.175581508e-01 -1.678904388e-01 /} /hfmatrix/{ order_gain 1.73205081e+00 1.00000000e+00 0.000000 0.000000 -add_row 1.66669447e-01 2.04127551e-01 1.17487922e-01 1.66927066e-01 -add_row 1.66669447e-01 -2.04127551e-01 1.17487922e-01 1.66927066e-01 -add_row 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 -add_row 1.66669447e-01 0.00000000e+00 2.36070520e-01 -1.66153012e-01 -add_row 1.66669447e-01 0.00000000e+00 -2.36070520e-01 1.66153012e-01 -add_row 1.66669447e-01 2.04127551e-01 -1.17487922e-01 -1.66927066e-01 -add_row 1.66669447e-01 -2.04127551e-01 -1.17487922e-01 -1.66927066e-01 +add_row 1.666666667e-01 2.033043281e-01 1.175581508e-01 1.678904388e-01 +add_row 1.666666667e-01 -2.033043281e-01 1.175581508e-01 1.678904388e-01 +add_row 0.000000000e+00 0.000000000e+00 0.000000000e+00 0.000000000e+00 +add_row 1.666666667e-01 0.000000000e+00 2.356640879e-01 -1.667265410e-01 +add_row 1.666666667e-01 0.000000000e+00 -2.356640879e-01 1.667265410e-01 +add_row 1.666666667e-01 2.033043281e-01 -1.175581508e-01 -1.678904388e-01 +add_row 1.666666667e-01 -2.033043281e-01 -1.175581508e-01 -1.678904388e-01 /} /end diff --git a/libs/openal-soft/presets/hex-quad.ambdec b/libs/openal-soft/presets/hex-quad.ambdec new file mode 100644 index 00000000..ce6cf8bf --- /dev/null +++ b/libs/openal-soft/presets/hex-quad.ambdec @@ -0,0 +1,53 @@ +# AmbDec configuration +# Written by Ambisonic Decoder Toolbox, version 8.0 + +# input channel order: W Y Z X + +/description 11_1_1h1v_allrad_5200_rE_max_1_band + +/version 3 + +/dec/chan_mask f +/dec/freq_bands 1 +/dec/speakers 11 +/dec/coeff_scale n3d + +/opt/input_scale n3d +/opt/nfeff_comp output +/opt/delay_comp on +/opt/level_comp on +/opt/xover_freq 400.000000 +/opt/xover_ratio 0.000000 + +/speakers/{ +# id dist azim elev conn +#----------------------------------------------------------------------- +add_spkr LF 1.000000 30.000000 0.000000 +add_spkr RF 1.000000 -30.000000 0.000000 +add_spkr CE 1.000000 0.000000 0.000000 +add_spkr LS 1.000000 90.000000 0.000000 +add_spkr RS 1.000000 -90.000000 0.000000 +add_spkr LB 1.000000 150.000000 0.000000 +add_spkr RB 1.000000 -150.000000 0.000000 +add_spkr LFT 1.000000 45.000000 35.000000 +add_spkr RFT 1.000000 -45.000000 35.000000 +add_spkr LBT 1.000000 135.000000 35.000000 +add_spkr RBT 1.000000 -135.000000 35.000000 +/} + +/matrix/{ +order_gain 1.00000000e+00 1.00000000e+00 0.000000 0.000000 +add_row 1.27149251e-01 7.63047539e-02 -3.64373750e-02 1.59700680e-01 +add_row 1.07005418e-01 -7.67638760e-02 -4.92129762e-02 1.29012797e-01 +add_row 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 +add_row 1.26400196e-01 1.77494694e-01 -3.71203389e-02 0.00000000e+00 +add_row 1.26396516e-01 -1.77488059e-01 -3.71297878e-02 0.00000000e+00 +add_row 1.06996956e-01 7.67615256e-02 -4.92166307e-02 -1.29001640e-01 +add_row 1.27145671e-01 -7.63003471e-02 -3.64353304e-02 -1.59697510e-01 +add_row 8.80919747e-02 7.48940670e-02 9.08786244e-02 6.22527183e-02 +add_row 1.57880745e-01 -7.28755272e-02 1.82364187e-01 8.74240284e-02 +add_row 1.57892225e-01 7.28944768e-02 1.82363474e-01 -8.74301086e-02 +add_row 8.80892603e-02 -7.48948724e-02 9.08779842e-02 -6.22480443e-02 +/} + +/end diff --git a/libs/openal-soft/presets/presets.txt b/libs/openal-soft/presets/presets.txt index 541416e2..f75a53e9 100644 --- a/libs/openal-soft/presets/presets.txt +++ b/libs/openal-soft/presets/presets.txt @@ -36,7 +36,19 @@ is defined for the decoder, meaning that speaker will be silent for 3D sound output). A "proper" 7.1 decoder may be provided in the future, but due to the nature of the speaker configuration will have trade-offs. +hex-quad.ambdec +Specifies a flat-front hexagonal speaker setup, plus an elevated quad speaker +setup, for 7.1.4 Surround output. The front left and right speakers are placed +at +30 and -30 degrees, the side speakers are placed at +90 and -90 degrees, +and the back speakers are placed at +150 and -150 degrees. The elevated +speakers are placed at an elevation of +35 degrees, with the top front left and +right speakers placed at +45 and -45 degrees, and the top back left and right +speakers placed at +135 and -135 degrees. Similar to 7.1, the front-center +speaker is not used for 3D sound, but will be used as appropriate with +AL_SOFT_direct_channels or ALC_EXT_DEDICATED. + 3D7.1.ambdec -Specifies a 3D7.1 speaker setup for 7.1 Surround output. Although it's for 7.1 -output, the speakers for such a configuration need to be placed in different -positions for proper results. Please see docs/3D7.1.txt for more information. +Specifies a 3D7.1 speaker setup for 3D7.1 Surround output. Please see +docs/3D7.1.txt for information about speaker placement. Similar to 7.1, the +front-center speaker is not used for 3D sound, but will be used as appropriate +with AL_SOFT_direct_channels or ALC_EXT_DEDICATED. diff --git a/libs/openal-soft/router/al.cpp b/libs/openal-soft/router/al.cpp index aabb5f30..57c8d179 100644 --- a/libs/openal-soft/router/al.cpp +++ b/libs/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); @@ -130,3 +133,42 @@ DECL_THUNK3(void, alGetBufferi, ALuint, ALenum, ALint*) DECL_THUNK5(void, alGetBuffer3i, ALuint, ALenum, ALint*, ALint*, ALint*) DECL_THUNK3(void, alGetBufferiv, ALuint, ALenum, ALint*) DECL_THUNK5(void, alBufferData, ALuint, ALenum, const ALvoid*, ALsizei, ALsizei) + +/* EFX 1.0. Required here to be exported from libOpenAL32.dll.a/OpenAL32.lib + * with the router enabled. + */ +DECL_THUNK2(void, alGenFilters, ALsizei, ALuint*) +DECL_THUNK2(void, alDeleteFilters, ALsizei, const ALuint*) +DECL_THUNK1(ALboolean, alIsFilter, ALuint) +DECL_THUNK3(void, alFilterf, ALuint, ALenum, ALfloat) +DECL_THUNK3(void, alFilterfv, ALuint, ALenum, const ALfloat*) +DECL_THUNK3(void, alFilteri, ALuint, ALenum, ALint) +DECL_THUNK3(void, alFilteriv, ALuint, ALenum, const ALint*) +DECL_THUNK3(void, alGetFilterf, ALuint, ALenum, ALfloat*) +DECL_THUNK3(void, alGetFilterfv, ALuint, ALenum, ALfloat*) +DECL_THUNK3(void, alGetFilteri, ALuint, ALenum, ALint*) +DECL_THUNK3(void, alGetFilteriv, ALuint, ALenum, ALint*) + +DECL_THUNK2(void, alGenEffects, ALsizei, ALuint*) +DECL_THUNK2(void, alDeleteEffects, ALsizei, const ALuint*) +DECL_THUNK1(ALboolean, alIsEffect, ALuint) +DECL_THUNK3(void, alEffectf, ALuint, ALenum, ALfloat) +DECL_THUNK3(void, alEffectfv, ALuint, ALenum, const ALfloat*) +DECL_THUNK3(void, alEffecti, ALuint, ALenum, ALint) +DECL_THUNK3(void, alEffectiv, ALuint, ALenum, const ALint*) +DECL_THUNK3(void, alGetEffectf, ALuint, ALenum, ALfloat*) +DECL_THUNK3(void, alGetEffectfv, ALuint, ALenum, ALfloat*) +DECL_THUNK3(void, alGetEffecti, ALuint, ALenum, ALint*) +DECL_THUNK3(void, alGetEffectiv, ALuint, ALenum, ALint*) + +DECL_THUNK2(void, alGenAuxiliaryEffectSlots, ALsizei, ALuint*) +DECL_THUNK2(void, alDeleteAuxiliaryEffectSlots, ALsizei, const ALuint*) +DECL_THUNK1(ALboolean, alIsAuxiliaryEffectSlot, ALuint) +DECL_THUNK3(void, alAuxiliaryEffectSlotf, ALuint, ALenum, ALfloat) +DECL_THUNK3(void, alAuxiliaryEffectSlotfv, ALuint, ALenum, const ALfloat*) +DECL_THUNK3(void, alAuxiliaryEffectSloti, ALuint, ALenum, ALint) +DECL_THUNK3(void, alAuxiliaryEffectSlotiv, ALuint, ALenum, const ALint*) +DECL_THUNK3(void, alGetAuxiliaryEffectSlotf, ALuint, ALenum, ALfloat*) +DECL_THUNK3(void, alGetAuxiliaryEffectSlotfv, ALuint, ALenum, ALfloat*) +DECL_THUNK3(void, alGetAuxiliaryEffectSloti, ALuint, ALenum, ALint*) +DECL_THUNK3(void, alGetAuxiliaryEffectSlotiv, ALuint, ALenum, ALint*) diff --git a/libs/openal-soft/router/alc.cpp b/libs/openal-soft/router/alc.cpp index 210f683e..58ba4464 100644 --- a/libs/openal-soft/router/alc.cpp +++ b/libs/openal-soft/router/alc.cpp @@ -6,9 +6,10 @@ #include #include -#include #include #include +#include +#include #include "AL/alc.h" #include "alstring.h" @@ -118,6 +119,41 @@ static const std::array alcFunctions{{ DECL(alDopplerVelocity), DECL(alSpeedOfSound), DECL(alDistanceModel), + + /* EFX 1.0 */ + DECL(alGenFilters), + DECL(alDeleteFilters), + DECL(alIsFilter), + DECL(alFilterf), + DECL(alFilterfv), + DECL(alFilteri), + DECL(alFilteriv), + DECL(alGetFilterf), + DECL(alGetFilterfv), + DECL(alGetFilteri), + DECL(alGetFilteriv), + DECL(alGenEffects), + DECL(alDeleteEffects), + DECL(alIsEffect), + DECL(alEffectf), + DECL(alEffectfv), + DECL(alEffecti), + DECL(alEffectiv), + DECL(alGetEffectf), + DECL(alGetEffectfv), + DECL(alGetEffecti), + DECL(alGetEffectiv), + DECL(alGenAuxiliaryEffectSlots), + DECL(alDeleteAuxiliaryEffectSlots), + DECL(alIsAuxiliaryEffectSlot), + DECL(alAuxiliaryEffectSlotf), + DECL(alAuxiliaryEffectSlotfv), + DECL(alAuxiliaryEffectSloti), + DECL(alAuxiliaryEffectSlotiv), + DECL(alGetAuxiliaryEffectSlotf), + DECL(alGetAuxiliaryEffectSlotfv), + DECL(alGetAuxiliaryEffectSloti), + DECL(alGetAuxiliaryEffectSlotiv), }}; #undef DECL @@ -309,7 +345,57 @@ static ALint GetDriverIndexForName(const EnumeratedList *list, const ALCchar *na } -ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) +static void InitCtxFuncs(DriverIface &iface) +{ + ALCdevice *device{iface.alcGetContextsDevice(iface.alcGetCurrentContext())}; + +#define LOAD_PROC(x) do { \ + iface.x = reinterpret_cast(iface.alGetProcAddress(#x));\ + if(!iface.x) \ + ERR("Failed to find entry point for %s in %ls\n", #x, \ + iface.Name.c_str()); \ +} while(0) + if(iface.alcIsExtensionPresent(device, "ALC_EXT_EFX")) + { + LOAD_PROC(alGenFilters); + LOAD_PROC(alDeleteFilters); + LOAD_PROC(alIsFilter); + LOAD_PROC(alFilterf); + LOAD_PROC(alFilterfv); + LOAD_PROC(alFilteri); + LOAD_PROC(alFilteriv); + LOAD_PROC(alGetFilterf); + LOAD_PROC(alGetFilterfv); + LOAD_PROC(alGetFilteri); + LOAD_PROC(alGetFilteriv); + LOAD_PROC(alGenEffects); + LOAD_PROC(alDeleteEffects); + LOAD_PROC(alIsEffect); + LOAD_PROC(alEffectf); + LOAD_PROC(alEffectfv); + LOAD_PROC(alEffecti); + LOAD_PROC(alEffectiv); + LOAD_PROC(alGetEffectf); + LOAD_PROC(alGetEffectfv); + LOAD_PROC(alGetEffecti); + LOAD_PROC(alGetEffectiv); + LOAD_PROC(alGenAuxiliaryEffectSlots); + LOAD_PROC(alDeleteAuxiliaryEffectSlots); + LOAD_PROC(alIsAuxiliaryEffectSlot); + LOAD_PROC(alAuxiliaryEffectSlotf); + LOAD_PROC(alAuxiliaryEffectSlotfv); + LOAD_PROC(alAuxiliaryEffectSloti); + LOAD_PROC(alAuxiliaryEffectSlotiv); + LOAD_PROC(alGetAuxiliaryEffectSlotf); + LOAD_PROC(alGetAuxiliaryEffectSlotfv); + LOAD_PROC(alGetAuxiliaryEffectSloti); + LOAD_PROC(alGetAuxiliaryEffectSlotiv); + } +#undef LOAD_PROC +} + + +ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) noexcept { ALCdevice *device = nullptr; ALint idx = 0; @@ -326,14 +412,14 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) if(devicename) { { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; if(DevicesList.Names.empty()) - (void)alcGetString(nullptr, ALC_DEVICE_SPECIFIER); + std::ignore = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); idx = GetDriverIndexForName(&DevicesList, devicename); if(idx < 0) { if(AllDevicesList.Names.empty()) - (void)alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); + std::ignore = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); idx = GetDriverIndexForName(&AllDevicesList, devicename); } } @@ -345,17 +431,17 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) return nullptr; } TRACE("Found driver %d for name \"%s\"\n", idx, devicename); - device = DriverList[idx].alcOpenDevice(devicename); + device = DriverList[idx]->alcOpenDevice(devicename); } else { for(const auto &drv : DriverList) { - if(drv.ALCVer >= MAKE_ALC_VER(1, 1) || - drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) + if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) { TRACE("Using default device from driver %d\n", idx); - device = drv.alcOpenDevice(nullptr); + device = drv->alcOpenDevice(nullptr); break; } ++idx; @@ -366,7 +452,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) { if(DeviceIfaceMap.insert(device, idx) != ALC_NO_ERROR) { - DriverList[idx].alcCloseDevice(device); + DriverList[idx]->alcCloseDevice(device); device = nullptr; } } @@ -374,7 +460,7 @@ 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; @@ -383,14 +469,14 @@ ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) LastError.store(ALC_INVALID_DEVICE); return ALC_FALSE; } - if(!DriverList[idx].alcCloseDevice(device)) + if(!DriverList[idx]->alcCloseDevice(device)) return ALC_FALSE; DeviceIfaceMap.removeByKey(device); return ALC_TRUE; } -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; @@ -400,12 +486,12 @@ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCin LastError.store(ALC_INVALID_DEVICE); return nullptr; } - context = DriverList[idx].alcCreateContext(device, attrlist); + context = DriverList[idx]->alcCreateContext(device, attrlist); if(context) { if(ContextIfaceMap.insert(context, idx) != ALC_NO_ERROR) { - DriverList[idx].alcDestroyContext(context); + DriverList[idx]->alcDestroyContext(context); context = nullptr; } } @@ -413,11 +499,11 @@ 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 _{ContextSwitchLock}; + std::lock_guard ctxlock{ContextSwitchLock}; if(context) { idx = ContextIfaceMap.lookupByKey(context); @@ -426,8 +512,11 @@ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) 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); } /* Unset the context from the old driver if it's different from the new @@ -443,10 +532,10 @@ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) else { DriverIface *oldiface = GetThreadDriver(); - if(oldiface && oldiface != &DriverList[idx]) + if(oldiface && oldiface != DriverList[idx].get()) oldiface->alcSetThreadContext(nullptr); - oldiface = CurrentCtxDriver.exchange(&DriverList[idx]); - if(oldiface && oldiface != &DriverList[idx]) + oldiface = CurrentCtxDriver.exchange(DriverList[idx].get()); + if(oldiface && oldiface != DriverList[idx].get()) oldiface->alcMakeContextCurrent(nullptr); } SetThreadDriver(nullptr); @@ -454,29 +543,29 @@ 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); + 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); + 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; @@ -486,42 +575,42 @@ ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) return; } - DriverList[idx].alcDestroyContext(context); + DriverList[idx]->alcDestroyContext(context); ContextIfaceMap.removeByKey(context); } -ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) +ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext() noexcept { 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); + 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); + return DriverList[idx]->alcGetError(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; @@ -534,7 +623,7 @@ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const A LastError.store(ALC_INVALID_DEVICE); return ALC_FALSE; } - return DriverList[idx].alcIsExtensionPresent(device, extname); + return DriverList[idx]->alcIsExtensionPresent(device, extname); } len = strlen(extname); @@ -553,7 +642,7 @@ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const A 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) { @@ -563,7 +652,7 @@ ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *f LastError.store(ALC_INVALID_DEVICE); return nullptr; } - return DriverList[idx].alcGetProcAddress(device, funcname); + return DriverList[idx]->alcGetProcAddress(device, funcname); } auto iter = std::find_if(alcFunctions.cbegin(), alcFunctions.cend(), @@ -573,7 +662,7 @@ 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) { @@ -583,7 +672,7 @@ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *e LastError.store(ALC_INVALID_DEVICE); return 0; } - return DriverList[idx].alcGetEnumValue(device, enumname); + return DriverList[idx]->alcGetEnumValue(device, enumname); } auto iter = std::find_if(alcEnumerations.cbegin(), alcEnumerations.cend(), @@ -593,7 +682,7 @@ 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) { @@ -603,7 +692,7 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para LastError.store(ALC_INVALID_DEVICE); return nullptr; } - return DriverList[idx].alcGetString(device, param); + return DriverList[idx]->alcGetString(device, param); } switch(param) @@ -625,16 +714,16 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para case ALC_DEVICE_SPECIFIER: { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; DevicesList.clear(); ALint 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")) + if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) AppendDeviceList(&DevicesList, - drv.alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); + drv->alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); ++idx; } /* Ensure the list is double-null termianted. */ @@ -646,7 +735,7 @@ 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}; for(const auto &drv : DriverList) @@ -654,13 +743,13 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para /* If the driver doesn't support ALC_ENUMERATE_ALL_EXT, substitute * standard enumeration. */ - if(drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) + if(drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) AppendDeviceList(&AllDevicesList, - drv.alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER), idx); - else if(drv.ALCVer >= MAKE_ALC_VER(1, 1) - || drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) + 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, - drv.alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); + drv->alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); ++idx; } /* Ensure the list is double-null termianted. */ @@ -672,15 +761,15 @@ 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}; for(const auto &drv : DriverList) { - if(drv.ALCVer >= MAKE_ALC_VER(1, 1) - || drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) + if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) AppendDeviceList(&CaptureDevicesList, - drv.alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER), idx); + drv->alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER), idx); ++idx; } /* Ensure the list is double-null termianted. */ @@ -692,39 +781,33 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para case ALC_DEFAULT_DEVICE_SPECIFIER: { - auto iter = std::find_if(DriverList.cbegin(), DriverList.cend(), - [](const DriverIface &drv) -> bool - { - return drv.ALCVer >= MAKE_ALC_VER(1, 1) - || drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); - } - ); - if(iter != DriverList.cend()) - return iter->alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); + for(const auto &drv : DriverList) + { + if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) + return drv->alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); + } return ""; } case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: { - auto iter = std::find_if(DriverList.cbegin(), DriverList.cend(), - [](const DriverIface &drv) -> bool - { return drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") != ALC_FALSE; }); - if(iter != DriverList.cend()) - return iter->alcGetString(nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); + for(const auto &drv : DriverList) + { + if(drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") != ALC_FALSE) + return drv->alcGetString(nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); + } return ""; } case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: { - auto iter = std::find_if(DriverList.cbegin(), DriverList.cend(), - [](const DriverIface &drv) -> bool - { - return drv.ALCVer >= MAKE_ALC_VER(1, 1) - || drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE"); - } - ); - if(iter != DriverList.cend()) - return iter->alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); + for(const auto &drv : DriverList) + { + if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) + return drv->alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); + } return ""; } @@ -735,7 +818,7 @@ 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) { @@ -745,7 +828,7 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsi LastError.store(ALC_INVALID_DEVICE); return; } - return DriverList[idx].alcGetIntegerv(device, param, size, values); + return DriverList[idx]->alcGetIntegerv(device, param, size, values); } if(size <= 0 || values == nullptr) @@ -790,7 +873,7 @@ 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; @@ -800,9 +883,9 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, if(devicename) { { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; if(CaptureDevicesList.Names.empty()) - (void)alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); + std::ignore = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); idx = GetDriverIndexForName(&CaptureDevicesList, devicename); } @@ -813,17 +896,17 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, return nullptr; } TRACE("Found driver %d for name \"%s\"\n", idx, devicename); - device = DriverList[idx].alcCaptureOpenDevice(devicename, frequency, format, buffersize); + device = DriverList[idx]->alcCaptureOpenDevice(devicename, frequency, format, buffersize); } else { for(const auto &drv : DriverList) { - if(drv.ALCVer >= MAKE_ALC_VER(1, 1) - || drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) + if(drv->ALCVer >= MAKE_ALC_VER(1, 1) + || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) { TRACE("Using default capture device from driver %d\n", idx); - device = drv.alcCaptureOpenDevice(nullptr, frequency, format, buffersize); + device = drv->alcCaptureOpenDevice(nullptr, frequency, format, buffersize); break; } ++idx; @@ -834,7 +917,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, { if(DeviceIfaceMap.insert(device, idx) != ALC_NO_ERROR) { - DriverList[idx].alcCaptureCloseDevice(device); + DriverList[idx]->alcCaptureCloseDevice(device); device = nullptr; } } @@ -842,7 +925,7 @@ 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; @@ -851,47 +934,47 @@ ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) LastError.store(ALC_INVALID_DEVICE); return ALC_FALSE; } - if(!DriverList[idx].alcCaptureCloseDevice(device)) + if(!DriverList[idx]->alcCaptureCloseDevice(device)) return ALC_FALSE; DeviceIfaceMap.removeByKey(device); return ALC_TRUE; } -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); + 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); + 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); + 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; @@ -908,23 +991,26 @@ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) idx = ContextIfaceMap.lookupByKey(context); if(idx >= 0) { - 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); + DriverIface *oldiface = GetThreadDriver(); - if(oldiface != &DriverList[idx]) + if(oldiface != DriverList[idx].get()) { - SetThreadDriver(&DriverList[idx]); + 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(); diff --git a/libs/openal-soft/router/router.cpp b/libs/openal-soft/router/router.cpp index 67c34812..1fdf45ac 100644 --- a/libs/openal-soft/router/router.cpp +++ b/libs/openal-soft/router/router.cpp @@ -17,19 +17,10 @@ #include "version.h" -std::vector DriverList; - -thread_local DriverIface *ThreadCtxDriver; - 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); +static void LoadDriverList(); BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*) @@ -84,13 +75,13 @@ static void AddModule(HMODULE module, const WCHAR *name) { for(auto &drv : DriverList) { - if(drv.Module == module) + if(drv->Module == module) { TRACE("Skipping already-loaded module %p\n", decltype(std::declval()){module}); FreeLibrary(module); return; } - if(drv.Name == name) + if(drv->Name == name) { TRACE("Skipping similarly-named module %ls\n", name); FreeLibrary(module); @@ -98,8 +89,8 @@ static void AddModule(HMODULE module, const WCHAR *name) } } - DriverList.emplace_back(name, module); - DriverIface &newdrv = DriverList.back(); + DriverList.emplace_back(std::make_unique(name, module)); + DriverIface &newdrv = *DriverList.back(); /* Load required functions. */ int err = 0; @@ -189,18 +180,6 @@ static void AddModule(HMODULE module, const WCHAR *name) LOAD_PROC(alGenBuffers); LOAD_PROC(alDeleteBuffers); LOAD_PROC(alIsBuffer); - 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); LOAD_PROC(alBufferData); LOAD_PROC(alDopplerFactor); LOAD_PROC(alDopplerVelocity); @@ -219,6 +198,28 @@ static void AddModule(HMODULE module, const WCHAR *name) newdrv.ALCVer = MAKE_ALC_VER(1, 0); } +#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( \ @@ -312,19 +313,18 @@ static int GetLoadedModuleDirectory(const WCHAR *name, WCHAR *moddir, DWORD leng return 1; } -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(GetLoadedModuleDirectory(L"OpenAL32.dll", dll_path, MAX_PATH)) TRACE("Got DLL path %ls\n", dll_path); GetCurrentDirectoryW(MAX_PATH, cwd_path); - len = lstrlenW(cwd_path); + auto len = wcslen(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); @@ -333,7 +333,7 @@ void LoadDriverList(void) TRACE("Got proc path %ls\n", proc_path); GetSystemDirectoryW(sys_path, MAX_PATH); - len = lstrlenW(sys_path); + len = wcslen(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); @@ -361,7 +361,7 @@ void LoadDriverList(void) PtrIntMap::~PtrIntMap() { std::lock_guard maplock{mLock}; - al_free(mKeys); + free(mKeys); mKeys = nullptr; mValues = nullptr; mSize = 0; @@ -382,8 +382,7 @@ ALenum PtrIntMap::insert(void *key, int value) ALsizei newcap{mCapacity ? (mCapacity<<1) : 4}; if(newcap > mCapacity) newkeys = static_cast( - al_calloc(16, (sizeof(mKeys[0])+sizeof(mValues[0]))*newcap) - ); + calloc(newcap, sizeof(mKeys[0])+sizeof(mValues[0]))); if(!newkeys) return AL_OUT_OF_MEMORY; auto newvalues = reinterpret_cast(&newkeys[newcap]); @@ -393,7 +392,7 @@ ALenum PtrIntMap::insert(void *key, int value) std::copy_n(mKeys, mSize, newkeys); std::copy_n(mValues, mSize, newvalues); } - al_free(mKeys); + free(mKeys); mKeys = newkeys; mValues = newvalues; mCapacity = newcap; diff --git a/libs/openal-soft/router/router.h b/libs/openal-soft/router/router.h index f49a96ef..f5c7f455 100644 --- a/libs/openal-soft/router/router.h +++ b/libs/openal-soft/router/router.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -24,6 +25,7 @@ struct DriverIface { std::wstring Name; HMODULE Module{nullptr}; int ALCVer{0}; + std::once_flag InitOnceCtx{}; LPALCCREATECONTEXT alcCreateContext{nullptr}; LPALCMAKECONTEXTCURRENT alcMakeContextCurrent{nullptr}; @@ -123,6 +125,41 @@ struct DriverIface { LPALSPEEDOFSOUND alSpeedOfSound{nullptr}; LPALDISTANCEMODEL alDistanceModel{nullptr}; + /* Functions to load after first context creation. */ + LPALGENFILTERS alGenFilters{nullptr}; + LPALDELETEFILTERS alDeleteFilters{nullptr}; + LPALISFILTER alIsFilter{nullptr}; + LPALFILTERF alFilterf{nullptr}; + LPALFILTERFV alFilterfv{nullptr}; + LPALFILTERI alFilteri{nullptr}; + LPALFILTERIV alFilteriv{nullptr}; + LPALGETFILTERF alGetFilterf{nullptr}; + LPALGETFILTERFV alGetFilterfv{nullptr}; + LPALGETFILTERI alGetFilteri{nullptr}; + LPALGETFILTERIV alGetFilteriv{nullptr}; + LPALGENEFFECTS alGenEffects{nullptr}; + LPALDELETEEFFECTS alDeleteEffects{nullptr}; + LPALISEFFECT alIsEffect{nullptr}; + LPALEFFECTF alEffectf{nullptr}; + LPALEFFECTFV alEffectfv{nullptr}; + LPALEFFECTI alEffecti{nullptr}; + LPALEFFECTIV alEffectiv{nullptr}; + LPALGETEFFECTF alGetEffectf{nullptr}; + LPALGETEFFECTFV alGetEffectfv{nullptr}; + LPALGETEFFECTI alGetEffecti{nullptr}; + LPALGETEFFECTIV alGetEffectiv{nullptr}; + LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots{nullptr}; + LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots{nullptr}; + LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot{nullptr}; + LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf{nullptr}; + LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv{nullptr}; + LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti{nullptr}; + LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv{nullptr}; + LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf{nullptr}; + LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv{nullptr}; + LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti{nullptr}; + LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv{nullptr}; + template DriverIface(T&& name, HMODULE mod) : Name(std::forward(name)), Module(mod) @@ -134,22 +171,15 @@ struct DriverIface { Module = nullptr; } }; +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 { diff --git a/libs/openal-soft/tests/CMakeLists.txt b/libs/openal-soft/tests/CMakeLists.txt new file mode 100644 index 00000000..492587e8 --- /dev/null +++ b/libs/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/libs/openal-soft/tests/example.t.cpp b/libs/openal-soft/tests/example.t.cpp new file mode 100644 index 00000000..30341e8b --- /dev/null +++ b/libs/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/libs/openal-soft/utils/CIAIR.def b/libs/openal-soft/utils/CIAIR.def index 5fabdb3f..79910417 100644 --- a/libs/openal-soft/utils/CIAIR.def +++ b/libs/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/libs/openal-soft/utils/alsoft-config/CMakeLists.txt b/libs/openal-soft/utils/alsoft-config/CMakeLists.txt index 65a70638..d9e6ca9a 100644 --- a/libs/openal-soft/utils/alsoft-config/CMakeLists.txt +++ b/libs/openal-soft/utils/alsoft-config/CMakeLists.txt @@ -12,10 +12,11 @@ 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}") - set_target_properties(alsoft-config PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR}) + set_target_properties(alsoft-config PROPERTIES ${DEFAULT_TARGET_PROPS} + RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR}) if(TARGET build_version) add_dependencies(alsoft-config build_version) endif() diff --git a/libs/openal-soft/utils/alsoft-config/mainwindow.cpp b/libs/openal-soft/utils/alsoft-config/mainwindow.cpp index baecf525..ead96e08 100644 --- a/libs/openal-soft/utils/alsoft-config/mainwindow.cpp +++ b/libs/openal-soft/utils/alsoft-config/mainwindow.cpp @@ -3,8 +3,9 @@ #include "mainwindow.h" -#include +#include #include +#include #include #include @@ -19,147 +20,148 @@ #include #endif +#include "almalloc.h" +#include "alspan.h" + namespace { -static 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", "SoundIO" }, #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 - { "", "" } }; -static 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" }, + /* 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" }, - { "Default (Linear)", "" }, - { "Cubic Spline", "cubic" }, - { "11th order Sinc (fast)", "fast_bsinc12" }, - { "11th order Sinc", "bsinc12" }, - { "23rd order Sinc (fast)", "fast_bsinc24" }, - { "23rd order Sinc", "bsinc24" }, - - { "", "" } -}, stereoModeList[] = { - { "Autodetect", "" }, - { "Speakers", "speakers" }, - { "Headphones", "headphones" }, - - { "", "" } -}, stereoEncList[] = { - { "Default", "" }, - { "Pan Pot", "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", "cubic" }, + NameValuePair{ "Default (Cubic Spline)", "" }, + 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" }, }; -static QString getDefaultConfigName() +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"); @@ -172,19 +174,22 @@ static QString getDefaultConfigName() return fname; } -static QString getBaseDataPath() +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"); @@ -195,7 +200,7 @@ static QString getBaseDataPath() return base; } -static QStringList getAllDataPaths(const QString &append) +QStringList getAllDataPaths(const QString &append) { QStringList list; list.append(getBaseDataPath()); @@ -225,24 +230,22 @@ static QStringList getAllDataPaths(const QString &append) return list; } -template -static 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 -static 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{}; } @@ -269,45 +272,29 @@ QString getCheckValue(const QCheckBox *checkbox) } -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->hrtfStateComboBox->adjustSize(); + 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); @@ -349,22 +336,28 @@ MainWindow::MainWindow(QWidget *parent) : #endif - mPeriodSizeValidator = new QIntValidator{64, 8192, this}; - ui->periodSizeEdit->setValidator(mPeriodSizeValidator); - mPeriodCountValidator = new QIntValidator{2, 16, this}; - ui->periodCountEdit->setValidator(mPeriodCountValidator); +#ifndef ALSOFT_EAX + ui->enableEaxCheck->setChecked(Qt::Unchecked); + ui->enableEaxCheck->setEnabled(false); + ui->enableEaxCheck->setVisible(false); +#endif - 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); + mPeriodSizeValidator = std::make_unique(64, 8192, this); + ui->periodSizeEdit->setValidator(mPeriodSizeValidator.get()); + mPeriodCountValidator = std::make_unique(2, 16, this); + ui->periodCountEdit->setValidator(mPeriodCountValidator.get()); - mJackBufferValidator = new QIntValidator{0, 8192, this}; - ui->jackBufferSizeLine->setValidator(mJackBufferValidator); + 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 = 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); @@ -397,7 +390,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->decoderDistCompCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->decoderNFEffectsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); auto qdsb_vcd = static_cast(&QDoubleSpinBox::valueChanged); - connect(ui->decoderNFRefDelaySpinBox, qdsb_vcd, this, &MainWindow::enableApplyButton); + connect(ui->decoderSpeakerDistSpinBox, qdsb_vcd, this, &MainWindow::enableApplyButton); connect(ui->decoderQuadLineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->decoderQuadButton, &QPushButton::clicked, this, &MainWindow::selectQuadDecoderFile); connect(ui->decoder51LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); @@ -406,9 +399,10 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->decoder61Button, &QPushButton::clicked, this, &MainWindow::select61DecoderFile); connect(ui->decoder71LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->decoder71Button, &QPushButton::clicked, this, &MainWindow::select71DecoderFile); + connect(ui->decoder3D71LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); + connect(ui->decoder3D71Button, &QPushButton::clicked, this, &MainWindow::select3D71DecoderFile); connect(ui->preferredHrtfComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); - connect(ui->hrtfStateComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->hrtfmodeSlider, &QSlider::valueChanged, this, &MainWindow::updateHrtfModeLabel); connect(ui->hrtfAddButton, &QPushButton::clicked, this, &MainWindow::addHrtfFile); @@ -448,6 +442,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->enableDedicatedCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enablePitchShifterCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableVocalMorpherCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->enableEaxCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->pulseAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->pulseAllowMovesCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); @@ -455,6 +450,9 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->pulseAdjLatencyCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->pwireAssumeAudioCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + connect(ui->pwireRtMixCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); + + connect(ui->wasapiResamplerCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->jackAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->jackConnectPortsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); @@ -484,10 +482,10 @@ 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); + std::data(backendList[i].full_string), Qt::MatchFixedString); foreach(QListWidgetItem *item, items) item->setHidden(false); } @@ -495,17 +493,7 @@ MainWindow::MainWindow(QWidget *parent) : 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) { @@ -572,7 +560,7 @@ QStringList MainWindow::collectHrtfs() break; } ++i; - } while(1); + } while(true); } } } @@ -607,7 +595,7 @@ QStringList MainWindow::collectHrtfs() break; } ++i; - } while(1); + } while(true); } } } @@ -675,7 +663,7 @@ void MainWindow::loadConfig(const QString &fname) QString resampler = settings.value("resampler").toString().trimmed(); ui->resamplerSlider->setValue(2); - ui->resamplerLabel->setText(resamplerList[2].name); + ui->resamplerLabel->setText(std::data(resamplerList[2].name)); /* The "sinc4" and "sinc8" resamplers are no longer supported. Use "cubic" * as a fallback. */ @@ -686,10 +674,10 @@ void MainWindow::loadConfig(const QString &fname) resampler = "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; } } @@ -752,13 +740,14 @@ 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 refdelay{settings.value("decoder/nfc-ref-delay", 0.0).toDouble()}; - ui->decoderNFRefDelaySpinBox->setValue(refdelay); + double speakerdist{settings.value("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()); QStringList disabledCpuExts{settings.value("disable-cpu-exts").toStringList()}; if(disabledCpuExts.size() == 1) @@ -773,16 +762,16 @@ void MainWindow::loadConfig(const QString &fname) QString hrtfmode{settings.value("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++) + 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; } } @@ -804,14 +793,6 @@ void MainWindow::loadConfig(const QString &fname) ui->hrtfFileList->addItems(hrtf_paths); updateHrtfRemoveButton(); - QString hrtfstate{settings.value("hrtf").toString().toLower()}; - if(hrtfstate == "true") - ui->hrtfStateComboBox->setCurrentIndex(1); - else if(hrtfstate == "false") - ui->hrtfStateComboBox->setCurrentIndex(2); - else - ui->hrtfStateComboBox->setCurrentIndex(0); - ui->preferredHrtfComboBox->clear(); ui->preferredHrtfComboBox->addItem("- Any -"); if(ui->defaultHrtfPathsCheckBox->isChecked()) @@ -840,7 +821,7 @@ void MainWindow::loadConfig(const QString &fname) ui->enabledBackendList->clear(); ui->disabledBackendList->clear(); QStringList drivers{settings.value("drivers").toStringList()}; - if(drivers.size() == 0) + if(drivers.empty()) ui->backendCheckBox->setChecked(true); else { @@ -865,22 +846,24 @@ void MainWindow::loadConfig(const QString &fname) 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; } } @@ -922,14 +905,18 @@ void MainWindow::loadConfig(const QString &fname) ui->enableDedicatedCheck->setChecked(!excludefx.contains("dedicated", Qt::CaseInsensitive)); ui->enablePitchShifterCheck->setChecked(!excludefx.contains("pshifter", Qt::CaseInsensitive)); ui->enableVocalMorpherCheck->setChecked(!excludefx.contains("vmorpher", Qt::CaseInsensitive)); + if(ui->enableEaxCheck->isEnabled()) + ui->enableEaxCheck->setChecked(getCheckState(settings.value("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->pwireAssumeAudioCheckBox->setCheckState(settings.value("pipewire/assume-audio").toBool() - ? Qt::Checked : Qt::Unchecked); + ui->pwireAssumeAudioCheckBox->setCheckState(getCheckState(settings.value("pipewire/assume-audio"))); + ui->pwireRtMixCheckBox->setCheckState(getCheckState(settings.value("pipewire/rt-mix"))); + + ui->wasapiResamplerCheckBox->setCheckState(getCheckState(settings.value("wasapi/allow-resampler"))); ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value("jack/spawn-server"))); ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value("jack/connect-ports"))); @@ -1004,7 +991,7 @@ void MainWindow::saveConfig(const QString &fname) const settings.setValue("sources", ui->srcCountLineEdit->text()); settings.setValue("slots", ui->effectSlotLineEdit->text()); - settings.setValue("resampler", resamplerList[ui->resamplerSlider->value()].value); + settings.setValue("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())); @@ -1016,15 +1003,16 @@ void MainWindow::saveConfig(const QString &fname) const settings.setValue("decoder/hq-mode", getCheckValue(ui->decoderHQModeCheckBox)); settings.setValue("decoder/distance-comp", getCheckValue(ui->decoderDistCompCheckBox)); settings.setValue("decoder/nfc", getCheckValue(ui->decoderNFEffectsCheckBox)); - double refdelay = ui->decoderNFRefDelaySpinBox->value(); - settings.setValue("decoder/nfc-ref-delay", - (refdelay > 0.0) ? QString::number(refdelay) : QString{} + double speakerdist{ui->decoderSpeakerDistSpinBox->value()}; + settings.setValue("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()); QStringList strlist; if(!ui->enableSSECheckBox->isChecked()) @@ -1039,14 +1027,7 @@ void MainWindow::saveConfig(const QString &fname) const strlist.append("neon"); settings.setValue("disable-cpu-exts", strlist.join(QChar(','))); - settings.setValue("hrtf-mode", hrtfModeList[ui->hrtfmodeSlider->value()].value); - - if(ui->hrtfStateComboBox->currentIndex() == 1) - settings.setValue("hrtf", "true"); - else if(ui->hrtfStateComboBox->currentIndex() == 2) - settings.setValue("hrtf", "false"); - else - settings.setValue("hrtf", QString{}); + settings.setValue("hrtf-mode", std::data(hrtfModeList[ui->hrtfmodeSlider->value()].value)); if(ui->preferredHrtfComboBox->currentIndex() == 0) settings.setValue("default-hrtf", QString{}); @@ -1068,11 +1049,11 @@ void MainWindow::saveConfig(const QString &fname) const 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; } } @@ -1080,16 +1061,16 @@ 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.size() == 0 && !ui->backendCheckBox->isChecked()) + if(strlist.empty() && !ui->backendCheckBox->isChecked()) strlist.append("-all"); else if(ui->backendCheckBox->isChecked()) strlist.append(QString{}); @@ -1134,15 +1115,20 @@ void MainWindow::saveConfig(const QString &fname) const if(!ui->enableVocalMorpherCheck->isChecked()) strlist.append("vmorpher"); settings.setValue("excludefx", strlist.join(QChar{','})); + settings.setValue("eax/enable", + (!ui->enableEaxCheck->isEnabled() || ui->enableEaxCheck->isChecked()) + ? QString{/*"true"*/} : QString{"false"}); + + settings.setValue("pipewire/assume-audio", getCheckValue(ui->pwireAssumeAudioCheckBox)); + settings.setValue("pipewire/rt-mix", getCheckValue(ui->pwireRtMixCheckBox)); + + settings.setValue("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("pipewire/assume-audio", ui->pwireAssumeAudioCheckBox->isChecked() - ? QString{"true"} : QString{/*"false"*/}); - settings.setValue("jack/spawn-server", getCheckValue(ui->jackAutospawnCheckBox)); settings.setValue("jack/connect-ports", getCheckValue(ui->jackConnectPortsCheckBox)); settings.setValue("jack/rt-mix", getCheckValue(ui->jackRtMixCheckBox)); @@ -1187,7 +1173,7 @@ void MainWindow::enableApplyButton() void MainWindow::updateResamplerLabel(int num) { - ui->resamplerLabel->setText(resamplerList[num].name); + ui->resamplerLabel->setText(std::data(resamplerList[num].name)); enableApplyButton(); } @@ -1240,6 +1226,8 @@ void MainWindow::select61DecoderFile() { selectDecoderFile(ui->decoder61LineEdit, "Select 6.1 Surround Decoder");} void MainWindow::select71DecoderFile() { selectDecoderFile(ui->decoder71LineEdit, "Select 7.1 Surround Decoder");} +void MainWindow::select3D71DecoderFile() +{ selectDecoderFile(ui->decoder3D71LineEdit, "Select 3D7.1 Surround Decoder");} void MainWindow::selectDecoderFile(QLineEdit *line, const char *caption) { QString dir{line->text()}; @@ -1285,7 +1273,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,18 +1290,17 @@ 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(); } } void MainWindow::updateHrtfRemoveButton() { - ui->hrtfRemoveButton->setEnabled(ui->hrtfFileList->selectedItems().size() != 0); + ui->hrtfRemoveButton->setEnabled(!ui->hrtfFileList->selectedItems().empty()); } void MainWindow::showEnabledBackendMenu(QPoint pt) @@ -1324,25 +1311,24 @@ void MainWindow::showEnabledBackendMenu(QPoint pt) QMenu ctxmenu; QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")}; - if(ui->enabledBackendList->selectedItems().size() == 0) + 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).size() != 0 || - ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0) + if(!ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).empty() || + !ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).empty()) action->setEnabled(false); } 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) @@ -1362,25 +1348,24 @@ void MainWindow::showDisabledBackendMenu(QPoint pt) QMenu ctxmenu; QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")}; - if(ui->disabledBackendList->selectedItems().size() == 0) + 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).size() != 0 || - ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0) + if(!ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).empty() || + !ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).empty()) action->setEnabled(false); } 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/libs/openal-soft/utils/alsoft-config/mainwindow.h b/libs/openal-soft/utils/alsoft-config/mainwindow.h index ca53582b..2d4cc3fd 100644 --- a/libs/openal-soft/utils/alsoft-config/mainwindow.h +++ b/libs/openal-soft/utils/alsoft-config/mainwindow.h @@ -1,6 +1,8 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include + #include #include @@ -8,14 +10,9 @@ namespace Ui { class MainWindow; } -class MainWindow : public QMainWindow -{ +class MainWindow : public QMainWindow { Q_OBJECT -public: - explicit MainWindow(QWidget *parent = 0); - ~MainWindow(); - private slots: void cancelCloseAction(); @@ -39,6 +36,7 @@ private slots: void select51DecoderFile(); void select61DecoderFile(); void select71DecoderFile(); + void select3D71DecoderFile(); void updateJackBufferSizeEdit(int size); void updateJackBufferSizeSlider(); @@ -59,20 +57,24 @@ 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); diff --git a/libs/openal-soft/utils/alsoft-config/mainwindow.ui b/libs/openal-soft/utils/alsoft-config/mainwindow.ui index ba8f83fb..b959cf74 100644 --- a/libs/openal-soft/utils/alsoft-config/mainwindow.ui +++ b/libs/openal-soft/utils/alsoft-config/mainwindow.ui @@ -116,9 +116,9 @@ float and converted to the output sample type as needed. - The output channel configuration. Note that not all backends -can properly detect the channel configuration and may default -to stereo output. + The default output channel configuration. Note that not all +backends can properly detect the channel configuration and +may default to stereo output. QComboBox::AdjustToContents @@ -436,7 +436,7 @@ frames needed for each mixing update. - Pan Pot uses standard amplitude panning (aka + Basic uses standard amplitude panning (aka pair-wise, stereo pair, etc). UHJ creates a stereo-compatible two-channel @@ -444,8 +444,8 @@ UHJ mix, which encodes some surround sound information into stereo output that can be decoded with a surround sound receiver. -Binaural applies HRTF filters to create a sense -of 3D space with headphones. +Binaural applies HRTF filters to create an +illusion of 3D placement with headphones. @@ -625,9 +625,7 @@ quantization with low-level whitenoise. Enables high-quality ambisonic rendering. This mode is capable of frequency-dependent processing, creating a better reproduction of 3D sound rendering over -surround sound speakers. Enabling this also requires -specifying decoder configuration files for the -appropriate speaker configuration you intend to use. +surround sound speakers. Qt::RightToLeft @@ -670,9 +668,9 @@ configuration file. -10 - 160 + 140 551 - 161 + 231 @@ -684,10 +682,10 @@ configuration file. - 120 + 130 30 - 311 - 21 + 301 + 25 @@ -696,8 +694,8 @@ configuration file. 20 30 - 91 - 21 + 101 + 25 @@ -713,7 +711,7 @@ configuration file. 440 30 91 - 21 + 25 @@ -723,10 +721,10 @@ configuration file. - 120 - 60 - 311 - 21 + 130 + 70 + 301 + 25 @@ -734,9 +732,9 @@ configuration file. 440 - 60 + 70 91 - 21 + 25 @@ -747,9 +745,9 @@ configuration file. 20 - 60 - 91 - 21 + 70 + 101 + 25 @@ -763,9 +761,9 @@ configuration file. 20 - 90 - 91 - 21 + 110 + 101 + 25 @@ -778,10 +776,10 @@ configuration file. - 120 - 90 - 311 - 21 + 130 + 110 + 301 + 25 @@ -789,9 +787,9 @@ configuration file. 440 - 90 + 110 91 - 21 + 25 @@ -802,9 +800,9 @@ configuration file. 440 - 120 + 150 91 - 21 + 25 @@ -814,10 +812,10 @@ configuration file. - 120 - 120 - 311 - 21 + 130 + 150 + 301 + 25 @@ -825,9 +823,9 @@ configuration file. 20 - 120 - 91 - 21 + 150 + 101 + 25 @@ -837,6 +835,45 @@ configuration file. Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + 20 + 190 + 101 + 25 + + + + 3D7.1 Surround: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 130 + 190 + 301 + 25 + + + + + + + 440 + 190 + 91 + 25 + + + + Browse... + + @@ -869,60 +906,63 @@ are set in the decoder configuration file. - -10 + 30 110 - 281 + 471 21 - The reference delay value for ambisonic output. When -Channels is set to one of the Ambisonic formats, this -option enables NFC-HOA output with the specified -Reference Delay parameter. The specified value can then -be shared with an appropriate NFC-HOA decoder to -reproduce correct near-field effects. Keep in mind that -despite being designed for higher-order ambisonics, this -applies to first-order output all the same. When left unset, -normal output is created with no near-field simulation. + Specifies the speaker distance in meters, used by the near-field control +filters with surround sound output. For ambisonic output modes, this value +is the basis for the NFC-HOA Reference Delay parameter (calculated as +delay_seconds = speaker_dist/343.3). This value is not used when a decoder +configuration is set for the output mode (since they specify the per-speaker +distances, overriding this setting), or when the NFC filters are off. - 20 + 45 0 - 171 + 111 21 - Near-Field Reference Delay: + Speaker Distance: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + - 200 + 165 0 - 81 + 101 21 - sec + meters - 4 + 2 + + + 0.100000000000000 - 1000.000000000000000 + 10.000000000000000 0.010000000000000 + + 1.000000000000000 + @@ -1050,54 +1090,6 @@ listed above. - - - - 50 - 60 - 71 - 31 - - - - HRTF Mode: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 130 - 60 - 161 - 31 - - - - Forces HRTF processing on or off, or leaves it to the -application or system to determine if it should be used. - - - QComboBox::AdjustToContentsOnFirstShow - - - - Application preference - - - - - Force on - - - - - Force off - - - @@ -1222,12 +1214,17 @@ application or system to determine if it should be used. - PulseAudio + PipeWire - PipeWire + WASAPI + + + + + PulseAudio @@ -1345,7 +1342,79 @@ duplicated names are ignored. + + + + + 20 + 10 + 161 + 21 + + + + Assumes PipeWire has support for audio, allowing +the backend to initialize even when no audio devices +are reported. + + + Assume audio support + + + true + + + + + + 20 + 40 + 161 + 21 + + + + 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 processing the +mixer needs to do. + + + Real-time Mixing + + + true + + + + + + + 20 + 10 + 191 + 21 + + + + 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 +than the actual output can accept, causing the backend to apply its own +resampling pass after OpenAL Soft mixes the sources and processes effects +for output. + + + Allow Resampler + + + true + + + + @@ -1432,26 +1501,6 @@ drop-outs. - - - - - 20 - 10 - 161 - 21 - - - - Assumes PipeWire has support for audio, allowing -the backend to initialize even when no audio devices -are reported. - - - Assume audio support - - - @@ -2110,7 +2159,7 @@ be useful for preventing those extensions from being used. 10 - 100 + 60 511 241 @@ -2516,6 +2565,22 @@ added by the ALC_EXT_DEDICATED extension. + + + + 30 + 320 + 231 + 21 + + + + Enables legacy EAX API support. + + + Enable EAX API support + + diff --git a/libs/openal-soft/utils/makemhr/loaddef.cpp b/libs/openal-soft/utils/makemhr/loaddef.cpp index d325eda1..bb274183 100644 --- a/libs/openal-soft/utils/makemhr/loaddef.cpp +++ b/libs/openal-soft/utils/makemhr/loaddef.cpp @@ -30,25 +30,33 @@ #include #include #include +#include +#include #include #include #include -#include +#include +#include +#include #include -#include "alfstream.h" +#include "albit.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" #include "alstring.h" #include "makemhr.h" +#include "polyphase_resampler.h" #include "mysofa.h" // Constants for accessing the token reader's ring buffer. -#define TR_RING_BITS (16) -#define TR_RING_SIZE (1 << TR_RING_BITS) -#define TR_RING_MASK (TR_RING_SIZE - 1) +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 { @@ -56,7 +64,7 @@ struct TokenReaderT { const char *mName{}; uint mLine{}; uint mColumn{}; - char mRing[TR_RING_SIZE]{}; + std::array mRing{}; std::streamsize mIn{}; std::streamsize mOut{}; @@ -67,44 +75,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 { @@ -142,7 +154,7 @@ struct SourceRefT { double mRadius; uint mSkip; uint mOffset; - char mPath[MAX_PATH_LEN+1]; + std::array mPath; }; @@ -194,30 +206,31 @@ static int TrLoad(TokenReaderT *tr) { 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) @@ -237,21 +250,23 @@ static void TrErrorVA(const TokenReaderT *tr, uint line, uint column, const char // Used to display an error at a saved line/column. static 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, ...) { + /* 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. @@ -261,7 +276,7 @@ static void TrSkipLine(TokenReaderT *tr) while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; tr->mOut++; if(ch == '\n') { @@ -278,7 +293,7 @@ static int TrSkipWhitespace(TokenReaderT *tr) { while(TrLoad(tr)) { - char ch{tr->mRing[tr->mOut&TR_RING_MASK]}; + char ch{tr->mRing[tr->mOut&TRRingMask]}; if(isspace(ch)) { tr->mOut++; @@ -312,7 +327,7 @@ static int TrIsIdent(TokenReaderT *tr) { 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); } @@ -331,7 +346,7 @@ static int TrIsOperator(TokenReaderT *tr, const char *op) len = 0; while(op[len] != '\0' && out < tr->mIn) { - ch = tr->mRing[out&TR_RING_MASK]; + ch = tr->mRing[out&TRRingMask]; if(ch != op[len]) break; len++; out++; @@ -356,7 +371,7 @@ static int TrReadIdent(TokenReaderT *tr, const uint maxLen, char *ident) if(TrSkipWhitespace(tr)) { col = tr->mColumn; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; if(ch == '_' || isalpha(ch)) { len = 0; @@ -367,7 +382,7 @@ static int TrReadIdent(TokenReaderT *tr, const uint maxLen, char *ident) 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; @@ -387,25 +402,23 @@ 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) { - 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; @@ -422,7 +435,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); @@ -438,15 +451,13 @@ 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) { - 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; @@ -454,10 +465,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; @@ -474,7 +485,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; @@ -500,7 +511,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; @@ -518,7 +529,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); @@ -544,14 +555,14 @@ static int TrReadString(TokenReaderT *tr, const uint maxLen, char *text) if(TrSkipWhitespace(tr)) { col = tr->mColumn; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; if(ch == '\"') { tr->mOut++; len = 0; while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; tr->mOut++; if(ch == '\"') break; @@ -597,7 +608,7 @@ static int TrReadOperator(TokenReaderT *tr, const char *op) len = 0; while(op[len] != '\0' && TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; if(ch != op[len]) break; len++; tr->mOut++; @@ -619,8 +630,8 @@ static int TrReadOperator(TokenReaderT *tr, const char *op) // storing it as a 32-bit unsigned integer. static int ReadBin4(std::istream &istream, const char *filename, const ByteOrderT order, const uint bytes, uint32_t *out) { - uint8_t in[4]; - istream.read(reinterpret_cast(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); @@ -648,29 +659,27 @@ static int ReadBin4(std::istream &istream, const char *filename, const ByteOrder // a 64-bit unsigned integer. static int ReadBin8(std::istream &istream, const char *filename, const ByteOrderT order, uint64_t *out) { - uint8_t in[8]; - uint64_t accum; - uint i; - - istream.read(reinterpret_cast(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,40 +694,32 @@ static int ReadBin8(std::istream &istream, const char *filename, const ByteOrder static int ReadBinAsDouble(std::istream &istream, const char *filename, const ByteOrderT order, const ElementTypeT type, const uint bytes, const int bits, double *out) { - union { - uint32_t ui; - int32_t i; - float f; - } v4; - union { - uint64_t ui; - double f; - } v8; - *out = 0.0; if(bytes > 4) { - if(!ReadBin8(istream, filename, order, &v8.ui)) + 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; @@ -774,20 +775,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) @@ -798,12 +799,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); } @@ -817,29 +818,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; @@ -848,7 +852,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; @@ -874,7 +879,8 @@ static int ReadWaveData(std::istream &istream, const SourceRefT *src, const Byte 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; } @@ -894,8 +900,8 @@ 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) @@ -904,17 +910,18 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte count = chunkSize / block; if(count < (src->mOffset + n)) { - 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); + using off_type = std::istream::off_type; + istream.seekg(off_type(src->mOffset) * off_type(block), std::ios::cur); if(!ReadWaveData(istream, src, order, n, &hrir[0])) 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) @@ -930,8 +937,8 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte lastSample = 0.0; while(offset < n && 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) @@ -939,7 +946,8 @@ 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; @@ -959,7 +967,7 @@ 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) @@ -983,7 +991,7 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte } if(offset < n) { - 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; @@ -995,22 +1003,25 @@ static int LoadAsciiSource(std::istream &istream, const SourceRefT *src, const uint n, double *hrir) { TokenReaderT tr{istream}; - uint i, j; - double dummy; TrSetup(nullptr, 0, nullptr, &tr); - for(i = 0;i < src->mOffset;i++) + 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(uint i{0};i < n;++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; } } @@ -1024,7 +1035,8 @@ static int LoadBinarySource(std::istream &istream, const SourceRefT *src, const istream.seekg(static_cast(src->mOffset), std::ios::beg); for(uint i{0};i < n;i++) { - if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) + 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); @@ -1039,8 +1051,8 @@ static int LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hri 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; @@ -1048,15 +1060,15 @@ 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)) @@ -1067,15 +1079,39 @@ static int LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hri } +namespace { + +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; + +} // namespace // Load a Spatially Oriented Format for Accoustics (SOFA) file. static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) { - struct MYSOFA_EASY *sofa{mysofa_cache_lookup(src->mPath, static_cast(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; @@ -1084,38 +1120,37 @@ 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. @@ -1129,40 +1164,39 @@ static void ExtractSofaHrir(const MYSOFA_EASY *sofa, const uint index, const uin // file. static int LoadSofaSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir) { - struct MYSOFA_EASY *sofa; - float target[3]; - int nearest; - float *coords; + MYSOFA_EASY *sofa{LoadSofaFile(src, hrirRate, n)}; + 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{&sofa->hrtf->SourcePosition.values[3_z * nearest], 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; } @@ -1174,16 +1208,17 @@ static int LoadSofaSource(SourceRefT *src, const uint hrirRate, const uint n, do // Load a source HRIR from a supported file type. static int LoadSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir) { - std::unique_ptr 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; } } @@ -1228,23 +1263,24 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc { 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::vector azCounts(MAX_FD_COUNT * MAX_EV_COUNT); + std::array evCounts; + auto azCounts = std::vector>(MAX_FD_COUNT); + for(auto &azs : azCounts) azs.fill(0u); TrIndication(tr, &line, &col); while(TrIsIdent(tr)) { TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + if(!TrReadIdent(tr, MaxIdentLen, ident.data())) return 0; - if(al::strcasecmp(ident, "rate") == 0) + if(al::strcasecmp(ident.data(), "rate") == 0) { if(hasRate) { @@ -1258,9 +1294,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) { @@ -1270,22 +1306,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, MaxIdentLen, type.data())) 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) { @@ -1315,7 +1351,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) { @@ -1324,12 +1360,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; @@ -1368,7 +1404,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; @@ -1385,7 +1421,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc { if(!TrReadInt(tr, MIN_AZ_COUNT, MAX_AZ_COUNT, &intVal)) return 0; - azCounts[(count * MAX_EV_COUNT) + evCounts[count]++] = static_cast(intVal); + azCounts[count][evCounts[count]++] = static_cast(intVal); if(TrIsOperator(tr, ",")) { if(evCounts[count] >= MAX_EV_COUNT) @@ -1402,7 +1438,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc TrErrorAt(tr, line, col, "Did not reach the minimum of %d azimuth counts.\n", MIN_EV_COUNT); return 0; } - if(azCounts[count * MAX_EV_COUNT] != 1 || azCounts[(count * MAX_EV_COUNT) + evCounts[count] - 1] != 1) + if(azCounts[count][0] != 1 || azCounts[count][evCounts[count] - 1] != 1) { TrError(tr, "Poles are not singular for field %d.\n", count - 1); return 0; @@ -1447,7 +1483,8 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc } if(hData->mChannelType == CT_NONE) hData->mChannelType = CT_MONO; - if(!PrepareHrirData(fdCount, distances, evCounts, azCounts.data(), 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); @@ -1460,9 +1497,9 @@ static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, { int intVal; - if(hData->mFdCount > 1) + if(hData->mFds.size() > 1) { - if(!TrReadInt(tr, 0, static_cast(hData->mFdCount) - 1, &intVal)) + if(!TrReadInt(tr, 0, static_cast(hData->mFds.size()-1), &intVal)) return 0; *fi = static_cast(intVal); if(!TrReadOperator(tr, ",")) @@ -1472,12 +1509,12 @@ static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, { *fi = 0; } - if(!TrReadInt(tr, 0, static_cast(hData->mFds[*fi].mEvCount) - 1, &intVal)) + if(!TrReadInt(tr, 0, static_cast(hData->mFds[*fi].mEvs.size()-1), &intVal)) return 0; *ei = static_cast(intVal); if(!TrReadOperator(tr, ",")) return 0; - if(!TrReadInt(tr, 0, static_cast(hData->mFds[*fi].mEvs[*ei].mAzCount) - 1, &intVal)) + if(!TrReadInt(tr, 0, static_cast(hData->mFds[*fi].mEvs[*ei].mAzs.size()-1), &intVal)) return 0; *ai = static_cast(intVal); return 1; @@ -1512,15 +1549,15 @@ static ElementTypeT MatchElementType(const char *ident) // Parse and validate a source reference from the data set definition. static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) { - 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, MaxIdentLen, ident.data())) 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"); @@ -1545,7 +1582,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; @@ -1555,7 +1592,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; @@ -1566,9 +1603,9 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) else { TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + if(!TrReadIdent(tr, MaxIdentLen, ident.data())) 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"); @@ -1580,7 +1617,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, ",")) @@ -1591,9 +1628,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; @@ -1617,7 +1654,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; @@ -1651,7 +1688,7 @@ 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, MAX_PATH_LEN, src->mPath.data())) return 0; return 1; } @@ -1659,14 +1696,14 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) // Parse and validate a SOFA source reference from the data set definition. static int ReadSofaRef(TokenReaderT *tr, SourceRefT *src) { - char ident[MAX_IDENT_LEN+1]; + std::array ident; uint line, col; int intVal; TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + if(!TrReadIdent(tr, MaxIdentLen, ident.data())) 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"); @@ -1690,7 +1727,7 @@ 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, MAX_PATH_LEN, src->mPath.data())) return 0; return 1; } @@ -1707,14 +1744,11 @@ 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 double AverageHrirOnset(const uint rate, const uint n, const double *hrir, const double f, const double onset) +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) { - std::vector upsampled(10 * n); - { - PPhaseResampler rs; - rs.init(rate, 10 * rate); - rs.process(n, hrir, 10 * n, upsampled.data()); - } + rs.process({hrir, n}, upsampled); auto abs_lt = [](const double &lhs, const double &rhs) -> bool { return std::abs(lhs) < std::abs(rhs); }; @@ -1731,9 +1765,9 @@ static void AverageHrirMagnitude(const uint points, const uint n, const double * std::vector r(n); for(i = 0;i < points;i++) - h[i] = complex_d{hrir[i], 0.0}; + h[i] = hrir[i]; for(;i < n;i++) - h[i] = complex_d{0.0, 0.0}; + h[i] = 0.0; FftForward(n, h.data()); MagnitudeResponse(n, h.data(), r.data()); for(i = 0;i < m;i++) @@ -1741,71 +1775,75 @@ static void AverageHrirMagnitude(const uint points, const uint n, const double * } // Process the list of sources in the data set definition. -static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) +static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) { - uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1; - hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize); + const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; + hData->mHrirsBase.resize(size_t{channels} * hData->mIrCount * hData->mIrSize); double *hrirs = hData->mHrirsBase.data(); - std::vector hrir(hData->mIrPoints); + auto hrir = std::vector(hData->mIrSize); uint line, col, fi, ei, ai; - int count; + + std::vector onsetSamples(size_t{OnsetRateMultiple} * hData->mIrPoints); + PPhaseResampler onsetResampler; + onsetResampler.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate); + + 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}; + const uint irPoints{outRate + ? std::min(static_cast(std::ceil(hData->mIrPoints*rateScale)), hData->mIrPoints) + : hData->mIrPoints}; printf("Loading sources..."); fflush(stdout); - count = 0; + 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, MaxIdentLen, type.data())) 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, MaxIdentLen, type.data())) return 0; - channelType = MatchChannelType(type); + ChannelTypeT channelType{MatchChannelType(type.data())}; if(channelType != CT_MONO) { TrErrorAt(tr, line, col, "Expected a mono channel type.\n"); @@ -1814,48 +1852,49 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) 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++) + 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] + std::array aer{ + sofa->hrtf->SourcePosition.values[3_uz*si], + sofa->hrtf->SourcePosition.values[3_uz*si + 1], + sofa->hrtf->SourcePosition.values[3_uz*si + 2] }; - mysofa_c2s(aer); + mysofa_c2s(aer.data()); if(std::fabs(aer[1]) >= 89.999f) aer[0] = 0.0f; else aer[0] = std::fmod(360.0f - aer[0], 360.0f); - for(fi = 0;fi < hData->mFdCount;fi++) - { - double delta = aer[2] - hData->mFds[fi].mDistance; - if(std::abs(delta) < 0.001) break; - } - if(fi >= hData->mFdCount) + auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(), + [&aer](const HrirFdT &fld) -> bool + { return (std::abs(aer[2] - fld.mDistance) < 0.001); }); + if(field == hData->mFds.cend()) continue; + fi = static_cast(std::distance(hData->mFds.cbegin(), field)); - double ef{(90.0 + aer[1]) / 180.0 * (hData->mFds[fi].mEvCount - 1)}; + const double evscale{180.0 / static_cast(field->mEvs.size()-1)}; + double ef{(90.0 + aer[1]) / evscale}; ei = static_cast(std::round(ef)); - ef = (ef - ei) * 180.0 / (hData->mFds[fi].mEvCount - 1); + ef = (ef - ei) * evscale; if(std::abs(ef) >= 0.1) continue; - double af{aer[0] / 360.0 * hData->mFds[fi].mEvs[ei].mAzCount}; + const double azscale{360.0 / static_cast(field->mEvs[ei].mAzs.size())}; + double af{aer[0] / azscale}; ai = static_cast(std::round(af)); - af = (af - ai) * 360.0 / hData->mFds[fi].mEvs[ei].mAzCount; - ai = ai % hData->mFds[fi].mEvs[ei].mAzCount; + af = (af - ai) * azscale; + ai %= static_cast(field->mEvs[ei].mAzs.size()); if(std::abs(af) >= 0.1) continue; - HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; + HrirAzT *azd = &field->mEvs[ei].mAzs[ai]; if(azd->mIrs[0] != nullptr) { TrErrorAt(tr, line, col, "Redefinition of source [ %d, %d, %d ].\n", fi, ei, ai); @@ -1863,16 +1902,25 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) } ExtractSofaHrir(sofa, si, 0, src.mOffset, hData->mIrPoints, hrir.data()); - azd->mIrs[0] = &hrirs[hData->mIrSize * azd->mIndex]; - azd->mDelays[0] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[0]); - AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir.data(), 1.0, azd->mIrs[0]); + azd->mIrs[0] = &hrirs[size_t{hData->mIrSize} * azd->mIndex]; + azd->mDelays[0] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate, + hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[0]); + if(resampler) + resampler->process(al::span{hrir}.first(hData->mIrPoints), + al::span{hrir}.first(hData->mIrSize)); + AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.data(), 1.0, azd->mIrs[0]); if(src.mChannel == 1) { ExtractSofaHrir(sofa, si, 1, src.mOffset, hData->mIrPoints, hrir.data()); - azd->mIrs[1] = &hrirs[hData->mIrSize * (hData->mIrCount + azd->mIndex)]; - azd->mDelays[1] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[1]); - AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir.data(), 1.0, azd->mIrs[1]); + azd->mIrs[1] = &hrirs[hData->mIrSize * (size_t{hData->mIrCount}+azd->mIndex)]; + azd->mDelays[1] = AverageHrirOnset(onsetResampler, onsetSamples, + hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[1]); + if(resampler) + resampler->process(al::span{hrir}.first(hData->mIrPoints), + al::span{hrir}.first(hData->mIrSize)); + AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.data(), 1.0, + azd->mIrs[1]); } // TODO: Since some SOFA files contain minimum phase HRIRs, @@ -1897,10 +1945,9 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) if(!TrReadOperator(tr, "=")) return 0; - for(;;) + while(true) { - SourceRefT src; - + SourceRefT src{}; if(!ReadSourceRef(tr, &src)) return 0; @@ -1917,20 +1964,24 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) 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, MaxIdentLen, ident.data())) 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)]; - azd->mDelays[ti] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0 / factor[ti], azd->mDelays[ti]); - AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir.data(), 1.0 / factor[ti], azd->mIrs[ti]); + azd->mIrs[ti] = &hrirs[hData->mIrSize * (ti*size_t{hData->mIrCount} + azd->mIndex)]; + azd->mDelays[ti] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate, + hData->mIrPoints, hrir.data(), 1.0 / factor[ti], azd->mDelays[ti]); + if(resampler) + resampler->process(al::span{hrir}.first(hData->mIrPoints), + al::span{hrir}.first(hData->mIrSize)); + AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.data(), 1.0 / factor[ti], + azd->mIrs[ti]); factor[ti] += 1.0; if(!TrIsOperator(tr, "+")) break; @@ -1943,7 +1994,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) TrErrorAt(tr, line, col, "Missing left ear source reference(s).\n"); return 0; } - else if(azd->mIrs[1] == nullptr) + if(azd->mIrs[1] == nullptr) { TrErrorAt(tr, line, col, "Missing right ear source reference(s).\n"); return 0; @@ -1951,28 +2002,35 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) } } printf("\n"); - for(fi = 0;fi < hData->mFdCount;fi++) + hrir.clear(); + if(resampler) { - for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) + hData->mIrRate = outRate; + hData->mIrPoints = irPoints; + resampler.reset(); + } + for(fi = 0;fi < hData->mFds.size();fi++) + { + for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + 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) break; } - if(ai < hData->mFds[fi].mEvs[ei].mAzCount) + if(ai < hData->mFds[fi].mEvs[ei].mAzs.size()) break; } - if(ei >= hData->mFds[fi].mEvCount) + if(ei >= hData->mFds[fi].mEvs.size()) { TrError(tr, "Missing source references [ %d, *, * ].\n", fi); return 0; } hData->mFds[fi].mEvStart = ei; - for(;ei < hData->mFds[fi].mEvCount;ei++) + for(;ei < hData->mFds[fi].mEvs.size();ei++) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; @@ -1986,40 +2044,40 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData) } for(uint ti{0};ti < channels;ti++) { - for(fi = 0;fi < hData->mFdCount;fi++) + for(fi = 0;fi < hData->mFds.size();fi++) { - for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) + for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + 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[hData->mIrSize * (ti*size_t{hData->mIrCount} + azd->mIndex)]; } } } } 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; } bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount, - const char *filename, const uint fftSize, const uint truncSize, const ChannelModeT chanMode, - HrirDataT *hData) + const char *filename, const uint fftSize, const uint truncSize, const uint outRate, + const ChannelModeT chanMode, HrirDataT *hData) { TokenReaderT tr{istream}; TrSetup(startbytes, startbytecount, filename, &tr); if(!ProcessMetrics(&tr, fftSize, truncSize, chanMode, hData) - || !ProcessSources(&tr, hData)) + || !ProcessSources(&tr, hData, outRate)) return false; return true; diff --git a/libs/openal-soft/utils/makemhr/loaddef.h b/libs/openal-soft/utils/makemhr/loaddef.h index 34fbb832..63600dcd 100644 --- a/libs/openal-soft/utils/makemhr/loaddef.h +++ b/libs/openal-soft/utils/makemhr/loaddef.h @@ -7,7 +7,7 @@ bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount, - const char *filename, const uint fftSize, const uint truncSize, const ChannelModeT chanMode, - HrirDataT *hData); + const char *filename, const uint fftSize, const uint truncSize, const uint outRate, + const ChannelModeT chanMode, HrirDataT *hData); #endif /* LOADDEF_H */ diff --git a/libs/openal-soft/utils/makemhr/loadsofa.cpp b/libs/openal-soft/utils/makemhr/loadsofa.cpp index 7d091be8..cbe8e65d 100644 --- a/libs/openal-soft/utils/makemhr/loadsofa.cpp +++ b/libs/openal-soft/utils/makemhr/loadsofa.cpp @@ -33,10 +33,14 @@ #include #include #include +#include #include +#include #include #include +#include "alspan.h" +#include "alnumeric.h" #include "makemhr.h" #include "polyphase_resampler.h" #include "sofa-support.h" @@ -44,6 +48,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 @@ -52,7 +59,7 @@ 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) +bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData) { fprintf(stdout, "Detecting compatible layout...\n"); @@ -63,9 +70,10 @@ static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData) return false; } - double distances[MAX_FD_COUNT]{}; - uint evCounts[MAX_FD_COUNT]{}; - auto azCounts = std::vector(MAX_FD_COUNT*MAX_EV_COUNT, 0u); + std::array distances{}; + std::array evCounts{}; + auto azCounts = std::vector>(MAX_FD_COUNT); + for(auto &azs : azCounts) azs.fill(0u); uint fi{0u}, ir_total{0u}; for(const auto &field : fds) @@ -74,21 +82,21 @@ static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData) evCounts[fi] = field.mEvCount; for(uint ei{0u};ei < field.mEvStart;ei++) - azCounts[fi*MAX_EV_COUNT + ei] = field.mAzCounts[field.mEvCount-ei-1]; + azCounts[fi][ei] = field.mAzCounts[field.mEvCount-ei-1]; for(uint ei{field.mEvStart};ei < field.mEvCount;ei++) { - azCounts[fi*MAX_EV_COUNT + ei] = field.mAzCounts[ei]; + azCounts[fi][ei] = field.mAzCounts[ei]; ir_total += field.mAzCounts[ei]; } ++fi; } fprintf(stdout, "Using %u of %u IRs.\n", ir_total, m); - return PrepareHrirData(fi, distances, evCounts, azCounts.data(), hData) != 0; + const auto azs = al::span{azCounts}.first(); + return PrepareHrirData(al::span{distances}.first(fi), evCounts, azs, hData); } - -bool PrepareSampleRate(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData) +float GetSampleRate(MYSOFA_HRTF *sofaHrtf) { const char *srate_dim{nullptr}; const char *srate_units{nullptr}; @@ -96,21 +104,21 @@ bool PrepareSampleRate(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData) 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) { fprintf(stderr, "Duplicate SampleRate.DIMENSION_LIST\n"); - return false; + return 0.0f; } srate_dim = srate_attrs->value; } - else if(std::string{"Units"} == srate_attrs->name) + else if("Units"sv == srate_attrs->name) { if(srate_units) { fprintf(stderr, "Duplicate SampleRate.Units\n"); - return false; + return 0.0f; } srate_units = srate_attrs->value; } @@ -122,78 +130,72 @@ bool PrepareSampleRate(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData) if(!srate_dim) { fprintf(stderr, "Missing sample rate dimensions\n"); - return false; + 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 false; + return 0.0f; } if(!srate_units) { fprintf(stderr, "Missing sample rate unit type\n"); - return false; + 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 false; + return 0.0f; } /* I dimensions guarantees 1 element, so just extract it. */ - hData->mIrRate = static_cast(srate_array->values[0] + 0.5f); - if(hData->mIrRate < MIN_RATE || hData->mIrRate > MAX_RATE) + if(srate_array->values[0] < float{MIN_RATE} || srate_array->values[0] > float{MAX_RATE}) { - fprintf(stderr, "Sample rate out of range: %u (expected %u to %u)", hData->mIrRate, + fprintf(stderr, "Sample rate out of range: %f (expected %u to %u)", srate_array->values[0], MIN_RATE, MAX_RATE); - return false; + return 0.0f; } - return true; + return srate_array->values[0]; } -bool PrepareDelay(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData) +enum class DelayType : uint8_t { + None, + I_R, /* [1][Channels] */ + M_R, /* [HRIRs][Channels] */ + Invalid, +}; +DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf) { 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 false; + return DelayType::Invalid; } delay_dim = delay_attrs->value; } else fprintf(stderr, "Unexpected delay attribute: %s = %s\n", delay_attrs->name, - delay_attrs->value); + delay_attrs->value ? delay_attrs->value : ""); delay_attrs = delay_attrs->next; } if(!delay_dim) { fprintf(stderr, "Missing delay dimensions\n"); - /*return false;*/ + return DelayType::None; } - else if(delay_dim != std::string{"I,R"}) - { - fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim); - return false; - } - else if(hData->mChannelType == CT_STEREO) - { - /* I,R is 1xChannelCount. Makemhr currently removes any delay constant, - * so we can ignore this as long as it's equal. - */ - if(delay_array->values[0] != delay_array->values[1]) - { - fprintf(stderr, "Mismatched delays not supported: %f, %f\n", delay_array->values[0], - delay_array->values[1]); - return false; - } - } - return true; + if(delay_dim == "I,R"sv) + return DelayType::I_R; + if(delay_dim == "M,R"sv) + return DelayType::M_R; + + fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim); + return DelayType::Invalid; } bool CheckIrData(MYSOFA_HRTF *sofaHrtf) @@ -203,7 +205,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) { @@ -214,7 +216,7 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf) } else fprintf(stderr, "Unexpected IR attribute: %s = %s\n", ir_attrs->name, - ir_attrs->value); + ir_attrs->value ? ir_attrs->value : ""); ir_attrs = ir_attrs->next; } if(!ir_dim) @@ -222,7 +224,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; @@ -232,11 +234,11 @@ 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, - std::vector &upsampled, const double *hrir) +constexpr int OnsetRateMultiple{10}; +double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n, + al::span upsampled, const double *hrir) { - rs.process(n, hrir, static_cast(upsampled.size()), upsampled.data()); + rs.process({hrir, n}, upsampled); auto abs_lt = [](const double &lhs, const double &rhs) -> bool { return std::abs(lhs) < std::abs(rhs); }; @@ -246,8 +248,7 @@ 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, std::vector &h, - double *hrir) +void CalcHrirMagnitude(const uint points, const uint n, al::span h, double *hrir) { auto iter = std::copy_n(hrir, points, h.begin()); std::fill(iter, h.end(), complex_d{0.0, 0.0}); @@ -256,26 +257,35 @@ static void CalcHrirMagnitude(const uint points, const uint n, std::vector loaded_count{0u}; - auto load_proc = [sofaHrtf,hData,&loaded_count]() -> bool + 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); + hData->mHrirsBase.resize(channels * size_t{hData->mIrCount} * hData->mIrSize, 0.0); double *hrirs = hData->mHrirsBase.data(); + std::vector restmp; + std::optional resampler; + if(outRate && outRate != hData->mIrRate) + { + resampler.emplace().init(hData->mIrRate, outRate); + restmp.resize(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] + std::array aer{ + sofaHrtf->SourcePosition.values[3_uz*si], + sofaHrtf->SourcePosition.values[3_uz*si + 1], + sofaHrtf->SourcePosition.values[3_uz*si + 2] }; - mysofa_c2s(aer); + mysofa_c2s(aer.data()); if(std::abs(aer[1]) >= 89.999f) aer[0] = 0.0f; @@ -284,22 +294,21 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData) auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(), [&aer](const HrirFdT &fld) -> bool - { - double delta = aer[2] - fld.mDistance; - return (std::abs(delta) < 0.001); - }); + { return (std::abs(aer[2] - fld.mDistance) < 0.001); }); if(field == hData->mFds.cend()) continue; - double ef{(90.0+aer[1]) / 180.0 * (field->mEvCount-1)}; - auto ei = static_cast(std::round(ef)); - ef = (ef-ei) * 180.0 / (field->mEvCount-1); + const double evscale{180.0 / static_cast(field->mEvs.size()-1)}; + double ef{(90.0 + aer[1]) / evscale}; + auto ei = static_cast(std::round(ef)); + ef = (ef - ei) * evscale; if(std::abs(ef) >= 0.1) continue; - double af{aer[0] / 360.0 * field->mEvs[ei].mAzCount}; - auto ai = static_cast(std::round(af)); - af = (af-ai) * 360.0 / field->mEvs[ei].mAzCount; - ai %= field->mEvs[ei].mAzCount; + const double azscale{360.0 / static_cast(field->mEvs[ei].mAzs.size())}; + double af{aer[0] / azscale}; + auto ai = static_cast(std::round(af)); + af = (af-ai) * azscale; + ai %= static_cast(field->mEvs[ei].mAzs.size()); if(std::abs(af) >= 0.1) continue; HrirAzT *azd = &field->mEvs[ei].mAzs[ai]; @@ -312,15 +321,40 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData) for(uint ti{0u};ti < channels;++ti) { - azd->mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd->mIndex)]; - std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N], - hData->mIrPoints, azd->mIrs[ti]); + azd->mIrs[ti] = &hrirs[(size_t{hData->mIrCount}*ti + azd->mIndex)*hData->mIrSize]; + if(!resampler) + std::copy_n(&sofaHrtf->DataIR.values[(size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N], + sofaHrtf->N, azd->mIrs[ti]); + else + { + std::copy_n(&sofaHrtf->DataIR.values[(size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N], + sofaHrtf->N, restmp.begin()); + resampler->process(restmp, {azd->mIrs[ti], hData->mIrSize}); + } } - /* TODO: Since some SOFA files contain minimum phase HRIRs, - * it would be beneficial to check for per-measurement delays - * (when available) to reconstruct the HRTDs. - */ + /* Include any per-channel or per-HRIR delays. */ + if(delayType == DelayType::I_R) + { + const float *delayValues{sofaHrtf->DataDelay.values}; + for(uint ti{0u};ti < channels;++ti) + azd->mDelays[ti] = delayValues[ti] / static_cast(hData->mIrRate); + } + else if(delayType == DelayType::M_R) + { + const float *delayValues{sofaHrtf->DataDelay.values}; + for(uint ti{0u};ti < channels;++ti) + azd->mDelays[ti] = delayValues[si*sofaHrtf->R + ti] / + static_cast(hData->mIrRate); + } + } + + if(outRate && outRate != hData->mIrRate) + { + const double scale{static_cast(outRate) / hData->mIrRate}; + hData->mIrRate = outRate; + hData->mIrPoints = std::min(static_cast(std::ceil(hData->mIrPoints*scale)), + hData->mIrSize); } return true; }; @@ -352,7 +386,7 @@ struct MagCalculator { { auto htemp = std::vector(mFftSize); - while(1) + while(true) { /* Load the current index to process. */ size_t idx{mCurrent.load()}; @@ -375,8 +409,10 @@ struct MagCalculator { } }; +} // namespace + bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize, - const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData) + const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData) { int err; MySofaHrtfPtr sofaHrtf{mysofa_load(filename, &err)}; @@ -429,39 +465,45 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz /* Assume a default head radius of 9cm. */ hData->mRadius = 0.09; - if(!PrepareSampleRate(sofaHrtf.get(), hData) || !PrepareDelay(sofaHrtf.get(), hData) - || !CheckIrData(sofaHrtf.get())) + hData->mIrRate = static_cast(std::lround(GetSampleRate(sofaHrtf.get()))); + if(!hData->mIrRate) + return false; + + DelayType delayType = PrepareDelay(sofaHrtf.get()); + if(delayType == DelayType::Invalid) + return false; + + if(!CheckIrData(sofaHrtf.get())) return false; if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData)) return false; - - if(!LoadResponses(sofaHrtf.get(), hData)) + if(!LoadResponses(sofaHrtf.get(), hData, delayType, outRate)) return false; sofaHrtf = nullptr; - for(uint fi{0u};fi < hData->mFdCount;fi++) + for(uint fi{0u};fi < hData->mFds.size();fi++) { uint ei{0u}; - for(;ei < hData->mFds[fi].mEvCount;ei++) + for(;ei < hData->mFds[fi].mEvs.size();ei++) { uint ai{0u}; - for(;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + 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(ai < hData->mFds[fi].mEvs[ei].mAzCount) + if(ai < hData->mFds[fi].mEvs[ei].mAzs.size()) break; } - if(ei >= hData->mFds[fi].mEvCount) + if(ei >= hData->mFds[fi].mEvs.size()) { fprintf(stderr, "Missing source references [ %d, *, * ].\n", fi); return false; } hData->mFds[fi].mEvStart = ei; - for(;ei < hData->mFds[fi].mEvCount;ei++) + for(;ei < hData->mFds[fi].mEvs.size();ei++) { - for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + 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) @@ -477,42 +519,41 @@ 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(); - for(uint fi{0u};fi < hData->mFdCount;fi++) + for(uint fi{0u};fi < hData->mFds.size();fi++) { for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++) { - for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + 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++) + for(size_t ti{0u};ti < channels;ti++) azd.mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd.mIndex)]; } } - for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++) - hrir_total += hData->mFds[fi].mEvs[ei].mAzCount * channels; + for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();ei++) + hrir_total += hData->mFds[fi].mEvs[ei].mAzs.size() * channels; } std::atomic hrir_done{0}; 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); - for(uint fi{0u};fi < hData->mFdCount;fi++) + for(auto &field : hData->mFds) { - for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++) + for(auto &elev : field.mEvs.subspan(field.mEvStart)) { - for(uint ai{0};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + for(auto &azd : elev.mAzs) { - HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; 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, + azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, hData->mIrPoints, upsampled, azd.mIrs[ti]); } } @@ -533,13 +574,12 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz return false; MagCalculator calculator{hData->mFftSize, hData->mIrPoints}; - for(uint fi{0u};fi < hData->mFdCount;fi++) + for(auto &field : hData->mFds) { - for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++) + for(auto &elev : field.mEvs.subspan(field.mEvStart)) { - for(uint ai{0};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + for(auto &azd : elev.mAzs) { - HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; for(uint ti{0};ti < channels;ti++) calculator.mIrs.push_back(azd.mIrs[ti]); } diff --git a/libs/openal-soft/utils/makemhr/loadsofa.h b/libs/openal-soft/utils/makemhr/loadsofa.h index 803bcf88..82dce85a 100644 --- a/libs/openal-soft/utils/makemhr/loadsofa.h +++ b/libs/openal-soft/utils/makemhr/loadsofa.h @@ -5,6 +5,6 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize, - const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData); + const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData); #endif /* LOADSOFA_H */ diff --git a/libs/openal-soft/utils/makemhr/makemhr.cpp b/libs/openal-soft/utils/makemhr/makemhr.cpp index 554bdfe0..1722be9b 100644 --- a/libs/openal-soft/utils/makemhr/makemhr.cpp +++ b/libs/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,11 +73,14 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -88,7 +91,9 @@ #include "../getopt.h" #endif -#include "alfstream.h" +#include "alcomplex.h" +#include "alnumbers.h" +#include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "loaddef.h" @@ -97,59 +102,61 @@ #include "win_main_utf8.h" +HrirDataT::~HrirDataT() = default; + namespace { -using namespace std::placeholders; +using namespace std::string_view_literals; -} // namespace +struct FileDeleter { + void operator()(gsl::owner f) { fclose(f); } +}; +using FilePtr = std::unique_ptr; -#ifndef M_PI -#define M_PI (3.14159265358979323846) -#endif +// The epsilon used to maintain signal stability. +constexpr double Epsilon{1e-9}; + +// The limits to the FFT window size override on the command line. +constexpr uint MinFftSize{65536}; +constexpr uint MaxFftSize{131072}; + +// The limits to the equalization range limit on the command line. +constexpr double MinLimit{2.0}; +constexpr double MaxLimit{120.0}; + +// The limits to the truncation window size on the command line. +constexpr uint MinTruncSize{16}; +constexpr uint MaxTruncSize{128}; + +// The limits to the custom head radius on the command line. +constexpr double MinCustomRadius{0.05}; +constexpr double MaxCustomRadius{0.15}; + +// The maximum propagation delay value supported by OpenAL Soft. +constexpr double MaxHrtd{63.0}; + +// The OpenAL Soft HRTF format marker. It stands for minimum-phase head +// response protocol 03. +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_None, + HM_Dataset, // Measure the onset from the dataset. + HM_Sphere, // Calculate the onset using a spherical head model. + + HM_Default = HM_Dataset }; -// The epsilon used to maintain signal stability. -#define EPSILON (1e-9) - -// The limits to the FFT window size override on the command line. -#define MIN_FFTSIZE (65536) -#define MAX_FFTSIZE (131072) - -// The limits to the equalization range limit on the command line. -#define MIN_LIMIT (2.0) -#define MAX_LIMIT (120.0) - -// The limits to the truncation window size on the command line. -#define MIN_TRUNCSIZE (16) -#define MAX_TRUNCSIZE (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 (32) -#define DEFAULT_HEAD_MODEL (HM_DATASET) -#define DEFAULT_CUSTOM_RADIUS (0.0) - -// The maximum propagation delay value supported by OpenAL Soft. -#define MAX_HRTD (63.0) - -// The OpenAL Soft HRTF format marker. It stands for minimum-phase head -// response protocol 03. -#define MHR_FORMAT ("MinPHR03") +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 { @@ -162,7 +169,7 @@ 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, +std::string StrSubst(al::span in, const al::span pat, const al::span rep) { std::string ret; @@ -195,12 +202,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; @@ -208,8 +215,8 @@ 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(double *RESTRICT out, const double *RESTRICT in, const double scale, + const uint count, const uint step, uint *seed) { static constexpr double PRNG_SCALE = 1.0 / std::numeric_limits::max(); @@ -222,94 +229,16 @@ static void TpdfDither(double *RESTRICT out, const double *RESTRICT in, const do } } -/* Fast Fourier transform routines. The number of points must be a power of - * two. - */ - -// Performs bit-reversal ordering. -static void FftArrange(const uint n, complex_d *inout) -{ - // Handle in-place arrangement. - uint rk{0u}; - for(uint k{0u};k < n;k++) - { - if(rk > k) - std::swap(inout[rk], inout[k]); - - uint m{n}; - while(rk&(m >>= 1)) - rk &= ~m; - rk |= m; - } -} - -// Performs the summation. -static void FftSummation(const uint n, const double s, complex_d *cplx) -{ - double pi; - uint m, m2; - uint i, k, mk; - - pi = s * M_PI; - for(m = 1, m2 = 2;m < n; m <<= 1, m2 <<= 1) - { - // v = Complex (-2.0 * sin (0.5 * pi / m) * sin (0.5 * pi / m), -sin (pi / m)) - double sm = std::sin(0.5 * pi / m); - auto v = complex_d{-2.0*sm*sm, -std::sin(pi / m)}; - auto w = complex_d{1.0, 0.0}; - for(i = 0;i < m;i++) - { - for(k = i;k < n;k += m2) - { - mk = k + m; - auto t = w * cplx[mk]; - cplx[mk] = cplx[k] - t; - cplx[k] = cplx[k] + t; - } - w += v*w; - } - } -} - -// Performs a forward FFT. -void FftForward(const uint n, complex_d *inout) -{ - FftArrange(n, inout); - FftSummation(n, 1.0, inout); -} - -// Performs an inverse FFT. -void FftInverse(const uint n, complex_d *inout) -{ - FftArrange(n, inout); - FftSummation(n, -1.0, inout); - double f{1.0 / n}; - for(uint i{0};i < n;i++) - inout[i] *= f; -} /* Calculate the complex helical sequence (or discrete-time analytical signal) * of the given input using the Hilbert transform. Given the natural logarithm * of a signal's magnitude response, the imaginary components can be used as * the angles for minimum-phase reconstruction. */ -static void Hilbert(const uint n, complex_d *inout) -{ - uint i; +inline void Hilbert(const uint n, complex_d *inout) +{ complex_hilbert({inout, n}); } - // Handle in-place operation. - for(i = 0;i < n;i++) - inout[i].imag(0.0); - - FftInverse(n, inout); - for(i = 1;i < (n+1)/2;i++) - inout[i] *= 2.0; - /* Increment i if n is even. */ - i += (n&1)^1; - for(;i < n;i++) - inout[i] = complex_d{0.0, 0.0}; - FftForward(n, inout); -} +} // namespace /* Calculate the magnitude response of the given input. This is used in * place of phase decomposition, since the phase residuals are discarded for @@ -321,14 +250,16 @@ 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); + out[i] = std::max(std::abs(in[i]), Epsilon); } +namespace { + /* 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 double *in, double *out) { double halfLim; uint i, lower, upper; @@ -358,7 +289,7 @@ 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 uint n, double *mags, complex_d *out) { const uint m{(n/2) + 1}; @@ -372,12 +303,9 @@ static void MinimumPhase(const uint n, double *mags, complex_d *out) } Hilbert(n, out); // Remove any DC offset the filter has. - mags[0] = EPSILON; + 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]; - } + out[i] = std::polar(mags[i], out[i].imag()); } @@ -386,14 +314,10 @@ 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) +int WriteAscii(const std::string_view out, FILE *fp, const char *filename) { - size_t len; - - len = strlen(out); - if(fwrite(out, 1, len, fp) != len) + if(fwrite(out.data(), 1, out.size(), fp) != out.size()) { - fclose(fp); fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename); return 0; } @@ -402,15 +326,13 @@ 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) +int WriteBin4(const uint bytes, const uint32_t in, FILE *fp, const char *filename) { - uint8_t out[4]; - uint i; - - for(i = 0;i < bytes;i++) + std::array out{}; + for(uint i{0};i < bytes;i++) out[i] = (in>>(i*8)) & 0x000000FF; - if(fwrite(out, 1, bytes, fp) != bytes) + if(fwrite(out.data(), 1, bytes, fp) != bytes) { fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename); return 0; @@ -419,89 +341,86 @@ static int WriteBin4(const uint bytes, const uint32_t in, FILE *fp, const char * } // Store the OpenAL Soft HRTF data set. -static int StoreMhr(const HrirDataT *hData, const char *filename) +bool StoreMhr(const HrirDataT *hData, const char *filename) { 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) + FilePtr fp{fopen(filename, "wb")}; + if(!fp) { fprintf(stderr, "\nError: Could not open MHR file '%s'.\n", filename); - return 0; + 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, hData->mFdCount, fp, filename)) - return 0; - for(fi = hData->mFdCount-1;fi < hData->mFdCount;fi--) + if(!WriteAscii(GetMHRMarker(), fp.get(), filename)) + return false; + if(!WriteBin4(4, hData->mIrRate, fp.get(), filename)) + return false; + if(!WriteBin4(1, static_cast(hData->mChannelType), fp.get(), filename)) + return false; + if(!WriteBin4(1, hData->mIrPoints, fp.get(), filename)) + return false; + if(!WriteBin4(1, static_cast(hData->mFds.size()), fp.get(), 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, hData->mFds[fi].mEvCount, fp, filename)) - return 0; - for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) + if(!WriteBin4(2, fdist, fp.get(), filename)) + return false; + if(!WriteBin4(1, static_cast(hData->mFds[fi].mEvs.size()), fp.get(), filename)) + return false; + for(size_t ei{0};ei < hData->mFds[fi].mEvs.size();++ei) { - if(!WriteBin4(1, hData->mFds[fi].mEvs[ei].mAzCount, fp, filename)) - return 0; + const auto &elev = hData->mFds[fi].mEvs[ei]; + if(!WriteBin4(1, static_cast(elev.mAzs.size()), fp.get(), filename)) + return false; } } - for(fi = hData->mFdCount-1;fi < hData->mFdCount;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].mEvCount;ei++) + for(const auto &evd : hData->mFds[fi].mEvs) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;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.data(), azd.mIrs[0], scale, n, channels, &dither_seed); if(hData->mChannelType == CT_STEREO) - TpdfDither(out+1, azd->mIrs[1], scale, n, channels, &dither_seed); - for(i = 0;i < (channels * n);i++) + TpdfDither(out.data()+1, azd.mIrs[1], scale, n, 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), fp.get(), filename)) + return false; } } } } - for(fi = hData->mFdCount-1;fi < hData->mFdCount;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].mEvCount;ei++) + static constexpr double DelayPrecScale{4.0}; + for(const auto &evd : hData->mFds[fi].mEvs) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + for(const auto &azd : evd.mAzs) { - const HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; - auto v = static_cast(std::round(azd.mDelays[0]*DelayPrecScale)); - if(!WriteBin4(1, v, fp, filename)) return 0; + if(!WriteBin4(1, v, fp.get(), 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, fp.get(), filename)) return false; } } } } - fclose(fp); - return 1; + return true; } @@ -513,25 +432,21 @@ 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, ai, ti, i; - + std::array maxMags{}; double maxMag{0.0}; - for(fi = 0;fi < hData->mFdCount;fi++) - { - maxMags[fi] = 0.0; - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++) + for(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(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { - HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - for(ti = 0;ti < channels;ti++) + for(size_t ti{0};ti < channels;++ti) { - for(i = 0;i < m;i++) - maxMags[fi] = std::max(azd->mIrs[ti][i], maxMags[fi]); + for(size_t i{0};i < m;++i) + maxMags[fi] = std::max(azd.mIrs[ti][i], maxMags[fi]); } } } @@ -539,19 +454,18 @@ static void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, maxMag = std::max(maxMags[fi], maxMag); } - for(fi = 0;fi < hData->mFdCount;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].mEvCount;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].mAzCount;ai++) + for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { - HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - for(ti = 0;ti < channels;ti++) + for(size_t ti{0};ti < channels;++ti) { - for(i = 0;i < m;i++) - azd->mIrs[ti][i] *= magFactor; + for(size_t i{0};i < m;++i) + azd.mIrs[ti][i] *= magFactor; } } } @@ -562,7 +476,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, double *weights) { double sum, innerRa, outerRa, evs, ev, upperEv, lowerEv; double solidAngle, solidVolume; @@ -571,30 +485,32 @@ static void CalculateDfWeights(const HrirDataT *hData, double *weights) sum = 0.0; // The head radius acts as the limit for the inner radius. innerRa = hData->mRadius; - for(fi = 0;fi < hData->mFdCount;fi++) + for(fi = 0;fi < hData->mFds.size();fi++) { // Each volume ends half way between progressive field measurements. - if((fi + 1) < hData->mFdCount) + if((fi + 1) < hData->mFds.size()) outerRa = 0.5f * (hData->mFds[fi].mDistance + hData->mFds[fi + 1].mDistance); // The final volume has its limit extended to some practical value. // This is done to emphasize the far-field responses in the average. else outerRa = 10.0f; - evs = M_PI / 2.0 / (hData->mFds[fi].mEvCount - 1); - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++) + const double raPowDiff{std::pow(outerRa, 3.0) - std::pow(innerRa, 3.0)}; + 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 = hData->mFds[fi].mEvs[ei].mElevation; - lowerEv = std::max(-M_PI / 2.0, ev - evs); - upperEv = std::min(M_PI / 2.0, ev + evs); + ev = elev.mElevation; + 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 * (std::pow(outerRa, 3.0) - std::pow(innerRa, 3.0)) / 3.0; + solidVolume = solidAngle * raPowDiff / 3.0; // Each weight is the volume of one extruded patch. - weights[(fi * MAX_EV_COUNT) + ei] = solidVolume / hData->mFds[fi].mEvs[ei].mAzCount; + weights[(fi*MAX_EV_COUNT) + ei] = solidVolume / static_cast(elev.mAzs.size()); // Sum the total coverage volume of the HRIRs for all fields. sum += solidAngle; } @@ -602,11 +518,11 @@ static void CalculateDfWeights(const HrirDataT *hData, double *weights) innerRa = outerRa; } - for(fi = 0;fi < hData->mFdCount;fi++) + for(fi = 0;fi < hData->mFds.size();fi++) { // Normalize the weights given the total surface coverage for all // fields. - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++) + for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) weights[(fi * MAX_EV_COUNT) + ei] /= sum; } } @@ -616,10 +532,11 @@ 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, double *dfa) { - std::vector weights(hData->mFdCount * MAX_EV_COUNT); - uint count, ti, fi, ei, i, ai; + std::vector weights(hData->mFds.size() * MAX_EV_COUNT); + uint count; if(weighted) { @@ -633,42 +550,42 @@ 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->mFdCount;fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = 0;ei < hData->mFds[fi].mEvStart;ei++) - count -= hData->mFds[fi].mEvs[ei].mAzCount; + 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->mFdCount;fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;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->mFdCount;fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;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].mAzCount;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) @@ -678,128 +595,37 @@ static void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint chan // 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 double *dfa, const HrirDataT *hData) { - uint ti, fi, ei, ai, i; - - for(fi = 0;fi < hData->mFdCount;fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;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].mAzCount;ai++) + for(auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { - HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - - for(ti = 0;ti < channels;ti++) + for(size_t ti{0};ti < channels;++ti) { - for(i = 0;i < m;i++) - azd->mIrs[ti][i] /= dfa[(ti * m) + i]; + for(size_t i{0};i < m;++i) + azd.mIrs[ti][i] /= dfa[(ti * m) + i]; } } } } } -// Resamples the HRIRs for use at the given sampling rate. -static void ResampleHrirs(const uint rate, HrirDataT *hData) -{ - struct Resampler { - const double scale; - const size_t m; - - /* Resampling from a lower rate to a higher rate. This likely only - * works properly when 1 <= scale <= 2. - */ - void upsample(double *resampled, const double *ir) const - { - std::fill_n(resampled, m, 0.0); - resampled[0] = ir[0]; - for(size_t in{1};in < m;++in) - { - const auto offset = static_cast(in) / scale; - const auto out = static_cast(offset); - - const double a{offset - static_cast(out)}; - if(out == m-1) - resampled[out] += ir[in]*(1.0-a); - else - { - resampled[out ] += ir[in]*(1.0-a); - resampled[out+1] += ir[in]*a; - } - } - } - - /* Resampling from a higher rate to a lower rate. This likely only - * works properly when 0.5 <= scale <= 1.0. - */ - void downsample(double *resampled, const double *ir) const - { - resampled[0] = ir[0]; - for(size_t out{1};out < m;++out) - { - const auto offset = static_cast(out) * scale; - const auto in = static_cast(offset); - - const double a{offset - static_cast(in)}; - if(in == m-1) - resampled[out] = ir[in]*(1.0-a); - else - resampled[out] = ir[in]*(1.0-a) + ir[in+1]*a; - } - } - }; - - while(rate > hData->mIrRate*2) - ResampleHrirs(hData->mIrRate*2, hData); - while(rate < (hData->mIrRate+1)/2) - ResampleHrirs((hData->mIrRate+1)/2, hData); - - const auto scale = static_cast(rate) / hData->mIrRate; - const size_t m{hData->mFftSize/2u + 1u}; - auto resampled = std::vector(m); - - const Resampler resampler{scale, m}; - auto do_resample = std::bind( - std::mem_fn((scale > 1.0) ? &Resampler::upsample : &Resampler::downsample), &resampler, - _1, _2); - - const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; - for(uint fi{0};fi < hData->mFdCount;++fi) - { - for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;++ei) - { - for(uint ai{0};ai < hData->mFds[fi].mEvs[ei].mAzCount;++ai) - { - HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - for(uint ti{0};ti < channels;++ti) - { - do_resample(resampled.data(), azd->mIrs[ti]); - /* This should probably be rescaled according to the scale, - * however it'll all be normalized in the end so a constant - * scalar is fine to leave. - */ - std::transform(resampled.cbegin(), resampled.cend(), azd->mIrs[ti], - [](const double d) { return std::max(d, EPSILON); }); - } - } - } - } - hData->mIrRate = rate; -} - /* Given field and elevation indices and an azimuth, calculate the indices of * the two HRIRs that bound the coordinate along with a factor for * calculating the continuous HRIR using interpolation. */ -static void CalcAzIndices(const HrirFdT &field, const uint ei, const double az, uint *a0, uint *a1, double *af) +void CalcAzIndices(const HrirFdT &field, const uint ei, const double az, uint *a0, uint *a1, double *af) { - double f{(2.0*M_PI + az) * field.mEvs[ei].mAzCount / (2.0*M_PI)}; - uint i{static_cast(f) % field.mEvs[ei].mAzCount}; + 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); *a0 = i; - *a1 = (i + 1) % field.mEvs[ei].mAzCount; + *a1 = (i + 1) % static_cast(field.mEvs[ei].mAzs.size()); *af = f; } @@ -807,7 +633,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}; @@ -829,13 +655,13 @@ static void SynthesizeOnsets(HrirDataT *hData) /* Take the polar opposite position of the desired measurement and * swap the ears. */ - field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvCount-1].mAzs[0].mDelays[1]; - field.mEvs[0].mAzs[0].mDelays[1] = field.mEvs[field.mEvCount-1].mAzs[0].mDelays[0]; + field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvs.size()-1].mAzs[0].mDelays[1]; + field.mEvs[0].mAzs[0].mDelays[1] = field.mEvs[field.mEvs.size()-1].mAzs[0].mDelays[0]; for(ei = 1u;ei < (upperElevReal+1)/2;++ei) { - const uint topElev{field.mEvCount-ei-1}; + const uint topElev{static_cast(field.mEvs.size()-ei-1)}; - for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++) + for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++) { uint a0, a1; double af; @@ -844,7 +670,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. */ @@ -859,12 +685,12 @@ static void SynthesizeOnsets(HrirDataT *hData) } else { - field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvCount-1].mAzs[0].mDelays[0]; + field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvs.size()-1].mAzs[0].mDelays[0]; for(ei = 1u;ei < (upperElevReal+1)/2;++ei) { - const uint topElev{field.mEvCount-ei-1}; + const uint topElev{static_cast(field.mEvs.size()-ei-1)}; - for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++) + for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++) { uint a0, a1; double af; @@ -876,8 +702,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( @@ -897,7 +723,7 @@ static void SynthesizeOnsets(HrirDataT *hData) const double ef{(field.mEvs[upperElevReal].mElevation - field.mEvs[ei].mElevation) / (field.mEvs[upperElevReal].mElevation - field.mEvs[lowerElevFake].mElevation)}; - for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++) + for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++) { uint a0, a1, a2, a3; double af0, af1; @@ -905,12 +731,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++) { @@ -923,7 +749,7 @@ static void SynthesizeOnsets(HrirDataT *hData) } } }; - std::for_each(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, proc_field); + std::for_each(hData->mFds.begin(), hData->mFds.end(), proc_field); } /* Attempt to synthesize any missing HRIRs at the bottom elevations of each @@ -931,7 +757,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); @@ -955,7 +781,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], @@ -967,7 +793,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); @@ -990,7 +816,7 @@ static void SynthesizeHrirs(HrirDataT *hData) std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(), [](const complex_d &c) -> double { return std::abs(c); }); - for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++) + for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++) { uint a0, a1; double af; @@ -1012,7 +838,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); @@ -1036,7 +862,7 @@ static void SynthesizeHrirs(HrirDataT *hData) field.mEvs[0].mAzs[0].mIrs[ti][i] *= filter[i]; } }; - std::for_each(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, proc_field); + std::for_each(hData->mFds.begin(), hData->mFds.end(), proc_field); } // The following routines assume a full set of HRIRs for all elevations. @@ -1047,10 +873,10 @@ static void SynthesizeHrirs(HrirDataT *hData) */ struct HrirReconstructor { std::vector mIrs; - std::atomic mCurrent; - std::atomic mDone; - uint mFftSize; - uint mIrPoints; + std::atomic mCurrent{}; + std::atomic mDone{}; + uint mFftSize{}; + uint mIrPoints{}; void Worker() { @@ -1058,7 +884,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()}; @@ -1077,7 +903,7 @@ struct HrirReconstructor { * time-domain response. */ for(size_t i{0};i < m;++i) - mags[i] = std::max(mIrs[idx][i], EPSILON); + mags[i] = std::max(mIrs[idx][i], Epsilon); MinimumPhase(mFftSize, mags.data(), h.data()); FftInverse(mFftSize, h.data()); for(uint i{0u};i < mIrPoints;++i) @@ -1089,7 +915,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}; @@ -1101,15 +927,12 @@ static void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) reconstructor.mDone.store(0, std::memory_order_relaxed); reconstructor.mFftSize = hData->mFftSize; reconstructor.mIrPoints = hData->mIrPoints; - for(uint fi{0u};fi < hData->mFdCount;fi++) + for(const auto &field : hData->mFds) { - const HrirFdT &field = hData->mFds[fi]; - for(uint ei{0};ei < field.mEvCount;ei++) + for(auto &elev : field.mEvs) { - const HrirEvT &elev = field.mEvs[ei]; - for(uint ai{0u};ai < elev.mAzCount;ai++) + for(const auto &azd : elev.mAzs) { - const HrirAzT &azd = elev.mAzs[ai]; for(uint ti{0u};ti < channels;ti++) reconstructor.mIrs.push_back(azd.mIrs[ti]); } @@ -1143,42 +966,35 @@ 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 proc0_field = [channels,irSize](const LevelPair levels0, const HrirFdT &field) -> LevelPair + auto mesasure_channel = [irSize](const LevelPair levels, const double *ir) { - auto proc_elev = [channels,irSize](const LevelPair levels1, const HrirEvT &elev) -> LevelPair - { - auto proc_azi = [channels,irSize](const LevelPair levels2, const HrirAzT &azi) -> LevelPair + /* Calculate the peak amplitude and RMS of this IR. */ + auto current = std::accumulate(ir, ir+irSize, LevelPair{0.0, 0.0}, + [](const LevelPair cur, const double impulse) { - auto proc_channel = [irSize](const LevelPair levels3, const double *ir) -> LevelPair - { - /* Calculate the peak amplitude and RMS of this IR. */ - auto current = std::accumulate(ir, ir+irSize, LevelPair{0.0, 0.0}, - [](const LevelPair cur, const double impulse) -> LevelPair - { - return {std::max(std::abs(impulse), cur.amp), - cur.rms + impulse*impulse}; - }); - current.rms = std::sqrt(current.rms / irSize); + return LevelPair{std::max(std::abs(impulse), cur.amp), cur.rms + impulse*impulse}; + }); + current.rms = std::sqrt(current.rms / irSize); - /* Accumulate levels by taking the maximum amplitude and RMS. */ - return LevelPair{std::max(current.amp, levels3.amp), - std::max(current.rms, levels3.rms)}; - }; - return std::accumulate(azi.mIrs, azi.mIrs+channels, levels2, proc_channel); - }; - return std::accumulate(elev.mAzs, elev.mAzs+elev.mAzCount, levels1, proc_azi); - }; - return std::accumulate(field.mEvs, field.mEvs+field.mEvCount, levels0, proc_elev); + /* Accumulate levels by taking the maximum amplitude and RMS. */ + return LevelPair{std::max(current.amp, levels.amp), std::max(current.rms, levels.rms)}; }; - const auto maxlev = std::accumulate(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, - LevelPair{0.0, 0.0}, proc0_field); + auto measure_azi = [channels,mesasure_channel](const LevelPair levels, const HrirAzT &azi) + { 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) + { return std::accumulate(field.mEvs.cbegin(), field.mEvs.cend(), levels, measure_elev); }; + + const auto maxlev = std::accumulate(hData->mFds.begin(), hData->mFds.end(), + LevelPair{0.0, 0.0}, measure_field); /* Normalize using the maximum RMS of the HRIRs. The RMS measure for the * non-filtered signal is of an impulse with equal length (to the filter): @@ -1195,35 +1011,27 @@ static void NormalizeHrirs(HrirDataT *hData) factor = std::min(factor, 0.99/maxlev.amp); /* Now scale all IRs by the given factor. */ - auto proc1_field = [channels,irSize,factor](HrirFdT &field) -> void - { - auto proc_elev = [channels,irSize,factor](HrirEvT &elev) -> void - { - auto proc_azi = [channels,irSize,factor](HrirAzT &azi) -> void - { - auto proc_channel = [irSize,factor](double *ir) -> void - { - std::transform(ir, ir+irSize, ir, - std::bind(std::multiplies{}, _1, factor)); - }; - std::for_each(azi.mIrs, azi.mIrs+channels, proc_channel); - }; - std::for_each(elev.mAzs, elev.mAzs+elev.mAzCount, proc_azi); - }; - std::for_each(field.mEvs, field.mEvs+field.mEvCount, proc_elev); - }; - std::for_each(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, proc1_field); + auto proc_channel = [irSize,factor](double *ir) + { std::transform(ir, ir+irSize, ir, [factor](double s){ return s * factor; }); }; + auto proc_azi = [channels,proc_channel](HrirAzT &azi) + { 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) + { std::for_each(field.mEvs.begin(), field.mEvs.end(), proc_elev); }; + + std::for_each(hData->mFds.begin(), hData->mFds.end(), proc1_field); } // Calculate the left-ear time delay using a spherical head model. -static double CalcLTD(const double ev, const double az, const double rad, const double dist) +double 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; @@ -1231,138 +1039,124 @@ 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, fi, ei, ai; + uint ti; - if(model == HM_SPHERE) + if(model == HM_Sphere) { - for(fi = 0;fi < hData->mFdCount;fi++) + for(auto &field : hData->mFds) { - for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) + for(auto &elev : field.mEvs) { - HrirEvT *evd = &hData->mFds[fi].mEvs[ei]; - - for(ai = 0;ai < evd->mAzCount;ai++) + for(auto &azd : elev.mAzs) { - HrirAzT *azd = &evd->mAzs[ai]; - for(ti = 0;ti < channels;ti++) - azd->mDelays[ti] = CalcLTD(evd->mElevation, azd->mAzimuth, radius, hData->mFds[fi].mDistance); + azd.mDelays[ti] = CalcLTD(elev.mElevation, azd.mAzimuth, radius, field.mDistance); } } } } else if(customRatio != 1.0) { - for(fi = 0;fi < hData->mFdCount;fi++) + for(auto &field : hData->mFds) { - for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) + for(auto &elev : field.mEvs) { - HrirEvT *evd = &hData->mFds[fi].mEvs[ei]; - - for(ai = 0;ai < evd->mAzCount;ai++) + for(auto &azd : elev.mAzs) { - HrirAzT *azd = &evd->mAzs[ai]; for(ti = 0;ti < channels;ti++) - azd->mDelays[ti] *= customRatio; + azd.mDelays[ti] *= customRatio; } } } } double maxHrtd{0.0}; - for(fi = 0;fi < hData->mFdCount;fi++) + for(auto &field : hData->mFds) { double minHrtd{std::numeric_limits::infinity()}; - for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) + for(auto &elev : field.mEvs) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + for(auto &azd : elev.mAzs) { - HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - for(ti = 0;ti < channels;ti++) - minHrtd = std::min(azd->mDelays[ti], minHrtd); + minHrtd = std::min(azd.mDelays[ti], minHrtd); } } - for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) + for(auto &elev : field.mEvs) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + for(auto &azd : elev.mAzs) { - HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - for(ti = 0;ti < channels;ti++) { - azd->mDelays[ti] = (azd->mDelays[ti]-minHrtd) * hData->mIrRate; - maxHrtd = std::max(maxHrtd, azd->mDelays[ti]); + azd.mDelays[ti] = (azd.mDelays[ti]-minHrtd) * hData->mIrRate; + maxHrtd = std::max(maxHrtd, azd.mDelays[ti]); } } } } - 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}; - for(fi = 0;fi < hData->mFdCount;fi++) + 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(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) + for(auto &elev : field.mEvs) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + for(auto &azd : elev.mAzs) { - HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; for(ti = 0;ti < channels;ti++) - azd->mDelays[ti] *= scale; + azd.mDelays[ti] *= scale; } } } } } -// Allocate and configure dynamic HRIR structures. -int PrepareHrirData(const uint fdCount, const double (&distances)[MAX_FD_COUNT], - const uint (&evCounts)[MAX_FD_COUNT], const uint azCounts[MAX_FD_COUNT * MAX_EV_COUNT], - HrirDataT *hData) -{ - uint evTotal = 0, azTotal = 0, fi, ei, ai; +} // namespace - for(fi = 0;fi < fdCount;fi++) +// Allocate and configure dynamic HRIR structures. +bool PrepareHrirData(const al::span distances, + const al::span evCounts, + const al::span,MAX_FD_COUNT> azCounts, HrirDataT *hData) +{ + uint evTotal{0}, azTotal{0}; + + for(size_t fi{0};fi < distances.size();++fi) { evTotal += evCounts[fi]; - for(ei = 0;ei < evCounts[fi];ei++) - azTotal += azCounts[(fi * MAX_EV_COUNT) + ei]; + for(size_t ei{0};ei < evCounts[fi];++ei) + azTotal += azCounts[fi][ei]; } - if(!fdCount || !evTotal || !azTotal) - return 0; + if(!evTotal || !azTotal) + return false; hData->mEvsBase.resize(evTotal); hData->mAzsBase.resize(azTotal); - hData->mFds.resize(fdCount); + hData->mFds.resize(distances.size()); hData->mIrCount = azTotal; - hData->mFdCount = fdCount; evTotal = 0; azTotal = 0; - for(fi = 0;fi < fdCount;fi++) + for(size_t fi{0};fi < distances.size();++fi) { hData->mFds[fi].mDistance = distances[fi]; - hData->mFds[fi].mEvCount = evCounts[fi]; hData->mFds[fi].mEvStart = 0; - hData->mFds[fi].mEvs = &hData->mEvsBase[evTotal]; + hData->mFds[fi].mEvs = {&hData->mEvsBase[evTotal], evCounts[fi]}; evTotal += evCounts[fi]; - for(ei = 0;ei < evCounts[fi];ei++) + for(uint ei{0};ei < evCounts[fi];++ei) { - uint azCount = azCounts[(fi * MAX_EV_COUNT) + ei]; + uint azCount = azCounts[fi][ei]; - hData->mFds[fi].mIrCount += azCount; - hData->mFds[fi].mEvs[ei].mElevation = -M_PI / 2.0 + M_PI * ei / (evCounts[fi] - 1); - hData->mFds[fi].mEvs[ei].mIrCount += azCount; - hData->mFds[fi].mEvs[ei].mAzCount = azCount; - hData->mFds[fi].mEvs[ei].mAzs = &hData->mAzsBase[azTotal]; - for(ai = 0;ai < azCount;ai++) + hData->mFds[fi].mEvs[ei].mElevation = -al::numbers::pi / 2.0 + al::numbers::pi * ei / + (evCounts[fi] - 1); + hData->mFds[fi].mEvs[ei].mAzs = {&hData->mAzsBase[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; @@ -1372,17 +1166,19 @@ int PrepareHrirData(const uint fdCount, const double (&distances)[MAX_FD_COUNT], azTotal += azCount; } } - return 1; + return true; } +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, +bool ProcessDefinition(const char *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 char *outName) { HrirDataT hData; @@ -1392,25 +1188,25 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann { inName = "stdin"; fprintf(stdout, "Reading HRIR definition from %s...\n", inName); - if(!LoadDefInput(std::cin, nullptr, 0, inName, fftSize, truncSize, chanMode, &hData)) - return 0; + if(!LoadDefInput(std::cin, nullptr, 0, 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; + return false; } - char startbytes[4]{}; - input->read(startbytes, sizeof(startbytes)); + std::array startbytes{}; + input->read(startbytes.data(), startbytes.size()); std::streamsize startbytecount{input->gcount()}; - if(startbytecount != sizeof(startbytes) || !input->good()) + if(startbytecount != startbytes.size() || !input->good()) { fprintf(stderr, "Error: Could not read input file '%s'\n", inName); - return 0; + return false; } if(startbytes[0] == '\x89' && startbytes[1] == 'H' && startbytes[2] == 'D' @@ -1418,14 +1214,15 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann { input = nullptr; fprintf(stdout, "Reading HRTF data from %s...\n", inName); - if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, chanMode, &hData)) - return 0; + if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, outRate, chanMode, &hData)) + return false; } else { fprintf(stdout, "Reading HRIR definition from %s...\n", inName); - if(!LoadDefInput(*input, startbytes, startbytecount, inName, fftSize, truncSize, chanMode, &hData)) - return 0; + if(!LoadDefInput(*input, startbytes.data(), startbytecount, inName, fftSize, truncSize, + outRate, chanMode, &hData)) + return false; } } @@ -1433,9 +1230,9 @@ 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.mFdCount > 1) + if(hData.mFds.size() > 1) { fprintf(stdout, "Balancing field magnitudes...\n"); BalanceFieldMagnitudes(&hData, c, m); @@ -1456,16 +1253,10 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann fprintf(stdout, "Clearing %zu near field%s...\n", hData.mFds.size()-1, (hData.mFds.size()-1 != 1) ? "s" : ""); hData.mFds.erase(hData.mFds.cbegin(), hData.mFds.cend()-1); - hData.mFdCount = 1; } } - if(outRate != 0 && outRate != hData.mIrRate) - { - fprintf(stdout, "Resampling HRIRs...\n"); - ResampleHrirs(outRate, &hData); - } 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"); @@ -1475,7 +1266,7 @@ 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}, @@ -1484,7 +1275,7 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann return StoreMhr(&hData, expName.c_str()); } -static void PrintHelp(const char *argv0, FILE *ofile) +void PrintHelp(const char *argv0, FILE *ofile) { fprintf(ofile, "Usage: %s [